RnK with some bugs

This commit is contained in:
Vlyan
2021-02-01 16:27:57 +01:00
parent 30f71e31cd
commit 68577737fc
14 changed files with 513 additions and 204 deletions

View File

@@ -4,7 +4,11 @@
"Maintainers": ["Team L5R"] "Maintainers": ["Team L5R"]
}, },
"SETTINGS": { "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": { "ACTOR": {
"TypeCharacter": "Player Character", "TypeCharacter": "Player Character",
@@ -105,7 +109,7 @@
"swap_drop_here": "Swap", "swap_drop_here": "Swap",
"keep_drop_here": "Keep", "keep_drop_here": "Keep",
"keep_chat": "New roll from an exploding die", "keep_chat": "New roll from an exploding die",
"bt_validate": "Finalize" "bt_validate": "Finalize this step"
}, },
"max": "Max", "max": "Max",
"current": "Current", "current": "Current",

View File

@@ -4,7 +4,11 @@
"Maintainers": ["Team L5R"] "Maintainers": ["Team L5R"]
}, },
"SETTINGS": { "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": { "ACTOR": {
"TypeCharacter": "Personaje jugador", "TypeCharacter": "Personaje jugador",

View File

@@ -4,7 +4,11 @@
"Maintainers": ["Team L5R"] "Maintainers": ["Team L5R"]
}, },
"SETTINGS": { "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": { "ACTOR": {
"TypeCharacter": "Personnage Joueur", "TypeCharacter": "Personnage Joueur",
@@ -105,7 +109,7 @@
"swap_drop_here": "Swap", "swap_drop_here": "Swap",
"keep_drop_here": "Keep", "keep_drop_here": "Keep",
"keep_chat": "New roll from a exploding dice", "keep_chat": "New roll from a exploding dice",
"bt_validate": "Finalize" "bt_validate": "Finalize this step"
}, },
"max": "Max", "max": "Max",
"current": "Actuel", "current": "Actuel",

View File

@@ -41,7 +41,7 @@ export class CombatL5r5e extends Combat {
// Get score for each combatant // Get score for each combatant
const updatedCombatants = []; const updatedCombatants = [];
ids.forEach((combatantId) => { for (const combatantId of ids) {
const combatant = game.combat.combatants.find((c) => c._id === combatantId); const combatant = game.combat.combatants.find((c) => c._id === combatantId);
// Skip if combatant already have a initiative value // Skip if combatant already have a initiative value
@@ -77,31 +77,41 @@ export class CombatL5r5e extends Combat {
formula = createFormula.join("+"); formula = createFormula.join("+");
} }
const roll = new game.l5r5e.RollL5r5e(formula); let roll;
roll.actor = combatant.actor; const flavor =
roll.l5r5e.stance = data.stance; game.i18n.localize("l5r5e.chatdices.initiative_roll") +
roll.l5r5e.skillId = skillId; " (" +
roll.l5r5e.skillCatId = skillCat; game.i18n.localize(`l5r5e.conflict.initiative.prepared_${isPrepared}`) +
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(); if (messageOptions.rnkRoll instanceof game.l5r5e.RollL5r5e && ids.length === 1) {
roll.toMessage({ // Specific RnK
flavor: roll = messageOptions.rnkRoll;
game.i18n.localize("l5r5e.chatdices.initiative_roll") + // Ugly but work... i need the new messageId
" (" + combatant.actor.rnkMessage = await roll.toMessage({ flavor });
game.i18n.localize(`l5r5e.conflict.initiative.prepared_${isPrepared}`) + } 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, // 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. // 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) { if (successes >= roll.l5r5e.summary.difficulty) {
initiative = initiative + 1 + Math.max(successes - roll.l5r5e.summary.difficulty, 0); initiative = initiative + 1 + Math.max(successes - roll.l5r5e.summary.difficulty, 0);
} }
@@ -111,7 +121,7 @@ export class CombatL5r5e extends Combat {
_id: combatant._id, _id: combatant._id,
initiative: initiative, initiative: initiative,
}); });
}); }
// Update all combatants at once // Update all combatants at once
await this.updateEmbeddedEntity("Combatant", updatedCombatants); await this.updateEmbeddedEntity("Combatant", updatedCombatants);

