/** * L5R GM Monitor Windows * @extends {FormApplication} */ export class GmMonitor extends FormApplication { /** * Settings */ object = { view: "characters", // characters|armies 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: 800, height: 300, resizable: true, closeOnSubmit: false, submitOnClose: false, submitOnChange: false, dragDrop: [{ dragSelector: null, dropSelector: null }], }); } /** * Add the Switch View button on top of sheet * @override */ _getHeaderButtons() { let buttons = super._getHeaderButtons(); // Switch view Characters/Armies buttons.unshift({ label: game.i18n.localize("l5r5e.gm.monitor.switch_view"), class: "switch-view", icon: "fas fa-users", onclick: () => game.l5r5e.HelpersL5r5e.debounce( "SwitchView-" + this.object.id, () => { this.object.view = this.object.view === "armies" ? "characters" : "armies"; this.render(false); }, 1000, true )(), }); 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.type === "character" && actor.hasPlayerOwner); this._saveActorsIds(); } // Sort by name asc actors.sort((a, b) => { return a.name.localeCompare(b.name); }); this.object.actors = actors; } /** * Prevent non GM to render this windows * @override */ render(force = false, options = {}) { if (!game.user.isGM) { return false; } 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 */ async getData(options = null) { return { ...(await super.getData(options)), data: { ...this.object, actors: this.object.actors.filter((e) => this.object.view === "armies" ? e.type === "army" : e.type !== "army" ), }, }; } /** * Listen to html elements * @param {jQuery} html HTML content of the sheet. * @override */ activateListeners(html) { super.activateListeners(html); if (!game.user.isGM) { return; } // Commons game.l5r5e.HelpersL5r5e.commonListeners(html); // Delete html.find(`.actor-remove-control`).on("click", this._removeActor.bind(this)); // Add/Subtract html.find(`.actor-modify-control`).on("mousedown", this._modifyActor.bind(this)); // Tooltips game.l5r5e.HelpersL5r5e.popupManager(html.find(".actor-infos-control"), async (event) => { const type = $(event.currentTarget).data("type"); if (!type) { return; } if (type === "text") { return $(event.currentTarget).data("text"); } const id = $(event.currentTarget).data("actor-id"); if (!id) { return; } const actor = this.object.actors.find((e) => e.id === id); if (!actor) { return; } switch (type) { case "armors": return this._getTooltipArmors(actor); case "weapons": return this._getTooltipWeapons(actor); case "global": return actor.type === "army" ? this._getTooltipArmiesGlobal(actor) : this._getTooltipGlobal(actor); } }); } /** * 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.uuid || !!this.object.actors.find((a) => a.uuid === data.uuid)) { return; } const actor = game.actors.find((a) => a.uuid === data.uuid); if (!actor) { return; } this.object.actors.push(actor); return this._saveActorsIds(); } /** * 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) ); } /** * Remove the link to a property for the current item * @param {Event} event * @return {Promise} * @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); return this._saveActorsIds(); } /** * Add or subtract fatigue/strife/void/casualties/panic * @param event * @return {Promise} * @private */ async _modifyActor(event) { event.preventDefault(); event.stopPropagation(); const type = $(event.currentTarget).data("type"); if (!type) { console.warn("L5R5E | type not set", type); return; } const id = $(event.currentTarget).data("actor-id"); if (!id) { console.warn("L5R5E | actor id not set", type); return; } const actor = game.actors.get(id); if (!actor) { console.warn("L5R5E | Actor not found", type); return; } // Mouse bt : middle = 0, left +1, right -1 const add = event.which === 2 ? -999 : event.which === 1 ? 1 : -1; // Stance let stanceIdx = CONFIG.l5r5e.stances.findIndex((s) => s === actor.system.stance) + (event.which === 1 ? 1 : -1); if (stanceIdx < 0) { stanceIdx = CONFIG.l5r5e.stances.length - 1; } else if (stanceIdx > CONFIG.l5r5e.stances.length - 1) { stanceIdx = 0; } const updateData = {}; switch (type) { // *** Characters *** case "fatigue": updateData["system.fatigue.value"] = Math.max(0, actor.system.fatigue.value + add); break; case "strife": updateData["system.strife.value"] = Math.max(0, actor.system.strife.value + add); break; case "void_points": updateData["system.void_points.value"] = Math.min( actor.system.void_points.max, Math.max(0, actor.system.void_points.value + add) ); break; case "stance": updateData["system.stance"] = CONFIG.l5r5e.stances[stanceIdx]; break; case "prepared": updateData["system.prepared"] = !actor.system.prepared; break; // *** Armies *** case "casualties": updateData["system.battle_readiness.casualties_strength.value"] = Math.max( 0, actor.system.battle_readiness.casualties_strength.value + add ); break; case "panic": updateData["system.battle_readiness.panic_discipline.value"] = Math.max( 0, actor.system.battle_readiness.panic_discipline.value + add ); break; default: console.warn("L5R5E | Unsupported type", type); break; } if (!foundry.utils.isEmpty(updateData)) { await actor.update(updateData); this.render(false); } } /** * Get tooltips information for this character * @param {ActorL5r5e} actor * @return {string} * @private */ async _getTooltipGlobal(actor) { const actorData = (await actor.sheet?.getData()?.data) || actor; // Peculiarities const pec = actor.items.filter((e) => e.type === "peculiarity"); const adv = pec .filter((e) => ["distinction", "passion"].includes(e.system.peculiarity_type)) .map((e) => e.name) .join(", "); const dis = pec .filter((e) => ["adversity", "anxiety"].includes(e.system.peculiarity_type)) .map((e) => e.name) .join(", "); // *** Template *** return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor-tooltips/global.html`, { actorData: actorData, advantages: adv, disadvantages: dis, suffix: actorData.system.template === "pow" ? "_pow" : "", actor_type: actor.type, }); } /** * Get tooltips information for this army * @param {ActorL5r5e} actor * @return {string} * @private */ async _getTooltipArmiesGlobal(actor) { const actorData = (await actor.sheet?.getData()?.data) || actor; // *** Template *** return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor-tooltips/global-armies.html`, { actorData: actorData, }); } /** * Get weapons information for this actor * @param {ActorL5r5e} actor * @return {string} * @private */ async _getTooltipWeapons(actor) { const display = (e) => { return ( e.name + ` ( ${e.data.data.range}` + ` / ${e.data.data.damage}` + ` / ${e.data.data.deadliness})` ); }; // Readied Weapons const readied = actor.items .filter((e) => e.type === "weapon" && e.system.equipped && !!e.system.readied) .map((e) => display(e)); // Equipped Weapons const sheathed = actor.items .filter((e) => e.type === "weapon" && e.system.equipped && !e.system.readied) .map((e) => display(e)); // *** Template *** return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor-tooltips/weapons.html`, { readied, sheathed, }); } /** * Get armors information for this actor * @param {ActorL5r5e} actor * @return {string} * @private */ async _getTooltipArmors(actor) { // Equipped Armors const armors = actor.items .filter((e) => e.type === "armor" && e.system.equipped) .map( (e) => e.name + ` (${e.system.armor.physical}` + ` / ${e.system.armor.supernatural})` ); // *** Template *** return renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor-tooltips/armors.html`, { armors, }); } }