diff --git a/system/lang/en-en.json b/system/lang/en-en.json index f4996a8..8acc28b 100644 --- a/system/lang/en-en.json +++ b/system/lang/en-en.json @@ -4,7 +4,11 @@ "Maintainers": ["Team L5R"] }, "SETTINGS": { - "None": "No option" + "None": "No option", + "RollNKeep": { + "DeleteOldMessage": "RnK Delete previous chat message", + "DeleteOldMessageHint": "Choose to keep or delete the previous message for a RnK series" + } }, "ACTOR": { "TypeCharacter": "Player Character", @@ -105,7 +109,7 @@ "swap_drop_here": "Swap", "keep_drop_here": "Keep", "keep_chat": "New roll from an exploding die", - "bt_validate": "Finalize" + "bt_validate": "Finalize this step" }, "max": "Max", "current": "Current", diff --git a/system/lang/es-es.json b/system/lang/es-es.json index 23cb255..6c668eb 100644 --- a/system/lang/es-es.json +++ b/system/lang/es-es.json @@ -4,7 +4,11 @@ "Maintainers": ["Team L5R"] }, "SETTINGS": { - "None": "Sin opción" + "None": "Sin opción", + "RollNKeep": { + "DeleteOldMessage": "RnK Delete previous chat message", + "DeleteOldMessageHint": "Choose to keep or delete the previous message for a RnK series" + } }, "ACTOR": { "TypeCharacter": "Personaje jugador", diff --git a/system/lang/fr-fr.json b/system/lang/fr-fr.json index c0a07f8..fd08835 100644 --- a/system/lang/fr-fr.json +++ b/system/lang/fr-fr.json @@ -4,7 +4,11 @@ "Maintainers": ["Team L5R"] }, "SETTINGS": { - "None": "Aucune option" + "None": "Aucune option", + "RollNKeep": { + "DeleteOldMessage": "RnK Delete previous chat message", + "DeleteOldMessageHint": "Choose to keep or delete the previous message for a RnK series" + } }, "ACTOR": { "TypeCharacter": "Personnage Joueur", @@ -105,7 +109,7 @@ "swap_drop_here": "Swap", "keep_drop_here": "Keep", "keep_chat": "New roll from a exploding dice", - "bt_validate": "Finalize" + "bt_validate": "Finalize this step" }, "max": "Max", "current": "Actuel", diff --git a/system/scripts/combat.js b/system/scripts/combat.js index 76a70e6..2d6d11c 100644 --- a/system/scripts/combat.js +++ b/system/scripts/combat.js @@ -41,7 +41,7 @@ export class CombatL5r5e extends Combat { // Get score for each combatant const updatedCombatants = []; - ids.forEach((combatantId) => { + for (const combatantId of ids) { const combatant = game.combat.combatants.find((c) => c._id === combatantId); // Skip if combatant already have a initiative value @@ -77,31 +77,41 @@ export class CombatL5r5e extends Combat { formula = createFormula.join("+"); } - const roll = new game.l5r5e.RollL5r5e(formula); - roll.actor = combatant.actor; - roll.l5r5e.stance = data.stance; - roll.l5r5e.skillId = skillId; - roll.l5r5e.skillCatId = skillCat; - roll.l5r5e.summary.difficulty = - messageOptions.difficulty !== undefined ? messageOptions.difficulty : cfg.difficulty; - roll.l5r5e.summary.difficultyHidden = - messageOptions.difficultyHidden !== undefined - ? messageOptions.difficultyHidden - : cfg.difficultyHidden; - roll.l5r5e.summary.voidPointUsed = !!messageOptions.useVoidPoint; + let roll; + const flavor = + game.i18n.localize("l5r5e.chatdices.initiative_roll") + + " (" + + game.i18n.localize(`l5r5e.conflict.initiative.prepared_${isPrepared}`) + + ")"; - roll.roll(); - roll.toMessage({ - flavor: - game.i18n.localize("l5r5e.chatdices.initiative_roll") + - " (" + - game.i18n.localize(`l5r5e.conflict.initiative.prepared_${isPrepared}`) + - ")", - }); + if (messageOptions.rnkRoll instanceof game.l5r5e.RollL5r5e && ids.length === 1) { + // Specific RnK + roll = messageOptions.rnkRoll; + // Ugly but work... i need the new messageId + combatant.actor.rnkMessage = await roll.toMessage({ flavor }); + } else { + // Regular + roll = new game.l5r5e.RollL5r5e(formula); + roll.actor = combatant.actor; + roll.l5r5e.isInitiativeRoll = true; + roll.l5r5e.stance = data.stance; + roll.l5r5e.skillId = skillId; + roll.l5r5e.skillCatId = skillCat; + roll.l5r5e.summary.difficulty = + messageOptions.difficulty !== undefined ? messageOptions.difficulty : cfg.difficulty; + roll.l5r5e.summary.difficultyHidden = + messageOptions.difficultyHidden !== undefined + ? messageOptions.difficultyHidden + : cfg.difficultyHidden; + roll.l5r5e.summary.voidPointUsed = !!messageOptions.useVoidPoint; + + roll.roll(); + roll.toMessage({ flavor }); + } // if the character succeeded on their Initiative check, they add 1 to their base initiative value, // plus an additional amount equal to their bonus successes. - const successes = Math.min(roll.l5r5e.summary.ringsUsed, roll.l5r5e.summary.totalSuccess); + const successes = roll.l5r5e.summary.totalSuccess; if (successes >= roll.l5r5e.summary.difficulty) { initiative = initiative + 1 + Math.max(successes - roll.l5r5e.summary.difficulty, 0); } @@ -111,7 +121,7 @@ export class CombatL5r5e extends Combat { _id: combatant._id, initiative: initiative, }); - }); + } // Update all combatants at once await this.updateEmbeddedEntity("Combatant", updatedCombatants); diff --git a/system/scripts/dice/dietype/l5r-base-die.js b/system/scripts/dice/dietype/l5r-base-die.js index 8d7c8c8..1bffb3b 100644 --- a/system/scripts/dice/dietype/l5r-base-die.js +++ b/system/scripts/dice/dietype/l5r-base-die.js @@ -72,13 +72,7 @@ export class L5rBaseDie extends DiceTerm { this._evaluateModifiers(); // Combine all results - this.l5r5e = { success: 0, explosive: 0, opportunity: 0, strife: 0 }; - this.results.forEach((term) => { - const face = this.constructor.FACES[term.result]; - ["success", "explosive", "opportunity", "strife"].forEach((props) => { - this.l5r5e[props] += parseInt(face[props]); - }); - }); + this.l5rSummary(); // Return the evaluated term this._evaluated = true; @@ -87,24 +81,33 @@ export class L5rBaseDie extends DiceTerm { return this; } + /** + * Summarise the total of success, strife... for L5R dices for the current Die + */ + l5rSummary() { + this.l5r5e = { success: 0, explosive: 0, opportunity: 0, strife: 0 }; + this.results.forEach((term) => { + const face = this.constructor.FACES[term.result]; + ["success", "explosive", "opportunity", "strife"].forEach((props) => { + this.l5r5e[props] += parseInt(face[props]); + }); + }); + } + /** * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term * @override */ roll(options) { const roll = super.roll(options); - //roll.l5r5e = this.l5r5e; - return roll; } /** @override */ static fromData(data) { const roll = super.fromData(data); - roll.l5r5e = data.l5r5e; - return roll; } @@ -114,9 +117,7 @@ export class L5rBaseDie extends DiceTerm { */ toJSON() { const json = super.toJSON(); - json.l5r5e = this.l5r5e; - return json; } } diff --git a/system/scripts/dice/roll-n-keep-dialog.js b/system/scripts/dice/roll-n-keep-dialog.js index 85b15f7..98ab863 100644 --- a/system/scripts/dice/roll-n-keep-dialog.js +++ b/system/scripts/dice/roll-n-keep-dialog.js @@ -32,7 +32,7 @@ export class RollnKeepDialog extends FormApplication { */ object = { currentStep: 0, - submitDisabled: true, + submitDisabled: false, swapDiceFaces: { rings: [], skills: [], @@ -52,6 +52,7 @@ export class RollnKeepDialog extends FormApplication { title: game.i18n.localize("l5r5e.roll_n_keep.title"), width: 660, height: 454, + closeOnSubmit: false, }); } @@ -62,21 +63,23 @@ export class RollnKeepDialog extends FormApplication { return `l5r5e-roll-n-keep-dialog-${this.message._id}`; } + set message(msg) { + this.message = msg instanceof ChatMessage ? duplicate(msg) : null; + } + /** * Create the Roll n Keep dialog * @param {number} messageId * @param {FormApplicationOptions} options */ constructor(messageId, options = {}) { - console.clear(); // TODO TMP - super({}, options); this.message = game.messages.get(messageId); - this.options.editable = this.message?._roll.l5r5e.actor.owner || false; - this._initializeDiceFaces(); - this._initialize(); + this.options.editable = + this.message?.isAuthor || this.message?._roll.l5r5e.actor?.owner || this.message?.owner || false; - console.log(this.object); // TODO TMP + this._initializeDiceFaces(); + this._initializeHistory(); } /** @@ -86,7 +89,7 @@ export class RollnKeepDialog extends FormApplication { if (!this.message) { return; } - this._initialize(); + this._initializeHistory(); this.render(false); } @@ -105,10 +108,10 @@ export class RollnKeepDialog extends FormApplication { } /** - * Initialize the dialog with the message + * Initialize the dice history list * @private */ - _initialize() { + _initializeHistory() { if (!this.message) { return; } @@ -118,12 +121,18 @@ export class RollnKeepDialog extends FormApplication { // Already history if (Array.isArray(this.roll.l5r5e.history)) { - this.object.currentStep = this.roll.l5r5e.history.length; this.object.dicesList = this.roll.l5r5e.history; + + let currentStep = this.roll.l5r5e.history.length - 1; + if (!this._haveChoice(currentStep)) { + currentStep += 1; + } + this.object.currentStep = currentStep; return; } // New + this.object.dicesList = [[]]; this.roll.terms.forEach((term) => { if (typeof term !== "object") { return; @@ -132,7 +141,6 @@ export class RollnKeepDialog extends FormApplication { this.object.dicesList[0].push({ type: term.constructor.name, face: res.result, - img: term.constructor.getResultSrc(res.result), choice: RollnKeepDialog.CHOICES.nothing, }); }); @@ -145,14 +153,10 @@ export class RollnKeepDialog extends FormApplication { */ _initializeDiceFaces() { // All faces are unique for rings - this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES).map((id) => { - return { id, img: game.l5r5e.RingDie.getResultSrc(id) }; - }); + this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES); // Only unique for Skills - this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12].map((id) => { - return { id, img: game.l5r5e.AbilityDie.getResultSrc(id) }; - }); + this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12]; } /** @@ -170,6 +174,15 @@ export class RollnKeepDialog extends FormApplication { ]; } + /** + * Define whether a user is able to begin a dragstart workflow for a given drag selector + * @param selector The candidate HTML selector for dragging + * @return Can the current user drag this selector? + */ + _canDragStart(selector) { + return this.options.editable; + } + /** * Callback actions which occur at the beginning of a drag start workflow. * @param {DragEvent} event The originating DragEvent @@ -192,8 +205,12 @@ export class RollnKeepDialog extends FormApplication { */ getData(options = null) { // Check only on 1st step - const kept = this.object.currentStep === 0 ? this._getKeepCount() : 1; - this.object.submitDisabled = kept < 1 || kept > this.roll.l5r5e.summary.ringsUsed; + if (this.object.currentStep === 0) { + const kept = this._getKeepCount(); + this.object.submitDisabled = kept < 1 || kept > this.roll.l5r5e.summary.ringsUsed; + } else if (!this.object.dicesList[this.object.currentStep]) { + this.options.editable = false; + } return { ...super.getData(options), @@ -263,39 +280,16 @@ export class RollnKeepDialog extends FormApplication { } current.choice = type; - current.img = game.l5r5e[current.type].getResultSrc(current.newFace ?? current.face); + + // Little time saving : on 1st step, if we reach the max kept dices, discard all dices without a choice + if (this.object.currentStep === 0 && this._getKeepCount() === this.roll.l5r5e.summary.ringsUsed) { + this._discardDiceWithoutChoice(); + } this.render(false); return false; } - /** - * Roll a new die avec return the result - * @private - */ - async _newRoll(dieType, actionType) { - const roll = await new game.l5r5e.RollL5r5e(dieType === "RingDie" ? "1dr" : "1ds"); - roll.actor = this.message.roll.l5r5e.actor; - roll.l5r5e.stance = this.message.roll.l5r5e.stance; - // roll.l5r5e.skillId = this.message.roll.l5r5e.skillId; - // roll.l5r5e.skillCatId = this.message.roll.l5r5e.skillCatId; - - await roll.roll(); - await roll.toMessage({ - flavor: game.i18n.localize(`l5r5e.roll_n_keep.${actionType}_chat`), - }); - - const dice = roll.terms[0]; - const result = dice.results[0].result; - - return { - type: dieType, - face: result, - img: dice.constructor.getResultSrc(result), - choice: RollnKeepDialog.CHOICES.nothing, - }; - } - /** * Return the current number of dices kept * @private @@ -321,6 +315,255 @@ export class RollnKeepDialog extends FormApplication { }, 0); } + /** + * Return true if the player can make a choice for the current step + * @private + */ + _haveChoice(currentStep) { + return ( + this.object.dicesList[currentStep] && + this.object.dicesList[currentStep].some((e) => !!e && e.choice === RollnKeepDialog.CHOICES.nothing) + ); + } + + /** + * Discard all dices without a choice for the current step + * @private + */ + _discardDiceWithoutChoice() { + this.object.dicesList[this.object.currentStep] + .filter((e) => !!e) + .map((e) => { + if (e.choice === RollnKeepDialog.CHOICES.nothing) { + e.choice = RollnKeepDialog.CHOICES.discard; + } + return e; + }); + } + + /** + * Apply all choices to build the next step + * @returns {Promise} + * @private + */ + async _applyChoices() { + const nextStep = this.object.currentStep + 1; + + // Foreach kept dices, apply choices + const newRolls = {}; + this.object.dicesList[this.object.currentStep].forEach((die, idx) => { + if (!die) { + return; + } + switch (die.choice) { + case RollnKeepDialog.CHOICES.keep: + // Exploding dice : add a new dice in the next step + if (game.l5r5e[die.type].FACES[die.face].explosive) { + if (!newRolls[die.type]) { + newRolls[die.type] = 0; + } + newRolls[die.type] += 1; + } + break; + + case RollnKeepDialog.CHOICES.reroll: + // Reroll : add a new dice in the next step + if (!newRolls[die.type]) { + newRolls[die.type] = 0; + } + newRolls[die.type] += 1; + break; + + case RollnKeepDialog.CHOICES.swap: + // FaceSwap : add a new dice with selected face in next step + if (!this.object.dicesList[nextStep]) { + this.object.dicesList[nextStep] = Array(this.object.dicesList[0].length).fill(null); + } + this.object.dicesList[nextStep][idx] = { + type: this.object.dicesList[this.object.currentStep][idx].type, + face: this.object.dicesList[this.object.currentStep][idx].newFace, + choice: RollnKeepDialog.CHOICES.keep, + }; + delete this.object.dicesList[this.object.currentStep][idx].newFace; + break; + } + }); + + // If new rolls, roll and add them + if (Object.keys(newRolls).length > 0) { + const newRollsResults = await this._newRoll(newRolls); + + if (!this.object.dicesList[nextStep]) { + this.object.dicesList[nextStep] = Array(this.object.dicesList[0].length).fill(null); + } + + this.object.dicesList[this.object.currentStep].forEach((die, idx) => { + if (!die) { + return; + } + if ( + die.choice === RollnKeepDialog.CHOICES.reroll || + (die.choice === RollnKeepDialog.CHOICES.keep && game.l5r5e[die.type].FACES[die.face].explosive) + ) { + this.object.dicesList[nextStep][idx] = newRollsResults[die.type].shift(); + } + }); + } + } + + /** + * Transform a array (of int or object) into a formula ring/skill + * @param rolls + * @returns {string} + * @private + */ + _arrayToFormula(rolls) { + const formula = []; + if (rolls["RingDie"]) { + const rings = Array.isArray(rolls["RingDie"]) ? rolls["RingDie"].length : rolls["RingDie"]; + formula.push(rings + "dr"); + } + if (rolls["AbilityDie"]) { + const skills = Array.isArray(rolls["AbilityDie"]) ? rolls["AbilityDie"].length : rolls["AbilityDie"]; + formula.push(skills + "ds"); + } + if (formula.length < 1) { + return ""; + } + return formula.join("+"); + } + + /** + * Roll all new dice at once (better performance) and return the result + * @private + */ + async _newRoll(newRolls) { + const out = { + RingDie: [], + AbilityDie: [], + }; + + const roll = await new game.l5r5e.RollL5r5e(this._arrayToFormula(newRolls)); + roll.l5r5e = { + ...this.message.roll.l5r5e, + summary: roll.l5r5e.summary, + }; + + await roll.roll(); + + // Show DsN dice for the new roll + if (game.dice3d !== undefined) { + game.dice3d.showForRoll(roll, game.user, true); + } + + roll.terms.forEach((term) => { + if (typeof term !== "object") { + return; + } + term.results.forEach((res) => { + out[term.constructor.name].push({ + type: term.constructor.name, + face: res.result, + choice: RollnKeepDialog.CHOICES.nothing, + }); + }); + }); + + return out; + } + + /** + * Rebuild the message roll + * @private + */ + async _rebuildRoll() { + // Get all kept dices + new (choice null) + const diceList = this.object.dicesList.reduce((acc, step) => { + step.forEach((die) => { + if (!!die && die.choice !== RollnKeepDialog.CHOICES.discard) { + if (!acc[die.type]) { + acc[die.type] = []; + } + acc[die.type].push(die); + } + }); + return acc; + }, {}); + + // Re create a new roll + const roll = await new game.l5r5e.RollL5r5e(this._arrayToFormula(diceList)); + roll.l5r5e = { + ...this.message.roll.l5r5e, + summary: roll.l5r5e.summary, + }; + + // Fill the data + roll.evaluate(); + + // Modify results + roll.terms.map((term) => { + if (term instanceof game.l5r5e.L5rBaseDie) { + term.results.map((res) => { + const die = diceList[term.constructor.name].shift(); + res.result = die.face; + return res; + }); + term.l5rSummary(); + } + return term; + }); + + // Recompute summary + roll.l5rSummary(); + + // Add roll & history to message + this.message._roll = roll; + this.message._roll.l5r5e.history = this.object.dicesList; + } + + /** + * Send the new roll in chat and delete the old message + * @returns {Promise} + * @private + */ + async _toChatMessage() { + // Keep old Ids + const appId = this.id; + const msgId = this.message._id; + + if (this.message._roll.l5r5e.isInitiativeRoll) { + await this.message._roll.l5r5e.actor.rollInitiative({ + rerollInitiative: true, + initiativeOptions: { + messageOptions: { + rnkRoll: this.message._roll, + }, + }, + }); + // Adhesive tape to get the messageId :/ + this.message = this.message._roll.l5r5e.actor.rnkMessage; + delete this.message._roll.l5r5e.actor.rnkMessage; + } else { + // Send it to chat, switch to new message + this.message = await this.message._roll.toMessage(); + } + + // Refresh viewers + game.l5r5e.sockets.updateMessageIdAndRefresh(appId, this.message._id); + + // Delete old chat message related to this series + if (game.settings.get("l5r5e", "rnk.deleteOldMessage")) { + if (game.user.isGM) { + const message = game.messages.get(msgId); + if (message) { + message.delete(); + } + } else { + game.l5r5e.sockets.deleteChatMessage(msgId); + } + } + } + /** * This method is called upon form submission after form data is validated * @param event The initial triggering submission event @@ -334,74 +577,26 @@ export class RollnKeepDialog extends FormApplication { return; } - console.log("**** _updateObject"); + // Discard all dices without a choice for the current step + this._discardDiceWithoutChoice(); - console.log(this.object.dicesList); + // Apply all choices to build the next step + await this._applyChoices(); - // Actions p 26 : change, ignore/discard, reroll, reserve, change face - // let addNewRoll = false; - // switch (type) { - // case RollnKeepDialog.CHOICES.keep: - // // current.explosive = term.constructor.FACES[current.face / newFace].explosive - // if (current.explosive) { - // addNewRoll = true; - // } - // break; - // - // case RollnKeepDialog.CHOICES.reroll: - // addNewRoll = true; - // break; - // } - // - // // New roll - // if (addNewRoll) { - // if (!this.object.dicesList[data.step + 1]) { - // this.object.dicesList[data.step + 1] = Array(this.object.dicesList[0].length).fill(null); - // } - // this.object.dicesList[data.step + 1][data.die] = await this._newRoll(current.type, type); - // } + // *** Below this the current step become the next step *** + this.object.currentStep += 1; - // if (isInitiativeRoll) { - // // Initiative roll - // this._actor.rollInitiative({ - // initiativeOptions: { - // formula: formula.join("+"), - // // updateTurn: true, - // messageOptions: { - // skillId: this.object.skill.id, - // difficulty: this.object.difficulty.value, - // difficultyHidden: this.object.difficulty.hidden, - // useVoidPoint: this.object.useVoidPoint, - // rerollInitiative: true, - // }, - // }, - // }); - // } else {} + // Rebuild the roll + await this._rebuildRoll(); - // Notify the change to other players - // game.l5r5e.sockets.refreshAppId(this.id); + // Send the new roll in chat and delete the old message + await this._toChatMessage(); - // this.message._roll.l5r5e.history = {test: "yahooo"}; - - // await message.update({ - // data: { - // roll: roll - // } - // }); - // message.render(false); - - // console.log(roll.toJSON(), this.message); - - // ui.chat.updateMessage(message); - // ui.chat.postOne(message, false); - - // if (game.user.isGM) { - // message.delete(); - // } else { - // game.l5r5e.sockets.deleteChatMessage(messageId); - // } - - // return this.close(); + // If a next step exist, rerender, else close + if (this.object.dicesList[this.object.currentStep]) { + return this.render(); + } + return this.close(); } /** @@ -411,6 +606,7 @@ export class RollnKeepDialog extends FormApplication { */ static async onChatAction(event) { event.preventDefault(); + event.stopPropagation(); // Extract card data const button = $(event.currentTarget); diff --git a/system/scripts/dice/roll.js b/system/scripts/dice/roll.js index d4bff90..e072c43 100644 --- a/system/scripts/dice/roll.js +++ b/system/scripts/dice/roll.js @@ -21,7 +21,7 @@ export class RollL5r5e extends Roll { difficulty: 2, difficultyHidden: false, voidPointUsed: false, - ringsUsed: 0, + ringsUsed: null, totalSuccess: 0, totalBonus: 0, success: 0, @@ -30,6 +30,7 @@ export class RollL5r5e extends Roll { strife: 0, }, history: null, + isInitiativeRoll: false, }; // Parse flavor for stance and skillId @@ -68,24 +69,10 @@ export class RollL5r5e extends Roll { // Roll super.evaluate({ minimize, maximize }); - - // Current terms - L5R Summary - this.terms.forEach((term) => this._l5rSummary(term)); - - // Check inner L5R rolls - L5R Summary - this._dice.forEach((term) => this._l5rSummary(term)); - - // Store final outputs this._rolled = true; - this.l5r5e.dicesTypes.std = this.dice.some( - (term) => term instanceof DiceTerm && !(term instanceof game.l5r5e.L5rBaseDie) - ); // ignore math symbols - this.l5r5e.dicesTypes.l5r = this.dice.some((term) => term instanceof game.l5r5e.L5rBaseDie); - this.l5r5e.summary.totalBonus = Math.max(0, this.l5r5e.summary.totalSuccess - this.l5r5e.summary.difficulty); - this.l5r5e.summary.ringsUsed = this.dice.reduce( - (acc, term) => (term instanceof game.l5r5e.RingDie ? acc + term.number : acc), - 0 - ); + + // Compute summary + this.l5rSummary(); return this; } @@ -93,10 +80,43 @@ export class RollL5r5e extends Roll { /** * Summarise the total of success, strife... for L5R dices for the current roll * + * @private + */ + l5rSummary() { + const summary = this.l5r5e.summary; + + // Reset totals + summary.success = 0; + summary.explosive = 0; + summary.opportunity = 0; + summary.strife = 0; + summary.totalSuccess = 0; + + // Current terms - L5R Summary + this.terms.forEach((term) => this._l5rTermSummary(term)); + + // Check inner L5R rolls - L5R Summary + this._dice.forEach((term) => this._l5rTermSummary(term)); + + // Store final outputs + this.l5r5e.dicesTypes.std = this.dice.some( + (term) => term instanceof DiceTerm && !(term instanceof game.l5r5e.L5rBaseDie) + ); // ignore math symbols + this.l5r5e.dicesTypes.l5r = this.dice.some((term) => term instanceof game.l5r5e.L5rBaseDie); + summary.totalBonus = Math.max(0, summary.totalSuccess - summary.difficulty); + summary.ringsUsed = this.dice.reduce( + (acc, term) => (term instanceof game.l5r5e.RingDie ? acc + term.number : acc), + 0 + ); + } + + /** + * Summarise the total of success, strife... for L5R dices for the current term + * * @param term * @private */ - _l5rSummary(term) { + _l5rTermSummary(term) { if (!(term instanceof game.l5r5e.L5rBaseDie)) { return; } diff --git a/system/scripts/handlebars.js b/system/scripts/handlebars.js index 15e191d..96b2f51 100644 --- a/system/scripts/handlebars.js +++ b/system/scripts/handlebars.js @@ -29,6 +29,13 @@ export const RegisterHandlebars = function () { return game.i18n.localize("l5r5e.techniques." + techniqueName.toLowerCase()); }); + /* ------------------------------------ */ + /* Dice */ + /* ------------------------------------ */ + Handlebars.registerHelper("getDiceFaceUrl", function (diceClass, faceId) { + return game.l5r5e[diceClass].getResultSrc(faceId); + }); + /* ------------------------------------ */ /* Utility */ /* ------------------------------------ */ diff --git a/system/scripts/hooks.js b/system/scripts/hooks.js index 373314e..7ac51d6 100644 --- a/system/scripts/hooks.js +++ b/system/scripts/hooks.js @@ -174,7 +174,7 @@ export default class HooksL5r5e { } /** - * DiceSoNice Hook + * DiceSoNice - Add L5R DicePresets */ static diceSoNiceReady(dice3d) { const texturePath = `${CONFIG.l5r5e.paths.assets}dices/default/3d/`; @@ -218,4 +218,16 @@ export default class HooksL5r5e { "d12" ); } + + /** + * DiceSoNice - Do not show 3D roll for the Roll n Keep series + * + * @param {string} messageId + * @param {object} context + */ + static diceSoNiceRollStart(messageId, context) { + if (context.roll.l5r5e?.history) { + context.blind = true; + } + } } diff --git a/system/scripts/main-l5r5e.js b/system/scripts/main-l5r5e.js index a9df18e..55559bd 100644 --- a/system/scripts/main-l5r5e.js +++ b/system/scripts/main-l5r5e.js @@ -129,3 +129,4 @@ Hooks.on("renderSidebarTab", (app, html, data) => HooksL5r5e.renderSidebarTab(ap Hooks.on("renderChatMessage", (message, html, data) => HooksL5r5e.renderChatMessage(message, html, data)); Hooks.on("renderCombatTracker", (app, html, data) => HooksL5r5e.renderCombatTracker(app, html, data)); Hooks.on("renderCompendium", async (app, html, data) => HooksL5r5e.renderCompendium(app, html, data)); +Hooks.on("diceSoNiceRollStart", (messageId, context) => HooksL5r5e.diceSoNiceRollStart(messageId, context)); diff --git a/system/scripts/settings.js b/system/scripts/settings.js index 882f1bc..c974cc3 100644 --- a/system/scripts/settings.js +++ b/system/scripts/settings.js @@ -2,6 +2,18 @@ * Custom system settings register */ export const RegisterSettings = function () { + /* ------------------------------------ */ + /* User settings */ + /* ------------------------------------ */ + game.settings.register("l5r5e", "rnk.deleteOldMessage", { + name: "SETTINGS.RollNKeep.DeleteOldMessage", + hint: "SETTINGS.RollNKeep.DeleteOldMessageHint", + scope: "world", + config: true, + default: true, + type: Boolean, + }); + /* ------------------------------------ */ /* Update */ /* ------------------------------------ */ diff --git a/system/scripts/socket-handler.js b/system/scripts/socket-handler.js index 6662689..43d2ae8 100644 --- a/system/scripts/socket-handler.js +++ b/system/scripts/socket-handler.js @@ -25,6 +25,10 @@ export class SocketHandlerL5r5e { this._onRefreshAppId(data); break; + case "updateMessageIdAndRefresh": + this._onUpdateMessageIdAndRefresh(data); + break; + default: console.warn(new Error("This socket event is not supported"), data); break; @@ -40,9 +44,13 @@ export class SocketHandlerL5r5e { }); } _onDeleteChatMessage(data) { + // Only delete the message if the user is a GM (otherwise it have no real effect) + // Currently only used in RnK + if (!game.user.isGM || !game.settings.get("l5r5e", "rnk.deleteOldMessage")) { + return; + } const message = game.messages.get(data.messageId); - // only delete the message if the user is a GM and the event emitter is one of the recipients - if (game.user.isGM && message.data["whisper"].includes(data.userId)) { + if (message) { message.delete(); } } @@ -65,4 +73,25 @@ export class SocketHandlerL5r5e { } app.refresh(); } + + /** + * Change in app message and refresh (used in RnK) + * @param appId + * @param msgId + */ + updateMessageIdAndRefresh(appId, msgId) { + game.socket.emit(SocketHandlerL5r5e.SOCKET_NAME, { + type: "updateMessageIdAndRefresh", + appId, + msgId, + }); + } + _onUpdateMessageIdAndRefresh(data) { + const app = Object.values(ui.windows).find((e) => e.id === data.appId); + if (!app || !app.message || typeof app.refresh !== "function") { + return; + } + app.message = game.messages.get(data.msgId); + app.refresh(); + } } diff --git a/system/templates/dice/chat-roll.html b/system/templates/dice/chat-roll.html index 95c6fb3..9a9607b 100644 --- a/system/templates/dice/chat-roll.html +++ b/system/templates/dice/chat-roll.html @@ -47,11 +47,11 @@
{{!-- Dices list --}} {{#each l5r5e.dices}} - {{#if this.diceTypeL5r}} - {{#each this.rolls}} - {{{this.result}}} - {{/each}} - {{/if}} + {{#if this.diceTypeL5r}} + {{#each this.rolls}} + {{{this.result}}} + {{/each}} + {{/if}} {{/each}} {{!-- Roll & Keep Button --}} diff --git a/system/templates/dice/roll-n-keep-dialog.html b/system/templates/dice/roll-n-keep-dialog.html index cbb895a..be72689 100644 --- a/system/templates/dice/roll-n-keep-dialog.html +++ b/system/templates/dice/roll-n-keep-dialog.html @@ -48,8 +48,8 @@ {{#each data.swapDiceFaces.rings}} -
- {{id}} +
+ {{this}}
{{/each}} @@ -83,15 +83,20 @@ {{#each item as |dice idxDie|}} -
- {{#if this.img}} - {{idxStep}}_{{idxDie}} - {{/if}} -
+ {{#if face}} +
+ + {{#if newFace}} + {{idxStep}}_{{idxDie}} + {{else}} + {{idxStep}}_{{idxDie}} + {{/if}} +
+ {{/if}} {{/each}} @@ -119,8 +124,8 @@ {{#each data.swapDiceFaces.skills}} -
- {{id}} +
+ {{this}}
{{/each}} @@ -132,20 +137,24 @@ {{else}} - {{!-- Non editable DiceList history (view for others players) --}} + {{!-- Non editable DiceList history --}} {{#each data.dicesList as |item idxStep|}} - - {{#each item as |dice idxDie|}} - - {{/each}} - + + {{#each item as |dice idxDie|}} + + {{/each}} + {{/each}}
-
- {{#if this.img}} - {{idxStep}}_{{idxDie}} - {{/if}} -
-
+ {{#if face}} +
+ {{#if newFace}} + {{idxStep}}_{{idxDie}} + {{else}} + {{idxStep}}_{{idxDie}} + {{/if}} +
+ {{/if}} +
{{/if}}