View File

@@ -72,13 +72,7 @@ export class L5rBaseDie extends DiceTerm {
this._evaluateModifiers(); this._evaluateModifiers();
// Combine all results // Combine all results
this.l5r5e = { success: 0, explosive: 0, opportunity: 0, strife: 0 }; this.l5rSummary();
this.results.forEach((term) => {
const face = this.constructor.FACES[term.result];
["success", "explosive", "opportunity", "strife"].forEach((props) => {
this.l5r5e[props] += parseInt(face[props]);
});
});
// Return the evaluated term // Return the evaluated term
this._evaluated = true; this._evaluated = true;
@@ -87,24 +81,33 @@ export class L5rBaseDie extends DiceTerm {
return this; 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 * Roll the DiceTerm by mapping a random uniform draw against the faces of the dice term
* @override * @override
*/ */
roll(options) { roll(options) {
const roll = super.roll(options); const roll = super.roll(options);
//roll.l5r5e = this.l5r5e; //roll.l5r5e = this.l5r5e;
return roll; return roll;
} }
/** @override */ /** @override */
static fromData(data) { static fromData(data) {
const roll = super.fromData(data); const roll = super.fromData(data);
roll.l5r5e = data.l5r5e; roll.l5r5e = data.l5r5e;
return roll; return roll;
} }
@@ -114,9 +117,7 @@ export class L5rBaseDie extends DiceTerm {
*/ */
toJSON() { toJSON() {
const json = super.toJSON(); const json = super.toJSON();
json.l5r5e = this.l5r5e; json.l5r5e = this.l5r5e;
return json; return json;
} }
} }

View File

@@ -32,7 +32,7 @@ export class RollnKeepDialog extends FormApplication {
*/ */
object = { object = {
currentStep: 0, currentStep: 0,
submitDisabled: true, submitDisabled: false,
swapDiceFaces: { swapDiceFaces: {
rings: [], rings: [],
skills: [], skills: [],
@@ -52,6 +52,7 @@ export class RollnKeepDialog extends FormApplication {
title: game.i18n.localize("l5r5e.roll_n_keep.title"), title: game.i18n.localize("l5r5e.roll_n_keep.title"),
width: 660, width: 660,
height: 454, height: 454,
closeOnSubmit: false,
}); });
} }
@@ -62,21 +63,23 @@ export class RollnKeepDialog extends FormApplication {
return `l5r5e-roll-n-keep-dialog-${this.message._id}`; 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 * Create the Roll n Keep dialog
* @param {number} messageId * @param {number} messageId
* @param {FormApplicationOptions} options * @param {FormApplicationOptions} options
*/ */
constructor(messageId, options = {}) { constructor(messageId, options = {}) {
console.clear(); // TODO TMP
super({}, options); super({}, options);
this.message = game.messages.get(messageId); this.message = game.messages.get(messageId);
this.options.editable = this.message?._roll.l5r5e.actor.owner || false; this.options.editable =
this._initializeDiceFaces(); this.message?.isAuthor || this.message?._roll.l5r5e.actor?.owner || this.message?.owner || false;
this._initialize();
console.log(this.object); // TODO TMP this._initializeDiceFaces();
this._initializeHistory();
} }
/** /**
@@ -86,7 +89,7 @@ export class RollnKeepDialog extends FormApplication {
if (!this.message) { if (!this.message) {
return; return;
} }
this._initialize(); this._initializeHistory();
this.render(false); this.render(false);
} }
@@ -105,10 +108,10 @@ export class RollnKeepDialog extends FormApplication {
} }
/** /**
* Initialize the dialog with the message * Initialize the dice history list
* @private * @private
*/ */
_initialize() { _initializeHistory() {
if (!this.message) { if (!this.message) {
return; return;
} }
@@ -118,12 +121,18 @@ export class RollnKeepDialog extends FormApplication {
// Already history // Already history
if (Array.isArray(this.roll.l5r5e.history)) { if (Array.isArray(this.roll.l5r5e.history)) {
this.object.currentStep = this.roll.l5r5e.history.length;
this.object.dicesList = this.roll.l5r5e.history; 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; return;
} }
// New // New
this.object.dicesList = [[]];
this.roll.terms.forEach((term) => { this.roll.terms.forEach((term) => {
if (typeof term !== "object") { if (typeof term !== "object") {
return; return;
@@ -132,7 +141,6 @@ export class RollnKeepDialog extends FormApplication {
this.object.dicesList[0].push({ this.object.dicesList[0].push({
type: term.constructor.name, type: term.constructor.name,
face: res.result, face: res.result,
img: term.constructor.getResultSrc(res.result),
choice: RollnKeepDialog.CHOICES.nothing, choice: RollnKeepDialog.CHOICES.nothing,
}); });
}); });
@@ -145,14 +153,10 @@ export class RollnKeepDialog extends FormApplication {
*/ */
_initializeDiceFaces() { _initializeDiceFaces() {
// All faces are unique for rings // All faces are unique for rings
this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES).map((id) => { this.object.swapDiceFaces.rings = Object.keys(game.l5r5e.RingDie.FACES);
return { id, img: game.l5r5e.RingDie.getResultSrc(id) };
});
// Only unique for Skills // Only unique for Skills
this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12].map((id) => { this.object.swapDiceFaces.skills = [1, 3, 6, 8, 10, 11, 12];
return { id, img: game.l5r5e.AbilityDie.getResultSrc(id) };
});
} }
/** /**
@@ -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. * Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent * @param {DragEvent} event The originating DragEvent
@@ -192,8 +205,12 @@ export class RollnKeepDialog extends FormApplication {
*/ */
getData(options = null) { getData(options = null) {
// Check only on 1st step // Check only on 1st step
const kept = this.object.currentStep === 0 ? this._getKeepCount() : 1; if (this.object.currentStep === 0) {
this.object.submitDisabled = kept < 1 || kept > this.roll.l5r5e.summary.ringsUsed; 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 { return {
...super.getData(options), ...super.getData(options),
@@ -263,39 +280,16 @@ export class RollnKeepDialog extends FormApplication {
} }
current.choice = type; 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); this.render(false);
return 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 * Return the current number of dices kept
* @private * @private
@@ -321,6 +315,255 @@ export class RollnKeepDialog extends FormApplication {
}, 0); }, 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<void>}
* @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<void>}
* @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 * This method is called upon form submission after form data is validated
* @param event The initial triggering submission event * @param event The initial triggering submission event
@@ -334,74 +577,26 @@ export class RollnKeepDialog extends FormApplication {
return; 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 // *** Below this the current step become the next step ***
// let addNewRoll = false; this.object.currentStep += 1;
// 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);
// }
// if (isInitiativeRoll) { // Rebuild the roll
// // Initiative roll await this._rebuildRoll();
// 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 {}
// Notify the change to other players // Send the new roll in chat and delete the old message
// game.l5r5e.sockets.refreshAppId(this.id); await this._toChatMessage();
// this.message._roll.l5r5e.history = {test: "yahooo"}; // If a next step exist, rerender, else close
if (this.object.dicesList[this.object.currentStep]) {
// await message.update({ return this.render();
// data: { }
// roll: roll return this.close();
// }
// });
// 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();
} }
/** /**
@@ -411,6 +606,7 @@ export class RollnKeepDialog extends FormApplication {
*/ */
static async onChatAction(event) { static async onChatAction(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation();
// Extract card data // Extract card data
const button = $(event.currentTarget); const button = $(event.currentTarget);

View File

@@ -21,7 +21,7 @@ export class RollL5r5e extends Roll {
difficulty: 2, difficulty: 2,
difficultyHidden: false, difficultyHidden: false,
voidPointUsed: false, voidPointUsed: false,
ringsUsed: 0, ringsUsed: null,
totalSuccess: 0, totalSuccess: 0,
totalBonus: 0, totalBonus: 0,
success: 0, success: 0,
@@ -30,6 +30,7 @@ export class RollL5r5e extends Roll {
strife: 0, strife: 0,
}, },
history: null, history: null,
isInitiativeRoll: false,
}; };
// Parse flavor for stance and skillId // Parse flavor for stance and skillId
@@ -68,24 +69,10 @@ export class RollL5r5e extends Roll {
// Roll // Roll
super.evaluate({ minimize, maximize }); 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._rolled = true;
this.l5r5e.dicesTypes.std = this.dice.some(
(term) => term instanceof DiceTerm && !(term instanceof game.l5r5e.L5rBaseDie) // Compute summary
); // ignore math symbols this.l5rSummary();
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
);
return this; return this;
} }
@@ -93,10 +80,43 @@ export class RollL5r5e extends Roll {
/** /**
* Summarise the total of success, strife... for L5R dices for the current 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 * @param term
* @private * @private
*/ */
_l5rSummary(term) { _l5rTermSummary(term) {
if (!(term instanceof game.l5r5e.L5rBaseDie)) { if (!(term instanceof game.l5r5e.L5rBaseDie)) {
return; return;
} }

View File

@@ -29,6 +29,13 @@ export const RegisterHandlebars = function () {
return game.i18n.localize("l5r5e.techniques." + techniqueName.toLowerCase()); return game.i18n.localize("l5r5e.techniques." + techniqueName.toLowerCase());
}); });
/* ------------------------------------ */
/* Dice */
/* ------------------------------------ */
Handlebars.registerHelper("getDiceFaceUrl", function (diceClass, faceId) {
return game.l5r5e[diceClass].getResultSrc(faceId);
});
/* ------------------------------------ */ /* ------------------------------------ */
/* Utility */ /* Utility */
/* ------------------------------------ */ /* ------------------------------------ */

View File

@@ -174,7 +174,7 @@ export default class HooksL5r5e {
} }
/** /**
* DiceSoNice Hook * DiceSoNice - Add L5R DicePresets
*/ */
static diceSoNiceReady(dice3d) { static diceSoNiceReady(dice3d) {
const texturePath = `${CONFIG.l5r5e.paths.assets}dices/default/3d/`; const texturePath = `${CONFIG.l5r5e.paths.assets}dices/default/3d/`;
@@ -218,4 +218,16 @@ export default class HooksL5r5e {
"d12" "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;
}
}
} }

View File

@@ -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("renderChatMessage", (message, html, data) => HooksL5r5e.renderChatMessage(message, html, data));
Hooks.on("renderCombatTracker", (app, html, data) => HooksL5r5e.renderCombatTracker(app, 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("renderCompendium", async (app, html, data) => HooksL5r5e.renderCompendium(app, html, data));
Hooks.on("diceSoNiceRollStart", (messageId, context) => HooksL5r5e.diceSoNiceRollStart(messageId, context));

View File

@@ -2,6 +2,18 @@
* Custom system settings register * Custom system settings register
*/ */
export const RegisterSettings = function () { 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 */ /* Update */
/* ------------------------------------ */ /* ------------------------------------ */

View File

@@ -25,6 +25,10 @@ export class SocketHandlerL5r5e {
this._onRefreshAppId(data); this._onRefreshAppId(data);
break; break;
case "updateMessageIdAndRefresh":
this._onUpdateMessageIdAndRefresh(data);
break;
default: default:
console.warn(new Error("This socket event is not supported"), data); console.warn(new Error("This socket event is not supported"), data);
break; break;
@@ -40,9 +44,13 @@ export class SocketHandlerL5r5e {
}); });
} }
_onDeleteChatMessage(data) { _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); 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 (message) {
if (game.user.isGM && message.data["whisper"].includes(data.userId)) {
message.delete(); message.delete();
} }
} }
@@ -65,4 +73,25 @@ export class SocketHandlerL5r5e {
} }
app.refresh(); 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();
}
} }

