diff --git a/lang/fr.json b/lang/fr.json index 6d5cbe9b..94d60c4a 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -40,5 +40,19 @@ "TypeOmbre": "Ombre de Thanatos", "TypeSouffle": "Souffle de Dragon", "TypeTete": "Tête de Dragon" + }, + "EFFECT": { + "StatusStunned": "Sonné", + "StatusUnconscious": "Inconscient", + "StatusBlind": "Aveugle", + "StatusBleeding": "Saigne", + "StatusProne": "Au sol", + "StatusUnarmed": "Désarmé", + "StatusGrappling": "Empoignade", + "StatusGrappled": "Empoigné", + "StatusRestrained": "Immobilisé", + "StatusComma": "Comma", + "StatusDead": "Mort", + "StatusDemiReve": "Demi-rêve" } -} +} \ No newline at end of file diff --git a/module/actor-sheet.js b/module/actor-sheet.js index d35afefe..b092334c 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -103,7 +103,7 @@ export class RdDActorSheet extends ActorSheet { formData.difficultesLibres = CONFIG.RDD.difficultesLibres; formData.hautreve = { - isDemiReve: this.actor.listeEffets( it => it.label == "Demi-rêve").length > 0, + isDemiReve: this.actor.getEffectByLabel("Demi-rêve"), sortsReserve: formData.data.reve.reserve.list, rencontres: duplicate(formData.data.reve.rencontre.list), casesTmr: formData.itemsByType.casetmr, @@ -327,7 +327,7 @@ export class RdDActorSheet extends ActorSheet { actor.sheet.render(true); } }); - + // Boutons spéciaux MJs html.find('.forcer-tmr-aleatoire').click(async event => { this.actor.cacheTMRetMessage(); @@ -335,7 +335,7 @@ export class RdDActorSheet extends ActorSheet { html.find('.afficher-tmr').click(async event => { this.actor.afficheTMRetMessage(); }); - + // Points de reve actuel html.find('.ptreve-actuel a').click(async event => { this.actor.rollCarac('reve-actuel'); @@ -344,8 +344,16 @@ export class RdDActorSheet extends ActorSheet { // Roll Weapon1 html.find('.arme-label a').click(async event => { let armeName = event.currentTarget.text; - let competenceName = event.currentTarget.attributes['data-competence-name'].value; - this.actor.rollArme(competenceName, armeName); + let compName = event.currentTarget.attributes['data-competence-name'].value; + let arme = this.actor.data.find(it => + it.name == armeName && + RdDItemArme.isArme(it) && + ((compName ?? it.data.competence) == it.data.competence) + ); + if (!arme) { + arme = { name: armeName, data: { competence: compName } }; + } + this.actor.rollArme(arme); }); // Initiative pour l'arme html.find('.arme-initiative a').click(async event => { @@ -376,6 +384,10 @@ export class RdDActorSheet extends ActorSheet { html.find('.repos').click(async event => { await DialogRepos.create(this.actor); }); + html.find('.delete-active-effect').click(async event => { + let id = $(event.currentTarget).parents(".active-effect").data('id'); + this.actor.enleverActiveEffectById(id); + }); html.find('.enlever-tous-effets').click(async event => { this.actor.enleverTousLesEffets(); }); @@ -441,6 +453,7 @@ export class RdDActorSheet extends ActorSheet { this.render(true); }); html.find('.lock-unlock-controls').click(async event => { + console.log("CONTROLS", this.options.hideControls) this.options.hideControls = !this.options.hideControls; this.render(true); }); @@ -514,9 +527,6 @@ export class RdDActorSheet extends ActorSheet { html.find('#endurance-moins').click(async event => { this.actor.santeIncDec("endurance", -1); }); - html.find('.data-sante-sonne').click(async event => { - this.actor.setSonne(event.currentTarget.checked); - }); html.find('#ptreve-actuel-plus').click(async event => { this.actor.reveActuelIncDec(1); }); @@ -560,7 +570,7 @@ export class RdDActorSheet extends ActorSheet { async _onSplitItem(item, split) { if (split >= 1 && split < Misc.data(item).data.quantite) { await item.diminuerQuantite(split); - const itemData = duplicate( Misc.data(item)); + const itemData = duplicate(Misc.data(item)); itemData.data.quantite = split; await this.actor.createEmbeddedDocuments('Item', [itemData]) } diff --git a/module/actor.js b/module/actor.js index 33381112..7a15d903 100644 --- a/module/actor.js +++ b/module/actor.js @@ -32,7 +32,6 @@ import { DialogFabriquerPotion } from "./dialog-fabriquer-potion.js"; import { RollDataAjustements } from "./rolldata-ajustements.js"; import { DialogItemAchat } from "./dialog-item-achat.js"; import { RdDItem } from "./item.js"; - /* -------------------------------------------- */ /** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. @@ -42,7 +41,6 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ static init() { Hooks.on("deleteActiveEffect", (effect, options, userId) => RdDActor.getParentActor(effect)?.onDeleteActiveEffect(effect, options)); - Hooks.on("createActiveEffect", (effect, options, userId) => RdDActor.getParentActor(effect)?.onCreateActiveEffect(effect, options)); Hooks.on("preUpdateItem", (item, change, options, id) => RdDActor.getParentActor(item)?.onPreUpdateItem(item, change, options, id)); Hooks.on("createItem", (item, options, id) => RdDActor.getParentActor(item)?.onCreateItem(item, options, id)); @@ -424,13 +422,13 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ getSurprise(isCombat = undefined) { - let niveauSurprise = Array.from(this.effects?.values() ?? []) + let niveauSurprise = this.getActiveEffects() .map(effect => StatusEffects.valeurSurprise(effect.data, isCombat)) .reduce(Misc.sum(), 0); if (niveauSurprise > 1) { return 'totale'; } - if (niveauSurprise == 1 || this.getSonne()) { + if (niveauSurprise == 1) { return 'demi'; } return ''; @@ -1507,21 +1505,20 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ getSonne() { - let data = Misc.templateData(this); - return !this.isEntiteCauchemar() && (data.sante?.sonne?.value ?? false); + return this.getEffectByLabel("EFFECT.StatusStunned"); } /* -------------------------------------------- */ - getSonneRound() { - return !this.isEntiteCauchemar() && (Misc.templateData(this).sante.sonne?.round ?? false); - } - - /* -------------------------------------------- */ - async verifierSonneRound(round) { - if (this.getSonne()) { - if (round > this.getSonneRound() + 1) { - await this.setSonne(false, -1); // Nettoyer l'état sonné - ChatMessage.create({ content: `${this.name} n'est plus sonné ce round !` }); + async finDeRound(options = {terminer:false}) { + for (let effect of this.getActiveEffects()) { + if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) { + if (effect.data.origin) { + await effect.update({ 'disabled': true }); + } + else { + await effect.delete(); + } + ChatMessage.create({ content: `${this.name} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.data.label))} !` }); } } } @@ -1531,20 +1528,11 @@ export class RdDActor extends Actor { if (this.isEntiteCauchemar()) { return; } - let round = (sonne && game.combat) ? game.combat.current.round : -1; // Sauvegarde du round de sonné en cas de combat - await this.setStatusSonne(sonne); - await this.setStateSonne(sonne, round); - } - - /* -------------------------------------------- */ - async setStateSonne(sonne, round = -1) { - if (this.isEntiteCauchemar()) { + if (!game.combat && sonne){ + ui.notifications.info("Le personnage est hors combat, il ne reste donc pas sonné"); return; } - let sonneData = duplicate(Misc.templateData(this).sante.sonne); - sonneData.value = sonne; - sonneData.round = round; - await this.update({ "data.sante.sonne": sonneData }); + await this.setStatusEffect("EFFECT.StatusStunned", sonne); } /* -------------------------------------------- */ @@ -1571,12 +1559,11 @@ export class RdDActor extends Actor { /* -------------------------------------------- */ - async testSiSonne(sante, endurance) { + async testSiSonne(endurance) { const result = await this._jetEndurance(endurance); if (result.roll.total == 1) { ChatMessage.create({ content: await this._gainXpConstitutionJetEndurance() }); } - sante.sonne.value ||= result.sonne; return result; } @@ -1645,7 +1632,7 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async santeIncDec(name, inc, options = { isCritique: false, ethylisme: false }) { + async santeIncDec(name, inc, isCritique = false) { if (name == 'fatigue' && !ReglesOptionelles.isUsing("appliquer-fatigue")) { return; } @@ -1664,7 +1651,7 @@ export class RdDActor extends Actor { //console.log("New value ", inc, minValue, result.newValue); let fatigue = 0; if (name == "endurance" && !this.isEntiteCauchemar()) { - if (result.newValue == 0 && inc < 0 && !options.isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie + if (result.newValue == 0 && inc < 0 && !isCritique) { // perte endurance et endurance devient 0 (sauf critique) -> -1 vie sante.vie.value--; result.perteVie = true; } @@ -1674,14 +1661,13 @@ export class RdDActor extends Actor { } const perte = compteur.value - result.newValue; result.perte = perte; - if (perte > 1 && !options.ethylisme) { + if (perte > 1) { // Peut-être sonné si 2 points d'endurance perdus d'un coup - const testIsSonne = await this.testSiSonne(sante, result.newValue); + const testIsSonne = await this.testSiSonne(result.newValue); result.sonne = testIsSonne.sonne; result.jetEndurance = testIsSonne.roll.total; } else if (inc > 0) { await this.setSonne(false); - sante.sonne.value = false; } if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost fatigue = perte; @@ -1695,7 +1681,7 @@ export class RdDActor extends Actor { } await this.update({ "data.sante": sante }); if (this.isDead()) { - await this.addStatusEffectById('dead'); + await this.setStatusEffect("EFFECT.StatusComma", true); } return result; } @@ -1948,7 +1934,7 @@ export class RdDActor extends Actor { ethylisme.nb_doses = 0; let perte = await RdDDice.rollTotal("1d6", { showDice: true }); - ethylismeData.perteEndurance = await this.santeIncDec("endurance", -perte, { ethylisme: true }); + ethylismeData.perteEndurance = await this.santeIncDec("endurance", -perte); if (!ethylisme.jet_moral) { ethylismeData.jetMoral = await this._jetMoral('heureuse'); @@ -2169,10 +2155,7 @@ export class RdDActor extends Actor { async appliquerAjoutExperience(rollData, display) { if (!this.isPersonnage()) return; let xpData = await this._appliquerExperience(rollData.rolled, rollData.selectedCarac.label, rollData.competence); - if (!xpData) { - return; - } - if (display) { + if (xpData && display) { let message = { whisher: ChatUtility.getWhisperRecipientsAndGMs(this.name), content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-gain-xp.html`, xpData) @@ -2182,7 +2165,7 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async _appliquerAppelMoral(rollData, display = true) { + async _appliquerAppelMoral(rollData) { if (!this.isPersonnage()) return; if (!rollData.useMoral) return; if (rollData.rolled.isEchec || @@ -3078,7 +3061,7 @@ export class RdDActor extends Actor { ui.notifications.warn("Vous êtes déja dans les TMR...."); return } - let demiReve = this.listeEffets(it => it.label == "Demi-rêve"); + let demiReve = this.getActiveEffects(it => it.data.label == "Demi-rêve"); if (mode != 'visu' && demiReve.length > 0) { ui.notifications.warn("Le joueur ou le MJ est déja dans les Terres Médianes avec ce personnage ! Visualisation uniquement"); mode = "visu"; // bascule le mode en visu automatiquement @@ -3094,7 +3077,7 @@ export class RdDActor extends Actor { }); return; } - await this.setStatusDemiReve(true); + await this.setStatusEffect("EFFECT.StatusDemiReve", true); } const actorData = Misc.data(this); @@ -3117,11 +3100,9 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - rollArme(compName, armeName = undefined) { - let arme = armeName ? this.data.items.find(it => Misc.data(it).name == armeName && RdDItemArme.isArme(it)) : undefined; - let competence = Misc.data(this.getCompetence(compName)); - - if (arme || armeName || (competence.type == 'competencecreature' && competence.data.iscombat)) { + rollArme(arme) { + let competence = Misc.data(this.getCompetence(arme.data.competence)); + if (arme || (competence.type == 'competencecreature' && competence.data.iscombat)) { RdDCombat.createUsingTarget(this)?.attaque(competence, arme); } else { this.rollCompetence(competence.name); @@ -3245,7 +3226,7 @@ export class RdDActor extends Actor { const perteVie = this.isEntiteCauchemar() ? { newValue: 0 } : await this.santeIncDec("vie", - encaissement.vie); - const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, { critiques: encaissement.critiques > 0 }); + const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, encaissement.critiques > 0); this.computeEtatGeneral(); @@ -3353,7 +3334,7 @@ export class RdDActor extends Actor { count--; } else { // TODO: status effect dead - this.addStatusEffectById('dead'); + this.setStatusEffect("EFFECT.StatusComma", true); ChatMessage.create({ content: `charge ${this.name} vient de succomber à une seconde blessure critique ! Que les Dragons gardent son Archétype en paix !` @@ -3992,109 +3973,64 @@ export class RdDActor extends Actor { async onUpdateActor(update, options, actorId) { const updatedEndurance = update?.data?.sante?.endurance; if (updatedEndurance && options.diff) { - this.forceStatusEffectId('unconscious', updatedEndurance.value == 0); - } - } - /* -------------------------------------------- */ - async onCreateActiveEffect(effect, options) { - switch (StatusEffects.statusId(effect)) { - case 'sonne': - await this.setStateSonne(true); - return; + await this.setStatusEffect("EFFECT.StatusUnconscious", updatedEndurance.value == 0); } } /* -------------------------------------------- */ async onDeleteActiveEffect(effect, options) { - switch (StatusEffects.statusId(effect)) { - case 'sonne': - await this.setStateSonne(false); + switch (effect.label) { + case 'EFFECT.StatusStunned': return; } } /* -------------------------------------------- */ - enleverTousLesEffets() { - const ids = Array.from(this.effects?.keys() ?? []); - this.deleteEmbeddedDocuments('ActiveEffect', ids); + getActiveEffects(matching = it => true) { + return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it)); } /* -------------------------------------------- */ - listeEffets(matching = it => true) { - const all = Array.from(this.effects?.values() ?? []); - const filtered = all.filter(it => matching(it.data)); - return filtered; + getEffectByLabel(label) { + return this.getActiveEffects().find(it => it.data.label == label); } /* -------------------------------------------- */ - async setStatusDemiReve(status) { - const demiReve = StatusEffects.demiReve(); - if (status) { - await this.addStatusEffect(demiReve) - } else { - await this.deleteStatusEffect(demiReve) - } + getEffectById(id) { + return this.getActiveEffects().find(it => it.id == id); } /* -------------------------------------------- */ - async setStatusSonne(sonne) { - if (this.isEntiteCauchemar()) { + async setStatusEffect(label, status, updates = {}) { + if (this.isEntiteCauchemar() || this.data.type == 'vehicule') { return; } - await this.forceStatusEffectId('sonne', sonne); - } - - /* -------------------------------------------- */ - async forceStatusEffectId(statusId, isSet) { - if (isSet) { - await this.addStatusEffectById(statusId); + console.log("setStatusEffect", label, status, updates) + const existing = this.getEffectByLabel(label); + if (existing) { + existing.delete(); } - else { - await this.deleteStatusEffectById(statusId); + if (status) { + const statusEffect = mergeObject(duplicate(StatusEffects.status(label)), updates); + await this.createEmbeddedDocuments("ActiveEffect", [statusEffect]); } } - /* -------------------------------------------- */ - async deleteStatusEffectById(id) { - - const ids = Array.from(this.effects?.values()) - .filter(it => it.data.flags.core?.statusId == id) - .map(it => it.id); - console.log("Delete effect IDS1: ", this.effects, ids); - if (ids.length > 0) { - await this.deleteEmbeddedDocuments('ActiveEffect', ids); + enleverActiveEffectById(id) { + if (game.user.isGM){ + const existing = this.getEffectById(id); + if (existing) { + existing.delete(); + } } } - /* -------------------------------------------- */ - async deleteStatusEffect(effect) { - const ids = Array.from(this.effects?.values()) - .filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect)) - .map(it => it.id); - console.log("Delete effect 1: ", this.effects, ids); - if (ids.length > 0) { - await this.deleteEmbeddedDocuments('ActiveEffect', ids); + enleverTousLesEffets() { + if (game.user.isGM){ + this.deleteEmbeddedDocuments('ActiveEffect', this.getActiveEffects().map(it => it.id)); } } - /* -------------------------------------------- */ - async addStatusEffectById(id) { - const statusEffect = CONFIG.statusEffects.find(it => it.id == id); - await this.addStatusEffect(statusEffect); - } - - /* -------------------------------------------- */ - async addStatusEffect(statusEffect) { - const effet = Misc.data(statusEffect); - await this.deleteStatusEffectById(effet.id); - effet.flags = effet.flags ?? { core: {} }; - effet.flags.core.statusId = effet.id; - let effectArray = await this.createEmbeddedDocuments('ActiveEffect', [effet]); - //if (effectArray[0]) { - //await effectArray[0].setFlag('core', 'statusId', effet.id); - //} - } - /* -------------------------------------------- */ async onPreUpdateItem(item, change, options, id) { const itemData = Misc.data(item); diff --git a/module/item-arme.js b/module/item-arme.js index 4eed2dca..f18bb712 100644 --- a/module/item-arme.js +++ b/module/item-arme.js @@ -3,7 +3,8 @@ import { Misc } from "./misc.js"; import { RdDCombatManager } from "./rdd-combat.js"; const nomCategorieParade = { - "sans-armes": "Sans arme / armes naturelles", + "sans-armes": "Sans arme", + "armes-naturelles": "Sans arme", "hast": "Armes d'hast", "batons": "Bâtons", "boucliers": "Boucliers", @@ -71,7 +72,7 @@ export class RdDItemArme extends Item { } // pour compatibilité avec des personnages existants if (armeData.type == 'competencecreature' || armeData.data.categorie == 'creature') { - return armeData.data.categorie_parade || (armeData.data.isparade ? 'sans-armes' : ''); + return armeData.data.categorie_parade || (armeData.data.isparade ? 'armes-naturelles' : ''); } if (!armeData.type.match(/arme|competencecreature/)) { return ''; @@ -137,7 +138,7 @@ export class RdDItemArme extends Item { /* -------------------------------------------- */ static armeUneOuDeuxMains(armeData, aUneMain) { armeData = Misc.data(armeData); - if (armeData) { + if (armeData && !armeData.data.cac) { armeData.data.unemain = armeData.data.unemain || !armeData.data.deuxmains; const uneOuDeuxMains = armeData.data.unemain && armeData.data.deuxmains; const containsSlash = !Number.isInteger(armeData.data.dommages) && armeData.data.dommages.includes("/"); @@ -170,11 +171,12 @@ export class RdDItemArme extends Item { let corpsACorps = competences.find(it => it.name == 'Corps à corps') ?? { data: { niveau: -6 } }; let init = RdDCombatManager.calculInitiative(corpsACorps.data.niveau, carac['melee'].value); armes.push(RdDItemArme.mainsNues({ niveau: corpsACorps.data.niveau, initiative: init })); + armes.push(RdDItemArme.empoignade({ niveau: corpsACorps.data.niveau, initiative: init })); } - static mainsNues(actorData = {}) { - const mainsNues = { - name: 'Mains nues', + static corpsACorps(actorData) { + const corpsACorps = { + name: 'Corps à corps', data: { equipe: true, rapide: true, @@ -186,9 +188,24 @@ export class RdDItemArme extends Item { categorie_parade: 'sans-armes' } }; - if (actorData) { - mergeObject(mainsNues.data, actorData, { overwrite: false }); - } - return mainsNues + mergeObject(corpsACorps.data, actorData ??{}, { overwrite: false }); + return corpsACorps; + } + + static mainsNues(actorData) { + const mainsNues = RdDItemArme.corpsACorps(actorData); + mainsNues.name = 'Mains nues'; + mainsNues.data.cac = 'pugilat'; + mainsNues.data.baseInit = 4; + return mainsNues; + } + + static empoignade(actorData) { + const empoignade = RdDItemArme.corpsACorps(actorData); + empoignade.name = 'Empoignade'; + empoignade.data.cac = 'empoignade'; + empoignade.data.baseInit = 3; + empoignade.data.mortalite = 'empoignade'; + return empoignade; } } diff --git a/module/misc.js b/module/misc.js index 3c115740..da74c589 100644 --- a/module/misc.js +++ b/module/misc.js @@ -13,6 +13,10 @@ export class Misc { return text.charAt(0).toUpperCase() + text.slice(1); } + static lowerFirst(text) { + return text.charAt(0).toLowerCase() + text.slice(1); + } + static toSignedString(number) { const value = parseInt(number) const isPositiveNumber = value != NaN && value > 0; diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 34ca5ec0..3105ade7 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -11,23 +11,23 @@ import { ReglesOptionelles } from "./regles-optionelles.js"; /* -------------------------------------------- */ const premierRoundInit = [ - { pattern: 'hast', init: 3.90 }, - { pattern: 'lance', init: 3.85 }, - { pattern: 'baton', init: 3.80 }, - { pattern: 'doubledragonne', init: 3.75 }, - { pattern: 'esparlongue', init: 3.70 }, - { pattern: 'epeedragonne', init: 3.65 }, - { pattern: 'epeebatarde', init: 3.60 }, - { pattern: 'epeecyane', init: 3.55 }, - { pattern: 'epeesorde', init: 3.50 }, - { pattern: 'grandehache', init: 3.45 }, - { pattern: 'bataille', init: 3.40 }, - { pattern: 'epeegnome', init: 3.35 }, - { pattern: 'masse', init: 3.30 }, - { pattern: 'gourdin', init: 3.25 }, - { pattern: 'fléau', init: 3.20 }, - { pattern: 'dague', init: 3.15 }, - { pattern: 'autre', init: 3.10 }, + { pattern: 'hast', init: 5.90 }, + { pattern: 'lance', init: 5.85 }, + { pattern: 'baton', init: 5.80 }, + { pattern: 'doubledragonne', init: 5.75 }, + { pattern: 'esparlongue', init: 5.70 }, + { pattern: 'epeedragonne', init: 5.65 }, + { pattern: 'epeebatarde', init: 5.60 }, + { pattern: 'epeecyane', init: 5.55 }, + { pattern: 'epeesorde', init: 5.50 }, + { pattern: 'grandehache', init: 5.45 }, + { pattern: 'bataille', init: 5.40 }, + { pattern: 'epeegnome', init: 5.35 }, + { pattern: 'masse', init: 5.30 }, + { pattern: 'gourdin', init: 5.25 }, + { pattern: 'fléau', init: 5.20 }, + { pattern: 'dague', init: 5.15 }, + { pattern: 'autre', init: 5.10 }, ]; /* -------------------------------------------- */ @@ -38,6 +38,9 @@ export class RdDCombatManager extends Combat { Hooks.on("getCombatTrackerEntryContext", (html, options) => { RdDCombatManager.pushInitiativeOptions(html, options); }); + Hooks.on("preDeleteCombat", (combat, html, id) => { + combat.onPreDeleteCombat() + }); } /* -------------------------------------------- */ @@ -48,10 +51,22 @@ export class RdDCombatManager extends Combat { } /* -------------------------------------------- */ - cleanSonne() { + async nextRound() { + this.cleanItemUse(); + await this.finDeRound(); + return await super.nextRound(); + } + + /* -------------------------------------------- */ + async onPreDeleteCombat() { + await this.finDeRound({ terminer: true }); + } + + /* -------------------------------------------- */ + async finDeRound(options = { terminer: false }) { for (let combatant of this.data.combatants) { if (combatant.actor) { - combatant.actor.verifierSonneRound(this.current.round); + await combatant.actor.finDeRound(options); } else { ui.notifications.warn(`Le combatant ${combatant.name} n'est pas associé à un acteur!`) @@ -59,14 +74,6 @@ export class RdDCombatManager extends Combat { } } - /* -------------------------------------------- */ - async nextRound() { - //console.log('New round !');s - this.cleanItemUse(); - this.cleanSonne(); - return super.nextRound(); - } - /************************************************************************************/ async rollInitiative(ids, formula = undefined, messageOptions = {}) { console.log(`${game.data.system.data.title} | Combat.rollInitiative()`, ids, formula, messageOptions); @@ -148,7 +155,8 @@ export class RdDCombatManager extends Combat { let compData = competences.map(c => Misc.data(c)).find(c => c.name == armeData.data.competence); armesEquipe.push(armeData); - armeData.data.initiative = RdDCombatManager.calculInitiative(armeData.data.niveau, carac[compData.data.defaut_carac].value); + armeData.data.niveau = compData.data.niveau; + armeData.data.initiative = RdDCombatManager.calculInitiative(compData.data.niveau, carac[compData.data.defaut_carac].value); // Dupliquer les armes pouvant être à 1 main et 2 mains en patchant la compétence if (armeData.data.unemain && !armeData.data.deuxmains) { armeData.data.mainInfo = "(1m)"; @@ -185,7 +193,8 @@ export class RdDCombatManager extends Combat { } else { // Recupération des items 'arme' let armes = items.filter(it => RdDItemArme.isArmeUtilisable(it)) - .concat(RdDItemArme.mainsNues()); + .concat(RdDItemArme.mainsNues()) + .concat(RdDItemArme.empoignade()); let competences = items.filter(it => it.type == 'competence'); actions = actions.concat(RdDCombatManager.finalizeArmeList(armes, competences, actorData.data.carac)); @@ -277,30 +286,23 @@ export class RdDCombatManager extends Combat { initOffset = 2; initInfo = "Autre Action" } else if (arme.name == "Draconic") { - initOffset = 7; + initOffset = 9; initInfo = "Draconic" } else { - initOffset = 3; // Melée = 3.XX compData = Misc.data(RdDItemCompetence.findCompetence(combatant.actor.data.items, arme.data.competence)); compNiveau = compData.data.niveau; initInfo = arme.name + " / " + arme.data.competence; - if (combatant.actor.data.type == 'creature' || combatant.actor.data.type == 'entite') { caracForInit = compData.data.carac_value; if (compData.data.categorie == "lancer") { + initOffset = 7; + } + else { initOffset = 5; } } else { caracForInit = Misc.data(combatant.actor).data.carac[compData.data.defaut_carac].value; - if (compData.data.categorie == "lancer") { // Offset de principe pour les armes de jet - initOffset = 4; - } - if (compData.data.categorie == "tir") { // Offset de principe pour les armes de jet - initOffset = 5; - } - if (compData.data.categorie == "melee") { // Offset de principe pour les armes de jet - initOffset = 3; - } + initOffset = RdDCombatManager._baseInitOffset(compData.data.categorie, arme); } } let malus = combatant.actor.getEtatGeneral(); // Prise en compte état général @@ -311,6 +313,21 @@ export class RdDCombatManager extends Combat { game.combat.rollInitiative(combatantId, rollFormula, { initInfo: initInfo }); } + static _baseInitOffset(categorie, arme) { + if (categorie == "tir") { // Offset de principe pour les armes de jet + return 8; + } + if (categorie == "lancer") { // Offset de principe pour les armes de jet + return 7; + } + // Offset de principe pour les armes de jet + switch (arme.data.cac) { + case "empoignade": return 3; + case "pugilat": return 4; + } + return 5; + } + /* -------------------------------------------- */ static displayInitiativeMenu(html, combatantId) { console.log("Combatant ; ", combatantId); @@ -706,10 +723,21 @@ export class RdDCombat { } /* -------------------------------------------- */ - async attaque(competence, arme = undefined) { + async attaque(competence, arme) { if (!await this.accorderEntite('avant-attaque')) { return; } + if (arme.data.cac == 'empoignade' && this.attacker.isCombatTouche()) { + ChatMessage.create({ + alias: this.attacker.name, + whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name), + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-actor-perte-empoignade.html', { + attacker: this.attacker, + competence: competence + }) + }); + return; + } let rollData = this._prepareAttaque(competence, arme); console.log("RdDCombat.attaque >>>", rollData); @@ -742,6 +770,7 @@ export class RdDCombat { _prepareAttaque(competence, arme) { let rollData = { passeArme: randomID(16), + mortalite: arme?.data.mortalite, coupsNonMortels: false, competence: competence, surprise: this.attacker.getSurprise(true), @@ -758,7 +787,9 @@ export class RdDCombat { } else { // sans armes: à mains nues - rollData.arme = RdDItemArme.mainsNues({ niveau: competence.data.niveau }); + const niveau = competence.data.niveau; + const init = RdDCombatManager.calculInitiative(niveau, Misc.templateData(this.attacker).carac['melee'].value); + rollData.arme = RdDItemArme.mainsNues({ niveau: niveau, initiative: init }); } return rollData; } @@ -767,8 +798,24 @@ export class RdDCombat { async _onAttaqueParticuliere(rollData) { RdDCombat._storeAttaque(this.attackerId, rollData); - // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum const isMeleeDiffNegative = (rollData.competence.type == 'competencecreature' || rollData.selectedCarac.label == "Mêlée") && rollData.diffLibre < 0; + // force toujours, sauf empoignade + // finesse seulement en mélée, pour l'empoignade, ou si la difficulté libre est de -1 minimum + // rapidité seulement en mêlée, si l'arme le permet, et si la difficulté libre est de -1 minimum + const isForce = !rollData.arme.data.empoignade; + const isFinesse = rollData.arme.data.empoignade || isMeleeDiffNegative; + const isRapide = !rollData.arme.data.empoignade && isMeleeDiffNegative && rollData.arme.data.rapide; + // si un seul choix possible, le prendre + if (isForce && !isFinesse && !isRapide) { + return await this.choixParticuliere(rollData, "force"); + } + else if (!isForce && isFinesse && !isRapide) { + return await this.choixParticuliere(rollData, "finesse"); + } + else if (!isForce && !isFinesse && isRapide) { + return await this.choixParticuliere(rollData, "rapidite"); + } + ChatMessage.create({ alias: this.attacker.name, whisper: ChatUtility.getWhisperRecipientsAndGMs(this.attacker.name), @@ -776,8 +823,9 @@ export class RdDCombat { alias: this.attacker.name, attackerId: this.attackerId, defenderTokenId: this.defenderTokenId, - isFinesse: isMeleeDiffNegative, - isRapide: isMeleeDiffNegative && rollData.arme.data.rapide, + isForce: isForce, + isFinesse: isFinesse, + isRapide: isRapide, passeArme: rollData.passeArme }) }); @@ -915,7 +963,7 @@ export class RdDCombat { console.log("RdDCombat._onEchecTotal >>>", rollData); const arme = rollData.arme; - const avecArme = arme && arme.data.categorie_parade != 'sans-armes'; + const avecArme = !['', 'sans-armes', 'armes-naturelles'].includes(arme?.data.categorie_parade ?? ''); const action = (rollData.attackerRoll ? (arme ? "la parade" : "l'esquive") : "l'attaque"); ChatUtility.createChatWithRollMode(this.defender.name, { content: `Maladresse à ${action}! ` + await RdDRollTables.getMaladresse({ arme: avecArme }) @@ -1176,7 +1224,7 @@ export class RdDCombat { defenderRoll.show.recul = 'encaisse'; } else if (rollRecul.rolled.isETotal || this._isReculCauseChute(impact)) { defenderRoll.show.recul = 'chute'; - await this.defender.addStatusEffectById('prone'); + await this.defender.setStatusEffect("EFFECT.StatusProne", true); } else { defenderRoll.show.recul = 'recul'; diff --git a/module/rdd-hotbar-drop.js b/module/rdd-hotbar-drop.js index a3efd6b6..2f4e6343 100644 --- a/module/rdd-hotbar-drop.js +++ b/module/rdd-hotbar-drop.js @@ -1,3 +1,4 @@ +import { Misc } from "./misc.js"; export class RdDHotbar { @@ -67,15 +68,14 @@ export class RdDHotbar { let actor; if (speaker.token) actor = game.actors.tokens[speaker.token]; if (!actor) actor = game.actors.get(speaker.actor); - let item = actor ? actor.items.find(i => i.name === itemName && i.type == itemType) : null; - if (!item) return ui.notifications.warn(`Impossible de trouver l'objet de cette macro`); - item = item.data; + let item = Misc.data(actor?.items.find(it => it.name === itemName && it.type == itemType)); + if (!item) return ui.notifications.warn(`Impossible de trouver l'objet de cette macro`); // Trigger the item roll switch (item.type) { case "arme": - return actor.rollArme(item.data.competence, itemName); + return actor.rollArme(item); case "competence": return actor.rollCompetence( itemName ); } diff --git a/module/rdd-roll-encaisser.js b/module/rdd-roll-encaisser.js index a8e0d6e5..8e87e284 100644 --- a/module/rdd-roll-encaisser.js +++ b/module/rdd-roll-encaisser.js @@ -8,8 +8,9 @@ export class RdDEncaisser extends Dialog { constructor(html, actor) { // Common conf const buttonsCreatures = { - "mortel": { label: "mortel", callback: html => this.performEncaisser("mortel") }, - "non-mortel": { label: "non-mortel", callback: html => this.performEncaisser("non-mortel") }, + "mortel": { label: "Mortel", callback: html => this.performEncaisser("mortel") }, + "non-mortel": { label: "Non-mortel", callback: html => this.performEncaisser("non-mortel") }, + "sonne": { label: "Sonné", callback: html => this.actor.setSonne() }, }; const buttonsEntitesCauchemar = { "cauchemar": { label: "cauchemar", callback: html => this.performEncaisser("cauchemar") } diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 335c2082..243d5b6a 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -221,7 +221,7 @@ export class RdDRoll extends Dialog { console.log("RdDRollSelectDialog - Cout reve", ptreve); this.updateRollResult(); }); - html.find('#coupsNonMortels').change((event) => { + html.find("[name='coupsNonMortels']").change((event) => { this.rollData.dmg.mortalite = event.currentTarget.checked ? "non-mortel" : "mortel"; this.updateRollResult(); }); @@ -300,12 +300,14 @@ export class RdDRoll extends Dialog { rollData.dmg = rollData.attackerRoll?.dmg ?? RdDBonus.dmg(rollData, this.actor.getBonusDegat()); rollData.caracValue = parseInt(rollData.selectedCarac.value); + rollData.mortalite = rollData.attackerRoll?.dmg.mortalite ?? rollData.dmg.mortalite ?? 'mortel'; rollData.coupsNonMortels = (rollData.attackerRoll?.dmg.mortalite ?? rollData.dmg.mortalite) == 'non-mortel'; rollData.use.appelAuMoral = this.actor.isPersonnage() && RdDCarac.isActionPhysique(rollData.selectedCarac); let dmgText = Misc.toSignedString(rollData.dmg.total); - if (rollData.coupsNonMortels) { - dmgText = `(${dmgText}) non-mortel` + switch (rollData.mortalite){ + case 'non-mortel': dmgText = `(${dmgText}) non-mortel`; break; + case 'empoignade': dmgText = `empoignade`; break; } RollDataAjustements.calcul(rollData, this.actor); @@ -318,7 +320,7 @@ export class RdDRoll extends Dialog { // Mise à jour valeurs $(".dialog-roll-title").text(this._getTitle(rollData)); - $('#coupsNonMortels').prop('checked', rollData.coupsNonMortels); + $("[name='coupsNonMortels']").prop('checked', rollData.mortalite == 'non-mortel'); $(".dmg-arme-actor").text(dmgText); $('.table-ajustement').remove(); $(".table-resolution").remove(); diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index 48770b48..41c3ac73 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -274,7 +274,7 @@ export class RdDTMRDialog extends Dialog { if ( this.actor.tmrApp ) { this.actor.tmrApp = undefined; // Cleanup reference if ( !this.viewOnly ) { - this.actor.setStatusDemiReve(false); + this.actor.setStatusEffect("EFFECT.StatusDemiReve", false); this._tellToGM(this.actor.name + " a quitté les terres médianes"); } this.actor.santeIncDec("fatigue", this.cumulFatigue).then(super.close()); // moving 1 cell costs 1 fatigue diff --git a/module/rdd-token-hud.js b/module/rdd-token-hud.js index d748964f..a754512f 100644 --- a/module/rdd-token-hud.js +++ b/module/rdd-token-hud.js @@ -53,8 +53,7 @@ export class RdDTokenHud { await RdDTokenHud._configureSubMenu(controlIconTarget, 'systems/foundryvtt-reve-de-dragon/templates/hud-actor-attaque.html', hudData, (event) => { let armeIndex = event.currentTarget.attributes['data-arme-id'].value; - let arme = armesList[armeIndex]; - actor.rollArme(arme.data.competence, arme.name); + actor.rollArme(armesList[armeIndex]); }); } diff --git a/module/rdd-utility.js b/module/rdd-utility.js index 6de5f96c..40f64b44 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -115,6 +115,7 @@ export class RdDUtility { 'systems/foundryvtt-reve-de-dragon/templates/actor-vehicule-sheet.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-sheet-competence-partial.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-sheet-categorie-competences-partial.html', + 'systems/foundryvtt-reve-de-dragon/templates/actor-sheet-effects-partial.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-sheet-oeuvre-partial.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-liste-blessures-partial.html', 'systems/foundryvtt-reve-de-dragon/templates/actor-blessure-partial.html', diff --git a/module/status-effects.js b/module/status-effects.js index 5b05324b..b8f838c1 100644 --- a/module/status-effects.js +++ b/module/status-effects.js @@ -1,23 +1,31 @@ -const demiReveStatusEffect = { id: 'demi-reve', rdd: true, label: 'Demi-rêve', icon: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' }; const rddStatusEffects = [ - { id: 'sonne', rdd: true, label: 'Sonné', icon: 'icons/svg/stoned.svg' }, - demiReveStatusEffect + { rdd: true, id: 'stun', label: 'EFFECT.StatusStunned', icon: 'icons/svg/stoned.svg', "duration.rounds": 1 }, + { rdd: true, id: 'bleeding', label: 'EFFECT.StatusBleeding', icon: 'icons/svg/blood.svg' }, + { rdd: true, id: 'prone', label: 'EFFECT.StatusProne', icon: 'icons/svg/falling.svg' }, + { rdd: true, id: 'grappling', tint: '#33cc33', label: 'EFFECT.StatusGrappling', icon: 'systems/foundryvtt-reve-de-dragon/icons/competence_corps_a_corps.webp' }, + { rdd: true, id: 'grappled', tint: '#ff9900', label: 'EFFECT.StatusGrappled', icon: 'systems/foundryvtt-reve-de-dragon/icons/competence_corps_a_corps.webp' }, + { rdd: true, id: 'restrain', label: 'EFFECT.StatusRestrained', icon: 'icons/svg/net.svg' }, + { rdd: true, id: 'unconscious', label: 'EFFECT.StatusUnconscious', icon: 'icons/svg/unconscious.svg' }, + { rdd: true, id: 'blind', label: 'EFFECT.StatusBlind', icon: 'icons/svg/blind.svg' }, + { rdd: true, id: 'comma', label: 'EFFECT.StatusComma', icon: 'icons/svg/skull.svg' }, + { rdd: true, id: 'dead', label: 'EFFECT.StatusDead', icon: 'icons/svg/skull.svg' }, + { rdd: true, id: 'demi-reve', label: 'EFFECT.StatusDemiReve', icon: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' } ]; -const statusDemiSurprise = new Set(['sonne', 'prone', 'restrain']); -const statusSurpriseTotale = new Set(['unconscious', 'blind', 'dead']); +const demiReveStatusEffect = rddStatusEffects.find(it => it.label == 'EFFECT.StatusDemiReve'); + +const statusDemiSurprise = new Set(['EFFECT.StatusStunned', 'EFFECT.StatusProne', 'EFFECT.StatusRestrain']); +const statusSurpriseTotale = new Set(['EFFECT.StatusUnconscious', 'EFFECT.StatusBlind', 'EFFECT.StatusComma']); export class StatusEffects { static onReady() { - StatusEffects.setCoreStatusId([demiReveStatusEffect]); - StatusEffects.setCoreStatusId(rddStatusEffects); - StatusEffects.setMandatoryRdd(); - const defaultUseStatusEffect = CONFIG.statusEffects.map(it => it.id).join(); + const rddStatusIds = rddStatusEffects.map(it => it.id); + const defaultStatusEffectIds = CONFIG.statusEffects.map(it => it.id); game.settings.register("foundryvtt-reve-de-dragon", "use-status-effects", { name: "use-status-effects", scope: "world", config: false, - default: defaultUseStatusEffect, + default: defaultStatusEffectIds.join(), type: String }); @@ -29,30 +37,21 @@ export class StatusEffects { type: StatusEffectsSettings, restricted: true }); - CONFIG.RDD.allEffects = rddStatusEffects.concat(CONFIG.statusEffects); - + + CONFIG.RDD.allEffects = rddStatusEffects.concat(CONFIG.statusEffects.filter(it => !rddStatusIds.includes(it.id))); + StatusEffects._setUseStatusEffects(StatusEffects._getUseStatusEffects()); console.log('statusEffects', CONFIG.statusEffects); } static valeurSurprise(effect, isCombat) { - const id = StatusEffects.statusId(effect); - if (statusSurpriseTotale.has(id)) { + // const id = StatusEffects.statusId(effect); + if (statusSurpriseTotale.has(effect.label)) { return 2; } - return statusDemiSurprise.has(id) || (isCombat && id == demiReveStatusEffect.id) ? 1 : 0; + return statusDemiSurprise.has(effect.label) || (isCombat && effect.label == demiReveStatusEffect.label) ? 1 : 0; } - static statusId(effectData) { - return effectData.flags?.core?.statusId ?? effectData["flags.core.statusId"]; - } - - static setCoreStatusId(list) { - list.forEach(it => { - it.flags = { core: { statusId: it.id } }; - it["flags.core.statusId"] = it.id; - }); - } static setMandatoryRdd() { CONFIG.statusEffects.filter(it => statusDemiSurprise.has(it.id) || statusSurpriseTotale.has(it.id)) .forEach(it => it.rdd = true); @@ -78,6 +77,9 @@ export class StatusEffects { return Array.from(useStatusEffects).join(); } + static status(label) { + return rddStatusEffects.find(it => it.label == label) ?? { label: label }; + } static demiReve() { return demiReveStatusEffect; } diff --git a/styles/simple.css b/styles/simple.css index ff9b32a0..e925f788 100644 --- a/styles/simple.css +++ b/styles/simple.css @@ -271,6 +271,11 @@ table {border: 1px solid #7a7971;} height: 16; border-width: 0; } +.button-effect-img:hover { + color: rgba(255, 255, 128, 0.7); + border: 1px solid rgba(255, 128, 0, 0.8); + cursor: pointer; +} .small-button-container { height: 16px; diff --git a/template.json b/template.json index c5bee0fa..14df291c 100644 --- a/template.json +++ b/template.json @@ -164,11 +164,6 @@ "value": 10, "label": "Endurance", "derivee": false - }, - "sonne": { - "value": false, - "round": -1, - "label": "Sonné" } }, "blessures": { @@ -374,10 +369,6 @@ "value": 0, "label": "Fatigue", "derivee": true - }, - "sonne": { - "value": false, - "label": "Sonné" } }, "blessures": { diff --git a/templates/actor-creature-sheet.html b/templates/actor-creature-sheet.html index 70b233a5..2ba0ebca 100644 --- a/templates/actor-creature-sheet.html +++ b/templates/actor-creature-sheet.html @@ -18,19 +18,7 @@ {{calc.resumeBlessures}}
- {{#if calc.surprise}}{{calc.surprise}}! {{/if}} - {{#if effects}} - {{#each effects as |effect key|}} - - {{effect.label}} - - {{/each}} - {{#if options.isGM}} - (enlever tout) - {{/if}} - {{else}} - Aucun effet actif - {{/if}} + {{>"systems/foundryvtt-reve-de-dragon/templates/actor-sheet-effects-partial.html"}}
@@ -94,10 +82,6 @@ -
  • - Sonné - -
  • Etat Général diff --git a/templates/actor-sheet-effects-partial.html b/templates/actor-sheet-effects-partial.html new file mode 100644 index 00000000..3e4ada8f --- /dev/null +++ b/templates/actor-sheet-effects-partial.html @@ -0,0 +1,13 @@ +{{#if calc.surprise}}{{calc.surprise}}! {{/if}} +{{#if effects}} +{{#each effects as |effect key|}} + + {{localize effect.label}} + +{{/each}} +{{#if options.isGM}} +(enlever tout) +{{/if}} +{{else}} +Aucun effet actif +{{/if}} diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index e6da1fc8..42ec06ab 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -37,12 +37,6 @@ +
  • -
  • - -
  • {{/each}}