diff --git a/module/actor.js b/module/actor.js index 03dbd000..58e5e170 100644 --- a/module/actor.js +++ b/module/actor.js @@ -35,6 +35,7 @@ import { ExperienceLog, XP_TOPIC } from "./actor/experience-log.js"; import { TYPES } from "./item.js"; import { RdDBaseActorSang } from "./actor/base-actor-sang.js"; import { RdDCoeur } from "./coeur/rdd-coeur.js"; +import { DialogChoixXpCarac } from "./dialog-choix-xp-carac.js"; export const MAINS_DIRECTRICES = ['Droitier', 'Gaucher', 'Ambidextre'] @@ -56,21 +57,20 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ $computeCaracDerivee() { + this.system.carac.force.value = Math.min(this.system.carac.force.value, parseInt(this.system.carac.taille.value) + 4); - - this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2); - let bonusDomKey = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2); - let tailleData = RdDCarac.getCaracDerivee(bonusDomKey); - this.system.attributs.plusdom.value = tailleData.plusdom; - - this.system.attributs.sconst.value = RdDCarac.calculSConst(this.system.carac.constitution.value); - this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.system.carac.taille.value).sust; - - this.system.attributs.encombrement.value = (parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2; this.system.carac.melee.value = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.agilite.value)) / 2); this.system.carac.tir.value = Math.floor((parseInt(this.system.carac.vue.value) + parseInt(this.system.carac.dexterite.value)) / 2); this.system.carac.lancer.value = Math.floor((parseInt(this.system.carac.tir.value) + parseInt(this.system.carac.force.value)) / 2); + this.system.carac.derobee.value = Math.floor(parseInt(((21 - this.system.carac.taille.value)) + parseInt(this.system.carac.agilite.value)) / 2); + let bonusDomKey = Math.floor((parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2); + let tailleData = RdDCarac.getCaracDerivee(bonusDomKey); + this.system.attributs.plusdom.value = tailleData.plusdom; + this.system.attributs.encombrement.value = (parseInt(this.system.carac.force.value) + parseInt(this.system.carac.taille.value)) / 2; + + this.system.attributs.sconst.value = RdDCarac.calculSConst(this.system.carac.constitution.value); + this.system.attributs.sust.value = RdDCarac.getCaracDerivee(this.system.carac.taille.value).sust; this.system.sante.vie.max = Math.ceil((parseInt(this.system.carac.taille.value) + parseInt(this.system.carac.constitution.value)) / 2); this.system.sante.vie.value = Math.min(this.system.sante.vie.value, this.system.sante.vie.max) @@ -659,7 +659,7 @@ export class RdDActor extends RdDBaseActorSang { this.setPointsDeChance(to); } } - let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = this.findCaracByName(caracName); const from = selectedCarac.value await this.update({ [`system.carac.${caracName}.value`]: to }); await ExperienceLog.add(this, XP_TOPIC.CARAC, from, to, caracName); @@ -670,7 +670,7 @@ export class RdDActor extends RdDBaseActorSang { if (caracName == 'Taille') { return; } - let selectedCarac = RdDBaseActor._findCaracByName(this.system.carac, caracName); + let selectedCarac = this.findCaracByName(caracName); if (!selectedCarac.derivee) { const from = Number(selectedCarac.xp); await this.update({ [`system.carac.${caracName}.xp`]: to }); @@ -684,7 +684,7 @@ export class RdDActor extends RdDBaseActorSang { if (caracName == 'Taille') { return; } - let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName); + let carac = this.findCaracByName(caracName); if (carac) { carac = duplicate(carac); const fromXp = Number(carac.xp); @@ -1472,8 +1472,14 @@ export class RdDActor extends RdDBaseActorSang { } /* -------------------------------------------- */ + isCaracMax(code) { + if (code == 'force' && parseInt(this.system.carac.force.value) >= parseInt(this.system.carac.taille.value) + 4) { + return true; + } + return false + } async checkCaracXP(caracName, display = true) { - let carac = RdDBaseActor._findCaracByName(this.system.carac, caracName); + let carac = this.findCaracByName(caracName); if (carac && carac.xp > 0) { const niveauSuivant = Number(carac.value) + 1; let xpNeeded = RdDCarac.getCaracNextXp(niveauSuivant); @@ -1534,8 +1540,11 @@ export class RdDActor extends RdDBaseActorSang { async appliquerAjoutExperience(rollData, hideChatMessage = 'show') { hideChatMessage = hideChatMessage == 'hide' || (Misc.isRollModeHiddenToPlayer() && !game.user.isGM) let xpData = await this._appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence, rollData.jetResistance); - if (xpData) { - const content = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-gain-xp.html`, xpData); + if (xpData.length) { + const content = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-gain-xp.html`, { + actor: this, + xpData + }); if (hideChatMessage) { ChatUtility.blindMessageToGM({ content: content }); } @@ -1698,7 +1707,7 @@ export class RdDActor extends RdDBaseActorSang { if (rollData.competence.name.includes('Thanatos')) { // Si Thanatos await this.update({ "system.reve.reve.thanatosused": true }); } - let reveActuel = this.system.reve.reve.value; + let reveActuel = parseInt(this.system.reve.reve.value) if (rolled.isSuccess) { // Réussite du sort ! if (rolled.isPart) { rollData.depenseReve = Math.max(Math.floor(rollData.depenseReve / 2), 1); @@ -2265,9 +2274,7 @@ export class RdDActor extends RdDBaseActorSang { }); return undefined; } - if (caracName == 'Vie') caracName = 'constitution'; - if (caracName == 'derobee') caracName = 'agilite'; - if (caracName == 'reve-actuel') caracName = 'reve'; + let xp = Math.abs(rolled.finalLevel); // impair: arrondi inférieur en carac let xpCarac = competence ? Math.floor(xp / 2) : Math.max(Math.floor(xp / 2), 1); @@ -2280,11 +2287,10 @@ export class RdDActor extends RdDBaseActorSang { // max 1 xp sur jets de résistance xpCarac = Math.min(1, xpCarac); } - let xpData = { alias: this.name, caracName, xpCarac, competence, xpCompetence }; - - await this._xpCompetence(xpData); - await this._xpCarac(xpData); - return xpData; + return [ + ...(await this._xpCompetence({ competence, xpCompetence })), + ...(await this._xpCarac({ caracName, xpCarac })) + ]; } /* -------------------------------------------- */ @@ -2292,31 +2298,67 @@ export class RdDActor extends RdDBaseActorSang { if (xpData.competence) { const from = Number(xpData.competence.system.xp); const to = from + xpData.xpCompetence; - let update = { _id: xpData.competence._id, 'system.xp': to }; - await this.updateEmbeddedDocuments('Item', [update]); + await this.updateEmbeddedDocuments('Item', [{ _id: xpData.competence._id, 'system.xp': to }]); xpData.checkComp = await this.checkCompetenceXP(xpData.competence.name, undefined, false); await ExperienceLog.add(this, XP_TOPIC.XP, from, to, xpData.competence.name); + return [xpData] } + return [] } /* -------------------------------------------- */ async _xpCarac(xpData) { if (xpData.xpCarac > 0) { - let carac = duplicate(this.system.carac); - let selectedCarac = RdDBaseActor._findCaracByName(carac, xpData.caracName); - if (!selectedCarac.derivee) { - const from = Number(selectedCarac.xp); - const to = from + xpData.xpCarac; - selectedCarac.xp = to; - await this.update({ "system.carac": carac }); - xpData.checkCarac = await this.checkCaracXP(selectedCarac.label, false); - await ExperienceLog.add(this, XP_TOPIC.XPCARAC, from, to, xpData.caracName); + const carac = duplicate(this.system.carac) + const code = RdDBaseActor._findCaracNode(carac, xpData.caracName) + const selectedCarac = carac[code] + if (this.isCaracMax(code)) { + ui.notifications.info(`Pas d'expérience: la caractéristique '${selectedCarac.label}' est déjà au maximum pour ${this.name}`) + return [] + } + if (selectedCarac && !selectedCarac.derivee) { + const from = Number(selectedCarac.xp) + const to = from + xpData.xpCarac + selectedCarac.xp = to + await this.update({ "system.carac": carac }) + xpData.checkCarac = await this.checkCaracXP(selectedCarac.label, false) + await ExperienceLog.add(this, XP_TOPIC.XPCARAC, from, to, xpData.caracName) + return [xpData] } else { - xpData.caracRepartitionManuelle = true; + return await this._xpCaracDerivee(xpData) } } + return [] } + async _xpCaracDerivee(xpData) { + const caracs = RdDActor._getComposantsCaracDerivee(xpData.caracName) + .map(c => mergeObject(this.system.carac[c], { isMax: this.isCaracMax(c) })) + switch (caracs.filter(it => !it.isMax).length) { + case 0: + xpData.caracRepartitionManuelle = true; + return [xpData] + case 1: + xpData.caracName = caracs.find(it => !it.isMax).label + return this._xpCarac(xpData) + default: + await DialogChoixXpCarac.choix(this, xpData, caracs) + return [] + } + } + + static _getComposantsCaracDerivee(caracName) { + switch (Grammar.toLowerCaseNoAccent(caracName)) { + case 'vie': return ['constitution'] + case 'tir': return ['vue', 'dexterite'] + case 'lancer': return ['force', 'dexterite', 'vue'] + case 'melee': return ['force', 'agilite'] + case 'derobee': return ['agilite'] + } + return [] + } + + /* -------------------------------------------- */ async resetNombresAstraux() { const deletions = this.itemTypes['nombreastral'].map(it => it._id); @@ -2418,7 +2460,7 @@ export class RdDActor extends RdDBaseActorSang { draconic: this.getDraconicList(), sort: this.itemTypes['sort'], signes: this.itemTypes['signedraconique'], - caracReve: this.system.carac.reve.value, + caracReve: parseInt(this.system.carac.reve.value), pointsReve: this.getReveActuel(), isRapide: isRapide, isGM: game.user.isGM, @@ -2509,7 +2551,7 @@ export class RdDActor extends RdDBaseActorSang { /* -------------------------------------------- */ verifierForceMin(item) { - if (item.type == 'arme' && item.system.force > this.system.carac.force.value) { + if (item.type == 'arme' && item.system.force > parseInt(this.system.carac.force.value)) { ChatMessage.create({ content: `${this.name} s'est équipé(e) de l'arme ${item.name}, mais n'a pas une force suffisante pour l'utiliser normalement (${item.system.force} nécessaire pour une Force de ${this.system.carac.force.value})` diff --git a/module/actor/base-actor.js b/module/actor/base-actor.js index 6ae29da5..2cd4f79c 100644 --- a/module/actor/base-actor.js +++ b/module/actor/base-actor.js @@ -11,16 +11,14 @@ import { SystemCompendiums } from "../settings/system-compendiums.js"; import { APP_ASTROLOGIE_REFRESH } from "../sommeil/app-astrologie.js"; export class RdDBaseActor extends Actor { - /* -------------------------------------------- */ - static _findCaracByName(carac, name) { - name = Grammar.toLowerCaseNoAccent(name); - switch (name) { - case 'reve-actuel': case 'reve actuel': - return carac.reve; - case 'chance-actuelle': case 'chance actuelle': - return carac.chance; - } + static _findCaracNode(carac, name) { + return Object.entries(carac) + .filter(it => Grammar.equalsInsensitive(it[1].label, name)) + .map(it => it[0]) + .find(it => it); + } + static $findCaracByName(carac, name) { const caracList = Object.entries(carac); let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique' }); if (!entry || entry.length == 0) { @@ -135,6 +133,21 @@ export class RdDBaseActor extends Actor { super(docData, context); } + findCaracByName(name) { + name = Grammar.toLowerCaseNoAccent(name) + switch (name) { + case 'reve-actuel': case 'reve actuel': + return this.system.carac.reve + case 'chance-actuelle': case 'chance actuelle': + return this.system.carac.chance + case 'vie': + return this.system.sante.vie + } + + const carac = this.system.carac; + return RdDBaseActor.$findCaracByName(carac, name); + } + getCaracByName(name) { switch (Grammar.toLowerCaseNoAccent(name)) { case 'reve-actuel': case 'reve actuel': @@ -142,7 +155,7 @@ export class RdDBaseActor extends Actor { case 'chance-actuelle': case 'chance-actuelle': return this.getCaracChanceActuelle(); } - return RdDBaseActor._findCaracByName(this.system.carac, name); + return this.findCaracByName(name); } /* -------------------------------------------- */ @@ -187,7 +200,7 @@ export class RdDBaseActor extends Actor { } listeSuivants(filter = suivant => true) { return [] } - listeSuivants(filter = suivant =>true) { return [] } + listeSuivants(filter = suivant => true) { return [] } listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); } filterItems(filter, type = undefined) { return (type ? this.itemTypes[type] : this.items)?.filter(filter) ?? []; } findItemLike(idOrName, type) { diff --git a/module/dialog-choix-xp-carac.js b/module/dialog-choix-xp-carac.js new file mode 100644 index 00000000..24038f5d --- /dev/null +++ b/module/dialog-choix-xp-carac.js @@ -0,0 +1,84 @@ + +export class DialogChoixXpCarac extends Dialog { + + static async choix(actor, xpData, caracs) { + caracs = caracs.map(it => mergeObject({ ajout: 0 }, it)) + xpData = mergeObject({ reste: xpData.xpCarac }, xpData) + const dialogData = { + title: `Choisissez la répartition d'expérience`, + content: await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-choix-xp-carac.hbs", { + actor, + caracDerivee: actor.findCaracByName(xpData.caracName), + xpData, + caracs + }), + } + + const dialogOptions = { + classes: ["rdd-dialog-select"], + width: 400, + height: 'fit-content', + 'z-index': 99999 + } + new DialogChoixXpCarac(dialogData, dialogOptions, actor, xpData, caracs).render(true) + } + + constructor(dialogData, dialogOptions, actor, xpData, caracs) { + dialogData = mergeObject(dialogData, { + default: 'appliquer', + buttons: { + 'appliquer': { icon:'', label: "Ajouter la répartition", callback: it => this.appliquerSelection() } + } + }) + super(dialogData, dialogOptions) + this.actor = actor + this.xpData = xpData + this.caracs = caracs + } + + activateListeners(html) { + //TODO + super.activateListeners(html) + this.html = html + this.html.find("li.xpCarac-option .xpCarac-moins").click(event => + this.ajouterXp(event, -1) + ) + this.html.find("li.xpCarac-option .xpCarac-plus").click(event => + this.ajouterXp(event, 1) + ) + } + + async ajouterXp(event, delta) { + const liCarac = this.html.find(event.currentTarget)?.parents("li.xpCarac-option") + const label = liCarac?.data("carac-label") + const carac = this.caracs.find(c => c.label == label) + if (carac.ajout + delta < 0) { + ui.notifications.warn(`Impossible de diminuer les points à répartir en ${carac.label} en dessous de 0`) + return + } + if (this.xpData.reste - delta < 0) { + ui.notifications.warn(`Il ne reste plus de points à répartir en ${carac.label}`) + return + } + carac.ajout += delta + this.xpData.reste -= delta + liCarac.find("input.xpCarac-view-ajout").val(carac.ajout) + this.html.find("input.xpCarac-reste").val(this.xpData.reste) + } + + async appliquerSelection() { + if (this.xpData.reste > 0) { + ui.notifications.warn(`Il vous reste ${this.xpData.reste} points à répartir`) + return + } + this.caracs.filter(c => c.ajout > 0).forEach(c => { + const xpData = { caracName: c.label, xpCarac: c.ajout } + this.actor._xpCarac(xpData) + }) + await super.close() + } + + async close() { } + + _getHeaderButtons() { return [] } +} \ No newline at end of file diff --git a/templates/chat-actor-gain-xp.html b/templates/chat-actor-gain-xp.html index ec447eba..9642333e 100644 --- a/templates/chat-actor-gain-xp.html +++ b/templates/chat-actor-gain-xp.html @@ -1,26 +1,29 @@ -