View File

@@ -47,11 +47,11 @@
<div class="l5r5e item-display dices-l5r"> <div class="l5r5e item-display dices-l5r">
{{!-- Dices list --}} {{!-- Dices list --}}
{{#each l5r5e.dices}} {{#each l5r5e.dices}}
{{#if this.diceTypeL5r}} {{#if this.diceTypeL5r}}
{{#each this.rolls}} {{#each this.rolls}}
<span class="l5r5e chat-dice">{{{this.result}}}</span> <span class="l5r5e chat-dice">{{{this.result}}}</span>
{{/each}} {{/each}}
{{/if}} {{/if}}
{{/each}} {{/each}}
{{!-- Roll & Keep Button --}} {{!-- Roll & Keep Button --}}

View File

@@ -48,8 +48,8 @@
</legend> </legend>
{{#each data.swapDiceFaces.rings}} {{#each data.swapDiceFaces.rings}}
<div class="dice dropbox faces-change" data-type="swap" data-face="{{id}}" data-die="RingDie"> <div class="dice dropbox faces-change" data-type="swap" data-face="{{this}}" data-die="RingDie">
<img src="{{img}}" alt="{{id}}" /> <img src="{{getDiceFaceUrl 'RingDie' this}}" alt="{{this}}" />
</div> </div>
{{/each}} {{/each}}
</td> </td>
@@ -83,15 +83,20 @@
<tr> <tr>
{{#each item as |dice idxDie|}} {{#each item as |dice idxDie|}}
<td> <td>
<div {{#if face}}
class="dice {{this.choice}} {{#ifCond ../../data.currentStep '==' idxStep }}draggable{{/ifCond}}" <div
data-step="{{idxStep}}" class="dice {{choice}}{{#ifCond face '&&' (ifCond ../../data.currentStep '==' idxStep) }} draggable{{/ifCond}}"
data-die="{{idxDie}}" data-step="{{idxStep}}"
> data-die="{{idxDie}}"
{{#if this.img}} >
<img src="{{this.img}}" alt="{{idxStep}}_{{idxDie}}" />
{{/if}} {{#if newFace}}
</div> <img src="{{getDiceFaceUrl type newFace}}" alt="{{idxStep}}_{{idxDie}}" />
{{else}}
<img src="{{getDiceFaceUrl type face}}" alt="{{idxStep}}_{{idxDie}}" />
{{/if}}
</div>
{{/if}}
</td> </td>
{{/each}} {{/each}}
</tr> </tr>
@@ -119,8 +124,8 @@
</legend> </legend>
{{#each data.swapDiceFaces.skills}} {{#each data.swapDiceFaces.skills}}
<div class="dice dropbox faces-change" data-type="swap" data-face="{{id}}" data-die="AbilityDie"> <div class="dice dropbox faces-change" data-type="swap" data-face="{{this}}" data-die="AbilityDie">
<img src="{{img}}" alt="{{id}}" /> <img src="{{getDiceFaceUrl 'AbilityDie' this}}" alt="{{this}}" />
</div> </div>
{{/each}} {{/each}}
</td> </td>
@@ -132,20 +137,24 @@
</button> </button>
{{else}} {{else}}
{{!-- Non editable DiceList history (view for others players) --}} {{!-- Non editable DiceList history --}}
<table> <table>
{{#each data.dicesList as |item idxStep|}} {{#each data.dicesList as |item idxStep|}}
<tr> <tr>
{{#each item as |dice idxDie|}} {{#each item as |dice idxDie|}}
<td> <td>
<div class="dice {{this.choice}}"> {{#if face}}
{{#if this.img}} <div class="dice {{choice}}">
<img src="{{this.img}}" alt="{{idxStep}}_{{idxDie}}" /> {{#if newFace}}
{{/if}} <img src="{{getDiceFaceUrl type newFace}}" alt="{{idxStep}}_{{idxDie}}" />
</div> {{else}}
</td> <img src="{{getDiceFaceUrl type face}}" alt="{{idxStep}}_{{idxDie}}" />
{{/each}} {{/if}}
</tr> </div>
{{/if}}
</td>
{{/each}}
</tr>
{{/each}} {{/each}}
</table> </table>
{{/if}} {{/if}}