feat(repos): nouveau mode sommeil avec sélection heures, dialogs CD intégrés, toolbar équipement

This commit is contained in:
fabres
2026-07-22 00:29:56 +02:00
parent 1db733f379
commit 7599db07ad
9 changed files with 360 additions and 30 deletions
+17
View File
@@ -5263,6 +5263,23 @@ body {
.system-foundryvtt-reve-de-dragon .conteneur-parent-badge::after {
content: ")";
}
.system-foundryvtt-reve-de-dragon .inventaire-toolbar {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-bottom: 0.5rem;
}
.system-foundryvtt-reve-de-dragon .inventaire-toolbar-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem;
}
.system-foundryvtt-reve-de-dragon .fortune-text {
font-weight: bold;
font-size: 0.9rem;
white-space: nowrap;
}
.system-foundryvtt-reve-de-dragon .blessure-control {
flex-grow: 1;
flex-direction: row;
+18
View File
@@ -486,6 +486,24 @@
}
}
.inventaire-toolbar {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-bottom: 0.5rem;
}
.inventaire-toolbar-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem;
}
.fortune-text {
font-weight: bold;
font-size: 0.9rem;
white-space: nowrap;
}
.blessure-control {
flex-grow: 1;
flex-direction: row;
+1
View File
@@ -9,6 +9,7 @@ const listeReglesOptionnelles = [
{ group: 'Récupération', name: 'recuperation-ethylisme', descr: "Récupérer l'éthylisme"},
{ group: 'Récupération', name: 'recuperation-reve', descr: "Récupérer le rêve pendant la nuit (les jets sont toujours faits pour les Rêves de Dragons)"},
{ group: 'Récupération', name: 'recuperation-moral', descr: "Le moral revient vers 0 durant Château Dormant"},
{ group: 'Récupération', name: 'nouveau-mode-repos', descr: "Utiliser le nouveau mode de repos (sélection heures + dialogue Château Dormant intégré)", default: false},
{ group: 'Règles de combat', name: 'armes-equipees', descr: "Proposer uniquement les armes équipées au combat", default: false},
{ group: 'Règles de combat', name: 'localisation-aleatoire', descr: "Proposer une localisation aléatoire des blessures" },
+46
View File
@@ -0,0 +1,46 @@
import { renderTemplate } from "../constants.js"
export class DialogNouveauReposCD {
static prompt(actor) {
return new Promise((resolve) => {
const date = game.system.rdd.calendrier.dateCourante()
let moralChoisi = actor.system.sommeil?.moral ?? 'neutre'
const data = { name: actor.name, moral: moralChoisi }
renderTemplate(
"systems/foundryvtt-reve-de-dragon/templates/sommeil/dialog-nouveau-repos-cd.hbs",
data
).then(async (html) => {
const dialog = new Dialog({
title: "Château Dormant — " + actor.name,
content: html,
buttons: {
valider: {
label: "Valider le Château Dormant",
callback: ($html) => {
const stress = Number($html.find('input[name="cd-stress"]').val()) || 0
resolve({
stress: { valeur: stress, motif: `Nuit du ${date}` },
moral: moralChoisi
})
}
}
},
close: () => resolve({ stress: { valeur: 0, motif: `Nuit du ${date}` }, moral: 'neutre' })
}, {
width: 400,
height: 'fit-content',
'z-index': 99999
})
await dialog.render(true)
const $win = $(`#${dialog.id}`)
$win.find('.sommeil-actor-moral a').click((event) => {
const target = $(event.currentTarget)
moralChoisi = target.data('moral')
$win.find('.sommeil-actor-moral a i').removeClass('fa-solid').addClass('fa-regular')
target.find('i').removeClass('fa-regular').addClass('fa-solid')
})
})
})
}
}
+196
View File
@@ -0,0 +1,196 @@
import { renderTemplate } from "../constants.js"
import { RdDTimestamp, RDD_HEURES_PAR_JOUR } from "../time/rdd-timestamp.js"
import { ChatUtility } from "../chat-utility.js"
import { DialogNouveauReposCD } from "./dialog-nouveau-repos-cd.js"
export class DialogNouveauRepos extends Dialog {
static async create(actor) {
if (!actor.isPersonnage()) return
const now = game.system.rdd.calendrier.timestamp
const heureCourante = now.heure
const hourDefs = RdDTimestamp.definitions()
const data = {
name: actor.name,
img: actor.img,
hourDefinitions: hourDefs,
heureDebutDefaut: heureCourante,
heureFinDefaut: Math.min(heureCourante + 7, 11),
}
const html = await renderTemplate(
"systems/foundryvtt-reve-de-dragon/templates/sommeil/dialog-nouveau-repos.hbs",
data
)
new DialogNouveauRepos(html, actor).render(true)
}
constructor(html, actor) {
super({
title: "Se reposer — " + actor.name,
content: html,
buttons: {
dormir: { label: "Dormir", callback: () => this._onDormir() },
chateauDormant: { label: "Passer Château Dormant", callback: () => this._onChateauDormant() }
}
}, {
classes: ["dialog-nouveau-repos"],
width: 450,
height: 'fit-content',
'z-index': 99999
})
this.actor = actor
}
activateListeners($html) {
super.activateListeners($html)
this.$html = $html
this.$html.find('select[name="heure-debut"], select[name="heure-fin"]').change(() => this._updateDuree())
this.$html.find('input[name="reveil-force"]').change(() => this._toggleReveilForce())
this._updateDuree()
}
_updateDuree() {
const debut = Number(this.$html.find('select[name="heure-debut"]').val())
const fin = Number(this.$html.find('select[name="heure-fin"]').val())
const duree = fin >= debut ? fin - debut + 1 : (RDD_HEURES_PAR_JOUR - debut) + fin + 1
this.$html.find('#duree-sommeil').text(duree)
}
_toggleReveilForce() {
const checked = this.$html.find('input[name="reveil-force"]').is(':checked')
this.$html.find('input[name="heures-force"]').prop('disabled', !checked)
}
async _onDormir() {
const debut = Number(this.$html.find('select[name="heure-debut"]').val())
const fin = Number(this.$html.find('select[name="heure-fin"]').val())
const reveilForce = this.$html.find('input[name="reveil-force"]').is(':checked')
const heuresForce = Number(this.$html.find('input[name="heures-force"]').val())
const duree = fin >= debut ? fin - debut + 1 : (RDD_HEURES_PAR_JOUR - debut) + fin + 1
const valide = await this._valider(debut, duree, reveilForce, heuresForce)
if (!valide) return
this.close()
await this._executerSommeil(debut, duree, reveilForce, heuresForce)
}
async _onChateauDormant() {
this.close()
const cdData = await DialogNouveauReposCD.prompt(this.actor)
if (cdData.stress.valeur > 0) {
await this.actor.distribuerStress('stress', cdData.stress.valeur, cdData.stress.motif)
}
await this.actor.update({
'system.sommeil': {
nouveaujour: true,
date: game.system.rdd.calendrier.getTimestampFinChateauDormant(),
moral: cdData.moral,
heures: 0
}
})
await this.actor.$dormirChateauDormant()
}
async _valider(debut, duree, reveilForce, heuresForce) {
const $av = this.$html.find('#avertissements')
$av.empty().show()
const fatigue = this.actor.system.sante.fatigue.value
const fatigueMin = this.actor.getFatigueMin()
const aFatigue = fatigue > fatigueMin
const aHeuresJour = this._aHeuresJour(debut, duree)
if (aHeuresJour && !aFatigue) {
$av.html('<p class="notification error">Vous n\'avez pas assez de fatigue pour dormir de jour.</p>')
return false
}
if (aHeuresJour && aFatigue) {
$av.append('<p class="notification warning">Vous vous réveillerez dès la fatigue récupérée (sommeil de jour).</p>')
}
if (reveilForce && heuresForce < duree) {
$av.append('<p class="notification info">Vous serez réveillé après ' + heuresForce + ' heures.</p>')
}
return true
}
_aHeuresJour(debut, duree) {
for (let h = 0; h < duree; h++) {
const heure = (debut + h) % RDD_HEURES_PAR_JOUR
if (heure > 0 && heure < 6) return true
}
return false
}
async _executerSommeil(heureDebut, duree, reveilForce, heuresForce) {
const message = {
whisper: ChatUtility.getOwners(this.actor),
content: this.actor.name + ': '
}
const dormi = { heures: 0, etat: 'dort', jetsReve: [] }
const insomnie = this.actor.system.sommeil?.insomnie || duree === 0
await this.actor.recupereEndurance({ message, demi: insomnie })
if (insomnie) {
message.content += 'Vous ne trouvez pas le sommeil'
} else {
const avaitFatigue = this.actor.system.sante.fatigue.value > this.actor.getFatigueMin()
if (!avaitFatigue && heureDebut >= 6 && heureDebut <= 11) {
duree = RDD_HEURES_PAR_JOUR - heureDebut + 1
}
let cdProcessed = false
let h = 0
while (true) {
if (dormi.etat !== 'dort') break
if (reveilForce && dormi.heures >= heuresForce) break
if (!avaitFatigue && h >= duree) break
if (avaitFatigue && this.actor.system.sante.fatigue.value <= this.actor.getFatigueMin()) break
const heureActuelle = (heureDebut + h) % RDD_HEURES_PAR_JOUR
await this.actor.$recupererEthylisme(message)
await this.actor.$recupererFatigue(message)
await this.actor.$jetRecuperationReve(dormi, message)
dormi.heures++
if (dormi.etat !== 'dort') break
if (heureActuelle === RDD_HEURES_PAR_JOUR - 1 && !cdProcessed) {
cdProcessed = true
const cdData = await DialogNouveauReposCD.prompt(this.actor)
if (cdData.stress.valeur > 0) {
await this.actor.distribuerStress('stress', cdData.stress.valeur, cdData.stress.motif)
}
await this.actor.update({
'system.sommeil': {
nouveaujour: true,
date: game.system.rdd.calendrier.getTimestampFinChateauDormant(),
moral: cdData.moral,
heures: Math.max(0, duree - h - 1)
}
})
await this.actor.$dormirChateauDormant()
}
h++
}
if (dormi.jetsReve.length > 0) {
message.content += 'Vous récupérez ' +
dormi.jetsReve.map(it => it < 0 ? '0 (réveil)' : it).join('+') +
' Points de rêve. '
}
if (dormi.etat === 'eveil') {
await this.actor.reveilReveDeDragon(message, dormi.heures)
}
message.content += `Vous avez dormi ${dormi.heures <= 1 ? 'une heure' : (dormi.heures + ' heures')}. `
}
await ChatMessage.create(message)
}
}
+11 -7
View File
@@ -1,9 +1,13 @@
<span class="item-name">
<h4>Argent et Monnaies (fortune: {{calc.fortune.sols}} sols {{calc.fortune.deniers}} deniers)</h4>
<div class="inventaire-toolbar">
<span class="inventaire-toolbar-row">
<span class="fortune-text">Argent (fortune: {{calc.fortune.sols}} sols {{calc.fortune.deniers}} deniers)</span>
</span>
{{#if @root.options.isOwner}}
<a class="gerer-deniers chat-card-button" data-tooltip="Gérer ses deniers">Gérer ses deniers</a>
{{#if herbes.length}}<a class="gerer-herbes chat-card-button">Gérer ses herbes</a>{{/if}}
{{#if ingredients.length}}<a class="gerer-ingredients chat-card-button">Gérer ses ingrédients</a>{{/if}}
{{#if potions.length}}<a class="gerer-potions chat-card-button">Gérer ses potions</a>{{/if}}
<span class="inventaire-toolbar-row">
<a class="chat-card-button gerer-deniers" data-tooltip="Gérer ses deniers"><i class="fas fa-coins"></i> Deniers</a>
{{#if potions.length}}<a class="chat-card-button gerer-potions" data-tooltip="Gérer ses potions"><i class="fas fa-flask"></i> Potions</a>{{/if}}
{{#if herbes.length}}<a class="chat-card-button gerer-herbes" data-tooltip="Gérer ses herbes"><i class="fas fa-leaf"></i> Herbes</a>{{/if}}
{{#if ingredients.length}}<a class="chat-card-button gerer-ingredients" data-tooltip="Gérer ses ingrédients"><i class="fas fa-mortar-pestle"></i> Ingrédients</a>{{/if}}
</span>
{{/if}}
</span>
</div>
+27 -23
View File
@@ -1,29 +1,33 @@
<span class="item-name">
<div class="inventaire-toolbar">
{{#if options.isOwner}}
<a class="chat-card-button creer-un-objet" data-tooltip="Créer un nouvel objet">Nouvel objet</a>
<a class="chat-card-button quick-add-ration" data-tooltip="Ajouter une ration">+ Ration</a>
<a class="chat-card-button quick-add-eau" data-tooltip="Ajouter une outre d'eau">+ Eau</a>
{{/if}}
{{#if options.isGM}}
<a class="chat-card-button nettoyer-conteneurs" data-tooltip="Vider tous les contenants">Tout vider</a>
{{/if}}
<span class="embed-inline">
<input class="recherche flex-grow" type="text" value="{{options.recherche.text}}" name="recherche" size="8" data-dtype="String" placeholder="" data-tooltip="Rechercher dans l'équipement"/>
<select class="tri-equipement" data-tooltip="Trier par">
{{#select options.triEquipement}}
<option value="name">Nom</option>
<option value="type">Type</option>
<option value="enc">Enc.</option>
<option value="quantite">Quantité</option>
{{/select}}
</select>
<span class="inventaire-toolbar-row">
<a class="chat-card-button creer-un-objet" data-tooltip="Créer un nouvel objet"><i class="fas fa-plus-circle"></i> Nouvel objet</a>
<a class="chat-card-button quick-add-ration" data-tooltip="Ajouter une ration"><i class="fas fa-bread-slice"></i> + Ration</a>
<a class="chat-card-button quick-add-eau" data-tooltip="Ajouter une outre d'eau"><i class="fas fa-tint"></i> + Eau</a>
{{#if options.isGM}}
<a class="chat-card-button nettoyer-conteneurs" data-tooltip="Vider tous les contenants"><i class="fas fa-trash-alt"></i> Tout vider</a>
{{/if}}
</span>
{{#if calc.surEncombrementMessage}}<b>{{calc.surEncombrementMessage}}</b> &hyphen;{{/if}}
Encombrement: {{numberFormat calc.encTotal decimals=2}} (max: {{system.attributs.encombrement.value}})
{{#if (regle-optionnelle 'afficher-prix-joueurs')}}
&hyphen; Valeur: {{numberFormat calc.prixTotalEquipement decimals=2}} Sols
{{/if}}
</span>
<span class="inventaire-toolbar-row">
<span class="embed-inline">
<input class="recherche flex-grow" type="text" value="{{options.recherche.text}}" name="recherche" size="8" data-dtype="String" placeholder="" data-tooltip="Rechercher dans l'équipement"/>
<select class="tri-equipement" data-tooltip="Trier par">
{{#select options.triEquipement}}
<option value="name">Nom</option>
<option value="type">Type</option>
<option value="enc">Enc.</option>
<option value="quantite">Quantité</option>
{{/select}}
</select>
</span>
{{#if calc.surEncombrementMessage}}<b>{{calc.surEncombrementMessage}}</b> &hyphen;{{/if}}
Encombrement: {{numberFormat calc.encTotal decimals=2}} (max: {{system.attributs.encombrement.value}})
{{#if (regle-optionnelle 'afficher-prix-joueurs')}}
&hyphen; Valeur: {{numberFormat calc.prixTotalEquipement decimals=2}} Sols
{{/if}}
</span>
</div>
<ul class="item-list alterne-list liste-equipement">
<li class="competence-header flexrow">
<span class="equipement-nom">Nom</span>
@@ -0,0 +1,16 @@
<form class="dialog-nouveau-repos-cd">
<div class="flexcol">
<h2>Château Dormant — {{name}}</h2>
<p>La nuit touche à sa fin. Renseignez les informations pour le passage à Vaisseau.</p>
<div class="form-group">
<label>Points de stress du jour</label>
<input type="number" name="cd-stress" value="0" min="0" max="200">
</div>
<div class="form-group">
<label>Condition du jet de moral</label>
<div class="sommeil-actor-moral">
{{> "systems/foundryvtt-reve-de-dragon/templates/sommeil/sommeil-actor-moral.hbs" this}}
</div>
</div>
</div>
</form>
@@ -0,0 +1,28 @@
<form class="dialog-nouveau-repos">
<div class="flexcol">
<div class="flexrow">
<img class="chat-icon" src="{{img}}" data-tooltip="{{name}}" />
<h2>{{name}} se repose</h2>
</div>
<div class="form-group">
<label>De l'heure</label>
<select name="heure-debut">
{{selectOptions hourDefinitions selected=heureDebutDefaut labelAttr='label' valueAttr='heure'}}
</select>
<label>à l'heure</label>
<select name="heure-fin">
{{selectOptions hourDefinitions selected=heureFinDefaut labelAttr='label' valueAttr='heure'}}
</select>
</div>
<div class="form-group">
<span>Durée : <strong><span id="duree-sommeil">0</span> heures</strong></span>
</div>
<div class="form-group">
<input type="checkbox" name="reveil-force" id="reveil-force">
<label for="reveil-force">Réveil forcé après</label>
<input type="number" name="heures-force" value="1" min="1" max="12" disabled>
<span>heures</span>
</div>
<div id="avertissements" class="hidden"></div>
</div>
</form>