Seamless update for 20Q (deleted refresh button)
Fix for error css Some start for roll n keep
This commit is contained in:
@@ -48,24 +48,10 @@ export class TwentyQuestionsDialog extends FormApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a refresh button on top of sheet
|
||||
* Allow a GM or player to see the change made by another player without closing the dialog
|
||||
* @override
|
||||
* Define a unique and dynamic element ID for the rendered ActorSheet application
|
||||
*/
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
||||
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;
|
||||
get id() {
|
||||
return `l5r5e-twenty-questions-dialog-${this.actor._id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,6 +62,18 @@ export class TwentyQuestionsDialog extends FormApplication {
|
||||
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
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
static getResultLabel(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
|
||||
* Always zero for L5R dices to not count in total for regular dices
|
||||
|
||||
330
system/scripts/dice/roll-n-keep-dialog.js
Normal file
330
system/scripts/dice/roll-n-keep-dialog.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { L5rBaseDie } from "./dietype/l5r-base-die.js";
|
||||
import { ActorL5r5e } from "../actor.js";
|
||||
|
||||
/**
|
||||
* Roll for L5R5e
|
||||
@@ -20,14 +21,15 @@ export class RollL5r5e extends Roll {
|
||||
l5r: false,
|
||||
},
|
||||
summary: {
|
||||
difficulty: 0,
|
||||
difficulty: 2,
|
||||
difficultyHidden: false,
|
||||
voidPointUsed: false,
|
||||
success: 0,
|
||||
explosive: 0,
|
||||
opportunity: 0,
|
||||
strife: 0,
|
||||
voidPointUsed: false,
|
||||
},
|
||||
history: null,
|
||||
};
|
||||
|
||||
// Parse flavor for stance and skillId
|
||||
@@ -198,9 +200,15 @@ export class RollL5r5e extends 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
|
||||
const chatData = {
|
||||
// borderColor: game.user.color, // don't work :/
|
||||
formula: isPrivate ? "???" : this._formula,
|
||||
flavor: isPrivate ? null : chatOptions.flavor,
|
||||
user: chatOptions.user,
|
||||
@@ -212,6 +220,7 @@ export class RollL5r5e extends Roll {
|
||||
? {}
|
||||
: {
|
||||
...this.l5r5e,
|
||||
canRnK: canRnK,
|
||||
dices: this.dice.map((d) => {
|
||||
return {
|
||||
diceTypeL5r: d instanceof L5rBaseDie,
|
||||
@@ -277,8 +286,16 @@ export class RollL5r5e extends Roll {
|
||||
static fromData(data) {
|
||||
const roll = super.fromData(data);
|
||||
|
||||
roll.data = data.data;
|
||||
roll.l5r5e = data.l5r5e;
|
||||
roll.data = duplicate(data.data);
|
||||
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;
|
||||
}
|
||||
@@ -290,8 +307,15 @@ export class RollL5r5e extends Roll {
|
||||
toJSON() {
|
||||
const json = super.toJSON();
|
||||
|
||||
json.data = this.data;
|
||||
json.l5r5e = this.l5r5e;
|
||||
json.data = duplicate(this.data);
|
||||
json.l5r5e = duplicate(this.l5r5e);
|
||||
|
||||
// lightweight the Actor
|
||||
if (json.l5r5e.actor) {
|
||||
json.l5r5e.actor = {
|
||||
id: json.l5r5e.actor._id,
|
||||
};
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Import Commons Modules
|
||||
import { L5R5E } from "./config.js";
|
||||
import { HelpersL5r5e } from "./helpers.js";
|
||||
import { SocketHandlerL5r5e } from "./socket-handler.js";
|
||||
import { RegisterSettings } from "./settings.js";
|
||||
import { PreloadTemplates } from "./preloadTemplates.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 { RollL5r5e } from "./dice/roll.js";
|
||||
import { DicePickerDialog } from "./dice/dice-picker-dialog.js";
|
||||
import { RollnKeepDialog } from "./dice/roll-n-keep-dialog.js";
|
||||
// Items
|
||||
import { ItemL5r5e } from "./item.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["r"] = RingDie;
|
||||
|
||||
// Add some helper classes in game
|
||||
// Add some classes in game
|
||||
game.l5r5e = {
|
||||
HelpersL5r5e,
|
||||
RollL5r5e,
|
||||
DicePickerDialog,
|
||||
RollnKeepDialog,
|
||||
HelpDialog,
|
||||
sockets: new SocketHandlerL5r5e(),
|
||||
};
|
||||
|
||||
// Register custom system settings
|
||||
@@ -223,6 +227,7 @@ Hooks.on("renderChatMessage", (message, html, data) => {
|
||||
// Add a extra CSS class to roll
|
||||
if (message.isRoll) {
|
||||
html.addClass("roll");
|
||||
html.on("click", ".chat-dice-rnk", RollnKeepDialog.onChatAction.bind(this));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
71
system/scripts/socket-handler.js
Normal file
71
system/scripts/socket-handler.js
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user