Added "Gm Monitor" & reset void point to gm toolbox
This commit is contained in:
315
system/scripts/gm/gm-monitor.js
Normal file
315
system/scripts/gm/gm-monitor.js
Normal file
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* L5R GM Monitor Windows
|
||||
* @extends {FormApplication}
|
||||
*/
|
||||
export class GmMonitor extends FormApplication {
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
object = {
|
||||
actors: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign the default options
|
||||
* @override
|
||||
*/
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "l5r5e-gm-monitor",
|
||||
classes: ["l5r5e", "gm-monitor"],
|
||||
template: CONFIG.l5r5e.paths.templates + "gm/gm-monitor.html",
|
||||
title: game.i18n.localize("l5r5e.gm_monitor.title"),
|
||||
width: 640,
|
||||
height: 300,
|
||||
resizable: true,
|
||||
closeOnSubmit: false,
|
||||
submitOnClose: false,
|
||||
submitOnChange: true,
|
||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Refresh button on top of sheet
|
||||
* @override
|
||||
*/
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
||||
// Send To Chat
|
||||
buttons.unshift({
|
||||
label: game.i18n.localize("l5r5e.global.refresh"),
|
||||
class: "refresh",
|
||||
icon: "fas fa-sync-alt",
|
||||
onclick: async () => this.refresh(),
|
||||
});
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {ApplicationOptions} options
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data (used from socket)
|
||||
*/
|
||||
async refresh() {
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
this._initialize();
|
||||
this.render(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the values
|
||||
* @private
|
||||
*/
|
||||
_initialize() {
|
||||
let actors;
|
||||
const ids = game.settings.get("l5r5e", "gm-monitor-actors");
|
||||
|
||||
if (ids.length > 0) {
|
||||
// get actors with stored ids
|
||||
actors = game.actors.filter((e) => ids.includes(e.id));
|
||||
} else {
|
||||
// If empty add pc with owner
|
||||
actors = game.actors.filter((actor) => actor.data.type === "character" && actor.hasPlayerOwner);
|
||||
this._saveActorsIds();
|
||||
}
|
||||
|
||||
// Sort by name asc
|
||||
actors.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
this.object = {
|
||||
actors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent non GM to render this windows
|
||||
* @override
|
||||
*/
|
||||
render(force = false, options = {}) {
|
||||
if (!game.user.isGM) {
|
||||
return false;
|
||||
}
|
||||
// this.position.width = "auto";
|
||||
// this.position.height = "auto";
|
||||
return super.render(force, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and return the data object used to render the HTML template for this form application.
|
||||
* @param options
|
||||
* @return {Object}
|
||||
* @override
|
||||
*/
|
||||
getData(options = null) {
|
||||
return {
|
||||
...super.getData(options),
|
||||
data: this.object,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to html elements
|
||||
* @param {jQuery} html HTML content of the sheet.
|
||||
* @override
|
||||
*/
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open sheet
|
||||
html.find(`.actor-sheet-control`).on("click", this._openActorSheet.bind(this));
|
||||
|
||||
// Delete
|
||||
html.find(`.actor-remove-control`).on("click", this._removeActor.bind(this));
|
||||
|
||||
// Tooltips
|
||||
html.find(".actor-infos-control")
|
||||
.on("mouseenter", async (event) => {
|
||||
$(document.body).find("#l5r5e-tooltip-ct").remove();
|
||||
|
||||
const id = $(event.currentTarget).data("actor-id");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actor = this.object.actors.find((e) => e.id === id);
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
const tpl = await this._getTooltipForActor(actor);
|
||||
|
||||
$(document.body).append(
|
||||
`<div id="l5r5e-tooltip-ct" class="l5r5e-tooltip l5r5e-tooltip-ct">${tpl}</div>`
|
||||
);
|
||||
})
|
||||
.on("mousemove", (event) => {
|
||||
const popup = $(document.body).find("#l5r5e-tooltip-ct");
|
||||
if (popup) {
|
||||
popup.css(game.l5r5e.HelpersL5r5e.popupPosition(event, popup));
|
||||
}
|
||||
})
|
||||
.on("mouseleave", () => {
|
||||
$(document.body).find("#l5r5e-tooltip-ct").remove();
|
||||
}); // tooltips
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.render(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dropped data on the Actor sheet
|
||||
* @param {DragEvent} event
|
||||
*/
|
||||
async _onDrop(event) {
|
||||
// *** Everything below here is only needed if the sheet is editable ***
|
||||
if (!this.isEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const json = event.dataTransfer.getData("text/plain");
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(json);
|
||||
if (!data || data.type !== "Actor" || !data.id || !!this.object.actors.find((e) => e.id === data.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actor = game.actors.filter((e) => e.id === data.id);
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.object.actors.push(actor[0]);
|
||||
|
||||
await this._saveActorsIds();
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the actors ids in settings
|
||||
* @return {Promise<*>}
|
||||
* @private
|
||||
*/
|
||||
async _saveActorsIds() {
|
||||
return game.settings.set(
|
||||
"l5r5e",
|
||||
"gm-monitor-actors",
|
||||
this.object.actors.map((e) => e.id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Sheet for this actor
|
||||
* @param {Event} event
|
||||
* @return {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
async _openActorSheet(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const id = $(event.currentTarget).data("actor-id");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.object.actors.find((e) => e.id === id)?.sheet?.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the link to a property for the current item
|
||||
* @param {Event} event
|
||||
* @return {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
async _removeActor(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const id = $(event.currentTarget).data("actor-id");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.object.actors = this.object.actors.filter((e) => e.id !== id);
|
||||
await this._saveActorsIds();
|
||||
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tooltips informations for this actor
|
||||
* @param {BaseSheetL5r5e} actor
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
async _getTooltipForActor(actor) {
|
||||
const data = actor.data.data;
|
||||
|
||||
// Peculiarities
|
||||
const pec = actor.items.filter((e) => e.type === "peculiarity");
|
||||
const adv = pec
|
||||
.filter((e) => ["distinction", "passion"].includes(e.data.data.peculiarity_type))
|
||||
.map((e) => e.name)
|
||||
.join(", ");
|
||||
const dis = pec
|
||||
.filter((e) => ["adversity", "anxiety"].includes(e.data.data.peculiarity_type))
|
||||
.map((e) => e.name)
|
||||
.join(", ");
|
||||
|
||||
// Equipped Armors & Weapons
|
||||
const arm = actor.items
|
||||
.filter((e) => e.type === "armor" && e.data.data.equipped)
|
||||
.map(
|
||||
(e) =>
|
||||
e.name +
|
||||
`(<i class="fas fa-tint">${e.data.data.armor.physical}</i> / <i class="fas fa-bolt">${e.data.data.armor.supernatural}</i>)`
|
||||
)
|
||||
.join(", ");
|
||||
const wea = actor.items
|
||||
.filter((e) => e.type === "weapon" && e.data.data.equipped)
|
||||
.map(
|
||||
(e) =>
|
||||
e.name +
|
||||
`(<i class="fas fa-arrows-alt-h"> ${e.data.data.range || 0}</i> / <i class="fas fa-tint"> ${
|
||||
e.data.data.damage
|
||||
}</i> / <i class="fas fa-skull"> ${e.data.data.deadliness}</i>)`
|
||||
)
|
||||
.join(", ");
|
||||
|
||||
// *** Template ***
|
||||
return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/gm-monitor-tooltip.html`, {
|
||||
actorData: data,
|
||||
advantages: adv,
|
||||
disadvantages: dis,
|
||||
armors: arm,
|
||||
weapons: wea,
|
||||
});
|
||||
}
|
||||
}
|
||||
245
system/scripts/gm/gm-toolbox.js
Normal file
245
system/scripts/gm/gm-toolbox.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* L5R GM Toolbox dialog
|
||||
* @extends {FormApplication}
|
||||
*/
|
||||
export class GmToolbox extends FormApplication {
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
object = {};
|
||||
|
||||
/**
|
||||
* Assign the default options
|
||||
* @override
|
||||
*/
|
||||
static get defaultOptions() {
|
||||
const x = $(window).width();
|
||||
const y = $(window).height();
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "l5r5e-gm-toolbox",
|
||||
classes: ["l5r5e", "gm-toolbox"],
|
||||
template: CONFIG.l5r5e.paths.templates + "gm/gm-toolbox.html",
|
||||
title: game.i18n.localize("l5r5e.gm_toolbox.title"),
|
||||
left: x - 605,
|
||||
top: y - 98,
|
||||
closeOnSubmit: false,
|
||||
submitOnClose: false,
|
||||
submitOnChange: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {ApplicationOptions} options
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data (used from socket)
|
||||
*/
|
||||
async refresh() {
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
this._initialize();
|
||||
this.render(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the values
|
||||
* @private
|
||||
*/
|
||||
_initialize() {
|
||||
this.object = {
|
||||
difficulty: game.settings.get("l5r5e", "initiative-difficulty-value"),
|
||||
difficultyHidden: game.settings.get("l5r5e", "initiative-difficulty-hidden"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not close this dialog
|
||||
* @override
|
||||
*/
|
||||
async close(options = {}) {
|
||||
// TODO better implementation needed : see KeyboardManager._onEscape(event, up, modifiers)
|
||||
// This windows is always open, so esc key is stuck at step 2 : Object.keys(ui.windows).length > 0
|
||||
// Case 3 (GM) - release controlled objects
|
||||
if (canvas?.ready && game.user.isGM && Object.keys(canvas.activeLayer._controlled).length) {
|
||||
canvas.activeLayer.releaseAll();
|
||||
} else {
|
||||
// Case 4 - toggle the main menu
|
||||
ui.menu.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent non GM to render this windows
|
||||
* @override
|
||||
*/
|
||||
render(force = false, options = {}) {
|
||||
if (!game.user.isGM) {
|
||||
return false;
|
||||
}
|
||||
this.position.width = "auto";
|
||||
this.position.height = "auto";
|
||||
return super.render(force, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the close button
|
||||
* @override
|
||||
*/
|
||||
_getHeaderButtons() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and return the data object used to render the HTML template for this form application.
|
||||
* @param options
|
||||
* @return {Object}
|
||||
* @override
|
||||
*/
|
||||
getData(options = null) {
|
||||
return {
|
||||
...super.getData(options),
|
||||
data: this.object,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to html elements
|
||||
* @param {jQuery} html HTML content of the sheet.
|
||||
* @override
|
||||
*/
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Modify difficulty hidden
|
||||
html.find(`.difficulty_hidden`).on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.object.difficultyHidden = !this.object.difficultyHidden;
|
||||
game.settings
|
||||
.set("l5r5e", "initiative-difficulty-hidden", this.object.difficultyHidden)
|
||||
.then(() => this.submit());
|
||||
});
|
||||
|
||||
// Modify difficulty (TN)
|
||||
html.find(`.difficulty`).on("mousedown", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
switch (event.which) {
|
||||
case 1:
|
||||
// left clic - add 1
|
||||
this.object.difficulty = Math.min(9, this.object.difficulty + 1);
|
||||
break;
|
||||
case 2:
|
||||
// middle clic - reset to 2
|
||||
this.object.difficulty = 2;
|
||||
break;
|
||||
case 3:
|
||||
// right clic - minus 1
|
||||
this.object.difficulty = Math.max(0, this.object.difficulty - 1);
|
||||
break;
|
||||
}
|
||||
game.settings.set("l5r5e", "initiative-difficulty-value", this.object.difficulty).then(() => this.submit());
|
||||
});
|
||||
|
||||
// Scene End, Sleep, void pt
|
||||
html.find(`.gm_actor_updates`).on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._updatesActors($(event.currentTarget).data("type"));
|
||||
});
|
||||
|
||||
// GM Monitor
|
||||
html.find(`.gm_monitor`).on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const app = Object.values(ui.windows).find((e) => e.id === "l5r5e-gm-monitor");
|
||||
if (app) {
|
||||
app.close();
|
||||
} else {
|
||||
new game.l5r5e.GmMonitor().render(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.render(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all actors
|
||||
* @param {string} type
|
||||
* @private
|
||||
*/
|
||||
_updatesActors(type) {
|
||||
if (!game.user.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
game.actors.contents.forEach((actor) => {
|
||||
switch (type) {
|
||||
case "sleep":
|
||||
// Remove 'water x2' fatigue points
|
||||
actor.data.data.fatigue.value = Math.max(
|
||||
0,
|
||||
actor.data.data.fatigue.value - Math.ceil(actor.data.data.rings.water * 2)
|
||||
);
|
||||
break;
|
||||
|
||||
case "scene_end":
|
||||
// If more than half the value => roundup half conflit & fatigue
|
||||
actor.data.data.fatigue.value = Math.min(
|
||||
actor.data.data.fatigue.value,
|
||||
Math.ceil(actor.data.data.fatigue.max / 2)
|
||||
);
|
||||
actor.data.data.strife.value = Math.min(
|
||||
actor.data.data.strife.value,
|
||||
Math.ceil(actor.data.data.strife.max / 2)
|
||||
);
|
||||
break;
|
||||
|
||||
case "reset_void":
|
||||
// only pc
|
||||
if (actor.data.type !== "character" || !actor.hasPlayerOwner) {
|
||||
return;
|
||||
}
|
||||
actor.data.data.void_points.value = Math.ceil(actor.data.data.void_points.max / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
actor.update({
|
||||
data: {
|
||||
fatigue: {
|
||||
value: actor.data.data.fatigue.value,
|
||||
},
|
||||
strife: {
|
||||
value: actor.data.data.strife.value,
|
||||
},
|
||||
void_points: {
|
||||
value: actor.data.data.void_points.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
ui.notifications.info(game.i18n.localize(`l5r5e.gm_toolbox.${type}_info`));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user