Seamless update for 20Q (deleted refresh button)

Fix for error css
Some start for roll n keep
This commit is contained in:
Vlyan
2021-01-10 21:12:36 +01:00
parent 2ad6b1f44b
commit e30e5d02a6
16 changed files with 647 additions and 45 deletions

View File

@@ -90,7 +90,8 @@
"difficulty": "TN", "difficulty": "TN",
"difficulty_hidden": "TN ???", "difficulty_hidden": "TN ???",
"dicepicker": "Dice Picker", "dicepicker": "Dice Picker",
"void_point_used": "Void point used" "void_point_used": "Void point used",
"roll_n_keep": "Roll & Keep"
}, },
"dicepicker": { "dicepicker": {
"difficulty_title": "Difficulty", "difficulty_title": "Difficulty",
@@ -100,6 +101,15 @@
"roll_label": "Roll", "roll_label": "Roll",
"bt_add_macro": "Add a macro" "bt_add_macro": "Add a macro"
}, },
"roll_n_keep": {
"title": "L5R Roll & Keep",
"discard_drop_here": "Discard",
"reroll_drop_here": "Re-roll",
"reroll_chat": "Re-rolled dice",
"keep_drop_here": "Keep",
"keep_chat": "New roll from a exploding dice",
"bt_validate": "Finalize"
},
"max": "Max", "max": "Max",
"current": "Current", "current": "Current",
"quantity": "Quantity", "quantity": "Quantity",

View File

@@ -90,7 +90,8 @@
"difficulty": "TN", "difficulty": "TN",
"difficulty_hidden": "TN ???", "difficulty_hidden": "TN ???",
"dicepicker": "Dice Picker", "dicepicker": "Dice Picker",
"void_point_used": "Void point used" "void_point_used": "Void point used",
"roll_n_keep": "Roll & Keep"
}, },
"dicepicker": { "dicepicker": {
"difficulty_title": "Difficulty", "difficulty_title": "Difficulty",
@@ -100,6 +101,15 @@
"roll_label": "Roll", "roll_label": "Roll",
"bt_add_macro": "Add a macro" "bt_add_macro": "Add a macro"
}, },
"roll_n_keep": {
"title": "L5R Roll & Keep",
"discard_drop_here": "Discard",
"reroll_drop_here": "Re-roll",
"reroll_chat": "Re-rolled dice",
"keep_drop_here": "Keep",
"keep_chat": "New roll from a exploding dice",
"bt_validate": "Finalize"
},
"max": "Max", "max": "Max",
"current": "Actuales", "current": "Actuales",
"quantity": "Cantidad", "quantity": "Cantidad",

View File

@@ -90,7 +90,8 @@
"difficulty": "ND", "difficulty": "ND",
"difficulty_hidden": "ND ???", "difficulty_hidden": "ND ???",
"dicepicker": "Dice Picker", "dicepicker": "Dice Picker",
"void_point_used": "Point de vide utilisé" "void_point_used": "Point de vide utilisé",
"roll_n_keep": "Roll & Keep"
}, },
"dicepicker": { "dicepicker": {
"difficulty_title": "Difficulté", "difficulty_title": "Difficulté",
@@ -100,6 +101,15 @@
"roll_label": "Lancer", "roll_label": "Lancer",
"bt_add_macro": "Ajouter une macro" "bt_add_macro": "Ajouter une macro"
}, },
"roll_n_keep": {
"title": "L5R Roll & Keep",
"discard_drop_here": "Discard",
"reroll_drop_here": "Re-roll",
"reroll_chat": "Re-rolled dice",
"keep_drop_here": "Keep",
"keep_chat": "New roll from a exploding dice",
"bt_validate": "Finalize"
},
"max": "Max", "max": "Max",
"current": "Actuel", "current": "Actuel",
"quantity": "Quantité", "quantity": "Quantité",

View File

