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:
2026-03-31 00:33:59 +02:00
parent 79a68ee9ab
commit 9dbd614c5a
40 changed files with 849 additions and 529 deletions

View File

@@ -1,5 +1,76 @@
<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 --}}
<div class="biography-section">
<div class="section-header">{{localize "CELESTOPOL.Actor.description"}}</div>

View File

@@ -4,19 +4,14 @@
<div class="track-header">
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
<strong>{{system.blessures.lvl}}</strong>
<strong>{{lookup @root.woundLevels system.blessures.lvl 'malus'}}</strong>
</span>
</div>
<div class="track-boxes">
{{#each (array "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8") as |key idx|}}
<div class="track-box {{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}">
<input type="checkbox" name="system.blessures.{{key}}.checked"
{{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}
{{#unless ../isEditable}}disabled{{/unless}}
class="wound-checkbox"
data-track="blessures"
data-index="{{idx}}">
<label class="box-label">{{lookup ../system.blessures key 'malus'}}</label>
{{#each (range 8) as |lvl|}}
<div class="track-box {{#if (lte lvl ../system.blessures.lvl)}}filled{{/if}}"
{{#if ../isEditable}}data-action="trackBox" data-path="system.blessures.lvl" data-index="{{lvl}}"{{/if}}>
<span class="box-label">{{lookup @root.woundLevels lvl 'malus'}}</span>
</div>
{{/each}}
</div>
@@ -29,18 +24,13 @@
{{!-- Destin --}}
<section class="track-section">
<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 class="track-boxes destin-boxes">
{{#each (array "d1" "d2" "d3" "d4" "d5" "d6" "d7" "d8") as |key|}}
<div class="track-box destiny {{#if (lookup ../system.destin key 'checked')}}checked{{/if}}">
<input type="checkbox" name="system.destin.{{key}}.checked"
{{#if (lookup ../system.destin key 'checked')}}checked{{/if}}
{{#unless ../isEditable}}disabled{{/unless}}
class="wound-checkbox"
data-track="destin"
data-index="{{@index}}">
</div>
{{#each (range 8) as |lvl|}}
<div class="track-box destiny {{#if (lte lvl ../system.destin.lvl)}}filled{{/if}}"
{{#if ../isEditable}}data-action="trackBox" data-path="system.destin.lvl" data-index="{{lvl}}"{{/if}}></div>
{{/each}}
</div>
<div class="track-level">
@@ -55,15 +45,9 @@
<span class="track-title">{{localize "CELESTOPOL.Track.spleen"}}</span>
</div>
<div class="track-boxes spleen-boxes">
{{#each (array "s1" "s2" "s3" "s4" "s5" "s6" "s7" "s8") as |key|}}
<div class="track-box spleen {{#if (lookup ../system.spleen key 'checked')}}checked{{/if}}">
<input type="checkbox" name="system.spleen.{{key}}.checked"
{{#if (lookup ../system.spleen key 'checked')}}checked{{/if}}
{{#unless ../isEditable}}disabled{{/unless}}
class="wound-checkbox"
data-track="spleen"
data-index="{{@index}}">
</div>
{{#each (range 8) as |lvl|}}
<div class="track-box spleen {{#if (lte lvl ../system.spleen.lvl)}}filled{{/if}}"
{{#if ../isEditable}}data-action="trackBox" data-path="system.spleen.lvl" data-index="{{lvl}}"{{/if}}></div>
{{/each}}
</div>
<div class="track-level">

View File

@@ -18,18 +18,13 @@
<span class="skill-name">{{localize skill.label}}</span>
<div class="skill-checkboxes-container">
<div class="skill-checkboxes">
{{#each (array 1 2 3 4 5 6 7 8) as |level|}}
<label class="skill-checkbox-wrapper">
<input type="checkbox" name="system.stats.{{statId}}.{{skillId}}.level{{level}}"
{{#if (lookup (lookup (lookup @root.system.stats statId) skillId) (concat 'level' level))}}checked{{/if}}
class="skill-level-checkbox">
</label>
{{#each (range 8) as |lvl|}}
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"
data-action="skillLevel" data-stat-id="{{statId}}" data-skill-id="{{skillId}}" data-index="{{lvl}}"></span>
{{/each}}
</div>
</div>
<input type="number" name="system.stats.{{statId}}.{{skillId}}.value"
value="{{lookup (lookup @root.system.stats statId) skillId 'value'}}"
min="0" max="8" class="skill-value-input">
<span class="skill-value">{{lookup @root.system.stats statId skillId 'value'}}</span>
</div>
{{else}}
<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>
<div class="skill-checkboxes-container">
<div class="skill-checkboxes">
{{#each (array 1 2 3 4 5 6 7 8) as |level|}}
<label class="skill-checkbox-wrapper">
<input type="checkbox"
{{#if (lookup (lookup (lookup @root.system.stats statId) skillId) (concat 'level' level))}}checked{{/if}}
disabled class="skill-level-checkbox">
</label>
{{#each (range 8) as |lvl|}}
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"></span>
{{/each}}
</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>
{{/if}}
{{/each}}

View File

@@ -18,6 +18,9 @@
<div class="item-controls">
{{#unless ../isEditMode}}
<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}}
<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}}

View File

@@ -13,32 +13,12 @@
<td class="faction-value">
<div class="faction-checkboxes-container">
<div class="faction-checkboxes">
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
{{#if @root.isEditMode}}
<label class="faction-checkbox-wrapper">
<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 (range 9) as |level|}}
<span class="faction-dot {{#if (lte level (lookup @root.system.factions factionId 'value'))}}filled{{/if}}"
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{factionId}}" data-index="{{level}}"{{/if}}></span>
{{/each}}
</div>
<span class="faction-count">
{{#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>
<span class="faction-count">{{lookup @root.system.factions factionId 'value'}}</span>
</div>
</td>
</tr>
@@ -58,32 +38,12 @@
<td>
<div class="faction-checkboxes-container">
<div class="faction-checkboxes">
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
{{#if ../isEditMode}}
<label class="faction-checkbox-wrapper">
<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 (range 9) as |level|}}
<span class="faction-dot {{#if (lte level ../system.factions.perso1.value)}}filled{{/if}}"
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso1" data-index="{{level}}"{{/if}}></span>
{{/each}}
</div>
<span class="faction-count">
{{#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>
<span class="faction-count">{{system.factions.perso1.value}}</span>
</div>
</td>
</tr>
@@ -100,32 +60,12 @@
<td>
<div class="faction-checkboxes-container">
<div class="faction-checkboxes">
{{#each (array 1 2 3 4 5 6 7 8 9) as |level|}}
{{#if ../isEditMode}}
<label class="faction-checkbox-wrapper">
<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 (range 9) as |level|}}
<span class="faction-dot {{#if (lte level ../system.factions.perso2.value)}}filled{{/if}}"
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso2" data-index="{{level}}"{{/if}}></span>
{{/each}}
</div>
<span class="faction-count">
{{#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>
<span class="faction-count">{{system.factions.perso2.value}}</span>
</div>
</td>
</tr>

View File

@@ -70,6 +70,18 @@
{{/if}}
</div>
{{/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>

View File

@@ -123,7 +123,11 @@
<span class="result-icon">✦✦</span>
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalSuccess"}}</span>
{{#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>
{{/if}}
{{else}}
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalSuccessDesc"}}</span>
{{/if}}
@@ -131,13 +135,23 @@
<span class="result-icon">✦</span>
<span class="result-label">{{localize "CELESTOPOL.Roll.success"}}</span>
{{#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>
{{/if}}
{{/if}}
{{else if isCriticalFailure}}
<span class="result-icon">✖✖</span>
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalFailure"}}</span>
{{#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}}
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalFailureDesc"}}</span>
{{/if}}
@@ -145,7 +159,13 @@
<span class="result-icon">✖</span>
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
{{#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}}
</div>
@@ -154,7 +174,7 @@
{{#if woundTaken}}
<div class="resistance-wound-notice">
<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>
{{/if}}

View File

@@ -4,22 +4,16 @@
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
</div>
<div class="track-boxes">
{{#each (array "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8") as |key|}}
<div class="track-box {{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}">
<input type="checkbox" name="system.blessures.{{key}}.checked"
{{#if (lookup ../system.blessures key 'checked')}}checked{{/if}}
{{#unless ../isEditable}}disabled{{/unless}}>
<label class="box-label">{{lookup ../system.blessures key 'malus'}}</label>
{{#each (range 8) as |lvl|}}
<div class="track-box {{#if (lte lvl ../system.blessures.lvl)}}filled{{/if}}"
{{#if ../isEditable}}data-action="trackBox" data-path="system.blessures.lvl" data-index="{{lvl}}"{{/if}}>
<span class="box-label">{{lookup @root.woundLevels lvl 'malus'}}</span>
</div>
{{/each}}
</div>
<div class="track-level">
<label>{{localize "CELESTOPOL.Track.level"}}</label>
{{#if isEditMode}}
<input type="number" name="system.blessures.lvl" value="{{system.blessures.lvl}}" min="0" max="8">
{{else}}
<span>{{system.blessures.lvl}}</span>
{{/if}}
<span>{{system.blessures.lvl}}</span>
</div>
</section>

View File

@@ -37,6 +37,18 @@
<span class="anomaly-type-display">{{localize (lookup (lookup anomalyTypes system.anomaly.type) 'label')}}</span>
{{/if}}
</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 class="header-buttons">

View File

@@ -6,9 +6,13 @@
{{!-- Arme (mode combat) --}}
{{#if isCombat}}
<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>
{{#if isRangedDefense}}
<span class="weapon-tag ranged-defense">{{localize "CELESTOPOL.Combat.rangedDefenseTag"}}</span>
{{else}}
<span class="weapon-degats">+{{weaponDegats}}</span>
{{/if}}
</div>
{{/if}}
<div class="roll-skill-line">