diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 1cca5819..c72a189b 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -8,6 +8,7 @@ import { HtmlUtility } from "./html-utility.js"; import { RdDItem } from "./item.js"; import { RdDItemArme } from "./item-arme.js"; import { RdDItemCompetence } from "./item-competence.js"; +import { RdDBonus } from "./rdd-bonus.js"; /* -------------------------------------------- */ export class RdDActorSheet extends ActorSheet { @@ -108,6 +109,7 @@ export class RdDActorSheet extends ActorSheet { // Common data data.data.competenceByCategory = data.competenceByCategory; data.data.encTotal = this.actor.encTotal; + data.data.surprise = RdDBonus.find(this.actor.getSurprise(false)).descr; data.data.isGM = game.user.isGM; data.ajustementsConditions = CONFIG.RDD.ajustementsConditions; data.difficultesLibres = CONFIG.RDD.difficultesLibres; @@ -126,7 +128,6 @@ export class RdDActorSheet extends ActorSheet { data.data.vehiculesList = this.actor.buildVehiculesList(); data.data.monturesList = this.actor.buildMonturesList(); data.data.suivantsList = this.actor.buildSuivantsList(); - return data; } @@ -332,7 +333,9 @@ export class RdDActorSheet extends ActorSheet { html.find('#dormir-chateau-dormant').click((event) => { this.actor.dormirChateauDormant(); }); - + html.find('#enlever-tous-effets').click((event) => { + this.actor.enleverTousLesEffets(); + }); // Display info about queue html.find('.queuesouffle-label a').click((event) => { let myID = event.currentTarget.attributes['data-item-id'].value; @@ -483,6 +486,10 @@ export class RdDActorSheet extends ActorSheet { this.actor.santeIncDec("endurance", -1); this.render(true); }); + html.find('.data-sante-sonne').click((event) => { + this.actor.setSonne(event.currentTarget.checked); + this.render(true); + }); html.find('#ptreve-actuel-plus').click((event) => { this.actor.reveActuelIncDec(1); this.render(true); diff --git a/module/actor.js b/module/actor.js index 4b918d10..f931bb56 100644 --- a/module/actor.js +++ b/module/actor.js @@ -18,6 +18,8 @@ import { RdDAudio } from "./rdd-audio.js"; import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemArme } from "./item-arme.js"; import { RdDAlchimie } from "./rdd-alchimie.js"; +import { StatusEffects } from "./status-effects.js"; + /* -------------------------------------------- */ /** @@ -25,6 +27,12 @@ import { RdDAlchimie } from "./rdd-alchimie.js"; * @extends {Actor} */ export class RdDActor extends Actor { + /* -------------------------------------------- */ + static init() { + Hooks.on("deleteActiveEffect", (actor, effect, options) => actor.onDeleteActiveEffect(effect, options)); + Hooks.on("createActiveEffect", (actor, effect, options) => actor.onCreateActiveEffect(effect, options)); + Hooks.on("updateToken", (scene, token, data, options) => { RdDActor.onUpdateToken(scene, token, data, options) }); + } /* -------------------------------------------- */ /** @@ -247,24 +255,19 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - getSurprise() { - // TODO: gérer une liste de flags demi-surprise (avec icône sur le token)? - if (this.getSonne()) { + getSurprise(isCombat = true) { + let niveauSurprise = Array.from(this.effects?.values() ?? []) + .map(effect => StatusEffects.valeurSurprise(effect.data, isCombat)) + .reduce((a,b)=> a+b, 0); + if (niveauSurprise>1) { + return 'totale'; + } + if (niveauSurprise==1 || this.getSonne()) { return 'demi'; } return ''; } - /* -------------------------------------------- */ - isDemiSurprise() { - return this.getSurprise() == 'demi'; - } - - /* -------------------------------------------- */ - isSurpriseTotale() { - return this.getSurprise() == 'totale'; - } - /* -------------------------------------------- */ async dormirChateauDormant() { let message = { @@ -693,14 +696,14 @@ export class RdDActor extends Actor { return true; } - /* -------------------------------------------- */ - buildSubConteneurObjetList( conteneurId, deleteList ) { - let conteneur = this.items.find( conteneur => conteneurId == conteneur._id); // recup conteneur - if ( conteneur && conteneur.type =='conteneur' ) { // Si présent - for ( let subId of conteneur.data.data.contenu ) { - let subObj = this.items.find( subobjet => subId == subobjet._id); // recup conteneur - if ( subObj && subObj.type == 'conteneur') { - this.buildSubConteneurObjetList( subId, deleteList ); + /* -------------------------------------------- */ + buildSubConteneurObjetList(conteneurId, deleteList) { + let conteneur = this.items.find(conteneur => conteneurId == conteneur._id); // recup conteneur + if (conteneur && conteneur.type == 'conteneur') { // Si présent + for (let subId of conteneur.data.data.contenu) { + let subObj = this.items.find(subobjet => subId == subobjet._id); // recup conteneur + if (subObj && subObj.type == 'conteneur') { + this.buildSubConteneurObjetList(subId, deleteList); } if ( subObj) // Robust... deleteList.push( {id: subId, conteneurId: conteneurId } ); @@ -765,8 +768,8 @@ export class RdDActor extends Actor { await this.updateOwnedItem(conteneurFixedList); } - /* -------------------------------------------- */ - async moveItemsBetweenActors( itemId, sourceActorId ) { + /* -------------------------------------------- */ + async moveItemsBetweenActors(itemId, sourceActorId) { let itemsList = [] let sourceActor = game.actors.get( sourceActorId ); itemsList.push( {id: itemId, conteneurId: undefined }); // Init list @@ -795,8 +798,8 @@ export class RdDActor extends Actor { } } - /* -------------------------------------------- */ - detectSurEncombrement( ) { + /* -------------------------------------------- */ + detectSurEncombrement() { let maxEnc = 0; if ( this.data.type == 'vehicule') maxEnc = this.data.data.capacite_encombrement; @@ -830,7 +833,6 @@ export class RdDActor extends Actor { } // Mise à jour valeur totale et états this.encTotal = encTotal; - console.log("Enco total : ", this.encTotal); this.detectSurEncombrement(); // Mise à jour éventuelle du malus armure if (this.data.data.attributs && this.data.data.attributs.malusarmure && newMalusArmure != malusArmureData.value) { @@ -1058,17 +1060,31 @@ export class RdDActor extends Actor { return !this.isEntiteCauchemar() && (this.data.data.sante.sonne?.value ?? false); } - /* -------------------------------------------- */ - getSConst() { - - if (!this.isEntiteCauchemar() && this.data.data.attributs) { - return this.data.data.attributs.sconst.value; + async setSonne(sonne = true) { + if (this.isEntiteCauchemar()) { + return; } - return 0; + await this.setStatusSonne(sonne); + await this.setStateSonne(sonne); + } + + async setStateSonne(sonne) { + if (this.isEntiteCauchemar()) { + return; + } + await this.update({ "data.sante.sonne.value": sonne }); } /* -------------------------------------------- */ - testSiSonne(sante, endurance) { + getSConst() { + if (this.isEntiteCauchemar()) { + return 0; + } + return this.data.data.attributs?.sconst?.value ?? 0; + } + + /* -------------------------------------------- */ + async testSiSonne(sante, endurance) { const roll = new Roll("1d20").roll(); roll.showDice = true; RdDDice.show(roll); @@ -1083,6 +1099,7 @@ export class RdDActor extends Actor { } if (result.sonne) { // 20 is always a failure + await this.setSonne(); sante.sonne.value = true; } return result; @@ -1127,8 +1144,8 @@ export class RdDActor extends Actor { async santeIncDec(name, inc, isCritique = false) { const sante = duplicate(this.data.data.sante); - let data = sante[name]; - if (!data) { + let compteur = sante[name]; + if (!compteur) { return; } let result = { @@ -1141,35 +1158,34 @@ export class RdDActor extends Actor { minValue = name == "vie" ? -Number(this.data.data.attributs.sconst.value) : 0; } - result.newValue = Math.max(minValue, Math.min(data.value + inc, data.max)); + result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max)); //console.log("New value ", inc, minValue, result.newValue); - + let fatigue = 0; if (name == "endurance" && this.data.type != 'entite') { - if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost - sante.fatigue.value = sante.fatigue.value - inc - } if (!isCritique && result.newValue == 0 && inc < 0) { // perte endurance et endurance devient 0 -> -1 vie sauf si coup critique sante.vie.value = sante.vie.value - 1; } result.newValue = Math.max(0, result.newValue); if (inc > 0) { // le max d'endurance s'applique seulement à la récupération - result.newValue = Math.min(result.newValue, this._computeEnduranceMax()) + result.newValue = Math.min(result.newValue, this._computeEnduranceMax()) } - if (data.value - result.newValue > 1) { + const perte = compteur.value - result.newValue; + if (perte > 1) { // Peut-être sonné si 2 points d'endurance perdus d'un coup - const testIsSonne = this.testSiSonne(sante, result.newValue); + const testIsSonne = await this.testSiSonne(sante, result.newValue); result.sonne = testIsSonne.sonne; result.jetEndurance = testIsSonne.roll.total; - } else if (inc > 0 && !this.isEntiteCauchemar()) { - sante.sonne.value = false; + } else if (inc > 0) { + await this.setSonne(false); + } + if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost + fatigue = perte; } } - data.value = result.newValue; + compteur.value = result.newValue; //console.log(name, inc, data.value, result.newValue, minValue, data.max); - if (sante.fatigue) { // If endurance lost, then the same amount of fatigue cannot be recovered - sante.fatigue.value = Math.max(sante.fatigue.value, this._computeFatigueMin()); - } - //console.log("SANTE::::", sante); + // If endurance lost, then the same amount of fatigue cannot be recovered + sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this._computeFatigueMin()); await this.update({ "data.sante": sante }); return result; @@ -1779,8 +1795,7 @@ export class RdDActor extends Actor { competence: competence, tache: tache, diffConditions: tache.data.difficulte, - editLibre: false, - editConditions: false, + use: { libre: false, conditions: false}, carac: {} }; rollData.carac[tache.data.carac] = duplicate(this.data.data.carac[tache.data.carac]); // Single carac @@ -1830,8 +1845,7 @@ export class RdDActor extends Actor { isPurification: false, }, diffConditions: 0, - editLibre: false, - editConditions: true, + use: { libre: false, conditions: true, }, carac: {} }; meditationData.carac["intellect"] = duplicate(this.data.data.carac["intellect"]); @@ -2196,37 +2210,37 @@ export class RdDActor extends Actor { } /* -------------------------------------------- */ - async encaisserDommages(attackerRoll, attacker = undefined) { + async encaisserDommages(rollData, attacker = undefined) { if (attacker && !await attacker.accorder(this, 'avant-encaissement')) { return; } - console.log("encaisserDommages", attackerRoll) + console.log("encaisserDommages", rollData) let santeOrig = duplicate(this.data.data.sante); - let encaissement = this.jetEncaissement(attackerRoll); + let encaissement = this.jetEncaissement(rollData); this.ajouterBlessure(encaissement); // Will upate the result table const perteVie = await this.santeIncDec("vie", - encaissement.vie); const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, (encaissement.critiques > 0)); - + this.computeEtatGeneral(); this.sheet.render(false); - + let santeActuelle = duplicate(this.data.data.sante); - + encaissement.alias = this.data.name; encaissement.hasPlayerOwner = this.hasPlayerOwner; encaissement.resteEndurance = santeActuelle.endurance.value encaissement.sonne = perteEndurance.sonne; - encaissement.jetEndurance = perteEndurance.jetEndurance, + encaissement.jetEndurance = perteEndurance.jetEndurance; encaissement.endurance = santeOrig.endurance.value - perteEndurance.newValue; encaissement.vie = santeOrig.vie.value - perteVie.newValue; - + ChatUtility.createChatWithRollMode(this.name, { - roll: encaissement.roll, - content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) - }); + roll: encaissement.roll, + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement) + }); if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) { encaissement = duplicate(encaissement); @@ -2513,7 +2527,6 @@ export class RdDActor extends Actor { } } - /* -------------------------------------------- */ _alchimieResult(rollData) { RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-alchimie.html'); @@ -2592,6 +2605,100 @@ export class RdDActor extends Actor { await this.update( { 'data.subacteurs.suivants': newSuivants }); await this.update( { 'data.subacteurs.montures': newMontures }); } + /* -------------------------------------------- */ + async onCreateActiveEffect(effect, options) { + switch (StatusEffects.statusId(effect)) { + case 'sonne': + await this.setStateSonne(true); + return; + } + } + + /* -------------------------------------------- */ + async onDeleteActiveEffect(effect, options) { + switch (StatusEffects.statusId(effect)) { + case 'sonne': + await this.setStateSonne(false); + return; + } + } + + /* -------------------------------------------- */ + enleverTousLesEffets() { + this.deleteEmbeddedEntity('ActiveEffect', Array.from(this.effects?.keys() ?? [])); + } + + /* -------------------------------------------- */ + listeEffets(matching = it => true) { + const all = Array.from(this.effects?.values() ?? []); + const filtered = all.filter(it => matching(it.data)); + return filtered; + } + + /* -------------------------------------------- */ + async setStatusDemiReve(status) { + const options = { renderSheet: true/*, noHook: from == 'hook' */ }; + if (status) { + await this.addEffect(StatusEffects.demiReve(), options) + } + else { + this.deleteEffect(StatusEffects.demiReve(), options) + } + } + + /* -------------------------------------------- */ + async setStatusSonne(sonne) { + if (this.isEntiteCauchemar()) { + return; + } + const id = 'sonne'; + const options = { renderSheet: true/*, noHook: from == 'hook' */ }; + + if (sonne) { + await this.addById(id, options); + } + else /* if (!sonne)*/ { + this.deleteById(id, options) + } + } + + /* -------------------------------------------- */ + deleteById(id, options) { + const effects = Array.from(this.effects?.values()) + .filter(it => it.data.flags.core?.statusId == id); + this._deleteAll(effects, options); + } + + /* -------------------------------------------- */ + deleteEffect(effect, options) { + const toDelete = Array.from(this.effects?.values()) + .filter(it => StatusEffects.statusId(it.data) == StatusEffects.statusId(effect)); + this._deleteAll(toDelete, options); + } + + /* -------------------------------------------- */ + _deleteAll(effects, options) { + this._deleteAllIds(effects.map(it => it.id), options); + } + + /* -------------------------------------------- */ + _deleteAllIds(effectIds, options) { + this.deleteEmbeddedEntity('ActiveEffect', effectIds, options); + this.applyActiveEffects(); + } + + /* -------------------------------------------- */ + async addById(id, options) { + const statusEffect = CONFIG.statusEffects.find(it => it.id == id); + await this.addEffect(statusEffect, options); + } + + /* -------------------------------------------- */ + async addEffect(statusEffect, options) { + this.deleteById(statusEffect.id, options); + const effet = duplicate(statusEffect); + effet["flags.core.statusId"] = effet.id; + await this.createEmbeddedEntity('ActiveEffect', effet, options); + this.applyActiveEffects(); + } } - - diff --git a/module/rdd-bonus.js b/module/rdd-bonus.js index 3a8806fd..bce20f3d 100644 --- a/module/rdd-bonus.js +++ b/module/rdd-bonus.js @@ -38,7 +38,7 @@ export class RdDBonus { dmg.penetration = RdDBonus._peneration(rollData); dmg.dmgTactique = RdDBonus.dmgBonus(rollData.tactique); dmg.dmgParticuliere = RdDBonus._dmgParticuliere(rollData); - dmg.dmgSurprise = RdDBonus.dmgBonus(rollData.surpriseDefenseur); + dmg.dmgSurprise = RdDBonus.dmgBonus(rollData.ajustements?.attaqueDefenseurSurpris.used); dmg.dmgActor = rollData.selectedCarac ? RdDBonus._dmgPerso(dmgActor, rollData.selectedCarac.label, dmg.dmgArme) : 0; dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere; dmg.mortalite = RdDBonus._calculMortalite(rollData, isCauchemar) diff --git a/module/rdd-combat.js b/module/rdd-combat.js index 14f3966b..87dffda8 100644 --- a/module/rdd-combat.js +++ b/module/rdd-combat.js @@ -9,6 +9,67 @@ import { RdDRollTables } from "./rdd-rolltables.js"; export class RdDCombat { + static init() { + this.initStorePasseArmes(); + Hooks.on("updateCombat", (combat, data) => { RdDCombat.onUpdateCombat(combat, data) }); + Hooks.on("preDeleteCombat", (combat, options) => { RdDCombat.onPreDeleteCombat(combat, options); }); + } + + /* -------------------------------------------- */ + static initStorePasseArmes() { + game.system.rdd.combatStore = { + attaques: {}, + defenses: {} + }; + } + + /* -------------------------------------------- */ + static onSocketMessage(sockmsg) { + switch (sockmsg.msg) { + case "msg_encaisser": + return RdDCombat.terminerPasseArmes(data); + case "msg_defense": + return RdDCombat.handleMsgDefense(sockmsg.data); + } + } + + /* -------------------------------------------- */ + static onUpdateCombat(combat, data) { + if (combat.data.round != 0 && combat.turns && combat.data.active) { + RdDCombat.combatNouveauRound(combat); + } + } + + /* -------------------------------------------- */ + static onPreDeleteCombat(combat, options) { + if (game.user.isGM) { + ChatUtility.removeMyChatMessageContaining(`
`) + /* + * TODO: support de plusieurs combats parallèles + * il faudrait avoir un id de combat en plus de celui de passe d'armes + */ + for (const key in game.system.rdd.combatStore.attaques) { + const attackerRoll = game.system.rdd.combatStore.attaques[key]; + ChatUtility.removeMyChatMessageContaining(`
`); + } + for (const key in game.system.rdd.combatStore.defenses) { + const defenderRoll = game.system.rdd.combatStore.defenses[key]; + ChatUtility.removeMyChatMessageContaining(`
`); + } + RdDCombat.initStorePasseArmes(); + } + } + + /* -------------------------------------------- */ + static combatNouveauRound(combat) { + let turn = combat.turns.find(t => t.tokenId == combat.current.tokenId); + if (game.user.isGM) { + // seul le GM notifie le status + this.displayActorCombatStatus(combat, turn.actor); + // TODO Playaudio for player?? + } + } + /* -------------------------------------------- */ static isActive() { return true; @@ -58,6 +119,33 @@ export class RdDCombat { return RdDCombat.createUsingTarget(attacker) } + /* -------------------------------------------- */ + static handleMsgDefense(data) { + let defenderToken = canvas.tokens.get(data.defenderTokenId); + if (defenderToken) { + if (!game.user.isGM && game.user.character == undefined) { // vérification / sanity check + ui.notifications.error("Le joueur " + game.user.name + " n'est connecté à aucun personnage. Impossible de continuer."); + return; + } + if ((game.user.isGM && !defenderToken.actor.hasPlayerOwner) || (defenderToken.actor.hasPlayerOwner && (game.user.character.id == defenderToken.actor.data._id))) { + //console.log("User is pushing message...", game.user.name); + game.system.rdd.combatStore.attaques[data.attackerId] = duplicate(data.rollData); + data.whisper = [game.user]; + data.blind = true; + data.rollMode = "blindroll"; + ChatMessage.create(data); + } + } + } + + static terminerPasseArmes(data) { + if (game.user.isGM) { // Seul le GM nettoie le stockage des données de combat + let attackerRoll = game.system.rdd.combatStore.attaques[data.attackerId]; // Retrieve the rolldata from the store + game.system.rdd.combatStore.attaques[data.attackerId] = undefined; + game.system.rdd.combatStore.defenses[attackerRoll.passeArme] = undefined; + } + } + /* -------------------------------------------- */ static _sendRollMessage(sender, recipient, defenderTokenId, topic, message, rollData) { let chatMessage = { @@ -129,12 +217,12 @@ export class RdDCombat { /* -------------------------------------------- */ async onEvent(button, event) { - let attackerRoll = game.system.rdd.rollDataHandler.attaques[this.attackerId]; + let attackerRoll = game.system.rdd.combatStore.attaques[this.attackerId]; if (!attackerRoll) { ui.notifications.warn("Action automatisée impossible, le jet de l'attaquant a été perdu (suite à un raffraichissement?)") return; } - const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId'].value; + const defenderTokenId = event.currentTarget.attributes['data-defenderTokenId']?.value; const armeParadeId = event.currentTarget.attributes['data-armeid']?.value; @@ -163,18 +251,18 @@ export class RdDCombat { /* -------------------------------------------- */ _consumeDefense(passeArme) { let defenderRoll = this._getDefense(passeArme); - game.system.rdd.rollDataHandler.defenses[passeArme] = undefined; + game.system.rdd.combatStore.defenses[passeArme] = undefined; return defenderRoll; } /* -------------------------------------------- */ _getDefense(passeArme) { - return game.system.rdd.rollDataHandler.defenses[passeArme]; + return game.system.rdd.combatStore.defenses[passeArme]; } /* -------------------------------------------- */ _storeDefense(defenderRoll) { - game.system.rdd.rollDataHandler.defenses[defenderRoll.passeArme] = defenderRoll; + game.system.rdd.combatStore.defenses[defenderRoll.passeArme] = defenderRoll; } /* -------------------------------------------- */ @@ -235,7 +323,7 @@ export class RdDCombat { /* -------------------------------------------- */ static isEchec(rollData) { - switch (rollData.surprise) { + switch (rollData.ajustements.surprise.used) { case 'totale': return true; } return rollData.rolled.isEchec; @@ -243,7 +331,7 @@ export class RdDCombat { /* -------------------------------------------- */ static isEchecTotal(rollData) { - if (!rollData.attackerRoll && rollData.surprise) { + if (!rollData.attackerRoll && rollData.ajustements.surprise.used) { return rollData.rolled.isEchec; } return rollData.rolled.isETotal; @@ -251,7 +339,7 @@ export class RdDCombat { /* -------------------------------------------- */ static isParticuliere(rollData) { - if (!rollData.attackerRoll && rollData.surprise) { + if (!rollData.attackerRoll && rollData.ajustements.surprise.used) { return false; } return rollData.rolled.isPart; @@ -259,7 +347,7 @@ export class RdDCombat { /* -------------------------------------------- */ static isReussite(rollData) { - switch (rollData.surprise) { + switch (rollData.ajustements.surprise.used) { case 'totale': return false; } return rollData.rolled.isSuccess; @@ -299,10 +387,11 @@ export class RdDCombat { passeArme: randomID(16), coupsNonMortels: false, competence: competence, - surprise: this.attacker.getSurprise(), - surpriseDefenseur: this.defender.getSurprise(), + surprise: this.attacker.getSurprise(true), + surpriseDefenseur: this.defender.getSurprise(true), essais: {} }; + rollData.diviseurSignificative = this._getDiviseurSignificative(rollData); if (this.attacker.isCreature()) { RdDItemCompetence.setRollDataCreature(rollData); @@ -320,13 +409,13 @@ export class RdDCombat { /* -------------------------------------------- */ async _onAttaqueParticuliere(rollData) { - game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(rollData); + game.system.rdd.combatStore.attaques[this.attackerId] = duplicate(rollData); // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum const isMeleeDiffNegative = rollData.selectedCarac.label == "Mêlée" && rollData.diffLibre < 0; ChatMessage.create({ whisper: ChatMessage.getWhisperRecipients(this.attacker.name), - content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-defense.html', { + content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-demande-attaque-particuliere.html', { attackerId: this.attackerId, defenderTokenId: this.defenderTokenId, isFinesse: isMeleeDiffNegative, @@ -342,7 +431,7 @@ export class RdDCombat { attackerRoll.dmg = RdDBonus.dmg(attackerRoll, this.attacker.getBonusDegat(), this.defender.isEntiteCauchemar()); // Save rollData for defender - game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(attackerRoll); + game.system.rdd.combatStore.attaques[this.attackerId] = duplicate(attackerRoll); attackerRoll.show = { cible: this.target ? this.defender.data.name : 'la cible', @@ -368,7 +457,8 @@ export class RdDCombat { const paramDemandeDefense = { passeArme: attackerRoll.passeArme, essais: attackerRoll.essais, - surprise: this.defender.getSurprise(), + // surprise: this.defender.getSurprise(true), + // surprise: attackerRoll.ajustements.attaqueDefenseurSurpris.used, defender: this.defender, attackerId: this.attackerId, defenderTokenId: this.defenderTokenId, @@ -401,7 +491,7 @@ export class RdDCombat { /* -------------------------------------------- */ async _onAttaqueEchecTotal(attackerRoll) { - game.system.rdd.rollDataHandler.attaques[this.attackerId] = duplicate(attackerRoll); + game.system.rdd.combatStore.attaques[this.attackerId] = duplicate(attackerRoll); // Finesse et Rapidité seulement en mêlée et si la difficulté libre est de -1 minimum ChatMessage.create({ @@ -469,10 +559,9 @@ export class RdDCombat { } _prepareParade(attackerRoll, armeParade) { - const isCreature = this.defender.isCreature(); const compName = armeParade.data.competence; const armeAttaque = attackerRoll.arme; - + let rollData = { passeArme: attackerRoll.passeArme, forceValue: this.defender.getForceValue(), @@ -480,14 +569,15 @@ export class RdDCombat { attackerRoll: attackerRoll, competence: this.defender.getCompetence(compName), arme: armeParade, - surprise: this.defender.getSurprise(), + surprise: this.defender.getSurprise(true), needParadeSignificative: RdDItemArme.needParadeSignificative(armeAttaque, armeParade), needResist: RdDItemArme.needArmeResist(armeAttaque, armeParade), carac: this.defender.data.data.carac, show: {} }; - rollData.diviseur = this._getDiviseurSignificative(rollData); - if (isCreature) { + rollData.diviseurSignificative = this._getDiviseurSignificative(rollData); + + if (this.defender.isCreature()) { RdDItemCompetence.setRollDataCreature(rollData); } return rollData; @@ -495,7 +585,13 @@ export class RdDCombat { /* -------------------------------------------- */ _getDiviseurSignificative(defenderRoll) { - let facteurSign = (this.defender.isDemiSurprise() || defenderRoll.needParadeSignificative) ? 2 : 1; + let facteurSign = 1; + if (defenderRoll.surprise == 'demi'){ + facteurSign *= 2; + } + if (defenderRoll.needParadeSignificative) { + facteurSign *= 2; + } if (RdDBonus.isDefenseAttaqueFinesse(defenderRoll)) { facteurSign *= 2; } @@ -568,12 +664,12 @@ export class RdDCombat { diffLibre: attackerRoll.diffLibre, attackerRoll: attackerRoll, competence: competence, - surprise: this.defender.getSurprise(), - surpriseDefenseur: this.defender.getSurprise(), + surprise: this.defender.getSurprise(true), + surpriseDefenseur: this.defender.getSurprise(true), carac: this.defender.data.data.carac, show: {} }; - rollData.diviseur = this._getDiviseurSignificative(rollData); + rollData.diviseurSignificative = this._getDiviseurSignificative(rollData); if (this.defender.isCreature()) { RdDItemCompetence.setRollDataCreature(rollData); @@ -590,21 +686,21 @@ export class RdDCombat { } /* -------------------------------------------- */ - async _onEsquiveNormale(rollData) { - console.log("RdDCombat._onEsquiveNormal >>>", rollData); - this._consumeDefense(rollData.passeArme); - await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-esquive.html'); + async _onEsquiveNormale(defenderRoll) { + console.log("RdDCombat._onEsquiveNormal >>>", defenderRoll); + this._consumeDefense(defenderRoll.passeArme); + await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-esquive.html'); } /* -------------------------------------------- */ - async _onEsquiveEchec(rollData) { - console.log("RdDCombat._onEsquiveEchec >>>", rollData); + async _onEsquiveEchec(defenderRoll) { + console.log("RdDCombat._onEsquiveEchec >>>", defenderRoll); - await RdDResolutionTable.displayRollData(rollData, this.defender, 'chat-resultat-esquive.html'); + await RdDResolutionTable.displayRollData(defenderRoll, this.defender, 'chat-resultat-esquive.html'); - this.removeChatMessageActionsPasseArme(rollData.passeArme); - this._sendMessageDefense(rollData.attackerRoll, { defense: true }) - this._storeDefense(rollData); + this.removeChatMessageActionsPasseArme(defenderRoll.passeArme); + this._sendMessageDefense(defenderRoll.attackerRoll, { defense: true }) + this._storeDefense(defenderRoll); } /* -------------------------------------------- */ @@ -688,12 +784,6 @@ export class RdDCombat { return Misc.toInt(this.defender.data.data.carac.taille.value) - (attaque.forceValue + attaque.arme.data.dommagesReels); } - /* -------------------------------------------- */ - _sendMessageEncaisser(rollData) { - let message = "" + this.defender.name + " doit:" + this._buildMessageEncaisser(rollData); - RdDCombat._sendRollMessage(this.attacker, this.defender, this.defenderTokenId, "msg_encaisser", message, rollData); - } - /* -------------------------------------------- */ async encaisser(attackerRoll, defenderTokenId) { defenderTokenId = defenderTokenId || this.defenderTokenId; @@ -755,8 +845,9 @@ export class RdDCombat { } /* -------------------------------------------- */ - static async displayActorCombatStatus(actor) { - let rollData = { + static async displayActorCombatStatus(combat, actor) { + let data = { + combatId: combat._id, alias: actor.name, etatGeneral: actor.getEtatGeneral(), isSonne: actor.getSonne(), @@ -767,22 +858,14 @@ export class RdDCombat { isCritique: false } if (actor.countBlessuresByName("critiques") > 0) { // Pour éviter le cumul grave + critique - rollData.isCritique = true; + data.isCritique = true; } else if (actor.countBlessuresByName("graves") > 0) { - rollData.isGrave = true; + data.isGrave = true; } + ChatUtility.createChatWithRollMode(actor.name, { - content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-summary.html`, rollData) + content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-actor-turn-summary.html`, data) }); } - /* -------------------------------------------- */ - static updateCombatRound(combat, data) { - if (combat.data.round != 0 && combat.turns && combat.data.active) { - let turn = combat.turns.find(t => t.tokenId == combat.current.tokenId); - this.displayActorCombatStatus(turn.actor); - // TODO Playaudio ?? - } - } - } \ No newline at end of file diff --git a/module/rdd-main.js b/module/rdd-main.js index 18be45a1..fc513fa7 100644 --- a/module/rdd-main.js +++ b/module/rdd-main.js @@ -23,6 +23,7 @@ import { RdDCommands } from "./rdd-commands.js"; import { RdDCombat } from "./rdd-combat.js"; import { ChatUtility } from "./chat-utility.js"; import { RdDItemCompetence } from "./item-competence.js"; +import { StatusEffects } from "./status-effects.js"; /* -------------------------------------------- */ /* Foundry VTT Initialization */ @@ -45,16 +46,16 @@ const _patch_initiative = () => { ids = typeof ids === "string" ? [ids] : ids; const currentId = this.combatant._id; // calculate initiative - for ( let cId = 0; cId < ids.length; cId++) { - const c = this.getCombatant( ids[cId] ); + for (let cId = 0; cId < ids.length; cId++) { + const c = this.getCombatant(ids[cId]); //if (!c) return results; let rollFormula = formula; // Init per default - if ( !rollFormula ) { + if (!rollFormula) { let armeCombat, competence; - if ( c.actor.data.type == 'creature' || c.actor.data.type == 'entite') { + if (c.actor.data.type == 'creature' || c.actor.data.type == 'entite') { for (const competenceItem of c.actor.data.items) { - if ( competenceItem.data.iscombat) { + if (competenceItem.data.iscombat) { competence = duplicate(competenceItem); } } @@ -65,17 +66,17 @@ const _patch_initiative = () => { armeCombat = duplicate(item); } } - let compName = ( armeCombat == undefined ) ? "Corps à corps" : armeCombat.data.competence; - competence = RdDItemCompetence.findCompetence( c.actor.data.items, compName ); - rollFormula = RdDUtility.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value); + let compName = (armeCombat == undefined) ? "Corps à corps" : armeCombat.data.competence; + competence = RdDItemCompetence.findCompetence(c.actor.data.items, compName); + rollFormula = RdDUtility.calculInitiative(competence.data.niveau, c.actor.data.data.carac[competence.data.defaut_carac].value); } } //console.log("Combatat", c); const roll = this._getInitiativeRoll(c, rollFormula); - if ( roll.total <= 0 ) roll.total = 1; + if (roll.total <= 0) roll.total = 1; //console.log("Compute init for", armeCombat, competence, rollFormula, roll.total); - await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total }); - + await this.updateEmbeddedEntity("Combatant", { _id: c._id, initiative: roll.total }); + // Send a chat message let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode"); let messageData = mergeObject( @@ -98,22 +99,15 @@ const _patch_initiative = () => { } /************************************************************************************/ -Hooks.once("init", async function() { +Hooks.once("init", async function () { console.log(`Initializing Reve de Dragon System`); - + // preload handlebars templates RdDUtility.preloadHandlebarsTemplates(); - // Create useful storage space - game.system.rdd = { - rollDataHandler: { - attaques: {}, - defenses: {} - }, - TMRUtility: TMRUtility - } + game.system.rdd = { TMRUtility: TMRUtility } - /* -------------------------------------------- */ + /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "accorder-entite-cauchemar", { name: "Accorder le rêve aux entités", hint: "A quel moment les personnages doivent accorder leur rêve aux entités de cauchemar", @@ -130,7 +124,7 @@ Hooks.once("init", async function() { /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "calendrier", { - name: "calendrier", + name: "calendrier", scope: "world", config: false, type: Object @@ -138,7 +132,7 @@ Hooks.once("init", async function() { /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "liste-nombre-astral", { - name: "liste-nombre-astral", + name: "liste-nombre-astral", scope: "world", config: false, type: Object @@ -146,7 +140,7 @@ Hooks.once("init", async function() { /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "calendrier-pos", { - name: "calendrierPos", + name: "calendrierPos", scope: "world", config: false, type: Object @@ -160,7 +154,7 @@ Hooks.once("init", async function() { config: true, default: false, type: Boolean - }); + }); /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "supprimer-dialogues-combat-chat", { @@ -170,7 +164,7 @@ Hooks.once("init", async function() { config: true, default: true, type: Boolean - }); + }); /* -------------------------------------------- */ game.settings.register("foundryvtt-reve-de-dragon", "activer-sons-audio", { name: "Activer les bruitages intégrés", @@ -180,82 +174,70 @@ Hooks.once("init", async function() { default: true, type: Boolean }); - + /* -------------------------------------------- */ - // Set an initiative formula for the system - CONFIG.Combat.initiative = { - formula: "1d20", - decimals: 2 + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1d20", + decimals: 0 }; - + /* -------------------------------------------- */ game.socket.on("system.foundryvtt-reve-de-dragon", data => { - RdDUtility.performSocketMesssage( data ); + RdDUtility.onSocketMesssage(data); + RdDCombat.onSocketMessage(data); }); /* -------------------------------------------- */ - // Define custom Entity classes + // Define custom Entity classes CONFIG.Actor.entityClass = RdDActor; - CONFIG.RDD = { - resolutionTable : RdDResolutionTable.resolutionTable, - carac_array : RdDUtility.getCaracArray(), - ajustementsConditions : RdDUtility.getAjustementsConditions(), - difficultesLibres : RdDUtility.getDifficultesLibres() + CONFIG.RDD = { + resolutionTable: RdDResolutionTable.resolutionTable, + carac_array: RdDUtility.getCaracArray(), + ajustementsConditions: RdDUtility.getAjustementsConditions(), + difficultesLibres: RdDUtility.getDifficultesLibres() } - + /* -------------------------------------------- */ // Register sheet application classes Actors.unregisterSheet("core", ActorSheet); - Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorSheet, { - types: ["personnage"], - makeDefault: true } - ); - Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorCreatureSheet, { - types: ["creature"], - makeDefault: true - }); - Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorVehiculeSheet, { - types: ["vehicule"], - makeDefault: true - }); - Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorEntiteSheet, { - types: ["entite"], - makeDefault: true - }); + Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorSheet, { types: ["personnage"], makeDefault: true }); + Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorCreatureSheet, { types: ["creature"], makeDefault: true }); + Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorVehiculeSheet, { types: ["vehicule"], makeDefault: true }); + Actors.registerSheet("foundryvtt-reve-de-dragon", RdDActorEntiteSheet, { types: ["entite"], makeDefault: true }); Items.unregisterSheet("core", ItemSheet); - Items.registerSheet("foundryvtt-reve-de-dragon", RdDItemSheet, {makeDefault: true}); + Items.registerSheet("foundryvtt-reve-de-dragon", RdDItemSheet, { makeDefault: true }); // Handlebar function pour container Handlebars.registerHelper('buildConteneur', (objet) => { return RdDUtility.buildConteneur(objet); }); // Patch the initiative formula _patch_initiative(); + + // préparation des différents modules + RdDCommands.init(); + RdDCombat.init(); + RdDTokenHud.init(); + RdDActor.init(); + StatusEffects.init(); }); /* -------------------------------------------- */ -function messageDeBienvenue(){ +function messageDeBienvenue() { ChatUtility.removeMyChatMessageContaining('
'); - ChatMessage.create( { + ChatMessage.create({ user: game.user._id, - whisper: [game.user._id], - content : `
Bienvenue dans le Rêve des Dragons ! + whisper: [game.user._id], + content: `
Bienvenue dans le Rêve des Dragons !
Vous trouverez quelques informations pour démarrer dans ce document : @Compendium[foundryvtt-reve-de-dragon.rappel-des-regles.7uGrUHGdPu0EmIu2]{Documentation MJ/Joueurs}
La commande /aide dans le chat permet de voir les commandes spécifiques à Rêve de Dragon.
` }); } -/* -------------------------------------------- */ -Hooks.once("renderApplication", () => { - messageDeBienvenue(); -}); - /* -------------------------------------------- */ /* Foundry VTT Initialization */ /* -------------------------------------------- */ -Hooks.once("ready", function() { - - // préparation des lignes de commandes - RdDCommands.init(); +Hooks.once("ready", function () { /* -------------------------------------------- */ /* Affiche/Init le calendrier */ let calendrier = new RdDCalendrier(); @@ -264,20 +246,20 @@ Hooks.once("ready", function() { let templateData = {}; renderTemplate(templatePath, templateData).then(html => { calendrier.render(true); - } ); + }); game.system.rdd.calendrier = calendrier; // Reference; // Avertissement si joueur sans personnage - if ( !game.user.isGM && game.user.character == undefined) { - ui.notifications.info( "Attention ! Vous n'êtes connecté à aucun personnage !" ); - ChatMessage.create( { content:"ATTENTION Le joueur " + game.user.name + " n'est connecté à aucun personnage !", - user: game.user._id } ); - //whisper: [ ChatMessage.getWhisperRecipients("GM") ] } ); + if (!game.user.isGM && game.user.character == undefined) { + ui.notifications.info("Attention ! Vous n'êtes connecté à aucun personnage !"); + ChatMessage.create({ + content: "ATTENTION Le joueur " + game.user.name + " n'est connecté à aucun personnage !", + user: game.user._id + }); + //whisper: [ ChatMessage.getWhisperRecipients("GM") ] } ); } - // Integration du TokenHUD - Hooks.on('renderTokenHUD', (app, html, data) => { RdDTokenHud.addTokenHudExtensions(app, html, data._id) }); - Hooks.on("updateCombat", (combat, data) => { RdDCombat.updateCombatRound(combat, data) } ); + messageDeBienvenue(); }); /* -------------------------------------------- */ @@ -287,7 +269,7 @@ Hooks.on("chatMessage", (html, content, msg) => { if (content[0] == '/') { let regExp = /(\S+)/g; let commands = content.toLowerCase().match(regExp); - if (game.system.rdd.commands.processChatCommand(commands, content, msg)){ + if (game.system.rdd.commands.processChatCommand(commands, content, msg)) { return false; } } @@ -296,5 +278,5 @@ Hooks.on("chatMessage", (html, content, msg) => { /* -------------------------------------------- */ Hooks.on("getCombatTrackerEntryContext", (html, options) => { - RdDUtility.pushInitiativeOptions( html, options ); + RdDUtility.pushInitiativeOptions(html, options); }) diff --git a/module/rdd-resolution-table.js b/module/rdd-resolution-table.js index fd516b21..16c2a806 100644 --- a/module/rdd-resolution-table.js +++ b/module/rdd-resolution-table.js @@ -82,7 +82,7 @@ export class RdDResolutionTable { static explain(rolled) { let message = "
Jet : " + rolled.roll + " sur " + rolled.score + "% "; if (rolled.caracValue != null && rolled.finalLevel != null) { - message += (rolled.diviseur > 1 ? `(1/${rolled.diviseur} de ` : "(") + message += (rolled.diviseurSignificative > 1 ? `(1/${rolled.diviseurSignificative} de ` : "(") + rolled.caracValue + " à " + Misc.toSignedString(rolled.finalLevel) + ") "; } message += '' + rolled.quality + '' @@ -104,7 +104,7 @@ export class RdDResolutionTable { /* -------------------------------------------- */ static async rollData(rollData) { - rollData.rolled = await this.roll(rollData.caracValue, rollData.finalLevel, rollData.bonus, rollData.diviseur, rollData.showDice); + rollData.rolled = await this.roll(rollData.caracValue, rollData.finalLevel, rollData.bonus, rollData.diviseurSignificative, rollData.showDice); return rollData; } diff --git a/module/rdd-roll-resolution-table.js b/module/rdd-roll-resolution-table.js index 6603babc..e988da68 100644 --- a/module/rdd-roll-resolution-table.js +++ b/module/rdd-roll-resolution-table.js @@ -70,44 +70,44 @@ export class RdDRollResolutionTable extends Dialog { this.bringToTop(); - var rollData = this.rollData; var dialog = this; - function updateRollResult(rollData) { - rollData.caracValue = parseInt(rollData.selectedCarac.value) - rollData.finalLevel = dialog._computeFinalLevel(rollData); - - // Mise à jour valeurs - $("#carac").val(rollData.caracValue); - $("#roll-param").text(rollData.selectedCarac.value + " / " + Misc.toSignedString(rollData.finalLevel)); - $(".table-resolution").remove(); - $("#resolutionTable").append(RdDResolutionTable.buildHTMLTable(rollData.caracValue, rollData.finalLevel, 1, 20, -10, 10)); - $(".table-proba-reussite").remove(); - $("#tableProbaReussite").append(RdDResolutionTable.buildHTMLResults(rollData.caracValue, rollData.finalLevel)); - } - // Setup everything onload - $(function () { - $("#diffLibre").val(Misc.toInt(rollData.diffLibre)); - $("#diffConditions").val(Misc.toInt(rollData.diffConditions)); - updateRollResult(rollData); - }); - + function onLoad(){ + $("#diffLibre").val(Misc.toInt(dialog.rollData.diffLibre)); + $("#diffConditions").val(Misc.toInt(dialog.rollData.diffConditions)); + dialog.updateRollResult(); + } + $(function () { onLoad();}); + // Update ! html.find('#diffLibre').change((event) => { - rollData.diffLibre = Misc.toInt(event.currentTarget.value); - updateRollResult(rollData); + this.rollData.diffLibre = Misc.toInt(event.currentTarget.value); + this.updateRollResult(); }); html.find('#diffConditions').change((event) => { rollData.diffConditions = Misc.toInt(event.currentTarget.value); - updateRollResult(rollData); + this.updateRollResult(); }); html.find('#carac').change((event) => { let caracKey = event.currentTarget.value; - this.rollData.selectedCarac = rollData.carac[caracKey]; - updateRollResult(rollData); + this.rollData.selectedCarac = this.rollData.carac[caracKey]; + this.updateRollResult(); }); } + async updateRollResult() { + let rollData = this.rollData; + rollData.caracValue = parseInt(rollData.selectedCarac.value) + rollData.finalLevel = this._computeFinalLevel(rollData); + + // Mise à jour valeurs + $("#carac").val(rollData.caracValue); + $("#roll-param").text(rollData.selectedCarac.value + " / " + Misc.toSignedString(rollData.finalLevel)); + $(".table-resolution").remove(); + $(".table-proba-reussite").remove(); + $("#tableResolution").append(RdDResolutionTable.buildHTMLTable(rollData.caracValue, rollData.finalLevel)); + $("#tableProbaReussite").append(RdDResolutionTable.buildHTMLResults(rollData.caracValue, rollData.finalLevel)); + } /* -------------------------------------------- */ _computeFinalLevel(rollData) { diff --git a/module/rdd-roll.js b/module/rdd-roll.js index 2de31f6a..6b998e44 100644 --- a/module/rdd-roll.js +++ b/module/rdd-roll.js @@ -40,7 +40,7 @@ export class RdDRoll extends Dialog { moral: actor.getMoralTotal(), carac: actor.data.data.carac, finalLevel: 0, - diffConditions: rollData.arme ? RdDBonus.bonusAttaque(rollData.surpriseDefenseur) : 0, + diffConditions: 0, diffLibre: rollData.competence?.data.default_diffLibre ?? 0, editLibre: true, editConditions: true, @@ -49,15 +49,12 @@ export class RdDRoll extends Dialog { surencMalusFlag: actor.isPersonnage() ? (actor.data.data.compteurs.surenc.value < 0) : false, surencMalusValue: actor.getSurenc(), useMalusSurenc: false, - use: { - surenc: false, - encTotal: false, - }, + use: { libre:true, conditions: true, surenc: false, encTotal: false, }, isMalusEncombrementTotal: RdDItemCompetence.isMalusEncombrementTotal(rollData.competence), useMalusEncTotal: false, encTotal: actor.getEncTotal(), ajustementAstrologique: actor.ajustementAstrologique(), - surprise: actor.getSurprise(), + surprise: actor.getSurprise(false), } mergeObject(rollData, defaultRollData, { recursive: true, overwrite: false }); RollDataAjustements.calcul(rollData, actor); @@ -228,7 +225,7 @@ export class RdDRoll extends Dialog { $("#compdialogTitle").text(this._getTitle(rollData)); $('#coupsNonMortels').prop('checked', rollData.coupsNonMortels); $("#dmg-arme-actor").text(dmgText); - $("#defenseur-surprise").text(RdDBonus.description(rollData.surpriseDefenseur)); +// $("#defenseur-surprise").text(RdDBonus.description(rollData.ajustements.attaqueDefenseurSurpris.descr)); $('.table-ajustement').remove(); $(".table-resolution").remove(); $(".table-proba-reussite").remove(); @@ -245,23 +242,7 @@ export class RdDRoll extends Dialog { /* -------------------------------------------- */ _computeFinalLevel(rollData) { - const etat = RdDCarac.isIgnoreEtatGeneral(rollData.selectedCarac) ? 0 : rollData.etat; - const diffConditions = Misc.toInt(rollData.diffConditions); - const malusSurenc = (rollData.useMalusSurenc) ? rollData.surencMalusValue : 0; - const bonusTactique = RdDBonus.bonusAttaque(rollData.tactique); - const malusEncTotal = (rollData.useMalusEncTotal) ? -rollData.encTotal : 0; - const ajustementChance = RdDResolutionTable.isAjustementAstrologique(rollData) ? rollData.ajustementAstrologique : 0; - // Gestion malus armure - const malusArmureValue = this._computeMalusArmure(rollData); - - const diffMeditation = RdDItemMeditation.calculDifficulte(rollData); - const diffLibre = this._computeDiffLibre(rollData); - const diffCompetence = this._computeDiffCompetence(rollData); - const diffMoral = rollData.selectedCarac == this.actor.data.data.carac.volonte ? rollData.moral : 0; - - let ajust = RollDataAjustements.sum(rollData.ajustements); - return ajust; - //return etat + diffCompetence + diffLibre + diffMoral + diffConditions + malusSurenc + malusEncTotal + malusArmureValue + diffMeditation + ajustementChance + bonusTactique; + return RollDataAjustements.sum(rollData.ajustements); } /* -------------------------------------------- */ diff --git a/module/rdd-tmr-dialog.js b/module/rdd-tmr-dialog.js index ebef73c7..4e827961 100644 --- a/module/rdd-tmr-dialog.js +++ b/module/rdd-tmr-dialog.js @@ -51,14 +51,17 @@ export class RdDTMRDialog extends Dialog { this.rencontreState = 'aucune'; this.pixiApp = new PIXI.Application({ width: 720, height: 860 }); if (!this.viewOnly){ + this.actor.setStatusDemiReve(true); this._tellToGM(this.actor.name + " monte dans les terres médianes (" + mode + ")"); } } - + /* -------------------------------------------- */ close() { this.actor.santeIncDec("fatigue", this.nbFatigue).then(super.close()); // moving 1 cell costs 1 fatigue this.actor.tmrApp = undefined; // Cleanup reference + this.actor.setStatusDemiReve(false); + this._tellToGM(this.actor.name + " a quitté les terres médianes") } diff --git a/module/rdd-token-hud.js b/module/rdd-token-hud.js index c056c61d..501f4928 100644 --- a/module/rdd-token-hud.js +++ b/module/rdd-token-hud.js @@ -5,6 +5,11 @@ import { RdDUtility } from "./rdd-utility.js"; /* -------------------------------------------- */ export class RdDTokenHud { + static init(){ + // Integration du TokenHUD + Hooks.on('renderTokenHUD', (app, html, data) => { RdDTokenHud.addTokenHudExtensions(app, html, data._id) }); + } + /* -------------------------------------------- */ static async removeExtensionHud( app, html, tokenId) { let combat = html.find('.control-icon.rdd-combat'); diff --git a/module/rdd-utility.js b/module/rdd-utility.js index f3dcea1d..94323548 100644 --- a/module/rdd-utility.js +++ b/module/rdd-utility.js @@ -629,13 +629,11 @@ export class RdDUtility { } /* -------------------------------------------- */ - static performSocketMesssage(sockmsg) { + static onSocketMesssage(sockmsg) { console.log(">>>>> MSG RECV", sockmsg); switch (sockmsg.msg) { case "msg_encaisser": return RdDUtility._handleMsgEncaisser(sockmsg.data); - case "msg_defense": - return RdDUtility._handleMsgDefense(sockmsg.data); case "msg_gm_chat_message": return ChatUtility.handleGMChatMessage(sockmsg.data); case "msg_sync_time": @@ -647,25 +645,6 @@ export class RdDUtility { } } - /* -------------------------------------------- */ - static _handleMsgDefense(data) { - let defenderToken = canvas.tokens.get(data.defenderTokenId); - if (defenderToken) { - if (!game.user.isGM && game.user.character == undefined) { // vérification / sanity check - ui.notifications.error("Le joueur " + game.user.name + " n'est connecté à aucun personnage. Impossible de continuer."); - return; - } - if ((game.user.isGM && !defenderToken.actor.hasPlayerOwner) || (defenderToken.actor.hasPlayerOwner && (game.user.character.id == defenderToken.actor.data._id))) { - //console.log("User is pushing message...", game.user.name); - game.system.rdd.rollDataHandler.attaques[data.attackerId] = duplicate(data.rollData); - data.whisper = [game.user]; - data.blind = true; - data.rollMode = "blindroll"; - ChatMessage.create(data); - } - } - } - /* -------------------------------------------- */ static rollInitiativeCompetence(combatantId, arme) { const combatant = game.combat.getCombatant(combatantId); @@ -760,12 +739,9 @@ export class RdDUtility { /* -------------------------------------------- */ static _handleMsgEncaisser(data) { if (game.user.isGM) { // Seul le GM effectue l'encaissement sur la fiche - let attackerRoll = game.system.rdd.rollDataHandler.attaques[data.attackerId]; // Retrieve the rolldata from the store - game.system.rdd.rollDataHandler.attaques[data.attackerId] = undefined; - game.system.rdd.rollDataHandler.defenses[attackerRoll.passeArme] = undefined; - let defender = canvas.tokens.get(data.defenderTokenId).actor; - defender.encaisserDommages(attackerRoll); + let attacker = data.attackerId ? game.actors.get(data.attackerId) : null; + defender.encaisserDommages(attackerRoll, attacker); } } diff --git a/module/rolldata-ajustements.js b/module/rolldata-ajustements.js index 5139a5da..5eff690d 100644 --- a/module/rolldata-ajustements.js +++ b/module/rolldata-ajustements.js @@ -45,7 +45,7 @@ export const referenceAjustements = { }, attaqueDefenseurSurpris: { isUsed: (rollData, actor) => rollData.surpriseDefenseur, - getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr, + getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr + (rollData.attackerRoll ? '' : ' défenseur'), getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque, }, etat: { @@ -88,8 +88,9 @@ export const referenceAjustements = { getValue: (rollData, actor) => actor.ajustementAstrologique() }, facteurSign: { - isUsed: (rollData, actor) => rollData.diviseur > 1, - getDescr: (rollData, actor) => rollData.diviseur > 1 ? `Facteur significative ×${Misc.getFractionHtml(rollData.diviseur)}` : '' + isUsed: (rollData, actor) => rollData.diviseurSignificative > 1, + getLabel: (rollData, actor) => Misc.getFractionHtml(rollData.diviseurSignificative), + getDescr: (rollData, actor) => rollData.diviseurSignificative > 1 ? `Facteur significative ×${Misc.getFractionHtml(rollData.diviseurSignificative)}` : '' }, finesse: { isUsed: (rollData, actor) => RdDBonus.isDefenseAttaqueFinesse(rollData), @@ -97,15 +98,15 @@ export const referenceAjustements = { }, armeParade: { isUsed: (rollData, actor) => RdDItemArme.needParadeSignificative(rollData.attackerRoll?.arme, rollData.arme), - getDescr: (rollData, actor) => rollData.attackerRoll && rollData.arme? `${RdDItemArme.getNomCategorieParade(rollData.attackerRoll?.arme)} vs ${RdDItemArme.getNomCategorieParade(rollData.arme)}`: '' + getDescr: (rollData, actor) => rollData.attackerRoll && rollData.arme ? `${RdDItemArme.getNomCategorieParade(rollData.attackerRoll?.arme)} vs ${RdDItemArme.getNomCategorieParade(rollData.arme)}` : '' }, surprise: { - isUsed: (rollData, actor) => actor.getSurprise(), + isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme), getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr }, bonusCase: { isUsed: (rollData, actor) => rollData.selectedSort && rollData.coord, - getDescr: (rollData, actor) => rollData.selectedSort && rollData.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.coord)}%`: '' + getDescr: (rollData, actor) => rollData.selectedSort && rollData.coord ? `Bonus de case: ${RdDItemSort.getCaseBonus(rollData.selectedSort, rollData.coord)}%` : '' } } diff --git a/module/status-effects.js b/module/status-effects.js new file mode 100644 index 00000000..cf420867 --- /dev/null +++ b/module/status-effects.js @@ -0,0 +1,128 @@ + +const rddStatusEffects = [ + { id: 'sonne', rdd: true, label: 'Sonné', icon: 'icons/svg/stoned.svg' }, +]; +const demiReveStatusEffect = { id: 'demi-reve', rdd: true, label: 'Demi-rêve', icon: 'systems/foundryvtt-reve-de-dragon/icons/heures/hd12.svg' }; +const rddPrivateStatusEffects = [demiReveStatusEffect,]; +const statusDemiSurpriseCombat = new Set(['sonne', 'demi-reve', 'prone', 'restrain']); +const statusDemiSurprise = new Set(['sonne', 'prone', 'restrain']); +const statusSurpriseTotale = new Set(['unconscious', 'blind']); + +export class StatusEffects { + static init() { + StatusEffects.setCoreStatusId(rddPrivateStatusEffects); + StatusEffects.setCoreStatusId(rddStatusEffects); + const defaultUseStatusEffect = CONFIG.statusEffects.map(it => it.id).join(); + game.settings.register("foundryvtt-reve-de-dragon", "use-status-effects", { + name: "use-status-effects", + scope: "world", + config: false, + default: defaultUseStatusEffect, + type: String + }); + + game.settings.registerMenu("foundryvtt-reve-de-dragon", "select-status-effect", { + name: "Choisir les effets disponibles", + label: "Choix des effets", + hint: "Ouvre la fenêtre de sélection des effets/status appliqués aux acteurs", + icon: "fas fa-bars", + type: StatusEffectsSettings, + restricted: true + }); + CONFIG.RDD.allEffects = rddStatusEffects.concat(CONFIG.statusEffects); + + StatusEffects._setUseStatusEffects(StatusEffects._getUseStatusEffects()); + + console.log('statusEffects', CONFIG.statusEffects); + } + + static valeurSurprise(effect, isCombat) { + const id = StatusEffects.statusId(effect); + if (statusSurpriseTotale.has(id)) { + return 2; + } + const status = (isCombat ? statusDemiSurpriseCombat : statusDemiSurprise); + return status.has(id) ? 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 _getUseStatusEffects() { + const setting = game.settings.get("foundryvtt-reve-de-dragon", "use-status-effects"); + return setting ? new Set(setting.split(',')) : new Set(); + } + + static _setUseStatusEffects(useStatusEffects) { + game.settings.set("foundryvtt-reve-de-dragon", "use-status-effects", StatusEffects._toSetting(useStatusEffects)); + + for (let effect of CONFIG.RDD.allEffects) { + effect.active = effect.rdd || useStatusEffects.has(effect.id); + } + CONFIG.statusEffects = CONFIG.RDD.allEffects.filter(it => it.active); + } + + static _toSetting(useStatusEffects) { + return Array.from(useStatusEffects).join(); + } + + static demiReve() { + return demiReveStatusEffect; + } +} + +class StatusEffectsSettings extends FormApplication { + constructor(...args) { + super(...args); + } + + static get defaultOptions() { + const options = super.defaultOptions; + mergeObject(options, { + id: "status-effects-settings", + template: "systems/foundryvtt-reve-de-dragon/templates/status-effects-settings.html", + height: "auto", + width: 350, + minimizable: false, + closeOnSubmit: true, + title: "Choix des status/effets" + }); + return options; + } + + getData() { + let data = super.getData(); + data.effects = CONFIG.RDD.allEffects; + return data; + } + + activateListeners(html) { + html.find(".select-effect").click((event) => { + let id = event.currentTarget.attributes.name?.value; + if (id) { + let selected = StatusEffects._getUseStatusEffects(); + let isChecked = event.currentTarget.checked; + if (isChecked) { + selected.add(id); + } + else { + selected.delete(id); + } + StatusEffects._setUseStatusEffects(selected); + } + }); + } + + async _updateObject(event, formData) { + this.close(); + } +} + diff --git a/styles/simple.css b/styles/simple.css index f1d91e6f..256ccf9c 100644 --- a/styles/simple.css +++ b/styles/simple.css @@ -241,8 +241,18 @@ table {border: 1px solid #7a7971;} } .button-img { + vertical-align: baseline; width: 8%; height: 8%; + max-height: 48px; + border-width: 0; +} + +.button-effect-img { + vertical-align: baseline; + width: 16px; + max-height: 16px; + height: 16; border-width: 0; } @@ -253,12 +263,6 @@ table {border: 1px solid #7a7971;} vertical-align: bottom; } -.img-sonne { - vertical-align: baseline; - max-height: 16px; - border: 0; -} - .foundryvtt-reve-de-dragon .sheet-header .header-fields { -webkit-box-flex: 1; -ms-flex: 1; diff --git a/templates/actor-creature-sheet.html b/templates/actor-creature-sheet.html index b9788a21..e98568dd 100644 --- a/templates/actor-creature-sheet.html +++ b/templates/actor-creature-sheet.html @@ -83,7 +83,7 @@ Sonné : - +
  • Etat Général : diff --git a/templates/actor-humanoide-sheet.html b/templates/actor-humanoide-sheet.html index d58d71ef..eb441bc3 100644 --- a/templates/actor-humanoide-sheet.html +++ b/templates/actor-humanoide-sheet.html @@ -83,7 +83,7 @@ Sonné : - +
  • Etat Général : diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html index 35d3cac3..439a78c8 100644 --- a/templates/actor-sheet.html +++ b/templates/actor-sheet.html @@ -1,3 +1,4 @@ +{{log "handlebar actor-sheet" this}}
    {{!-- Sheet Header --}} @@ -40,8 +41,8 @@
  • @@ -71,10 +72,23 @@ {{data.blessures.resume}}
  • - {{data.compteurs.etat.label}}: {{data.compteurs.etat.value}} + {{data.compteurs.etat.label}}: {{data.compteurs.etat.value}} + {{data.compteurs.surenc.label}}: {{data.compteurs.surenc.value}}
    -
    - {{data.compteurs.surenc.label}}: {{data.compteurs.surenc.value}} +
    + {{#if actor.effects}} + {{data.surprise}}! + {{#each actor.effects as |effect key|}} + + {{effect.label}} + + {{/each}} + {{#if data.isGM}} + (enlever tout) + {{/if}} + {{else}} + Aucun effet actif + {{/if}}
    diff --git a/templates/chat-actor-turn-summary.html b/templates/chat-actor-turn-summary.html index c87f041e..f0f89a1e 100644 --- a/templates/chat-actor-turn-summary.html +++ b/templates/chat-actor-turn-summary.html @@ -1,5 +1,5 @@ -

    C'est au tour de {{alias}} !

    -
    {{blessuresStatus}}
    +

    C'est au tour de {{alias}} !

    +
    {{blessuresStatus}}
    Son état général est de : {{etatGeneral}} {{#if isSonne}} et est sonné{{/if}}
    {{#if isGrave}}
    {{alias}} souffre de Blessure(s) Grave(s) : n'oubliez pas de faire un Je de Vie toutes les SC ({{SConst}}) minutes.
    diff --git a/templates/chat-resultat-attaque.html b/templates/chat-resultat-attaque.html index 577e07f9..498a04ae 100644 --- a/templates/chat-resultat-attaque.html +++ b/templates/chat-resultat-attaque.html @@ -1,5 +1,5 @@ -

    {{alias}} attaque: {{arme.name}}

    -
    {{selectedCarac.label}}{{#unless (eq selectedCarac.label competence.name)}} / {{competence.name}}{{/unless}}, difficulté {{diffLibre}}
    +

    {{alias}} attaque à {{diffLibre}}: {{arme.name}}

    +
    {{selectedCarac.label}}{{#unless (eq selectedCarac.label competence.name)}} / {{competence.name}}{{/unless}}
    {{> "systems/foundryvtt-reve-de-dragon/templates/chat-infojet.html"}}
    {{#if tactique}} diff --git a/templates/chat-resultat-encaissement.html b/templates/chat-resultat-encaissement.html index 8413dd3e..086e2c41 100644 --- a/templates/chat-resultat-encaissement.html +++ b/templates/chat-resultat-encaissement.html @@ -24,7 +24,7 @@ {{#if (gt eraflures 0)}}une contusion {{else if (gt legeres 0)}}une blessure légère {{else if (gt graves 0)}}une blessure grave - {{else if (gt critique 0)}}une blessure critique + {{else if (gt critiques 0)}}une blessure critique {{else}}Rien du tout {{/if}} diff --git a/templates/dialog-competence.html b/templates/dialog-competence.html index 5bd5e70a..1d8b4025 100644 --- a/templates/dialog-competence.html +++ b/templates/dialog-competence.html @@ -1,3 +1,4 @@ +{{log "handlebar dialog-competence" this}}

    @@ -17,7 +18,7 @@ {{else}} - {{#select diffLibre}} {{#each difficultesLibres as |key|}} @@ -26,7 +27,7 @@ {{/if}} - {{#select diffConditions}} {{#each ajustementsConditions as |key|}} @@ -45,7 +46,7 @@ {{else}} - @@ -54,8 +55,8 @@ {{/if}} - {{#if surpriseDefenseur}} - + {{#if ajustements.attaqueDefenseurSurpris.used}} + {{/if}}
    {{/if}} diff --git a/templates/dialog-roll-ajustements.html b/templates/dialog-roll-ajustements.html index ffb2ec8f..e555db73 100644 --- a/templates/dialog-roll-ajustements.html +++ b/templates/dialog-roll-ajustements.html @@ -1,5 +1,5 @@
    - + + {{#if ajustements.facteurSign.used}} + + Significative requise ×{{{ajustements.facteurSign.label}}}! + + {{/if}}
    \ No newline at end of file diff --git a/templates/dialog-roll-resolution.html b/templates/dialog-roll-resolution.html index 34feacf4..cd6a5a3f 100644 --- a/templates/dialog-roll-resolution.html +++ b/templates/dialog-roll-resolution.html @@ -28,8 +28,6 @@ {{/select}}
    -
    -
    diff --git a/templates/status-effects-settings.html b/templates/status-effects-settings.html new file mode 100644 index 00000000..a6862f12 --- /dev/null +++ b/templates/status-effects-settings.html @@ -0,0 +1,16 @@ + +
      + {{#each effects as |effect key|}} +
    • + {{#if effect.rdd}} + + {{else}} + + {{/if}} + {{ localize  effect.label}} + +
    • + {{/each}} +
    + +