@@ -48,24 +48,10 @@ export class TwentyQuestionsDialog extends FormApplication {
} }
/** /**
* Add a refresh button on top of sheet * Define a unique and dynamic element ID for the rendered ActorSheet application
* Allow a GM or player to see the change made by another player without closing the dialog
* @override
*/ */
_getHeaderButtons() { get id() {
let buttons = super._getHeaderButtons(); return `l5r5e-twenty-questions-dialog-${this.actor._id}`;
buttons.unshift({
label: game.i18n.localize("l5r5e.twenty_questions.bt_refresh"),
class: "twenty-questions",
icon: "fas fa-sync-alt",
onclick: async () => {
this._initialize(game.actors.get(this.actor._id));
await this._constructCache();
this.render(false);
},
});
return buttons;
} }
/** /**
@@ -76,6 +62,18 @@ export class TwentyQuestionsDialog extends FormApplication {
this._initialize(actor); this._initialize(actor);
} }
/**
* Refresh data (used from socket)
*/
async refresh() {
if (!this.actor) {
return;
}
this._initialize(game.actors.get(this.actor._id));
await this._constructCache();
this.render(false);
}
/** /**
* Initialize actor and object for dialog * Initialize actor and object for dialog
* @private * @private
@@ -343,6 +341,9 @@ export class TwentyQuestionsDialog extends FormApplication {
}, },
}); });
// Notify the change to other players
game.l5r5e.sockets.refreshAppId(this.id);
this.render(false); this.render(false);
} }

View File

@@ -23,13 +23,20 @@ export class L5rBaseDie extends DiceTerm {
} }
/** /**
* Return a string used as the label for each rolled result * Return the full img string used as the label for each rolled result
* @override * @override
*/ */
static getResultLabel(result) { static getResultLabel(result) {
return `<img src="${CONFIG.l5r5e.paths.assets}dices/default/${this.FACES[result].image}.svg" alt="${result}" />`; return `<img src="${CONFIG.l5r5e.paths.assets}dices/default/${this.FACES[result].image}.svg" alt="${result}" />`;
} }
/**
* Return the url of the result face
*/
static getResultSrc(result) {
return `${CONFIG.l5r5e.paths.assets}dices/default/${this.FACES[result].image}.svg`;
}
/** /**
* Return the total result of the DiceTerm if it has been evaluated * Return the total result of the DiceTerm if it has been evaluated
* Always zero for L5R dices to not count in total for regular dices * Always zero for L5R dices to not count in total for regular dices

View File

@@ -0,0 +1,330 @@
/**
* L5R Dice Roll n Keep dialog
* @extends {FormApplication}
*/
export class RollnKeepDialog extends FormApplication {
/**
* Player choice list
*/
static CHOICES = {
discard: "discard",
face_change: "face-change",
keep: "keep",
nothing: null,
reroll: "reroll",
reserve: "reserve",
};
/**
* The current ChatMessage where we come from
* @param {ChatMessage} message
*/
message = null;
/**
* Payload Object
*/
object = {
dicesList: [[]],
};
/**
* Assign the default options
* @override
*/
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
id: "l5r5e-roll-n-keep-dialog",
classes: ["l5r5e", "roll-n-keep-dialog"],
template: CONFIG.l5r5e.paths.templates + "dice/roll-n-keep-dialog.html",
title: game.i18n.localize("l5r5e.roll_n_keep.title"),
width: 660,
height: 660,
});
}
/**
* Define a unique and dynamic element ID for the rendered ActorSheet application
*/
get id() {
return `l5r5e-roll-n-keep-dialog-${this.message._id}`;
}
/**
* Create the Roll n Keep dialog
* @param {ChatMessage} message
* @param {FormApplicationOptions} options
*/
constructor(message, options = {}) {
super({}, options);
this.message = message;
this._initialize();
}
/**
* Refresh data (used from socket)
*/
async refresh() {
if (!this.message) {
return;
}
this._initialize();
this.render(false);
}
/**
* Initialize the dialog with the message
* @private
*/
_initialize() {
// Get the roll
const roll = game.l5r5e.RollL5r5e.fromData(this.message.roll);
console.clear();
console.log(roll); // TODO TMP
// Already history
if (Array.isArray(roll.l5r5e.history)) {
this.object.dicesList = roll.l5r5e.history;
return;
}
// New
roll.terms.forEach((term) => {
if (typeof term !== "object") {
return;
}
term.results.forEach((res) => {
this.object.dicesList[0].push({
type: term.constructor.name,
face: res.result,
explosive: term.constructor.FACES[res.result].explosive,
img: term.constructor.getResultSrc(res.result),
choice: RollnKeepDialog.CHOICES.nothing,
});
});
});
}
/**
* Create drag-and-drop workflow handlers for this Application
* @return An array of DragDrop handlers
*/
_createDragDropHandlers() {
return [
new DragDrop({
dragSelector: ".dice.draggable",
dropSelector: ".dropbox",
permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) },
callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDropItem.bind(this) },
}),
];
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
*/
_onDragStart(event) {
const target = $(event.currentTarget);
event.dataTransfer.setData(
"text/plain",
JSON.stringify({
step: target.data("step"),
die: target.data("die"),
})
);
}
/**
* Construct and return the data object used to render the HTML template for this form application.
* @param options
* @return {Object}
*/
getData(options = null) {
const draggableList = [];
this.object.dicesList.forEach((step, idx) => {
step.forEach((die, dieNum) => {
if (die) {
draggableList[dieNum] = idx;
}
});
});
return {
...super.getData(options),
data: this.object,
draggableList: draggableList,
l5r5e: this.message._roll.l5r5e,
};
}
/**
* Listen to html elements
* @override
*/
activateListeners(html) {
super.activateListeners(html);
// Finalize Button
html.find("#finalize").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
if (this._getKeepCount() > 0) {
this.submit();
}
});
}
/**
* Handle dropped items
*/
async _onDropItem(event) {
const type = $(event.currentTarget).data("type");
const json = event.dataTransfer.getData("text/plain");
if (!json || !Object.values(RollnKeepDialog.CHOICES).some((e) => !!e && e === type)) {
return;
}
const data = JSON.parse(json);
if (!data) {
return;
}
let addNewRoll = false;
const current = this.object.dicesList[data.step][data.die];
current.choice = type;
// Actions p 26 : change, ignore/discard, reroll, reserve, change face
switch (type) {
case RollnKeepDialog.CHOICES.keep:
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);
}
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,
explosive: dice.constructor.FACES[result].explosive,
img: dice.constructor.getResultSrc(result),
choice: RollnKeepDialog.CHOICES.nothing,
};
}
/**
* Return the current number of dices kept
* @private
*/
_getKeepCount() {
return this.object.dicesList.reduce((acc, step) => {
return (
acc +
step.reduce((acc2, die) => {
if (!!die && die.choice === RollnKeepDialog.CHOICES.keep) {
acc2 = acc2 + 1;
}
return acc2;
}, 0)
);
}, 0);
}
/**
* This method is called upon form submission after form data is validated
* @param event The initial triggering submission event
* @param formData The object of validated form data with which to update the object
* @returns A Promise which resolves once the update operation has completed
* @override
*/
async _updateObject(event, formData) {
console.log("**** _updateObject");
// Notify the change to other players
// game.l5r5e.sockets.refreshAppId(this.id);
// 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();
}
/**
* Handle execution of a chat card action via a click event on the RnK button
* @param {Event} event The originating click event
* @returns {Promise} A promise which resolves once the handler workflow is complete
*/
static async onChatAction(event) {
event.preventDefault();
// Extract card data
const button = $(event.currentTarget);
button.attr("disabled", true);
const card = button.parents(".l5r5e.item-display.dices-l5r");
const messageId = card.parents(".chat-message").data("message-id");
const message = game.messages.get(messageId);
// Validate permission to proceed with the roll n keep
if (!message || !message._roll.l5r5e.actor.owner) {
return;
}
new RollnKeepDialog(message).render(true);
// Re-enable the button
button.attr("disabled", false);
}
}

