feat: gestion de l'expérience (XP)
- Schéma xp dans CelestopolCharacter : actuel (éditable), log[] ({montant, raison, date}), depense (calculé dans prepareDerivedData)
- Bouton 'Dépenser XP' → DialogV2 (montant + raison) : décrémente actuel, logge l'entrée
- Suppression d'entrée de log avec remboursement des points (mode édition)
- Section XP en haut de l'onglet Biographie : compteurs, tableau du log, référentiel des coûts
- i18n : section CELESTOPOL.XP.* complète
- CSS : .xp-section avec compteurs, tableau de log et accordéon de référence
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -68,7 +68,7 @@ Hooks.once("init", () => {
|
|||||||
|
|
||||||
// ── Sheets: unregister core, register system sheets ─────────────────────
|
// ── Sheets: unregister core, register system sheets ─────────────────────
|
||||||
foundry.applications.sheets.ActorSheetV2.unregisterSheet?.("core", "Actor", { types: ["character", "npc"] })
|
foundry.applications.sheets.ActorSheetV2.unregisterSheet?.("core", "Actor", { types: ["character", "npc"] })
|
||||||
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
|
foundry.appv1?.sheets?.ActorSheet && foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
|
||||||
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, CelestopolCharacterSheet, {
|
foundry.documents.collections.Actors.registerSheet(SYSTEM_ID, CelestopolCharacterSheet, {
|
||||||
types: ["character"],
|
types: ["character"],
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
@@ -80,7 +80,7 @@ Hooks.once("init", () => {
|
|||||||
label: "CELESTOPOL.Sheet.npc",
|
label: "CELESTOPOL.Sheet.npc",
|
||||||
})
|
})
|
||||||
|
|
||||||
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet)
|
foundry.appv1?.sheets?.ItemSheet && foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet)
|
||||||
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolAnomalySheet, {
|
foundry.documents.collections.Items.registerSheet(SYSTEM_ID, CelestopolAnomalySheet, {
|
||||||
types: ["anomaly"],
|
types: ["anomaly"],
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
@@ -130,6 +130,7 @@ Hooks.once("ready", () => {
|
|||||||
// Migration : supprime les items de types obsolètes (ex: "attribute")
|
// Migration : supprime les items de types obsolètes (ex: "attribute")
|
||||||
if (game.user.isGM) {
|
if (game.user.isGM) {
|
||||||
_migrateObsoleteItems()
|
_migrateObsoleteItems()
|
||||||
|
_migrateIntegerTracks()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -156,6 +157,68 @@ async function _migrateObsoleteItems() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration : convertit les anciennes données booléennes (level1..level8, b1..b8, etc.)
|
||||||
|
* vers le nouveau stockage entier direct.
|
||||||
|
* Ne s'applique qu'aux acteurs ayant encore l'ancien format dans leur source.
|
||||||
|
*/
|
||||||
|
async function _migrateIntegerTracks() {
|
||||||
|
const validActors = game.actors.contents.filter(a => ["character", "npc"].includes(a.type))
|
||||||
|
|
||||||
|
for (const actor of validActors) {
|
||||||
|
const src = actor._source?.system
|
||||||
|
if (!src) continue
|
||||||
|
|
||||||
|
const updateData = {}
|
||||||
|
|
||||||
|
// Blessures : si b1 existe dans la source, recalculer lvl depuis les booléens
|
||||||
|
const blessures = src.blessures ?? {}
|
||||||
|
if ("b1" in blessures) {
|
||||||
|
const lvl = [1,2,3,4,5,6,7,8].filter(i => blessures[`b${i}`]?.checked === true).length
|
||||||
|
updateData["system.blessures.lvl"] = lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.type === "character") {
|
||||||
|
// Destin
|
||||||
|
const destin = src.destin ?? {}
|
||||||
|
if ("d1" in destin) {
|
||||||
|
const lvl = [1,2,3,4,5,6,7,8].filter(i => destin[`d${i}`]?.checked === true).length
|
||||||
|
updateData["system.destin.lvl"] = lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spleen
|
||||||
|
const spleen = src.spleen ?? {}
|
||||||
|
if ("s1" in spleen) {
|
||||||
|
const lvl = [1,2,3,4,5,6,7,8].filter(i => spleen[`s${i}`]?.checked === true).length
|
||||||
|
updateData["system.spleen.lvl"] = lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domaines : si level1 existe dans un domaine, recalculer value depuis les booléens
|
||||||
|
const stats = src.stats ?? {}
|
||||||
|
for (const [statId, statData] of Object.entries(stats)) {
|
||||||
|
for (const [skillId, skill] of Object.entries(statData ?? {})) {
|
||||||
|
if (typeof skill !== "object" || !("level1" in skill)) continue
|
||||||
|
const value = [1,2,3,4,5,6,7,8].filter(i => skill[`level${i}`] === true).length
|
||||||
|
updateData[`system.stats.${statId}.${skillId}.value`] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factions : si level1 existe dans une faction, recalculer value depuis les booléens
|
||||||
|
const factions = src.factions ?? {}
|
||||||
|
for (const [factionId, faction] of Object.entries(factions)) {
|
||||||
|
if (typeof faction !== "object" || !("level1" in faction)) continue
|
||||||
|
const value = [1,2,3,4,5,6,7,8,9].filter(i => faction[`level${i}`] === true).length
|
||||||
|
updateData[`system.factions.${factionId}.value`] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updateData).length > 0) {
|
||||||
|
console.log(`${SYSTEM_ID} | Migration tracks → entiers : ${actor.name}`, updateData)
|
||||||
|
await actor.update(updateData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Handlebars helpers ─────────────────────────────────────────────────── */
|
/* ─── Handlebars helpers ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function _registerHandlebarsHelpers() {
|
function _registerHandlebarsHelpers() {
|
||||||
@@ -183,6 +246,9 @@ function _registerHandlebarsHelpers() {
|
|||||||
// Helper : build array from args (Handlebars doesn't have arrays natively)
|
// Helper : build array from args (Handlebars doesn't have arrays natively)
|
||||||
Handlebars.registerHelper("array", (...args) => args.slice(0, -1))
|
Handlebars.registerHelper("array", (...args) => args.slice(0, -1))
|
||||||
|
|
||||||
|
// Helper : range(n) → [1, 2, ..., n] — pour les boucles de cases à cocher
|
||||||
|
Handlebars.registerHelper("range", (n) => Array.from({ length: n }, (_, i) => i + 1))
|
||||||
|
|
||||||
// Helper : nested object lookup with dot path or multiple keys
|
// Helper : nested object lookup with dot path or multiple keys
|
||||||
Handlebars.registerHelper("lookup", (obj, ...args) => {
|
Handlebars.registerHelper("lookup", (obj, ...args) => {
|
||||||
const options = args.pop() // last arg is Handlebars options hash
|
const options = args.pop() // last arg is Handlebars options hash
|
||||||
|
|||||||
73
lang/fr.json
73
lang/fr.json
@@ -95,29 +95,40 @@
|
|||||||
"destin": "Destin",
|
"destin": "Destin",
|
||||||
"spleen": "Spleen",
|
"spleen": "Spleen",
|
||||||
"level": "Niveau",
|
"level": "Niveau",
|
||||||
"currentMalus": "Malus actuel"
|
"currentMalus": "Malus actuel",
|
||||||
|
"destinTooltip": "Usages du Destin :\n• Réaliser un test avec 3d8\n• Gagner l'initiative lors d'un combat\n• Trouver l'ensemble des indices\n• Éviter une blessure\n• Sortir de l'inconscience\n• Obtenir un Triomphe"
|
||||||
},
|
},
|
||||||
"Wound": {
|
"Wound": {
|
||||||
"none": "Aucune blessure",
|
"none": "Aucune blessure",
|
||||||
"anodin": "Anodin",
|
"anodin": "Anodin",
|
||||||
"derisoire": "Dérisoire",
|
"derisoire": "Dérisoire",
|
||||||
"negligeable": "Négligeable",
|
"negligeable": "Négligeable",
|
||||||
"superficiel": "Superficiel",
|
"superficiel": "Superficiel",
|
||||||
"leger": "Léger",
|
"leger": "Léger",
|
||||||
"modere": "Modéré",
|
"modere": "Modéré",
|
||||||
"grave": "Grave",
|
"grave": "Grave",
|
||||||
"dramatique": "Dramatique (hors combat)"
|
"dramatique": "Dramatique (hors combat)",
|
||||||
|
"duration1min": "1 min",
|
||||||
|
"duration10min": "10 min",
|
||||||
|
"duration30min": "30 min",
|
||||||
|
"duration1jour": "1 journée",
|
||||||
|
"status": "État : "
|
||||||
},
|
},
|
||||||
"Combat": {
|
"Combat": {
|
||||||
"attack": "Attaquer",
|
"attack": "Attaquer",
|
||||||
"corpsPnj": "Corps du PNJ",
|
"corpsPnj": "Corps du PNJ",
|
||||||
"tie": "ÉGALITÉ",
|
"tie": "ÉGALITÉ",
|
||||||
"tieDesc": "Personne n'est blessé",
|
"tieDesc": "Personne n'est blessé",
|
||||||
"successHit": "PNJ touché — 1 blessure",
|
"successHit": "PNJ touché — 1 blessure",
|
||||||
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
||||||
"distanceNoWound": "Raté — pas de riposte",
|
"distanceNoWound": "Raté — pas de riposte",
|
||||||
"weaponDamage": "dégâts supplémentaires",
|
"weaponDamage": "dégâts supplémentaires",
|
||||||
"playerWounded": "Blessure infligée au joueur (mêlée)"
|
"playerWounded": "Blessure infligée au joueur (mêlée)",
|
||||||
|
"rangedDefenseTitle": "Esquiver (Mobilité)",
|
||||||
|
"rangedDefenseTag": "Défense à distance",
|
||||||
|
"rangedDefenseSuccess": "Attaque esquivée — pas de blessure",
|
||||||
|
"rangedDefenseFailure": "Touché par le PNJ — 1 blessure",
|
||||||
|
"rangedDefensePlayerWounded":"Blessure infligée par attaque à distance"
|
||||||
},
|
},
|
||||||
"Tab": {
|
"Tab": {
|
||||||
"main": "Principal",
|
"main": "Principal",
|
||||||
@@ -294,6 +305,30 @@
|
|||||||
},
|
},
|
||||||
"Aspect": {
|
"Aspect": {
|
||||||
"valeur": "Valeur"
|
"valeur": "Valeur"
|
||||||
|
},
|
||||||
|
"XP": {
|
||||||
|
"title": "Expérience",
|
||||||
|
"actuel": "XP disponible",
|
||||||
|
"depense": "XP dépensée",
|
||||||
|
"depenser": "Dépenser XP",
|
||||||
|
"confirmer": "Confirmer",
|
||||||
|
"montant": "Montant",
|
||||||
|
"raison": "Raison",
|
||||||
|
"raisonPlaceholder": "Ex : Augmentation Mobilité à 4",
|
||||||
|
"date": "Date",
|
||||||
|
"supprimer": "Annuler cette dépense",
|
||||||
|
"disponible": "{n} XP disponibles",
|
||||||
|
"insuffisant": "XP insuffisante — seulement {n} disponibles",
|
||||||
|
"montantInvalide": "Le montant doit être supérieur à 0",
|
||||||
|
"refTitle": "Tableau des coûts",
|
||||||
|
"refAmelioration": "Amélioration",
|
||||||
|
"refCout": "Coût (XP)",
|
||||||
|
"refAugmenterSpec": "Augmenter une Spécialisation",
|
||||||
|
"refCoutNiveau": "= niveau à atteindre",
|
||||||
|
"refAcquerirAspect": "Acquérir un nouvel Aspect",
|
||||||
|
"refAugmenterAspect":"Augmenter / Diminuer un Aspect",
|
||||||
|
"refAcquerirAttribut":"Acquérir ou augmenter un Attribut",
|
||||||
|
"refCoutAttributTotal":"= total des points × 10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
export default class CelestopolActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
export default class CelestopolActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||||
@@ -18,11 +20,15 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
window: { resizable: true },
|
window: { resizable: true },
|
||||||
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
|
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
|
||||||
actions: {
|
actions: {
|
||||||
editImage: CelestopolActorSheet.#onEditImage,
|
editImage: CelestopolActorSheet.#onEditImage,
|
||||||
toggleSheet: CelestopolActorSheet.#onToggleSheet,
|
toggleSheet: CelestopolActorSheet.#onToggleSheet,
|
||||||
edit: CelestopolActorSheet.#onItemEdit,
|
edit: CelestopolActorSheet.#onItemEdit,
|
||||||
delete: CelestopolActorSheet.#onItemDelete,
|
delete: CelestopolActorSheet.#onItemDelete,
|
||||||
attack: CelestopolActorSheet.#onAttack,
|
attack: CelestopolActorSheet.#onAttack,
|
||||||
|
rangedDefense: CelestopolActorSheet.#onRangedDefense,
|
||||||
|
trackBox: CelestopolActorSheet.#onTrackBox,
|
||||||
|
skillLevel: CelestopolActorSheet.#onSkillLevel,
|
||||||
|
factionLevel: CelestopolActorSheet.#onFactionLevel,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +48,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
isEditMode: this.isEditMode,
|
isEditMode: this.isEditMode,
|
||||||
isPlayMode: this.isPlayMode,
|
isPlayMode: this.isPlayMode,
|
||||||
isEditable: this.isEditable,
|
isEditable: this.isEditable,
|
||||||
|
woundLevels: SYSTEM.WOUND_LEVELS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,28 +58,9 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
this.element.querySelectorAll(".rollable").forEach(el => {
|
this.element.querySelectorAll(".rollable").forEach(el => {
|
||||||
el.addEventListener("click", this._onRoll.bind(this))
|
el.addEventListener("click", this._onRoll.bind(this))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup sequential checkbox logic for wound tracks
|
|
||||||
this._setupSequentialCheckboxes()
|
|
||||||
|
|
||||||
// Setup sequential checkbox logic for factions
|
|
||||||
this._setupFactionCheckboxes()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
_onClick(event) {
|
|
||||||
// Skip checkbox clicks in edit mode
|
|
||||||
if (this.isEditMode && event.target.classList.contains('skill-level-checkbox')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
super._onClick(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRoll(event) {
|
async _onRoll(event) {
|
||||||
// Don't roll if clicking on a checkbox
|
|
||||||
if (event.target.classList.contains('skill-level-checkbox')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.isPlayMode) return
|
if (!this.isPlayMode) return
|
||||||
const el = event.currentTarget
|
const el = event.currentTarget
|
||||||
const statId = el.dataset.statId
|
const statId = el.dataset.statId
|
||||||
@@ -158,132 +146,39 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
await this.document.system.rollAttack(itemId)
|
await this.document.system.rollAttack(itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static async #onRangedDefense(_event, target) {
|
||||||
* Setup sequential checkbox logic for wound/destin/spleen tracks
|
const itemId = target.getAttribute("data-item-id")
|
||||||
* Only allows checking the next checkbox in sequence
|
if (!itemId) return
|
||||||
*/
|
await this.document.system.rollRangedDefense(itemId)
|
||||||
_setupSequentialCheckboxes() {
|
|
||||||
this.element.querySelectorAll('.wound-checkbox').forEach(checkbox => {
|
|
||||||
checkbox.addEventListener('change', (event) => {
|
|
||||||
this._handleSequentialCheckboxChange(event)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Met à jour une jauge de piste (blessures/destin/spleen) par clic sur une case. */
|
||||||
* Handle sequential checkbox change logic
|
static #onTrackBox(_event, target) {
|
||||||
* @param {Event} event - The change event
|
if (!this.isEditable) return
|
||||||
*/
|
const path = target.dataset.path
|
||||||
_handleSequentialCheckboxChange(event) {
|
const index = parseInt(target.dataset.index)
|
||||||
const checkbox = event.target
|
const current = foundry.utils.getProperty(this.document, path) ?? 0
|
||||||
if (!checkbox.classList.contains('wound-checkbox') || checkbox.disabled) return
|
const newValue = (index <= current) ? index - 1 : index
|
||||||
|
this.document.update({ [path]: Math.max(0, newValue) })
|
||||||
const track = checkbox.dataset.track
|
|
||||||
const currentIndex = parseInt(checkbox.dataset.index)
|
|
||||||
const isChecked = checkbox.checked
|
|
||||||
|
|
||||||
// Get all checkboxes in this track
|
|
||||||
const trackCheckboxes = Array.from(this.element.querySelectorAll(`.wound-checkbox[data-track="${track}"]`))
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
// Checking a box: uncheck all boxes after this one
|
|
||||||
for (let i = currentIndex + 1; i < trackCheckboxes.length; i++) {
|
|
||||||
trackCheckboxes[i].checked = false
|
|
||||||
}
|
|
||||||
// Check all boxes before this one
|
|
||||||
for (let i = 0; i < currentIndex; i++) {
|
|
||||||
trackCheckboxes[i].checked = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unchecking a box: uncheck all boxes after this one
|
|
||||||
for (let i = currentIndex; i < trackCheckboxes.length; i++) {
|
|
||||||
trackCheckboxes[i].checked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the visual state
|
|
||||||
this._updateTrackVisualState()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Met à jour la valeur d'un domaine par clic sur un point de niveau. */
|
||||||
* Update visual state of track boxes based on checkbox states
|
static #onSkillLevel(_event, target) {
|
||||||
*/
|
if (!this.isEditable) return
|
||||||
_updateTrackVisualState() {
|
const { statId, skillId } = target.dataset
|
||||||
this.element.querySelectorAll('.track-box').forEach(box => {
|
const index = parseInt(target.dataset.index)
|
||||||
const checkbox = box.querySelector('.wound-checkbox')
|
const current = this.document.system.stats[statId]?.[skillId]?.value ?? 0
|
||||||
if (checkbox) {
|
const newValue = (index <= current) ? index - 1 : index
|
||||||
if (checkbox.checked) {
|
this.document.update({ [`system.stats.${statId}.${skillId}.value`]: Math.max(0, newValue) })
|
||||||
box.classList.add('checked')
|
|
||||||
} else {
|
|
||||||
box.classList.remove('checked')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Met à jour le score d'une faction par clic sur un point. */
|
||||||
* Setup sequential checkbox logic for faction tracks
|
static #onFactionLevel(_event, target) {
|
||||||
*/
|
if (!this.isEditable) return
|
||||||
_setupFactionCheckboxes() {
|
const factionId = target.dataset.faction
|
||||||
this.element.querySelectorAll('.faction-checkbox').forEach(checkbox => {
|
const index = parseInt(target.dataset.index)
|
||||||
checkbox.addEventListener('change', (event) => {
|
const current = this.document.system.factions[factionId]?.value ?? 0
|
||||||
this._handleFactionCheckboxChange(event)
|
const newValue = (index <= current) ? index - 1 : index
|
||||||
})
|
this.document.update({ [`system.factions.${factionId}.value`]: Math.max(0, newValue) })
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle faction checkbox change logic
|
|
||||||
* @param {Event} event - The change event
|
|
||||||
*/
|
|
||||||
_handleFactionCheckboxChange(event) {
|
|
||||||
const checkbox = event.target
|
|
||||||
if (!checkbox.classList.contains('faction-checkbox') || checkbox.disabled) return
|
|
||||||
|
|
||||||
const factionId = checkbox.dataset.faction
|
|
||||||
const currentLevel = parseInt(checkbox.dataset.level)
|
|
||||||
const isChecked = checkbox.checked
|
|
||||||
|
|
||||||
// Get all checkboxes for this faction
|
|
||||||
const factionCheckboxes = Array.from(this.element.querySelectorAll(`.faction-checkbox[data-faction="${factionId}"]`))
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
// Checking a box: check all boxes before this one, uncheck all boxes after this one
|
|
||||||
for (let i = 0; i < currentLevel; i++) {
|
|
||||||
factionCheckboxes[i].checked = true
|
|
||||||
}
|
|
||||||
for (let i = currentLevel; i < factionCheckboxes.length; i++) {
|
|
||||||
factionCheckboxes[i].checked = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unchecking a box: uncheck all boxes after this one
|
|
||||||
for (let i = currentLevel - 1; i < factionCheckboxes.length; i++) {
|
|
||||||
factionCheckboxes[i].checked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the count display
|
|
||||||
this._updateFactionCount(factionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the faction count display based on checked checkboxes
|
|
||||||
* @param {string} factionId - The faction ID
|
|
||||||
*/
|
|
||||||
_updateFactionCount(factionId) {
|
|
||||||
const checkboxes = Array.from(this.element.querySelectorAll(`.faction-checkbox[data-faction="${factionId}"]:checked`))
|
|
||||||
const count = checkboxes.length
|
|
||||||
|
|
||||||
// Update the hidden input field
|
|
||||||
const input = this.element.querySelector(`input[name="system.factions.${factionId}.value"]`)
|
|
||||||
if (input) {
|
|
||||||
input.value = count
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the visual count display
|
|
||||||
const countDisplay = this.element.querySelector(`.faction-row[data-faction="${factionId}"] .faction-count`)
|
|
||||||
if (countDisplay) {
|
|
||||||
countDisplay.textContent = count
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
createArmure: CelestopolCharacterSheet.#onCreateArmure,
|
createArmure: CelestopolCharacterSheet.#onCreateArmure,
|
||||||
useAnomaly: CelestopolCharacterSheet.#onUseAnomaly,
|
useAnomaly: CelestopolCharacterSheet.#onUseAnomaly,
|
||||||
resetAnomalyUses: CelestopolCharacterSheet.#onResetAnomalyUses,
|
resetAnomalyUses: CelestopolCharacterSheet.#onResetAnomalyUses,
|
||||||
|
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
|
||||||
|
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +97,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
|
|
||||||
case "biography":
|
case "biography":
|
||||||
context.tab = context.tabs.biography
|
context.tab = context.tabs.biography
|
||||||
|
context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
doc.system.description, { async: true })
|
doc.system.description, { async: true })
|
||||||
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
@@ -111,36 +114,36 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateAnomaly() {
|
static async #onCreateAnomaly() {
|
||||||
if (this.document.itemTypes.anomaly.length > 0) {
|
if (this.document.itemTypes.anomaly.length > 0) {
|
||||||
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.maxAnomaly"))
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Anomaly.maxAnomaly"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("CELESTOPOL.Item.newAnomaly"), type: "anomaly",
|
name: game.i18n.localize("CELESTOPOL.Item.newAnomaly"), type: "anomaly",
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateAspect() {
|
static async #onCreateAspect() {
|
||||||
this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("CELESTOPOL.Item.newAspect"), type: "aspect",
|
name: game.i18n.localize("CELESTOPOL.Item.newAspect"), type: "aspect",
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateEquipment() {
|
static async #onCreateEquipment() {
|
||||||
this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("TYPES.Item.equipment"), type: "equipment",
|
name: game.i18n.localize("TYPES.Item.equipment"), type: "equipment",
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateWeapon() {
|
static async #onCreateWeapon() {
|
||||||
this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("TYPES.Item.weapon"), type: "weapon",
|
name: game.i18n.localize("TYPES.Item.weapon"), type: "weapon",
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
static #onCreateArmure() {
|
static async #onCreateArmure() {
|
||||||
this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
@@ -163,4 +166,73 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
if (!anomaly) return
|
if (!anomaly) return
|
||||||
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
|
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ouvre un dialogue pour dépenser de l'XP. */
|
||||||
|
static async #onDepenseXp() {
|
||||||
|
const actor = this.document
|
||||||
|
const currentXp = actor.system.xp?.actuel ?? 0
|
||||||
|
const i18n = game.i18n
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<form class="cel-dialog-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.XP.montant")}</label>
|
||||||
|
<input type="number" name="montant" value="1" min="1" max="${currentXp}" autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.XP.raison")}</label>
|
||||||
|
<input type="text" name="raison" placeholder="${i18n.localize("CELESTOPOL.XP.raisonPlaceholder")}" />
|
||||||
|
</div>
|
||||||
|
<p class="xp-dialog-hint">${i18n.format("CELESTOPOL.XP.disponible", { n: currentXp })}</p>
|
||||||
|
</form>`
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: i18n.localize("CELESTOPOL.XP.depenser") },
|
||||||
|
content,
|
||||||
|
ok: {
|
||||||
|
label: i18n.localize("CELESTOPOL.XP.confirmer"),
|
||||||
|
callback: (event, button) => {
|
||||||
|
const form = button.form
|
||||||
|
return {
|
||||||
|
montant: parseInt(form.querySelector("[name=montant]").value) || 0,
|
||||||
|
raison: form.querySelector("[name=raison]").value.trim(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) return
|
||||||
|
const { montant, raison } = result
|
||||||
|
|
||||||
|
if (montant <= 0) {
|
||||||
|
ui.notifications.warn(i18n.localize("CELESTOPOL.XP.montantInvalide"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (montant > currentXp) {
|
||||||
|
ui.notifications.warn(i18n.format("CELESTOPOL.XP.insuffisant", { n: currentXp }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date().toLocaleDateString("fr-FR")
|
||||||
|
const log = [...(actor.system.xp.log ?? []), { montant, raison, date }]
|
||||||
|
await actor.update({
|
||||||
|
"system.xp.actuel": currentXp - montant,
|
||||||
|
"system.xp.log": log,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Supprime une entrée du log XP et rembourse les points (mode édition). */
|
||||||
|
static async #onSupprimerXpLog(event, target) {
|
||||||
|
const idx = parseInt(target.dataset.idx)
|
||||||
|
const actor = this.document
|
||||||
|
const log = [...(actor.system.xp.log ?? [])]
|
||||||
|
if (isNaN(idx) || idx < 0 || idx >= log.length) return
|
||||||
|
|
||||||
|
const entry = log[idx]
|
||||||
|
log.splice(idx, 1)
|
||||||
|
await actor.update({
|
||||||
|
"system.xp.actuel": (actor.system.xp?.actuel ?? 0) + entry.montant,
|
||||||
|
"system.xp.log": log,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,15 +90,15 @@ export const FACTIONS = {
|
|||||||
|
|
||||||
/** Niveaux de blessures avec leur malus associé. */
|
/** Niveaux de blessures avec leur malus associé. */
|
||||||
export const WOUND_LEVELS = [
|
export const WOUND_LEVELS = [
|
||||||
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0 },
|
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" },
|
||||||
{ id: 1, label: "CELESTOPOL.Wound.anodin", malus: 0 },
|
{ id: 1, label: "CELESTOPOL.Wound.anodin", malus: 0, duration: "CELESTOPOL.Wound.duration1min" },
|
||||||
{ id: 2, label: "CELESTOPOL.Wound.derisoire", malus: 0 },
|
{ id: 2, label: "CELESTOPOL.Wound.negligeable", malus: 0, duration: "CELESTOPOL.Wound.duration1min" },
|
||||||
{ id: 3, label: "CELESTOPOL.Wound.negligeable", malus: -1 },
|
{ id: 3, label: "CELESTOPOL.Wound.derisoire", malus: -1, duration: "CELESTOPOL.Wound.duration10min" },
|
||||||
{ id: 4, label: "CELESTOPOL.Wound.superficiel", malus: -1 },
|
{ id: 4, label: "CELESTOPOL.Wound.superficiel", malus: -1, duration: "CELESTOPOL.Wound.duration10min" },
|
||||||
{ id: 5, label: "CELESTOPOL.Wound.leger", malus: -2 },
|
{ id: 5, label: "CELESTOPOL.Wound.leger", malus: -2, duration: "CELESTOPOL.Wound.duration30min" },
|
||||||
{ id: 6, label: "CELESTOPOL.Wound.modere", malus: -2 },
|
{ id: 6, label: "CELESTOPOL.Wound.modere", malus: -2, duration: "CELESTOPOL.Wound.duration30min" },
|
||||||
{ id: 7, label: "CELESTOPOL.Wound.grave", malus: -3 },
|
{ id: 7, label: "CELESTOPOL.Wound.grave", malus: -3, duration: "CELESTOPOL.Wound.duration1jour" },
|
||||||
{ id: 8, label: "CELESTOPOL.Wound.dramatique", malus: -999 },
|
{ id: 8, label: "CELESTOPOL.Wound.dramatique", malus: -999, duration: "" },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Seuils de difficulté pour les jets de dés. */
|
/** Seuils de difficulté pour les jets de dés. */
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
export default class CelestopolActor extends Actor {
|
export default class CelestopolActor extends Actor {
|
||||||
/** @override */
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData()
|
|
||||||
this.system.prepareDerivedData?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
return this.toObject(false).system
|
return this.toObject(false).system
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
export default class CelestopolChatMessage extends ChatMessage {
|
export default class CelestopolChatMessage extends ChatMessage {}
|
||||||
/** @override */
|
|
||||||
async renderHTML(options = {}) {
|
|
||||||
return super.renderHTML(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
export default class CelestopolItem extends Item {
|
export default class CelestopolItem extends Item {
|
||||||
/** @override */
|
|
||||||
prepareDerivedData() {
|
|
||||||
super.prepareDerivedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
return this.toObject(false).system
|
return this.toObject(false).system
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
const fortuneValue = options.fortuneValue ?? 0
|
const fortuneValue = options.fortuneValue ?? 0
|
||||||
const isResistance = options.isResistance ?? false
|
const isResistance = options.isResistance ?? false
|
||||||
const isCombat = options.isCombat ?? false
|
const isCombat = options.isCombat ?? false
|
||||||
|
const isRangedDefense = options.isRangedDefense ?? false
|
||||||
const weaponType = options.weaponType ?? "melee"
|
const weaponType = options.weaponType ?? "melee"
|
||||||
const weaponName = options.weaponName ?? null
|
const weaponName = options.weaponName ?? null
|
||||||
const weaponDegats = options.weaponDegats ?? "0"
|
const weaponDegats = options.weaponDegats ?? "0"
|
||||||
@@ -72,6 +73,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
defaultRollMoonDie: options.rollMoonDie ?? false,
|
defaultRollMoonDie: options.rollMoonDie ?? false,
|
||||||
isResistance,
|
isResistance,
|
||||||
isCombat,
|
isCombat,
|
||||||
|
isRangedDefense,
|
||||||
weaponType,
|
weaponType,
|
||||||
weaponName,
|
weaponName,
|
||||||
weaponDegats,
|
weaponDegats,
|
||||||
@@ -221,6 +223,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
autoSuccess,
|
autoSuccess,
|
||||||
isResistance,
|
isResistance,
|
||||||
isCombat,
|
isCombat,
|
||||||
|
isRangedDefense,
|
||||||
weaponType,
|
weaponType,
|
||||||
weaponName,
|
weaponName,
|
||||||
weaponDegats,
|
weaponDegats,
|
||||||
@@ -243,66 +246,49 @@ export class CelestopolRoll extends Roll {
|
|||||||
// Test de résistance échoué → cocher automatiquement la prochaine case de blessure
|
// Test de résistance échoué → cocher automatiquement la prochaine case de blessure
|
||||||
const actor = game.actors.get(options.actorId)
|
const actor = game.actors.get(options.actorId)
|
||||||
if (isResistance && actor && roll.options.resultType === "failure") {
|
if (isResistance && actor && roll.options.resultType === "failure") {
|
||||||
const wounds = actor.system.blessures
|
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1
|
||||||
const nextIdx = [1,2,3,4,5,6,7,8].find(i => !wounds[`b${i}`]?.checked)
|
if (nextLvl <= 8) {
|
||||||
if (nextIdx) {
|
await actor.update({ "system.blessures.lvl": nextLvl })
|
||||||
await actor.update({ [`system.blessures.b${nextIdx}.checked`]: true })
|
roll.options.woundTaken = nextLvl
|
||||||
roll.options.woundTaken = nextIdx
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combat mêlée échoué → joueur prend une blessure
|
// Mêlée échouée OU défense à distance échouée → joueur prend une blessure
|
||||||
if (isCombat && weaponType === "melee" && actor && roll.options.resultType === "failure") {
|
if (isCombat && (weaponType === "melee" || isRangedDefense) && actor && roll.options.resultType === "failure") {
|
||||||
const wounds = actor.system.blessures
|
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1
|
||||||
const nextIdx = [1,2,3,4,5,6,7,8].find(i => !wounds[`b${i}`]?.checked)
|
if (nextLvl <= 8) {
|
||||||
if (nextIdx) {
|
await actor.update({ "system.blessures.lvl": nextLvl })
|
||||||
await actor.update({ [`system.blessures.b${nextIdx}.checked`]: true })
|
roll.options.woundTaken = nextLvl
|
||||||
roll.options.woundTaken = nextIdx
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await roll.toMessage({}, { rollMode: rollData.rollMode })
|
await roll.toMessage({}, { rollMode: rollData.rollMode })
|
||||||
|
|
||||||
// Destin utilisé → vider la jauge (reset à 0)
|
// Batching de toutes les mises à jour de l'acteur en un seul appel réseau
|
||||||
if (rollData.useDestin && actor) {
|
|
||||||
await actor.update({
|
|
||||||
"system.destin.lvl": 0,
|
|
||||||
"system.destin.d1.checked": false,
|
|
||||||
"system.destin.d2.checked": false,
|
|
||||||
"system.destin.d3.checked": false,
|
|
||||||
"system.destin.d4.checked": false,
|
|
||||||
"system.destin.d5.checked": false,
|
|
||||||
"system.destin.d6.checked": false,
|
|
||||||
"system.destin.d7.checked": false,
|
|
||||||
"system.destin.d8.checked": false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fortune utilisée → décrémenter de 1 (min 0)
|
|
||||||
if (rollData.useFortune && actor) {
|
|
||||||
const currentFortune = actor.system.attributs.fortune.value ?? 0
|
|
||||||
await actor.update({ "system.attributs.fortune.value": Math.max(0, currentFortune - 1) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Puiser dans ses ressources → coche une case de spleen
|
|
||||||
if (rollData.puiserRessources && actor) {
|
|
||||||
const currentSpleen = actor.system.spleen.lvl ?? 0
|
|
||||||
if (currentSpleen < 8) {
|
|
||||||
const newLvl = currentSpleen + 1
|
|
||||||
const key = `s${newLvl}`
|
|
||||||
await actor.update({
|
|
||||||
"system.spleen.lvl": newLvl,
|
|
||||||
[`system.spleen.${key}.checked`]: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mémoriser les préférences sur l'acteur
|
|
||||||
if (actor) {
|
if (actor) {
|
||||||
await actor.update({
|
const updateData = {}
|
||||||
"system.prefs.rollMoonDie": rollData.rollMoonDie,
|
|
||||||
"system.prefs.difficulty": difficulty,
|
if (rollData.useDestin) {
|
||||||
})
|
updateData["system.destin.lvl"] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rollData.useFortune) {
|
||||||
|
const currentFortune = actor.system.attributs.fortune.value ?? 0
|
||||||
|
updateData["system.attributs.fortune.value"] = Math.max(0, currentFortune - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rollData.puiserRessources) {
|
||||||
|
const currentSpleen = actor.system.spleen.lvl ?? 0
|
||||||
|
if (currentSpleen < 8) {
|
||||||
|
updateData["system.spleen.lvl"] = currentSpleen + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mémoriser les préférences
|
||||||
|
updateData["system.prefs.rollMoonDie"] = rollData.rollMoonDie
|
||||||
|
updateData["system.prefs.difficulty"] = difficulty
|
||||||
|
|
||||||
|
await actor.update(updateData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return roll
|
return roll
|
||||||
@@ -421,6 +407,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
weaponName: this.options.weaponName ?? null,
|
weaponName: this.options.weaponName ?? null,
|
||||||
weaponDegats: this.options.weaponDegats ?? null,
|
weaponDegats: this.options.weaponDegats ?? null,
|
||||||
weaponType: this.options.weaponType ?? null,
|
weaponType: this.options.weaponType ?? null,
|
||||||
|
isRangedDefense: this.options.isRangedDefense ?? false,
|
||||||
woundTaken: this.options.woundTaken ?? null,
|
woundTaken: this.options.woundTaken ?? null,
|
||||||
// Dé de lune
|
// Dé de lune
|
||||||
hasMoonDie: moonDieResult !== null,
|
hasMoonDie: moonDieResult !== null,
|
||||||
|
|||||||
@@ -21,18 +21,10 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Les 4 stats avec leurs domaines
|
// Les 4 stats avec leurs domaines — niveau stocké directement comme entier
|
||||||
const skillField = (label) => new fields.SchemaField({
|
const skillField = (label) => new fields.SchemaField({
|
||||||
label: new fields.StringField({ required: true, initial: label }),
|
label: new fields.StringField({ required: true, initial: label }),
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
level1: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level2: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level3: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level4: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level5: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level6: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level7: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level8: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const statField = (statId) => {
|
const statField = (statId) => {
|
||||||
@@ -55,32 +47,19 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
esprit: statField("esprit"),
|
esprit: statField("esprit"),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Blessures (8 cases)
|
// Blessures — niveau entier direct (0 = aucune, 8 = fatale)
|
||||||
const woundField = (idx) => new fields.SchemaField({
|
|
||||||
checked: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
malus: new fields.NumberField({ ...reqInt, initial: SYSTEM.WOUND_LEVELS[idx]?.malus ?? 0 }),
|
|
||||||
})
|
|
||||||
schema.blessures = new fields.SchemaField({
|
schema.blessures = new fields.SchemaField({
|
||||||
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
b1: woundField(1), b2: woundField(2), b3: woundField(3), b4: woundField(4),
|
|
||||||
b5: woundField(5), b6: woundField(6), b7: woundField(7), b8: woundField(8),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Destin (8 cases)
|
// Destin — jauge entière directe
|
||||||
const destField = () => new fields.SchemaField({
|
|
||||||
checked: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
})
|
|
||||||
schema.destin = new fields.SchemaField({
|
schema.destin = new fields.SchemaField({
|
||||||
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
d1: destField(), d2: destField(), d3: destField(), d4: destField(),
|
|
||||||
d5: destField(), d6: destField(), d7: destField(), d8: destField(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Spleen (8 cases)
|
// Spleen — jauge entière directe
|
||||||
schema.spleen = new fields.SchemaField({
|
schema.spleen = new fields.SchemaField({
|
||||||
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
s1: destField(), s2: destField(), s3: destField(), s4: destField(),
|
|
||||||
s5: destField(), s6: destField(), s7: destField(), s8: destField(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Attributs de personnage (Entregent, Fortune, Rêve, Vision)
|
// Attributs de personnage (Entregent, Fortune, Rêve, Vision)
|
||||||
@@ -95,18 +74,9 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
vision: persoAttrField(),
|
vision: persoAttrField(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Factions - 9 checkboxes per faction (like wound tracks)
|
// Factions — score entier direct (0-9)
|
||||||
const factionField = () => new fields.SchemaField({
|
const factionField = () => new fields.SchemaField({
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 9 }),
|
||||||
level1: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level2: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level3: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level4: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level5: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level6: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level7: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level8: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
level9: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
})
|
})
|
||||||
schema.factions = new fields.SchemaField({
|
schema.factions = new fields.SchemaField({
|
||||||
pinkerton: factionField(),
|
pinkerton: factionField(),
|
||||||
@@ -133,6 +103,16 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
|
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Expérience
|
||||||
|
schema.xp = new fields.SchemaField({
|
||||||
|
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
log: new fields.ArrayField(new fields.SchemaField({
|
||||||
|
montant: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
raison: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
date: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
// Description & notes
|
// Description & notes
|
||||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
@@ -156,15 +136,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
super.prepareDerivedData()
|
super.prepareDerivedData()
|
||||||
|
|
||||||
// Calcul automatique de la valeur de chaque domaine = nombre de cases cochées
|
// Résistance par stat = +2 par domaine atteignant son seuil de spécialisation
|
||||||
for (const stat of Object.values(this.stats)) {
|
|
||||||
for (const skill of Object.values(stat)) {
|
|
||||||
if (typeof skill !== "object" || !("level1" in skill)) continue
|
|
||||||
skill.value = [1,2,3,4,5,6,7,8].filter(i => skill[`level${i}`]).length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calcul automatique de la Résistance par stat = +2 par domaine atteignant son seuil
|
|
||||||
for (const [statId, statData] of Object.entries(this.stats)) {
|
for (const [statId, statData] of Object.entries(this.stats)) {
|
||||||
let res = 0
|
let res = 0
|
||||||
for (const [skillId, skill] of Object.entries(statData)) {
|
for (const [skillId, skill] of Object.entries(statData)) {
|
||||||
@@ -175,19 +147,11 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
statData.res = res
|
statData.res = res
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcul automatique de la valeur de chaque faction = nombre de cases cochées
|
// Initiative PJ : 4 + Mobilité (Corps) + Inspiration (Cœur)
|
||||||
for (const faction of Object.values(this.factions)) {
|
|
||||||
if (typeof faction !== "object" || !("level1" in faction)) continue
|
|
||||||
faction.value = [1,2,3,4,5,6,7,8,9].filter(i => faction[`level${i}`]).length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calcul automatique du niveau des jauges depuis les cases cochées
|
|
||||||
this.blessures.lvl = [1,2,3,4,5,6,7,8].filter(i => this.blessures[`b${i}`]?.checked).length
|
|
||||||
this.destin.lvl = [1,2,3,4,5,6,7,8].filter(i => this.destin[`d${i}`]?.checked).length
|
|
||||||
this.spleen.lvl = [1,2,3,4,5,6,7,8].filter(i => this.spleen[`s${i}`]?.checked).length
|
|
||||||
|
|
||||||
// Initiative PJ : 4 + Mobilité (Corps) + Inspiration (Cœur) [après calcul des domaines]
|
|
||||||
this.initiative = 4 + (this.stats.corps.mobilite?.value ?? 0) + (this.stats.coeur.inspiration?.value ?? 0)
|
this.initiative = 4 + (this.stats.corps.mobilite?.value ?? 0) + (this.stats.coeur.inspiration?.value ?? 0)
|
||||||
|
|
||||||
|
// XP dépensée = somme des montants du log
|
||||||
|
this.xp.depense = this.xp.log.reduce((sum, entry) => sum + entry.montant, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,10 +203,29 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
const statData = this.stats[statId]
|
const statData = this.stats[statId]
|
||||||
if (!statData) return null
|
if (!statData) return null
|
||||||
|
|
||||||
|
return CelestopolRoll.prompt({
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
statId,
|
||||||
|
skillId: null,
|
||||||
|
statLabel: SYSTEM.STATS[statId]?.label,
|
||||||
|
skillLabel: "CELESTOPOL.Roll.resistanceTest",
|
||||||
|
skillValue: statData.res,
|
||||||
|
woundMalus: this.getWoundMalus(),
|
||||||
|
woundLevel: this.blessures.lvl,
|
||||||
|
isResistance: true,
|
||||||
|
rollMoonDie: false,
|
||||||
|
destGaugeFull: false,
|
||||||
|
fortuneValue: 0,
|
||||||
|
difficulty: "normal",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lance une attaque avec une arme (test Échauffourée vs Corps PNJ).
|
* Lance une attaque avec une arme.
|
||||||
* Mêlée : échec → blessure joueur auto-cochée.
|
* Mêlée : test Échauffourée vs Corps PNJ ; échec → blessure joueur.
|
||||||
* Distance : échec → simple raté, pas de blessure joueur.
|
* Distance : test Échauffourée vs Corps PNJ ; échec → pas de blessure joueur.
|
||||||
* Égalité (marge=0) → personne n'est blessé.
|
* Égalité (marge=0) → personne n'est blessé.
|
||||||
* @param {string} itemId - Id de l'item arme
|
* @param {string} itemId - Id de l'item arme
|
||||||
*/
|
*/
|
||||||
@@ -255,23 +238,60 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
if (!echauffouree) return null
|
if (!echauffouree) return null
|
||||||
|
|
||||||
return CelestopolRoll.prompt({
|
return CelestopolRoll.prompt({
|
||||||
actorId: this.parent.id,
|
actorId: this.parent.id,
|
||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
statId: "corps",
|
statId: "corps",
|
||||||
skillId: "echauffouree",
|
skillId: "echauffouree",
|
||||||
statLabel: SYSTEM.STATS.corps.label,
|
statLabel: SYSTEM.STATS.corps.label,
|
||||||
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
||||||
skillValue: echauffouree.value,
|
skillValue: echauffouree.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
destGaugeFull: this.destin.lvl > 0,
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
fortuneValue: this.attributs.fortune.value,
|
fortuneValue: this.attributs.fortune.value,
|
||||||
isCombat: true,
|
isCombat: true,
|
||||||
weaponType: item.system.type,
|
isRangedDefense: false,
|
||||||
weaponName: item.name,
|
weaponType: item.system.type,
|
||||||
weaponDegats: item.system.degats,
|
weaponName: item.name,
|
||||||
|
weaponDegats: item.system.degats,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lance un jet de défense contre une attaque à distance (test Mobilité vs Corps PNJ).
|
||||||
|
* Succès → esquive réussie.
|
||||||
|
* Échec → blessure automatique (le PNJ touche).
|
||||||
|
* @param {string} itemId - Id de l'item arme (distance uniquement)
|
||||||
|
*/
|
||||||
|
async rollRangedDefense(itemId) {
|
||||||
|
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||||
|
const item = this.parent.items.get(itemId)
|
||||||
|
if (!item || item.type !== "weapon" || item.system.type !== "distance") return null
|
||||||
|
|
||||||
|
const mobilite = this.stats.corps.mobilite
|
||||||
|
if (!mobilite) return null
|
||||||
|
|
||||||
|
return CelestopolRoll.prompt({
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
statId: "corps",
|
||||||
|
skillId: "mobilite",
|
||||||
|
statLabel: SYSTEM.STATS.corps.label,
|
||||||
|
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
||||||
|
skillValue: mobilite.value,
|
||||||
|
woundMalus: this.getWoundMalus(),
|
||||||
|
woundLevel: this.blessures.lvl,
|
||||||
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
|
fortuneValue: this.attributs.fortune.value,
|
||||||
|
isCombat: true,
|
||||||
|
isRangedDefense: true,
|
||||||
|
weaponType: "distance",
|
||||||
|
weaponName: item.name,
|
||||||
|
weaponDegats: "0",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,8 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
esprit: statField("esprit"),
|
esprit: statField("esprit"),
|
||||||
})
|
})
|
||||||
|
|
||||||
const woundField = (idx) => new fields.SchemaField({
|
|
||||||
checked: new fields.BooleanField({ required: true, initial: false }),
|
|
||||||
malus: new fields.NumberField({ ...reqInt, initial: SYSTEM.WOUND_LEVELS[idx]?.malus ?? 0 }),
|
|
||||||
})
|
|
||||||
schema.blessures = new fields.SchemaField({
|
schema.blessures = new fields.SchemaField({
|
||||||
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
b1: woundField(1), b2: woundField(2), b3: woundField(3), b4: woundField(4),
|
|
||||||
b5: woundField(5), b6: woundField(6), b7: woundField(7), b8: woundField(8),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.prefs = new fields.SchemaField({
|
schema.prefs = new fields.SchemaField({
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
MANIFEST-000006
|
MANIFEST-000018
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
2026/03/29-17:12:00.740305 7f4bda7ed6c0 Recovering log #4
|
2026/03/30-23:54:32.064751 7ff9c7fff6c0 Recovering log #16
|
||||||
2026/03/29-17:12:00.787211 7f4bda7ed6c0 Delete type=3 #2
|
2026/03/30-23:54:32.074311 7ff9c7fff6c0 Delete type=3 #14
|
||||||
2026/03/29-17:12:00.787276 7f4bda7ed6c0 Delete type=0 #4
|
2026/03/30-23:54:32.074383 7ff9c7fff6c0 Delete type=0 #16
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
2026/03/28-09:47:34.669467 7f0018fff6c0 Delete type=3 #1
|
2026/03/30-09:43:32.818417 7f4bda7ed6c0 Recovering log #12
|
||||||
2026/03/29-17:08:09.756858 7effca7fc6c0 Level-0 table #5: started
|
2026/03/30-09:43:32.832361 7f4bda7ed6c0 Delete type=3 #10
|
||||||
2026/03/29-17:08:09.756892 7effca7fc6c0 Level-0 table #5: 0 bytes OK
|
2026/03/30-09:43:32.832436 7f4bda7ed6c0 Delete type=0 #12
|
||||||
2026/03/29-17:08:09.762851 7effca7fc6c0 Delete type=0 #3
|
2026/03/30-14:14:04.399110 7f4bd8fea6c0 Level-0 table #17: started
|
||||||
2026/03/29-17:08:09.769416 7effca7fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
2026/03/30-14:14:04.399143 7f4bd8fea6c0 Level-0 table #17: 0 bytes OK
|
||||||
|
2026/03/30-14:14:04.436937 7f4bd8fea6c0 Delete type=0 #15
|
||||||
|
2026/03/30-14:14:04.520163 7f4bd8fea6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
BIN
packs-system/anomalies/MANIFEST-000018
Normal file
BIN
packs-system/anomalies/MANIFEST-000018
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000006
|
MANIFEST-000018
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
2026/03/29-17:12:00.691509 7f4bd9fec6c0 Recovering log #4
|
2026/03/30-23:54:32.051664 7ff9fd1fe6c0 Recovering log #16
|
||||||
2026/03/29-17:12:00.738164 7f4bd9fec6c0 Delete type=3 #2
|
2026/03/30-23:54:32.062889 7ff9fd1fe6c0 Delete type=3 #14
|
||||||
2026/03/29-17:12:00.738214 7f4bd9fec6c0 Delete type=0 #4
|
2026/03/30-23:54:32.062954 7ff9fd1fe6c0 Delete type=0 #16
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
2026/03/28-09:47:34.653497 7effcaffd6c0 Delete type=3 #1
|
2026/03/30-09:43:32.805788 7f4bd9fec6c0 Recovering log #12
|
||||||
2026/03/29-17:08:09.762957 7effca7fc6c0 Level-0 table #5: started
|
2026/03/30-09:43:32.816248 7f4bd9fec6c0 Delete type=3 #10
|
||||||
2026/03/29-17:08:09.762977 7effca7fc6c0 Level-0 table #5: 0 bytes OK
|
2026/03/30-09:43:32.816303 7f4bd9fec6c0 Delete type=0 #12
|
||||||
2026/03/29-17:08:09.769218 7effca7fc6c0 Delete type=0 #3
|
2026/03/30-14:14:04.367410 7f4bd8fea6c0 Level-0 table #17: started
|
||||||
2026/03/29-17:08:09.769426 7effca7fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
2026/03/30-14:14:04.367477 7f4bd8fea6c0 Level-0 table #17: 0 bytes OK
|
||||||
|
2026/03/30-14:14:04.398962 7f4bd8fea6c0 Delete type=0 #15
|
||||||
|
2026/03/30-14:14:04.520149 7f4bd8fea6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
BIN
packs-system/aspects/MANIFEST-000018
Normal file
BIN
packs-system/aspects/MANIFEST-000018
Normal file
Binary file not shown.
@@ -131,39 +131,27 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track de niveau (cases à cocher Art Déco)
|
// Points de niveau Art Déco (remplacent les cases à cocher)
|
||||||
.skill-checkboxes-container {
|
.skill-checkboxes-container {
|
||||||
.skill-checkboxes {
|
.skill-checkboxes {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.skill-checkbox-wrapper {
|
.skill-level-dot {
|
||||||
line-height: 0;
|
display: inline-block;
|
||||||
cursor: pointer;
|
width: 13px;
|
||||||
.skill-level-checkbox {
|
height: 13px;
|
||||||
appearance: none;
|
border: 1px solid var(--cel-border);
|
||||||
-webkit-appearance: none;
|
border-radius: 1px;
|
||||||
display: inline-block;
|
background: rgba(255,255,255,0.3);
|
||||||
width: 13px;
|
vertical-align: middle;
|
||||||
height: 13px;
|
transition: background 0.1s, border-color 0.1s;
|
||||||
border: 1px solid var(--cel-border);
|
&.filled {
|
||||||
border-radius: 1px;
|
background: var(--cel-orange);
|
||||||
background: rgba(255,255,255,0.3);
|
border-color: var(--cel-border);
|
||||||
cursor: pointer;
|
|
||||||
vertical-align: middle;
|
|
||||||
transition: background 0.1s, border-color 0.1s;
|
|
||||||
&:checked {
|
|
||||||
background: var(--cel-orange);
|
|
||||||
border-color: var(--cel-border);
|
|
||||||
}
|
|
||||||
&:disabled { cursor: default; }
|
|
||||||
&:disabled:checked {
|
|
||||||
background: var(--cel-orange);
|
|
||||||
border-color: var(--cel-border);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
&[data-action] { cursor: pointer; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +203,11 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
.track-title-destin {
|
||||||
|
cursor: help;
|
||||||
|
border-bottom: 1px dashed currentColor;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-boxes {
|
.track-boxes {
|
||||||
@@ -228,17 +221,29 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
width: 22px;
|
||||||
|
min-height: 22px;
|
||||||
|
border: 2px solid var(--cel-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: rgba(255,255,255,0.45);
|
||||||
|
transition: background 0.1s, border-color 0.1s;
|
||||||
|
|
||||||
|
&.filled {
|
||||||
|
background: var(--cel-orange);
|
||||||
|
border-color: var(--cel-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-action] { cursor: pointer; }
|
||||||
|
|
||||||
input[type="checkbox"] { .cel-box(); accent-color: var(--cel-orange); }
|
|
||||||
.box-label {
|
.box-label {
|
||||||
font-size: 0.65em;
|
font-size: 0.6em;
|
||||||
color: var(--cel-border);
|
color: var(--cel-border);
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.checked input[type="checkbox"] {
|
&.filled .box-label { color: rgba(30,10,0,0.65); }
|
||||||
accent-color: var(--cel-orange);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +279,38 @@
|
|||||||
td { padding: 4px 8px; border-bottom: 1px solid rgba(122,92,32,0.2); }
|
td { padding: 4px 8px; border-bottom: 1px solid rgba(122,92,32,0.2); }
|
||||||
&.custom td { font-style: italic; color: #666; }
|
&.custom td { font-style: italic; color: #666; }
|
||||||
|
|
||||||
|
.faction-checkboxes-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-checkboxes {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-radius: 1px;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
transition: background 0.1s;
|
||||||
|
&.filled { background: var(--cel-orange); border-color: var(--cel-orange); }
|
||||||
|
&[data-action] { cursor: pointer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-count {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
min-width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.faction-value input[type="number"] {
|
.faction-value input[type="number"] {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
.cel-input-std();
|
.cel-input-std();
|
||||||
@@ -335,6 +372,156 @@
|
|||||||
.enriched-html { font-size: 0.9em; line-height: 1.6; }
|
.enriched-html { font-size: 0.9em; line-height: 1.6; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Section Expérience (onglet Biographie) ──────────────────────────────
|
||||||
|
.xp-section {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
|
.section-header { .cel-section-header(); }
|
||||||
|
|
||||||
|
.xp-counters {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 6px 0 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.xp-counter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0,0,0,0.18);
|
||||||
|
border: 1px solid var(--cel-orange);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
min-width: 80px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 0.6em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 52px;
|
||||||
|
text-align: center;
|
||||||
|
.cel-input-std();
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.xp-depense-counter {
|
||||||
|
border-color: rgba(196,154,26,0.4);
|
||||||
|
.xp-depense-value {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(196,154,26,0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.xp-btn-depenser {
|
||||||
|
background: var(--cel-green);
|
||||||
|
border: 1px solid var(--cel-orange);
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-size: 0.78em;
|
||||||
|
padding: 5px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
margin-left: auto;
|
||||||
|
&:hover { background: var(--cel-green-light); }
|
||||||
|
i { margin-right: 4px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.xp-log-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.82em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
thead tr {
|
||||||
|
background: rgba(12,76,12,0.35);
|
||||||
|
th {
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
font-size: 0.75em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 3px 6px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.12);
|
||||||
|
&:nth-child(even) { background: rgba(0,0,0,0.08); }
|
||||||
|
|
||||||
|
td { padding: 3px 6px; color: var(--cel-text-dark, #3a2a0a); }
|
||||||
|
.xp-date { white-space: nowrap; color: var(--cel-border); font-size: 0.9em; }
|
||||||
|
.xp-montant { font-weight: bold; color: #c04040; white-space: nowrap; }
|
||||||
|
|
||||||
|
.xp-suppr-cell {
|
||||||
|
text-align: center;
|
||||||
|
.xp-btn-suppr {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: rgba(180,60,60,0.6);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 1px 4px;
|
||||||
|
&:hover { color: #c04040; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Référentiel de coûts (accordéon)
|
||||||
|
.xp-ref {
|
||||||
|
margin-top: 6px;
|
||||||
|
summary {
|
||||||
|
font-size: 0.78em;
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
user-select: none;
|
||||||
|
&:hover { color: var(--cel-orange); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.xp-ref-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.78em;
|
||||||
|
margin-top: 5px;
|
||||||
|
opacity: 0.75;
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85em;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.25);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.1);
|
||||||
|
color: var(--cel-text-dark, #3a2a0a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Bloc Anomalie sur l'onglet Domaines ──────────────────────────────────
|
// ── Bloc Anomalie sur l'onglet Domaines ──────────────────────────────────
|
||||||
.anomaly-block {
|
.anomaly-block {
|
||||||
border: 1px solid rgba(196,154,26,0.5);
|
border: 1px solid rgba(196,154,26,0.5);
|
||||||
|
|||||||
@@ -277,18 +277,63 @@
|
|||||||
&:disabled { cursor: default; opacity: 0.7; }
|
&:disabled { cursor: default; opacity: 0.7; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.faction-count {
|
// ── Badge d'état de blessure intégré dans header-stats-row ─────────────────
|
||||||
margin-left: 8px;
|
.wound-status-badge {
|
||||||
font-weight: bold;
|
// Supprime le fond/bord générique du .header-stat
|
||||||
color: var(--cel-orange);
|
background: transparent;
|
||||||
min-width: 20px;
|
border-color: currentColor;
|
||||||
text-align: right;
|
|
||||||
|
label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wound-value {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 0.95em;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.wound-duration { font-weight: normal; opacity: 0.85; }
|
||||||
|
.wound-malus { opacity: 0.9; }
|
||||||
|
|
||||||
|
// Niveaux 1-2 : aucun malus → vert doux
|
||||||
|
&.wound-level-1, &.wound-level-2 {
|
||||||
|
color: #6abf5e;
|
||||||
|
background: rgba(106,191,94,0.10);
|
||||||
|
label { color: #6abf5e; }
|
||||||
|
}
|
||||||
|
// Niveaux 3-4 : malus -1 → ambre
|
||||||
|
&.wound-level-3, &.wound-level-4 {
|
||||||
|
color: #e8a020;
|
||||||
|
background: rgba(232,160,32,0.13);
|
||||||
|
label { color: #e8a020; }
|
||||||
|
}
|
||||||
|
// Niveaux 5-6 : malus -2 → orange vif
|
||||||
|
&.wound-level-5, &.wound-level-6 {
|
||||||
|
color: #e06020;
|
||||||
|
background: rgba(224,96,32,0.13);
|
||||||
|
label { color: #e06020; }
|
||||||
|
}
|
||||||
|
// Niveau 7 : malus -3 → rouge
|
||||||
|
&.wound-level-7 {
|
||||||
|
color: #d43030;
|
||||||
|
background: rgba(212,48,48,0.13);
|
||||||
|
label { color: #d43030; }
|
||||||
|
}
|
||||||
|
// Niveau 8 : hors combat → rouge sombre + pulsation
|
||||||
|
&.wound-level-8 {
|
||||||
|
color: #c00;
|
||||||
|
background: rgba(192,0,0,0.18);
|
||||||
|
label { color: #c00; }
|
||||||
|
animation: wound-pulse 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.faction-value-input { width: 40px; margin-left: 8px; }
|
@keyframes wound-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
.faction-row {
|
50% { opacity: 0.55; }
|
||||||
pointer-events: auto !important;
|
|
||||||
td { pointer-events: auto !important; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
"distance": 5,
|
"distance": 5,
|
||||||
"units": "m"
|
"units": "m"
|
||||||
},
|
},
|
||||||
"primaryTokenAttribute": "resource",
|
"primaryTokenAttribute": "blessures.lvl",
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"background": "systems/fvtt-celestopol/assets/ui/celestopol_background.webp"
|
"background": "systems/fvtt-celestopol/assets/ui/celestopol_background.webp"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,76 @@
|
|||||||
<div class="tab biography {{tab.cssClass}}" data-group="sheet" data-tab="biography">
|
<div class="tab biography {{tab.cssClass}}" data-group="sheet" data-tab="biography">
|
||||||
|
|
||||||
|
{{!-- Section XP --}}
|
||||||
|
<div class="xp-section">
|
||||||
|
<div class="section-header">{{localize "CELESTOPOL.XP.title"}}</div>
|
||||||
|
|
||||||
|
<div class="xp-counters">
|
||||||
|
<div class="xp-counter">
|
||||||
|
<label>{{localize "CELESTOPOL.XP.actuel"}}</label>
|
||||||
|
{{formInput systemFields.xp.fields.actuel value=system.xp.actuel name="system.xp.actuel"}}
|
||||||
|
</div>
|
||||||
|
<div class="xp-counter xp-depense-counter">
|
||||||
|
<label>{{localize "CELESTOPOL.XP.depense"}}</label>
|
||||||
|
<span class="xp-depense-value">{{system.xp.depense}}</span>
|
||||||
|
</div>
|
||||||
|
{{#if isPlayMode}}
|
||||||
|
<button type="button" class="xp-btn-depenser" data-action="depenseXp">
|
||||||
|
<i class="fa-solid fa-coins"></i> {{localize "CELESTOPOL.XP.depenser"}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Log des dépenses --}}
|
||||||
|
{{#unless xpLogEmpty}}
|
||||||
|
<table class="xp-log-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{localize "CELESTOPOL.XP.date"}}</th>
|
||||||
|
<th>{{localize "CELESTOPOL.XP.raison"}}</th>
|
||||||
|
<th>{{localize "CELESTOPOL.XP.montant"}}</th>
|
||||||
|
{{#if isEditMode}}<th></th>{{/if}}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each system.xp.log}}
|
||||||
|
<tr>
|
||||||
|
<td class="xp-date">{{this.date}}</td>
|
||||||
|
<td class="xp-raison">{{this.raison}}</td>
|
||||||
|
<td class="xp-montant">−{{this.montant}}</td>
|
||||||
|
{{#if ../isEditMode}}
|
||||||
|
<td class="xp-suppr-cell">
|
||||||
|
<button type="button" class="xp-btn-suppr" data-action="supprimerXpLog"
|
||||||
|
data-idx="{{@index}}" title="{{localize 'CELESTOPOL.XP.supprimer'}}">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{!-- Tableau de référence des coûts --}}
|
||||||
|
<details class="xp-ref">
|
||||||
|
<summary>{{localize "CELESTOPOL.XP.refTitle"}}</summary>
|
||||||
|
<table class="xp-ref-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{localize "CELESTOPOL.XP.refAmelioration"}}</th>
|
||||||
|
<th>{{localize "CELESTOPOL.XP.refCout"}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>{{localize "CELESTOPOL.XP.refAugmenterSpec"}}</td><td>{{localize "CELESTOPOL.XP.refCoutNiveau"}}</td></tr>
|
||||||
|
<tr><td>{{localize "CELESTOPOL.XP.refAcquerirAspect"}}</td><td>5</td></tr>
|
||||||
|
<tr><td>{{localize "CELESTOPOL.XP.refAugmenterAspect"}}</td><td>5</td></tr>
|
||||||
|
<tr><td>{{localize "CELESTOPOL.XP.refAcquerirAttribut"}}</td><td>{{localize "CELESTOPOL.XP.refCoutAttributTotal"}}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{!-- Description / Biographie --}}
|
{{!-- Description / Biographie --}}
|
||||||
<div class="biography-section">
|
<div class="biography-section">
|
||||||
<div class="section-header">{{localize "CELESTOPOL.Actor.description"}}</div>
|
<div class="section-header">{{localize "CELESTOPOL.Actor.description"}}</div>
|
||||||
|
|||||||
@@ -4,19 +4,14 @@
|
|||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
||||||
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
|
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
|
||||||
<strong>{{system.blessures.lvl}}</strong>
|
<strong>{{lookup @root.woundLevels system.blessures.lvl 'malus'}}</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes">
|
<div class="track-boxes">
|
||||||
{{#each (array "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8") as |key idx|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<div class="track-box {{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}">
|
<div class="track-box {{#if (lte lvl ../system.blessures.lvl)}}filled{{/if}}"
|
||||||
<input type="checkbox" name="system.blessures.{{key}}.checked"
|
{{#if ../isEditable}}data-action="trackBox" data-path="system.blessures.lvl" data-index="{{lvl}}"{{/if}}>
|
||||||
{{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}
|
<span class="box-label">{{lookup @root.woundLevels lvl 'malus'}}</span>
|
||||||
{{#unless ../isEditable}}disabled{{/unless}}
|
|
||||||
class="wound-checkbox"
|
|
||||||
data-track="blessures"
|
|
||||||
data-index="{{idx}}">
|
|
||||||
<label class="box-label">{{lookup ../system.blessures key 'malus'}}</label>
|
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
@@ -29,18 +24,13 @@
|
|||||||
{{!-- Destin --}}
|
{{!-- Destin --}}
|
||||||
<section class="track-section">
|
<section class="track-section">
|
||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title">{{localize "CELESTOPOL.Track.destin"}}</span>
|
<span class="track-title track-title-destin"
|
||||||
|
title="{{localize 'CELESTOPOL.Track.destinTooltip'}}">{{localize "CELESTOPOL.Track.destin"}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes destin-boxes">
|
<div class="track-boxes destin-boxes">
|
||||||
{{#each (array "d1" "d2" "d3" "d4" "d5" "d6" "d7" "d8") as |key|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<div class="track-box destiny {{#if (lookup ../system.destin key 'checked')}}checked{{/if}}">
|
<div class="track-box destiny {{#if (lte lvl ../system.destin.lvl)}}filled{{/if}}"
|
||||||
<input type="checkbox" name="system.destin.{{key}}.checked"
|
{{#if ../isEditable}}data-action="trackBox" data-path="system.destin.lvl" data-index="{{lvl}}"{{/if}}></div>
|
||||||
{{#if (lookup ../system.destin key 'checked')}}checked{{/if}}
|
|
||||||
{{#unless ../isEditable}}disabled{{/unless}}
|
|
||||||
class="wound-checkbox"
|
|
||||||
data-track="destin"
|
|
||||||
data-index="{{@index}}">
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="track-level">
|
<div class="track-level">
|
||||||
@@ -55,15 +45,9 @@
|
|||||||
<span class="track-title">{{localize "CELESTOPOL.Track.spleen"}}</span>
|
<span class="track-title">{{localize "CELESTOPOL.Track.spleen"}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes spleen-boxes">
|
<div class="track-boxes spleen-boxes">
|
||||||
{{#each (array "s1" "s2" "s3" "s4" "s5" "s6" "s7" "s8") as |key|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<div class="track-box spleen {{#if (lookup ../system.spleen key 'checked')}}checked{{/if}}">
|
<div class="track-box spleen {{#if (lte lvl ../system.spleen.lvl)}}filled{{/if}}"
|
||||||
<input type="checkbox" name="system.spleen.{{key}}.checked"
|
{{#if ../isEditable}}data-action="trackBox" data-path="system.spleen.lvl" data-index="{{lvl}}"{{/if}}></div>
|
||||||
{{#if (lookup ../system.spleen key 'checked')}}checked{{/if}}
|
|
||||||
{{#unless ../isEditable}}disabled{{/unless}}
|
|
||||||
class="wound-checkbox"
|
|
||||||
data-track="spleen"
|
|
||||||
data-index="{{@index}}">
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="track-level">
|
<div class="track-level">
|
||||||
|
|||||||
@@ -18,18 +18,13 @@
|
|||||||
<span class="skill-name">{{localize skill.label}}</span>
|
<span class="skill-name">{{localize skill.label}}</span>
|
||||||
<div class="skill-checkboxes-container">
|
<div class="skill-checkboxes-container">
|
||||||
<div class="skill-checkboxes">
|
<div class="skill-checkboxes">
|
||||||
{{#each (array 1 2 3 4 5 6 7 8) as |level|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<label class="skill-checkbox-wrapper">
|
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"
|
||||||
<input type="checkbox" name="system.stats.{{statId}}.{{skillId}}.level{{level}}"
|
data-action="skillLevel" data-stat-id="{{statId}}" data-skill-id="{{skillId}}" data-index="{{lvl}}"></span>
|
||||||
{{#if (lookup (lookup (lookup @root.system.stats statId) skillId) (concat 'level' level))}}checked{{/if}}
|
|
||||||
class="skill-level-checkbox">
|
|
||||||
</label>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" name="system.stats.{{statId}}.{{skillId}}.value"
|
<span class="skill-value">{{lookup @root.system.stats statId skillId 'value'}}</span>
|
||||||
value="{{lookup (lookup @root.system.stats statId) skillId 'value'}}"
|
|
||||||
min="0" max="8" class="skill-value-input">
|
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="skill-row rollable" data-stat-id="{{statId}}" data-skill-id="{{skillId}}"
|
<div class="skill-row rollable" data-stat-id="{{statId}}" data-skill-id="{{skillId}}"
|
||||||
@@ -37,16 +32,12 @@
|
|||||||
<span class="skill-name">{{localize skill.label}}</span>
|
<span class="skill-name">{{localize skill.label}}</span>
|
||||||
<div class="skill-checkboxes-container">
|
<div class="skill-checkboxes-container">
|
||||||
<div class="skill-checkboxes">
|
<div class="skill-checkboxes">
|
||||||
{{#each (array 1 2 3 4 5 6 7 8) as |level|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<label class="skill-checkbox-wrapper">
|
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"></span>
|
||||||
<input type="checkbox"
|
|
||||||
{{#if (lookup (lookup (lookup @root.system.stats statId) skillId) (concat 'level' level))}}checked{{/if}}
|
|
||||||
disabled class="skill-level-checkbox">
|
|
||||||
</label>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="skill-value">{{lookup (lookup @root.system.stats statId) skillId 'value'}}</span>
|
<span class="skill-value">{{lookup @root.system.stats statId skillId 'value'}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
{{#unless ../isEditMode}}
|
{{#unless ../isEditMode}}
|
||||||
<a data-action="attack" data-item-id="{{item.id}}" title="{{localize 'CELESTOPOL.Combat.attack'}}"><i class="fas fa-khanda"></i></a>
|
<a data-action="attack" data-item-id="{{item.id}}" title="{{localize 'CELESTOPOL.Combat.attack'}}"><i class="fas fa-khanda"></i></a>
|
||||||
|
{{#if (eq item.system.type "distance")}}
|
||||||
|
<a data-action="rangedDefense" data-item-id="{{item.id}}" title="{{localize 'CELESTOPOL.Combat.rangedDefenseTitle'}}"><i class="fas fa-shield-halved"></i></a>
|
||||||
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
||||||
|
|||||||
@@ -13,32 +13,12 @@
|
|||||||
<td class="faction-value">
|
<td class="faction-value">
|
||||||
<div class="faction-checkboxes-container">
|
<div class="faction-checkboxes-container">
|
||||||
<div class="faction-checkboxes">
|
<div class="faction-checkboxes">
|
||||||
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
|
{{#each (range 9) as |level|}}
|
||||||
{{#if @root.isEditMode}}
|
<span class="faction-dot {{#if (lte level (lookup @root.system.factions factionId 'value'))}}filled{{/if}}"
|
||||||
<label class="faction-checkbox-wrapper">
|
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{factionId}}" data-index="{{level}}"{{/if}}></span>
|
||||||
<input type="checkbox" name="system.factions.{{factionId}}.level{{level}}"
|
|
||||||
{{#if (lookup (lookup @root.system.factions factionId) (concat 'level' level))}}checked{{/if}}
|
|
||||||
class="faction-checkbox"
|
|
||||||
data-faction="{{factionId}}"
|
|
||||||
data-level="{{level}}">
|
|
||||||
</label>
|
|
||||||
{{else}}
|
|
||||||
<label class="faction-checkbox-wrapper">
|
|
||||||
<input type="checkbox"
|
|
||||||
{{#if (lookup (lookup @root.system.factions factionId) (concat 'level' level))}}checked{{/if}}
|
|
||||||
disabled class="faction-checkbox">
|
|
||||||
</label>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="faction-count">
|
<span class="faction-count">{{lookup @root.system.factions factionId 'value'}}</span>
|
||||||
{{#if ../isEditMode}}
|
|
||||||
<input type="number" name="system.factions.{{factionId}}.value"
|
|
||||||
value="{{lookup (lookup ../system.factions factionId) 'value'}}" min="0" max="9" class="faction-value-input">
|
|
||||||
{{else}}
|
|
||||||
{{lookup (lookup ../system.factions factionId) 'value'}}
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -58,32 +38,12 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="faction-checkboxes-container">
|
<div class="faction-checkboxes-container">
|
||||||
<div class="faction-checkboxes">
|
<div class="faction-checkboxes">
|
||||||
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
|
{{#each (range 9) as |level|}}
|
||||||
{{#if ../isEditMode}}
|
<span class="faction-dot {{#if (lte level ../system.factions.perso1.value)}}filled{{/if}}"
|
||||||
<label class="faction-checkbox-wrapper">
|
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso1" data-index="{{level}}"{{/if}}></span>
|
||||||
<input type="checkbox" name="system.factions.perso1.level{{level}}"
|
|
||||||
{{#if (lookup ../system.factions.perso1 (concat 'level' level))}}checked{{/if}}
|
|
||||||
class="faction-checkbox"
|
|
||||||
data-faction="perso1"
|
|
||||||
data-level="{{level}}">
|
|
||||||
</label>
|
|
||||||
{{else}}
|
|
||||||
<label class="faction-checkbox-wrapper">
|
|
||||||
<input type="checkbox"
|
|
||||||
{{#if (lookup ../system.factions.perso1 (concat 'level' level))}}checked{{/if}}
|
|
||||||
disabled class="faction-checkbox">
|
|
||||||
</label>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="faction-count">
|
<span class="faction-count">{{system.factions.perso1.value}}</span>
|
||||||
{{#if ../isEditMode}}
|
|
||||||
<input type="number" name="system.factions.perso1.value"
|
|
||||||
value="{{system.factions.perso1.value}}" min="0" max="9" class="faction-value-input">
|
|
||||||
{{else}}
|
|
||||||
{{system.factions.perso1.value}}
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -100,32 +60,12 @@
|
|||||||
<td>
|
<td>
|
||||||
<div class="faction-checkboxes-container">
|
<div class="faction-checkboxes-container">
|
||||||
<div class="faction-checkboxes">
|
<div class="faction-checkboxes">
|
||||||
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
|
{{#each (range 9) as |level|}}
|
||||||
{{#if ../isEditMode}}
|
<span class="faction-dot {{#if (lte level ../system.factions.perso2.value)}}filled{{/if}}"
|
||||||
<label class="faction-checkbox-wrapper">
|
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso2" data-index="{{level}}"{{/if}}></span>
|
||||||
<input type="checkbox" name="system.factions.perso2.level{{level}}"
|
|
||||||
{{#if (lookup ../system.factions.perso2 (concat 'level' level))}}checked{{/if}}
|
|
||||||
class="faction-checkbox"
|
|
||||||
data-faction="perso2"
|
|
||||||
data-level="{{level}}">
|
|
||||||
</label>
|
|
||||||
{{else}}
|
|
||||||
<label class="faction-checkbox-wrapper">
|
|
||||||
<input type="checkbox"
|
|
||||||
{{#if (lookup ../system.factions.perso2 (concat 'level' level))}}checked{{/if}}
|
|
||||||
disabled class="faction-checkbox">
|
|
||||||
</label>
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="faction-count">
|
<span class="faction-count">{{system.factions.perso2.value}}</span>
|
||||||
{{#if ../isEditMode}}
|
|
||||||
<input type="number" name="system.factions.perso2.value"
|
|
||||||
value="{{system.factions.perso2.value}}" min="0" max="9" class="faction-value-input">
|
|
||||||
{{else}}
|
|
||||||
{{system.factions.perso2.value}}
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -70,6 +70,18 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
{{#with (lookup woundLevels system.blessures.lvl) as |wound|}}
|
||||||
|
{{#if wound.id}}
|
||||||
|
<div class="header-stat wound-status-badge wound-level-{{wound.id}}">
|
||||||
|
<label>{{localize "CELESTOPOL.Wound.status"}}</label>
|
||||||
|
<span class="wound-value">
|
||||||
|
<span class="wound-label">{{localize wound.label}}</span>
|
||||||
|
{{#if wound.duration}}<span class="wound-duration"> — {{localize wound.duration}}</span>{{/if}}
|
||||||
|
{{#if wound.malus}}<span class="wound-malus"> ({{wound.malus}})</span>{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/with}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,11 @@
|
|||||||
<span class="result-icon">✦✦</span>
|
<span class="result-icon">✦✦</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalSuccess"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalSuccess"}}</span>
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
|
{{#if isRangedDefense}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseSuccess"}}</span>
|
||||||
|
{{else}}
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalSuccessDesc"}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalSuccessDesc"}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -131,13 +135,23 @@
|
|||||||
<span class="result-icon">✦</span>
|
<span class="result-icon">✦</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.success"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.success"}}</span>
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
|
{{#if isRangedDefense}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseSuccess"}}</span>
|
||||||
|
{{else}}
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
{{else if isCriticalFailure}}
|
{{else if isCriticalFailure}}
|
||||||
<span class="result-icon">✖✖</span>
|
<span class="result-icon">✖✖</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalFailure"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalFailure"}}</span>
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
<span class="result-desc">{{#if (eq weaponType "melee")}}{{localize "CELESTOPOL.Combat.failureHit"}}{{else}}{{localize "CELESTOPOL.Combat.distanceNoWound"}}{{/if}}</span>
|
{{#if (eq weaponType "melee")}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.failureHit"}}</span>
|
||||||
|
{{else if isRangedDefense}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseFailure"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.distanceNoWound"}}</span>
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalFailureDesc"}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalFailureDesc"}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -145,7 +159,13 @@
|
|||||||
<span class="result-icon">✖</span>
|
<span class="result-icon">✖</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
<span class="result-desc">{{#if (eq weaponType "melee")}}{{localize "CELESTOPOL.Combat.failureHit"}}{{else}}{{localize "CELESTOPOL.Combat.distanceNoWound"}}{{/if}}</span>
|
{{#if (eq weaponType "melee")}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.failureHit"}}</span>
|
||||||
|
{{else if isRangedDefense}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseFailure"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.distanceNoWound"}}</span>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +174,7 @@
|
|||||||
{{#if woundTaken}}
|
{{#if woundTaken}}
|
||||||
<div class="resistance-wound-notice">
|
<div class="resistance-wound-notice">
|
||||||
<span class="wound-icon">🩹</span>
|
<span class="wound-icon">🩹</span>
|
||||||
<span>{{#if isCombat}}{{localize "CELESTOPOL.Combat.playerWounded"}}{{else}}{{localize "CELESTOPOL.Roll.woundTaken"}}{{/if}}</span>
|
<span>{{#if isCombat}}{{#if isRangedDefense}}{{localize "CELESTOPOL.Combat.rangedDefensePlayerWounded"}}{{else}}{{localize "CELESTOPOL.Combat.playerWounded"}}{{/if}}{{else}}{{localize "CELESTOPOL.Roll.woundTaken"}}{{/if}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,16 @@
|
|||||||
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes">
|
<div class="track-boxes">
|
||||||
{{#each (array "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8") as |key|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<div class="track-box {{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}">
|
<div class="track-box {{#if (lte lvl ../system.blessures.lvl)}}filled{{/if}}"
|
||||||
<input type="checkbox" name="system.blessures.{{key}}.checked"
|
{{#if ../isEditable}}data-action="trackBox" data-path="system.blessures.lvl" data-index="{{lvl}}"{{/if}}>
|
||||||
{{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}
|
<span class="box-label">{{lookup @root.woundLevels lvl 'malus'}}</span>
|
||||||
{{#unless ../isEditable}}disabled{{/unless}}>
|
|
||||||
<label class="box-label">{{lookup ../system.blessures key 'malus'}}</label>
|
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="track-level">
|
<div class="track-level">
|
||||||
<label>{{localize "CELESTOPOL.Track.level"}}</label>
|
<label>{{localize "CELESTOPOL.Track.level"}}</label>
|
||||||
{{#if isEditMode}}
|
<span>{{system.blessures.lvl}}</span>
|
||||||
<input type="number" name="system.blessures.lvl" value="{{system.blessures.lvl}}" min="0" max="8">
|
|
||||||
{{else}}
|
|
||||||
<span>{{system.blessures.lvl}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,18 @@
|
|||||||
<span class="anomaly-type-display">{{localize (lookup (lookup anomalyTypes system.anomaly.type) 'label')}}</span>
|
<span class="anomaly-type-display">{{localize (lookup (lookup anomalyTypes system.anomaly.type) 'label')}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{#with (lookup woundLevels system.blessures.lvl) as |wound|}}
|
||||||
|
{{#if wound.id}}
|
||||||
|
<div class="header-stat wound-status-badge wound-level-{{wound.id}}">
|
||||||
|
<label>{{localize "CELESTOPOL.Wound.status"}}</label>
|
||||||
|
<span class="wound-value">
|
||||||
|
<span class="wound-label">{{localize wound.label}}</span>
|
||||||
|
{{#if wound.duration}}<span class="wound-duration"> — {{localize wound.duration}}</span>{{/if}}
|
||||||
|
{{#if wound.malus}}<span class="wound-malus"> ({{wound.malus}})</span>{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/with}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
|||||||
@@ -6,9 +6,13 @@
|
|||||||
{{!-- Arme (mode combat) --}}
|
{{!-- Arme (mode combat) --}}
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
<div class="roll-weapon-line">
|
<div class="roll-weapon-line">
|
||||||
<span class="weapon-icon">⚔</span>
|
<span class="weapon-icon">{{#if isRangedDefense}}🛡{{else}}⚔{{/if}}</span>
|
||||||
<span class="weapon-name">{{weaponName}}</span>
|
<span class="weapon-name">{{weaponName}}</span>
|
||||||
|
{{#if isRangedDefense}}
|
||||||
|
<span class="weapon-tag ranged-defense">{{localize "CELESTOPOL.Combat.rangedDefenseTag"}}</span>
|
||||||
|
{{else}}
|
||||||
<span class="weapon-degats">+{{weaponDegats}}</span>
|
<span class="weapon-degats">+{{weaponDegats}}</span>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="roll-skill-line">
|
<div class="roll-skill-line">
|
||||||
|
|||||||
Reference in New Issue
Block a user