View File

@@ -1,4 +1,5 @@
import { L5rBaseDie } from "./dietype/l5r-base-die.js"; import { L5rBaseDie } from "./dietype/l5r-base-die.js";
import { ActorL5r5e } from "../actor.js";
/** /**
* Roll for L5R5e * Roll for L5R5e
@@ -20,14 +21,15 @@ export class RollL5r5e extends Roll {
l5r: false, l5r: false,
}, },
summary: { summary: {
difficulty: 0, difficulty: 2,
difficultyHidden: false, difficultyHidden: false,
voidPointUsed: false,
success: 0, success: 0,
explosive: 0, explosive: 0,
opportunity: 0, opportunity: 0,
strife: 0, strife: 0,
voidPointUsed: false,
}, },
history: null,
}; };
// Parse flavor for stance and skillId // Parse flavor for stance and skillId
@@ -198,9 +200,15 @@ export class RollL5r5e extends Roll {
this.roll(); this.roll();
} }
const canRnK = false; // TODO TMP dev in progress
// const canRnK = !this.l5r5e.dicesTypes.std
// && this.l5r5e.dicesTypes.l5r
// && this.dice.length > 1
// && this.l5r5e.actor // pb with dice with no actor
// && this.l5r5e.actor.owner;
// Define chat data // Define chat data
const chatData = { const chatData = {
// borderColor: game.user.color, // don't work :/
formula: isPrivate ? "???" : this._formula, formula: isPrivate ? "???" : this._formula,
flavor: isPrivate ? null : chatOptions.flavor, flavor: isPrivate ? null : chatOptions.flavor,
user: chatOptions.user, user: chatOptions.user,
@@ -212,6 +220,7 @@ export class RollL5r5e extends Roll {
? {} ? {}
: { : {
...this.l5r5e, ...this.l5r5e,
canRnK: canRnK,
dices: this.dice.map((d) => { dices: this.dice.map((d) => {
return { return {
diceTypeL5r: d instanceof L5rBaseDie, diceTypeL5r: d instanceof L5rBaseDie,
@@ -277,8 +286,16 @@ export class RollL5r5e extends Roll {
static fromData(data) { static fromData(data) {
const roll = super.fromData(data); const roll = super.fromData(data);
roll.data = data.data; roll.data = duplicate(data.data);
roll.l5r5e = data.l5r5e; roll.l5r5e = duplicate(data.l5r5e);
// get real Actor object
if (data.l5r5e.actor && !(data.l5r5e.actor instanceof ActorL5r5e)) {
const actor = game.actors.get(data.l5r5e.actor.id);
if (actor) {
roll.l5r5e.actor = actor;
}
}
return roll; return roll;
} }
@@ -290,8 +307,15 @@ export class RollL5r5e extends Roll {
toJSON() { toJSON() {
const json = super.toJSON(); const json = super.toJSON();
json.data = this.data; json.data = duplicate(this.data);
json.l5r5e = this.l5r5e; json.l5r5e = duplicate(this.l5r5e);
// lightweight the Actor
if (json.l5r5e.actor) {
json.l5r5e.actor = {
id: json.l5r5e.actor._id,
};
}
return json; return json;
} }

View File

@@ -1,6 +1,7 @@
// Import Commons Modules // Import Commons Modules
import { L5R5E } from "./config.js"; import { L5R5E } from "./config.js";
import { HelpersL5r5e } from "./helpers.js"; import { HelpersL5r5e } from "./helpers.js";
import { SocketHandlerL5r5e } from "./socket-handler.js";
import { RegisterSettings } from "./settings.js"; import { RegisterSettings } from "./settings.js";
import { PreloadTemplates } from "./preloadTemplates.js"; import { PreloadTemplates } from "./preloadTemplates.js";
import { HelpDialog } from "./help/help-dialog.js"; import { HelpDialog } from "./help/help-dialog.js";
@@ -13,6 +14,7 @@ import { AbilityDie } from "./dice/dietype/ability-die.js";
import { RingDie } from "./dice/dietype/ring-die.js"; import { RingDie } from "./dice/dietype/ring-die.js";
import { RollL5r5e } from "./dice/roll.js"; import { RollL5r5e } from "./dice/roll.js";
import { DicePickerDialog } from "./dice/dice-picker-dialog.js"; import { DicePickerDialog } from "./dice/dice-picker-dialog.js";
import { RollnKeepDialog } from "./dice/roll-n-keep-dialog.js";
// Items // Items
import { ItemL5r5e } from "./item.js"; import { ItemL5r5e } from "./item.js";
import { ItemSheetL5r5e } from "./items/item-sheet.js"; import { ItemSheetL5r5e } from "./items/item-sheet.js";
@@ -59,12 +61,14 @@ Hooks.once("init", async function () {
CONFIG.Dice.terms["s"] = AbilityDie; CONFIG.Dice.terms["s"] = AbilityDie;
CONFIG.Dice.terms["r"] = RingDie; CONFIG.Dice.terms["r"] = RingDie;
// Add some helper classes in game // Add some classes in game
game.l5r5e = { game.l5r5e = {
HelpersL5r5e, HelpersL5r5e,
RollL5r5e, RollL5r5e,
DicePickerDialog, DicePickerDialog,
RollnKeepDialog,
HelpDialog, HelpDialog,
sockets: new SocketHandlerL5r5e(),
}; };
// Register custom system settings // Register custom system settings
@@ -223,6 +227,7 @@ Hooks.on("renderChatMessage", (message, html, data) => {
// Add a extra CSS class to roll // Add a extra CSS class to roll
if (message.isRoll) { if (message.isRoll) {
html.addClass("roll"); html.addClass("roll");
html.on("click", ".chat-dice-rnk", RollnKeepDialog.onChatAction.bind(this));
} }
}); });

View File

@@ -0,0 +1,71 @@
/**
* L5R Socket Handler
*/
export class SocketHandlerL5r5e {
/**
* Namespace in FVTT
*/
static SOCKET_NAME = "system.l5r5e";
constructor() {
this.registerSocketListeners();
}
/**
* registers all the socket listeners
*/
registerSocketListeners() {
game.socket.on(SocketHandlerL5r5e.SOCKET_NAME, (data) => {
switch (data.type) {
case "deleteChatMessage":
this._onDeleteChatMessage(data);
break;
case "refreshAppId":
this._onRefreshAppId(data);
break;
default:
console.warn(new Error("This socket event is not supported"), data);
break;
}
});
}
deleteChatMessage(messageId) {
game.socket.emit(SocketHandlerL5r5e.SOCKET_NAME, {
type: "deleteChatMessage",
messageId,
userId: game.userId,
});
}
_onDeleteChatMessage(data) {
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)) {
message.delete();
}
}
/**
* Refresh a app by it's id, not windowsId (ex "l5r5e-twenty-questions-dialog-kZHczAFghMNYFRWe", not "65")
* usage : game.l5r5e.sockets.refreshAppId(appId);
* @param appId
*/
refreshAppId(appId) {
game.socket.emit(SocketHandlerL5r5e.SOCKET_NAME, {
type: "refreshAppId",
appId,
});
}
_onRefreshAppId(data) {
const app = Object.values(ui.windows).find((e) => e.id === data.appId);
if (!app) {
return;
}
if (typeof app.refresh !== "function") {
return;
}
app.refresh();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -143,3 +143,44 @@
} }
} }
} }
.roll-n-keep-dialog {
img {
border: 0;
}
.profil {
border-bottom: 1px solid #782e22;
}
.dropbox {
min-height: 100px;
&.discards {
border: 1px solid gray;
}
&.rerolls {
border: 1px solid orangered;
}
&.keeps {
border: 1px solid green;
}
}
.dice {
height: 40px;
width: 40px;
}
.discard {
filter: opacity(0.5);
border: 3px solid gray;
}
.reroll {
filter: opacity(0.5);
border: 3px solid orangered;
}
.keep {
border: 3px solid green;
}
}

View File

@@ -11,6 +11,18 @@
padding: 0.25rem; padding: 0.25rem;
border-bottom: 0 none; border-bottom: 0 none;
} }
.errors {
position: sticky;
top: 0px;
left: 0px;
z-index: 999;
display: block;
width: 100%;
background-color: $l5r5e-red;
border: 1px solid $dark-red;
border-radius: 1rem;
text-align: center;
}
h3 { h3 {
font-size: 1.25rem; font-size: 1.25rem;
color: $l5r5e-label; color: $l5r5e-label;
@@ -128,18 +140,6 @@
.dropbox { .dropbox {
min-height: 75px; min-height: 75px;
} }
.errors {
position: sticky;
top: 0px;
left: 0px;
z-index: 999;
display: block;
width: 100%;
background-color: $l5r5e-red;
border: 1px solid $dark-red;
border-radius: 1rem;
text-align: center;
}
.checklist { .checklist {
margin: 0.25rem 0.25rem 1rem; margin: 0.25rem 0.25rem 1rem;
strong { strong {

View File

@@ -2,6 +2,7 @@
button { button {
font-size: 0.75rem; font-size: 0.75rem;
cursor: url("../assets/cursors/pointer.webp"), pointer;
} }
// sidebar // sidebar

View File

@@ -5,6 +5,7 @@
"version": "0.5.1", "version": "0.5.1",
"minimumCoreVersion": "0.7.9", "minimumCoreVersion": "0.7.9",
"compatibleCoreVersion": "0.7.9", "compatibleCoreVersion": "0.7.9",
"socket": true,
"author": "Team L5R", "author": "Team L5R",
"background": "L5R-Header.webp", "background": "L5R-Header.webp",
"scripts": [], "scripts": [],

View File

@@ -43,7 +43,6 @@
<div class="l5r5e dice-formula">{{formula}}</div> <div class="l5r5e dice-formula">{{formula}}</div>
<div class="l5r5e dice-result"> <div class="l5r5e dice-result">
{{#if l5r5e.dicesTypes.l5r}} {{#if l5r5e.dicesTypes.l5r}}
<div class="l5r5e item-display dices-l5r"> <div class="l5r5e item-display dices-l5r">
{{#each l5r5e.dices}} {{#each l5r5e.dices}}
@@ -54,7 +53,10 @@
{{/if}} {{/if}}
{{/each}} {{/each}}
<!-- TODO add Button for Roll & Keep dialog --> {{!-- Roll & Keep Button --}}
{{#if l5r5e.canRnK}}
<button class="l5r5e chat-dice-rnk">{{localize "l5r5e.chatdices.roll_n_keep"}}</button>
{{/if}}
{{#l5r5e.summary}} {{#l5r5e.summary}}
<ul> <ul>
@@ -77,7 +79,6 @@
{{/if}} {{/if}}
{{#if l5r5e.dicesTypes.std}} {{#if l5r5e.dicesTypes.std}}
<div class="l5r5e dices-std"> <div class="l5r5e dices-std">
{{#each results}} {{#each results}}
{{#each this.rolls}} {{#each this.rolls}}

View File

@@ -0,0 +1,90 @@
<form class="l5r5e roll-n-keep-dialog" autocomplete="off">
{{!-- Profil --}}
<div class="l5r5e profil">
<header class="part-header flexrow chat-profil">
<span class="chat-profil-element">
<img class="profile-img"
src="{{#if l5r5e.actor.img}}{{l5r5e.actor.img}}{{else}}icons/svg/mystery-man.svg{{/if}}"
data-edit="img"
height="40"
width="40"
alt="{{#if l5r5e.actor.name}}{{l5r5e.actor.name}}{{else}}mystery-man{{/if}}"
>
</span>
<span class="chat-profil-element">
<i class="chat-profil-stance {{l5r5e.stance}} i_{{l5r5e.stance}}" title="{{localizeRing l5r5e.stance}}"></i>
</span>
<span class="chat-profil-element-skill">
{{#if l5r5e.skillId}}
{{localizeSkillId l5r5e.skillId}}
{{else}}
{{#if l5r5e.skillCatId}}{{localizeSkill l5r5e.skillCatId 'title'}}{{/if}}
{{/if}}
</span>
<span class="chat-profil-element">
{{#if l5r5e.summary.difficultyHidden}}
{{localize 'l5r5e.chatdices.difficulty_hidden'}}
{{else}}
{{localize 'l5r5e.chatdices.difficulty'}} {{l5r5e.summary.difficulty}}
{{/if}}
{{#if l5r5e.summary.voidPointUsed}}
<br><i class="i_void" title="{{localize 'l5r5e.chatdices.void_point_used'}}"></i>
{{/if}}
</span>
</header>
</div>
{{!-- Discard & ReRoll --}}
<table>
<tr>
<td>
<fieldset class="dropbox discards" data-type="discard">
<legend class="section-header"><i class="fa fa-arrow-down" aria-hidden="true"></i> {{ localize 'l5r5e.roll_n_keep.discard_drop_here' }}</legend>
</fieldset>
</td>
<td>
&nbsp;
</td>
<td>
<fieldset class="dropbox rerolls" data-type="reroll">
<legend class="section-header"><i class="fa fa-arrow-down" aria-hidden="true"></i> {{ localize 'l5r5e.roll_n_keep.reroll_drop_here' }}</legend>
</fieldset>
</td>
</tr>
</table>
{{!-- DiceList history --}}
<table>
{{#each data.dicesList as |item idxStep|}}
<tr>
{{#each item as |dice idxDie|}}
<td>
<div class="dice {{this.choice}} {{#ifCond (lookup ../../draggableList idxDie) '==' idxStep }}draggable{{/ifCond}}" data-step="{{idxStep}}" data-die="{{idxDie}}">
{{#if this.img}}
<img src="{{this.img}}" alt="{{idxStep}}_{{idxDie}}" />
{{/if}}
</div>
</td>
{{/each}}
</tr>
{{/each}}
</table>
{{!-- Keep --}}
<table>
<tr>
<td>
<fieldset class="dropbox keeps" data-type="keep">
<legend class="section-header"><i class="fa fa-arrow-down" aria-hidden="true"></i> {{ localize 'l5r5e.roll_n_keep.keep_drop_here' }}</legend>
</fieldset>
</td>
</tr>
</table>
<button id="finalize" name="finalize" type="button">{{ localize 'l5r5e.roll_n_keep.bt_validate' }} <i class='fas fa-arrow-circle-right'></i></button>
</form>