COrrections, WIP

This commit is contained in:
2026-04-24 22:04:09 +02:00
parent d75b6cb945
commit e200b5f7b0
126 changed files with 768 additions and 5805 deletions

View File

@@ -56,22 +56,6 @@ export class ActorL5r5e extends Actor {
);
break;
case "army":
foundry.utils.mergeObject(
docData.prototypeToken,
{
actorLink: true,
disposition: 0, // neutral
bar1: {
attribute: "battle_readiness.casualties_strength",
},
bar2: {
attribute: "battle_readiness.panic_discipline",
},
},
{ overwrite: false }
);
break;
}
await super.create(docData, options);
}

View File

@@ -1,330 +0,0 @@
import { BaseSheetL5r5e } from "./base-sheet.js";
/**
* Sheet for Army "actor"
*/
export class ArmySheetL5r5e extends BaseSheetL5r5e {
/**
* Commons options
*/
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "actor", "army"],
template: CONFIG.l5r5e.paths.templates + "actors/army-sheet.html",
width: 600,
height: 800,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "army" }],
});
}
/** @override */
constructor(options = {}) {
super(options);
this._initialize();
}
/**
* Initialize once
* @private
*/
_initialize() {
const data = this.object.system;
// update linked actor datas
if (data.commander_actor_id) {
const commander = game.actors.get(data.commander_actor_id);
if (commander) {
this._updateLinkedActorData("commander", commander, true);
} else {
this._removeLinkedActorData("commander");
}
}
if (data.warlord_actor_id) {
const warlord = game.actors.get(data.warlord_actor_id);
if (warlord) {
this._updateLinkedActorData("warlord", warlord, true);
} else {
this._removeLinkedActorData("warlord");
}
}
}
/**
* Create drag-and-drop workflow handlers for this Application
* @return An array of DragDrop handlers
*/
_createDragDropHandlers() {
return [
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".warlord",
callbacks: { drop: this._onDropActors.bind(this, "warlord") },
}),
new foundry.applications.ux.DragDrop.implementation({
dropSelector: ".commander",
callbacks: { drop: this._onDropActors.bind(this, "commander") },
}),
new foundry.applications.ux.DragDrop.implementation({
dropSelector: null,
callbacks: { drop: this._onDrop.bind(this) },
}),
];
}
/**
* Activate a named TinyMCE text editor
* @param {string} name The named data field which the editor modifies.
* @param {object} options TinyMCE initialization options passed to TextEditor.create
* @param {string} initialContent Initial text content for the editor area.
* @override
*/
activateEditor(name, options = {}, initialContent = "") {
// Symbols Compatibility with old compendium modules (PRE l5r v1.7.2)
if (
["system.army_abilities", "system.supplies_logistics", "system.past_battles"].includes(name) &&
initialContent
) {
initialContent = game.l5r5e.HelpersL5r5e.convertSymbols(initialContent, false);
}
return super.activateEditor(name, options, initialContent);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
*/
activateListeners(html) {
super.activateListeners(html);
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Casualties/Panic +/-
html.find(".addsub-control").on("click", this._modifyCasualtiesOrPanic.bind(this));
if (this.actor.system.soft_locked) {
return;
}
// Delete the linked Actor (warlord/commander)
html.find(".actor-remove-control").on("click", this._removeLinkedActor.bind(this));
}
/** @inheritdoc */
async getData(options = {}) {
const sheetData = await super.getData(options);
// Split Items by types
sheetData.data.splitItemsList = this._splitItems(sheetData);
// Editors enrichment
for (const name of ["army_abilities", "supplies_logistics", "past_battles"]) {
sheetData.data.enrichedHtml[name] = await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system[name], {
async: true,
});
}
return sheetData;
}
/**
* Split Items by types for better readability
* @private
*/
_splitItems(sheetData) {
const out = {
army_cohort: [],
army_fortification: [],
};
sheetData.items.forEach((item) => {
if (["army_cohort", "army_fortification"].includes(item.type)) {
out[item.type].push(item);
}
});
return out;
}
/**
* Handle dropped Item data on the Actor sheet (cohort, fortification)
* @param {DragEvent} event
*/
async _onDrop(event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable || this.actor.system.soft_locked) {
return;
}
const item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || item.documentName !== "Item" || !["army_cohort", "army_fortification"].includes(item.type)) {
// actor dual trigger...
if (item?.documentName !== "Actor") {
console.warn("L5R5E | AS | Characters items are not allowed", item?.type, item);
}
return;
}
// Can add the item - Foundry override cause props
const allowed = Hooks.call("dropActorSheetData", this.actor, this, item);
if (allowed === false) {
return;
}
let itemData = item.toObject(true);
// Finally, create the embed
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
/**
* Handle dropped Actor data on the Actor sheet
* @param {string} type warlord|commander|item
* @param {DragEvent} event
*/
async _onDropActors(type, event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable || this.actor.system.soft_locked) {
return;
}
const droppedActor = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
return this._updateLinkedActorData(type, droppedActor, false);
}
/**
* Remove the linked actor (commander/warlord)
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _removeLinkedActor(event) {
event.preventDefault();
event.stopPropagation();
const id = $(event.currentTarget).data("actor-id");
const type = $(event.currentTarget).data("type");
if (!id || !type) {
return;
}
return this._removeLinkedActorData(type);
}
/**
* Update actor datas for this army sheet
* @param {string} type commander|warlord
* @param {ActorL5r5e} actor actor object
* @param {boolean} isInit If it's initialization process
* @return {Promise<abstract.Document>}
* @private
*/
async _updateLinkedActorData(type, actor, isInit = false) {
if (!actor || actor.documentName !== "Actor" || !actor.isCharacterType) {
console.warn("L5R5E | AS | Wrong actor type", actor?.type, actor);
return;
}
const actorPath = `${CONFIG.l5r5e.paths.assets}icons/actors/`;
const actorData = {};
switch (type) {
case "commander":
actorData["system.commander"] = actor.name;
actorData["system.commander_actor_id"] = actor._id;
actorData["system.commander_standing.honor"] = actor.system.social.honor;
actorData["system.commander_standing.glory"] = actor.system.social.glory;
actorData["system.commander_standing.status"] = actor.system.social.status;
// Replace the image by commander's image
if (
!isInit &&
this.actor.img !== actor.img &&
![`${actorPath}character.svg`, `${actorPath}npc.svg`].includes(actor.img)
) {
actorData["img"] = actor.img;
actorData["prototypeToken.texture.src"] = actor.prototypeToken.texture.src;
}
break;
case "warlord":
actorData["system.warlord"] = actor.name;
actorData["system.warlord_actor_id"] = actor._id;
break;
default:
console.warn("L5R5E | AS | Unknown type", type);
return;
}
return this.actor.update(actorData);
}
/**
* Clean ActorId for army sheet
* @param {string} type commander|warlord
* @return {Promise<abstract.Document>}
* @private
*/
async _removeLinkedActorData(type) {
const actorData = {};
switch (type) {
case "commander":
actorData.commander_actor_id = null;
break;
case "warlord":
actorData.warlord_actor_id = null;
break;
default:
console.warn("L5R5E | AS | Unknown type", type);
return;
}
return this.actor.update({ system: actorData });
}
/**
* Add or Subtract Casualties/Panic (+/- buttons)
* @param {Event} event
* @private
*/
async _modifyCasualtiesOrPanic(event) {
event.preventDefault();
event.stopPropagation();
const elmt = $(event.currentTarget);
const type = elmt.data("type");
let mod = elmt.data("value");
if (!mod) {
return;
}
switch (type) {
case "casualties":
await this.actor.update({
system: {
battle_readiness: {
casualties_strength: {
value: Math.max(0, this.actor.system.battle_readiness.casualties_strength.value + mod),
},
},
},
});
break;
case "panic":
await this.actor.update({
system: {
battle_readiness: {
panic_discipline: {
value: Math.max(0, this.actor.system.battle_readiness.panic_discipline.value + mod),
},
},
},
});
break;
default:
console.warn("L5R5E | AS | Unsupported type", type);
break;
}
}
}

View File

@@ -212,9 +212,10 @@ export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
switch (itemData.type) {
case "army_cohort":
case "army_fortification":
console.warn("L5R5E | BCS | Army items are not allowed", item?.type, item);
// Army item types removed — skip silently
return;
case "advancement":
// Specific advancements, remove 1 to selected ring/skill
await this.actor.addBonus(item);

View File

@@ -116,6 +116,9 @@ export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
sheetData.data.enrichedHtml.identity_text2 = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.actor.system.identity_text2 ?? "", { async: true }
);
sheetData.data.enrichedHtml.notes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.actor.system.notes ?? "", { async: true }
);
return sheetData;
}

View File

@@ -4,7 +4,7 @@ export const L5R5E = {
assets: "systems/l5rx-chiaroscuro/assets/",
templates: "systems/l5rx-chiaroscuro/templates/",
},
money: [50, 10],
money: [100, 10],
stances: ["earth", "air", "water", "fire", "void"],
roles: ["artisan", "bushi", "courtier", "monk", "sage", "shinobi", "shugenja"],
xp: {

View File

@@ -228,6 +228,7 @@ export class ChiaroscuroDiceDialog extends FormApplication {
difficulty: difficultyObj,
success,
bonus,
missBy: success ? 0 : difficultyValue - total,
});
return this.close();
@@ -309,7 +310,10 @@ export class ChiaroscuroDiceDialog extends FormApplication {
{
actor: this._actor,
profileImg: this._actor?.img ?? "icons/svg/mystery-man.svg",
ring: this.object.ring,
ring: {
...this.object.ring,
label: game.i18n.localize(`l5r5e.rings.${this.object.ring.id}`),
},
skill: this.object.skill,
difficulty: this.object.difficulty,
useAspectPoint: this.object.useAspectPoint,

View File

@@ -1,671 +0,0 @@
import { L5r5ePopupManager } from '../misc/l5r5e-popup-manager.js';
const HandlebarsApplicationMixin = foundry.applications.api.HandlebarsApplicationMixin;
const ApplicationV2 = foundry.applications.api.ApplicationV2;
export class GmMonitor extends HandlebarsApplicationMixin(ApplicationV2) {
/** @override ApplicationV2 */
static get DEFAULT_OPTIONS() {
return {
id: "l5r5e-gm-monitor",
tag: "div",
window: {
contentClasses: ["l5r5e", "gm-monitor"],
title: "l5r5e.gm.monitor.title",
minimizable: true,
controls: [
{
label: game.i18n.localize("l5r5e.gm.monitor.add_selected_tokens"),
icon: "fas fa-users",
action: "add_selected_tokens",
},
{
label: game.i18n.localize("l5r5e.gm.monitor.switch_view"),
icon: "fas fa-repeat",
action: "change_view_tab"
}
],
resizable: true,
editable: true,
},
position: {
width: "600",
height: "150"
},
actions: {
add_selected_tokens: GmMonitor.#addSelectedTokens,
change_view_tab: GmMonitor.#rotateViewTab,
remove_actor: GmMonitor.#removeActor,
toggle_prepared: GmMonitor.#togglePrepared,
change_stance: {
buttons: [0, 2],
handler: GmMonitor.#changeStance,
},
modify_fatigue: {
buttons: [0, 1, 2],
handler: GmMonitor.#modifyFatigue,
},
modify_strife: {
buttons: [0, 1, 2],
handler: GmMonitor.#modifyStrife,
},
modify_voidPoint: {
buttons: [0, 1, 2],
handler: GmMonitor.#modifyVoidPoint,
},
modify_casualties: {
buttons: [0, 1, 2],
handler: GmMonitor.#modifyCasualties,
},
modify_panic: {
buttons: [0, 1, 2],
handler: GmMonitor.#modifyPanic,
}
},
dragDrop: [{ dragSelector: null, dropSelector: null }],
}
};
/** @override HandlebarsApplicationMixin */
static PARTS = {
hidden_tabs: {
template: "templates/generic/tab-navigation.hbs"
},
character: {
id: "character",
template: "systems/l5rx-chiaroscuro/templates/" + "gm/monitor/character-view.html"
},
army: {
if: "army",
template: "systems/l5rx-chiaroscuro/templates/" + "gm/monitor/army-view.html"
}
};
/**
* @type {Record<string, string>}
* @override ApplicationV2
*/
tabGroups = {
view: "character"
};
/**
* Data that is pushed to html
*/
context = {
actors: []
}
/**
* hooks we act upon, saved since we need to remove them when this window is not open
*/
#hooks = [];
/**
* The DragDrop instance which handles interactivity resulting from DragTransfer events.
* @type {DragDrop}
*/
#dragDrop;
constructor() {
super();
this.#initialize();
}
/** @override ApplicationV2 */
async _preClose(options) {
await super._preClose(options);
options.animate = false;
for (const hook of this.#hooks) {
Hooks.off(hook.hook, hook.fn);
}
}
/** @override ApplicationV2 */
async _onRender(context, options) {
await super._onRender(context, options);
// Todo: Move this to common l5r5e application v2
game.l5r5e.HelpersL5r5e.commonListeners($(this.element));
this.#dragDrop = new foundry.applications.ux.DragDrop.implementation({
dragSelector: null,
dropSelector: null,
callbacks: {
drop: this.#onDrop.bind(this)
}
}).bind(this.element);
// Tooltips
new L5r5ePopupManager(
$(this.element).find(".actor-infos-control"),
async (event) => {
const type = $(event.currentTarget).data("type");
if (!type) return;
if (type === "text") {
return $(event.currentTarget).data("text");
}
const uuid = $(event.currentTarget).data("actor-uuid");
if (!uuid) return;
const actor = this.context.actors.find(actor => actor.uuid === uuid);
if (!actor) return;
switch (type) {
case "armors":
return this.#getTooltipArmors(actor);
case "weapons":
return this.#getTooltipWeapons(actor);
case "global":
return actor.isArmy
? this.#getTooltipArmiesGlobal(actor)
: this.#getTooltipGlobal(actor);
}
}
);
// Apply global interface theme if it is set
if (!this.options.classes.includes("themed")) {
this.element.classList.remove("theme-light", "theme-dark");
const {colorScheme} = game.settings.get("core", "uiConfig");
if (colorScheme.interface) {
this.element.classList.add("themed", `theme-${colorScheme.interface}`);
}
}
}
/** @override ApplicationV2 */
async _prepareContext() {
return {
tabs: this.getTabs(),
}
}
/**
* @param {string} partId The part being rendered
* @param {ApplicationRenderContext} context Shared context provided by _prepareContext
* @returns {Promise<ApplicationRenderContext>} Context data for a specific part
*
* @override HandlebarsApplicationMixin
*/
async _preparePartContext(partId, context) {
switch (partId) {
case "character":
context.characters = this.context.actors.filter((actor) => !actor.isArmy);
break;
case "army":
context.armies = this.context.actors.filter((actor) => actor.isArmy);
break;
}
return context;
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
getTabs() {
const tabs = {
character: { id: "character", group: "view", icon: "fa-solid fa-tag", label: "REGION.SECTIONS.identity" },
army: { id: "army", group: "view", icon: "fa-solid fa-shapes", label: "REGION.SECTIONS.shapes" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id;
v.cssClass = v.active ? "active" : "";
}
return tabs;
}
/**
* Handle dropped data on the Actor sheet
* @param {DragEvent} event The originating DragEvent
*/
async #onDrop(event) {
if (!this.options.window.editable) {
return;
}
const json = event.dataTransfer.getData("text/plain");
if (!json) {
return;
}
const data = JSON.parse(json);
if (!data || data.type !== "Actor" || !data.uuid || !!this.context.actors.find((a) => a.uuid === data.uuid)) {
return;
}
const actor = fromUuidSync(data.uuid);
if (!actor) {
return;
}
// Switch view to current character type
if (actor.isArmy) {
this.changeTab("army", "view");
}
else {
this.changeTab("character", "view");
}
this.context.actors.push(actor);
return this.saveActorsIds();
}
/** required for updating via our socket implementation game.l5r5e.HelpersL5r5e.refreshLocalAndSocket("l5r5e-gm-monitor")*/
async refresh() {
this.render();
}
/**
* Save the actors ids in setting
* @private
*/
async saveActorsIds() {
return game.settings.set(
CONFIG.l5r5e.namespace,
"gm-monitor-actors",
this.context.actors.map((a) => a.uuid)
);
}
#initialize() {
let actors;
const uuidList = game.settings.get(CONFIG.l5r5e.namespace, "gm-monitor-actors");
if (uuidList.length > 0) {
// Get actors from stored uuids
actors = uuidList
.map(uuid => {
const doc = fromUuidSync(uuid);
if (doc instanceof TokenDocument) {
return doc.actor;
}
return doc;
})
.filter(actor => !!actor); // skip null
} else {
// If empty add pc with owner
actors = game.actors.filter((actor) => actor.type === "character" && actor.hasPlayerOwnerActive);
this.saveActorsIds();
}
// Sort by name asc
actors.sort((a, b) => {
return a.name.localeCompare(b.name);
});
this.context.actors = actors;
this.#hooks.push({
hook: "updateActor",
fn: Hooks.on("updateActor", (actor) => this.#onUpdateActor(actor))
});
this.#hooks.push({
hook: "updateSetting",
fn: Hooks.on("updateSetting", (actor) => this.#onUpdateSetting(actor))
});
}
/**
* Switch between the available views in sequence
*/
static #rotateViewTab() {
const tabArray = Object.values(this.getTabs());
const activeTabIndex = tabArray.findIndex((tab) => tab.active);
const nextTabIndex = activeTabIndex + 1 < tabArray.length ? activeTabIndex + 1 : 0;
this.changeTab(tabArray[nextTabIndex].id, tabArray[nextTabIndex].group)
}
/**
* Add selected token on monitor if not already present
*/
static #addSelectedTokens() {
if (canvas.tokens.controlled.length > 0) {
const actors2Add = canvas.tokens.controlled
.map(t => t.actor)
.filter(t => !!t && !this.context.actors.find((a) => a.uuid === t.uuid));
if (actors2Add.length < 1) {
return;
}
this.context.actors = [
...this.context.actors,
...actors2Add
];
this.saveActorsIds();
}
}
/**
* Update baseValue based on the type of event
* @param {Int} baseValue The Base value we can to modify
* @param {Int} whichButton The type of click made
*/
static #newValue(baseValue, whichButton) {
switch (whichButton) {
case 0: //Left click
return Math.max(0, baseValue + 1);
case 1: //Middle click
return 0;
case 2: //Right click
return Math.max(0, baseValue - 1);
}
}
/**
* @param {HTMLElement} target Html target to get actor information from
*/
static async #getActorValidated(target) {
const uuid = $(target).data("actor-uuid");
if (!uuid) {
console.warn("L5R5E | GMM | actor uuid not set", type);
return {isValid: false, actor: null};
}
const actor = await fromUuid(uuid);
if (!actor) {
console.warn("L5R5E | GMM | Actor not found", type);
return {isValid: false, actor: null};
}
return {isValid:true, actor: actor};
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #modifyCasualties(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
const casualties_strength = actor.system.battle_readiness.casualties_strength.value;
return actor.update({
system: {
battle_readiness: {
casualties_strength: {
value: GmMonitor.#newValue(casualties_strength, event.button),
}
},
},
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #modifyPanic(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
const panic_discipline = actor.system.battle_readiness.panic_discipline.value;
return actor.update({
system: {
battle_readiness: {
panic_discipline: {
value: GmMonitor.#newValue(panic_discipline, event.button),
}
},
},
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #togglePrepared(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
return actor.update({
system: {
prepared: !actor.system.prepared
}
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #changeStance(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
let stanceIdx = CONFIG.l5r5e.stances.findIndex((stance) => stance === actor.system.stance) + (event.button === 0 ? 1 : -1);
if (stanceIdx < 0) {
stanceIdx = CONFIG.l5r5e.stances.length - 1;
} else if (stanceIdx > CONFIG.l5r5e.stances.length - 1) {
stanceIdx = 0;
}
return actor.update({
system: {
stance: CONFIG.l5r5e.stances[stanceIdx]
},
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #modifyFatigue(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
const fatigue = actor.system.fatigue.value;
return actor.update({
system: {
fatigue: {
value: GmMonitor.#newValue(fatigue, event.button)
}
}
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #modifyStrife(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
const strife = actor.system.strife.value;
return actor.update({
system: {
strife: {
value: GmMonitor.#newValue(strife, event.button),
},
},
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #modifyVoidPoint(event, target) {
const {isValid, actor} = await GmMonitor.#getActorValidated(target);
if (!isValid) {
return;
}
const void_points = actor.system.void_points.value;
const void_points_max = actor.system.void_points.max;
return actor.update({
system: {
void_points: {
value: Math.min(
void_points_max,
GmMonitor.#newValue(void_points, event.button)
),
},
},
});
}
/**
* @param {PointerEvent} event The originating click event
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
*/
static async #removeActor(event, target) {
const uuid = $(target).data("actor-uuid");
if (!uuid) {
return;
}
this.context.actors = this.context.actors.filter((actor) => actor.uuid !== uuid);
return this.saveActorsIds();
}
/**
* Get armors information for this actor
* @param {ActorL5r5e} actor
* @return {string}
* @private
*/
async #getTooltipArmors(actor) {
// Equipped Armors
const armors = actor.items
.filter((item) => item.type === "armor" && item.system.equipped)
.map(
(item) =>
item.name +
` (<i class="fas fa-tint">${item.system.armor.physical}</i>` +
` / <i class="fas fa-bolt">${item.system.armor.supernatural}</i>)`
);
// *** Template ***
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/armors.html`, {
armors,
});
}
/**
* Get weapons information for this actor
* @param {ActorL5r5e} actor
* @return {string}
* @private
*/
async #getTooltipWeapons(actor) {
const display = (weapon) => {
return (
weapon.name +
` (<i class="fas fa-arrows-alt-h"> ${weapon.system.range}</i>` +
` / <i class="fas fa-tint"> ${weapon.system.damage}</i>` +
` / <i class="fas fa-skull"> ${weapon.system.deadliness}</i>)`
);
};
// Readied Weapons
const equippedWeapons = actor.items.filter((item) => item.type === "weapon" && item.system.equipped);
const readied = equippedWeapons
.filter((weapon) => !!weapon.system.readied)
.map((weapon) => display(weapon));
// Equipped Weapons
const sheathed = equippedWeapons
.filter((weapon) => !weapon.system.readied)
.map((weapon) => display(weapon));
// *** Template ***
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/weapons.html`, {
readied,
sheathed,
});
}
/**
* 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 Peculiarities = actor.items.filter((e) => e.type === "peculiarity");
const advantages = Peculiarities
.filter((item) => ["distinction", "passion"].includes(item.system.peculiarity_type))
.map((item) => item.name)
.join(", ");
const disadvantages = Peculiarities
.filter((item) => ["adversity", "anxiety"].includes(item.system.peculiarity_type))
.map((item) => item.name)
.join(", ");
// *** Template ***
return foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global.html`, {
actorData: actorData,
advantages: advantages,
disadvantages: disadvantages,
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 foundry.applications.handlebars.renderTemplate(`${CONFIG.l5r5e.paths.templates}gm/monitor/tooltips/global-armies.html`, {
actorData: actorData,
});
}
/**
* @param {ActorL5r5e} actor The actor that is being updated
*/
#onUpdateActor(actor) {
if (this.context.actors.includes(actor)) {
this.render(false);
}
}
/**
* @param {Setting} setting The setting that is being updated
*/
#onUpdateSetting(setting) {
switch (setting.key) {
case "l5r5e.gm-monitor-actors":
this.render(false);
break;
case "l5r5e.initiative-prepared-character":
case "l5r5e.initiative-prepared-adversary":
case "l5r5e.initiative-prepared-minion":
this.render(false);
break;
default:
return;
}
}
}

View File

@@ -1,273 +0,0 @@
const HandlebarsApplicationMixin = foundry.applications.api.HandlebarsApplicationMixin;
const ApplicationV2 = foundry.applications.api.ApplicationV2;
export class GmToolbox extends HandlebarsApplicationMixin(ApplicationV2) {
/** @override ApplicationV2 */
static get DEFAULT_OPTIONS() { return {
id: "l5r5e-gm-toolbox",
classes: ["faded-ui"],
window: {
contentClasses: ["l5r5e", "gm-toolbox"],
title: "l5r5e.gm.toolbox.title",
minimizable: false,
},
position: {
width: "auto",
height: "auto"
},
actions: {
open_gm_monitor: GmToolbox.#openGmMonitor,
toggle_hide_difficulty: GmToolbox.#onToggleHideDifficulty,
// Buttons map (0: left, 1: middle, 2: right, 3: extra 1, 4: extra 2)
// Foundry v13 use middle (1) for popup and currently not bind it for custom
// See : https://github.com/foundryvtt/foundryvtt/issues/12531
change_difficulty: {
buttons: [0, 1, 2],
handler: GmToolbox.#onChangeDifficulty
},
reset_void: {
buttons: [0, 1, 2, 3, 4],
handler: GmToolbox.#onResetVoid
},
sleep: {
buttons: [0, 1, 2, 3, 4],
handler: GmToolbox.#onSleep
},
scene_end: {
buttons: [0, 1, 2, 3, 4],
handler: GmToolbox.#onSceneEnd
},
}
}};
/** @override HandlebarsApplicationMixin */
static PARTS = {
main: {
id: "gm-tool-content",
template: "systems/l5rx-chiaroscuro/templates/" + "gm/gm-toolbox.html"
}
};
/**
* hooks we act upon, saved since we need to remove them when this window is not open
*/
#hooks = [];
constructor() {
super();
this.#hooks.push({
hook: "updateSetting",
fn: Hooks.on("updateSetting", (setting) => this.#onUpdateSetting(setting))
});
}
/** @override ApplicationV2*/
async _prepareContext() {
return {
difficulty: game.settings.get(CONFIG.l5r5e.namespace, "initiative-difficulty-value"),
difficultyHidden: game.settings.get(CONFIG.l5r5e.namespace, "initiative-difficulty-hidden"),
};
}
/**
* The ApplicationV2 always adds the close button so just remove it when redering the frame
* @override ApplicationV2
*/
async _renderFrame(options) {
const frame = await super._renderFrame(options);
$(frame).find('button[data-action="close"]').remove();
return frame;
}
/**
* The ApplicationV2 always adds the close button so just remove it when redering the frame
* @override ApplicationV2
*/
_onFirstRender(context, options) {
//const x = $(window).width();
const y = $(window).height();
options.position.top = y - 220;
options.position.left = 220; //x - 630;
}
async _onRender(context, options) {
await super._onRender(context, options);
if (this.options.classes.includes("themed")) {
return;
}
this.element.classList.remove("theme-light", "theme-dark");
const {colorScheme} = game.settings.get("core", "uiConfig");
if (colorScheme.interface) {
this.element.classList.add("themed", `theme-${colorScheme.interface}`);
}
}
/**
* The GM Toolbox should not be removed when toggling the main menu with the esc key etc.
* @override ApplicationV2
*/
async close(options) {
return Promise.resolve(this);
}
/**
* Refresh data (used from socket)
*/
async refresh() {
if (!game.user.isGM) {
return;
}
this.render(false);
}
static #openGmMonitor() {
const app = foundry.applications.instances.get("l5r5e-gm-monitor")
if (app) {
app.close();
} else {
new game.l5r5e.GmMonitor().render(true);
}
}
/**
* @param {PointerEvent} event The originating click event
*/
static #onChangeDifficulty(event) {
let difficulty = game.settings.get(CONFIG.l5r5e.namespace, "initiative-difficulty-value");
switch (event.button) {
case 0: // left click
difficulty = Math.min(9, difficulty + 1);
break;
case 1: // middle click
difficulty = 2;
break;
case 2: // right click
difficulty = Math.max(0, difficulty - 1);
break;
}
game.settings.set(CONFIG.l5r5e.namespace, "initiative-difficulty-value", difficulty);
}
static #onToggleHideDifficulty() {
const hiddenSetting = game.settings.get(CONFIG.l5r5e.namespace, "initiative-difficulty-hidden")
game.settings.set(CONFIG.l5r5e.namespace, "initiative-difficulty-hidden", !hiddenSetting);
}
/**
* @param {Boolean} allActors
* @param {ActorL5r5e} actor
* @returns {Boolean}
*/
static #updatableCharacter(allActors, actor) {
if (!actor.isCharacterType) {
return false;
}
if (allActors) {
return true;
}
return actor.isCharacter && actor.hasPlayerOwnerActive
}
/**
* @param {Boolean} allActors
* @param {String} type
*/
static #uiNotification(allActors, type) {
ui.notifications.info(
` <i class="fas fa-user${allActors ? "s" : ""}"></i> ` + game.i18n.localize(`l5r5e.gm.toolbox.${type}_info`)
);
}
/**
* @param {PointerEvent} event The originating click event
*/
static async #onResetVoid(event) {
const allActors = event.button !== 0;
for await (const actor of game.actors.contents) {
if (!GmToolbox.#updatableCharacter(allActors, actor)) {
continue;
}
await actor.update({
system: {
void_points: {
value: Math.ceil(actor.system.void_points.max / 2),
},
},
});
}
GmToolbox.#uiNotification(allActors, "reset_void");
}
/**
* @param {PointerEvent} event The originating click event
*/
static async #onSleep(event) {
const allActors = event.button !== 0;
for await (const actor of game.actors.contents) {
if (!GmToolbox.#updatableCharacter(allActors, actor)) {
continue;
}
await actor.update({
system: {
fatigue: {
value: Math.max(0,
actor.system.fatigue.value - Math.ceil(actor.system.rings.water * 2)
),
}
},
});
await actor.removeConditions(new Set(["exhausted"]));
}
GmToolbox.#uiNotification(allActors, "sleep");
}
/**
* @param {PointerEvent} event The originating click event
*/
static async #onSceneEnd(event) {
const allActors = event.button !== 0;
for await (const actor of game.actors.contents) {
if (!GmToolbox.#updatableCharacter(allActors, actor)
|| actor.statuses.has("exhausted")) {
continue;
}
await actor.update({
system: {
fatigue: {
value: Math.min(
actor.system.fatigue.value,
Math.ceil(actor.system.fatigue.max / 2)
)
},
strife: {
value: Math.min(
actor.system.strife.value,
Math.ceil(actor.system.strife.max / 2)
)
}
}
});
}
GmToolbox.#uiNotification(allActors, "scene_end");
}
/**
* @param {Setting} setting The setting that is being updated
*/
async #onUpdateSetting(setting) {
switch (setting.key) {
case "l5r5e.initiative-difficulty-value":
case "l5r5e.initiative-difficulty-hidden":
this.render(false);
break;
default:
return;
}
}
}

View File

@@ -57,11 +57,6 @@ export default class HooksL5r5e {
// For some reason, not always really ready, so wait a little
await new Promise((r) => setTimeout(r, 2000));
// Settings TN and EncounterType
if (game.user.isGM) {
new game.l5r5e.GmToolbox().render(true);
}
// ***** UI *****
// If any disclaimer "not translated by Edge"
const disclaimer = game.i18n.localize("l5r5e.global.edge_translation_disclaimer");

View File

@@ -10,6 +10,8 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e {
static types = [
{ id: "ring", label: "l5r5e.rings.label" },
{ id: "skill", label: "l5r5e.skills.label" },
{ id: "arcane", label: "l5r5e.chiaroscuro.arcane.label" },
{ id: "mot_invocation", label: "l5r5e.chiaroscuro.technique.mot_invocation" },
// others have theirs own xp count
];
@@ -27,6 +29,12 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e {
sheetData.data.subTypesList = AdvancementSheetL5r5e.types;
sheetData.data.skillsList = game.l5r5e.HelpersL5r5e.getSkillsList(true);
// Invocation sub-types (Général / Neutre / Précis)
const invTypes = game.l5r5e.HelpersL5r5e.getLocalizedRawObject("l5r5e.chiaroscuro.technique.invocation_types") ?? {};
sheetData.data.invocationTypesList = [{ id: "", label: "—" }].concat(
Object.entries(invTypes).map(([id, label]) => ({ id, label }))
);
return sheetData;
}
@@ -49,15 +57,22 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e {
html.find("#advancement_type").on("change", (event) => {
const targetEvt = $(event.target);
targetEvt.prop("disabled", true);
const val = targetEvt.val();
if (targetEvt.val() === "skill") {
if (val === "skill") {
this._updateChoice({ ring: currentRing }, { skill: currentSkill }).then(
targetEvt.prop("disabled", false)
);
} else if (targetEvt.val() === "ring") {
} else if (val === "ring") {
this._updateChoice({ skill: currentSkill }, { ring: currentRing }).then(
targetEvt.prop("disabled", false)
);
} else {
// arcane or mot_invocation: just save the type, no auto-calc
this.object.update({ system: { advancement_type: val } }).then(() => {
targetEvt.prop("disabled", false);
this.render(true);
});
}
});

View File

@@ -10,7 +10,8 @@ export class ArcaneSheetL5r5e extends BaseItemSheetL5r5e {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "arcane"],
template: CONFIG.l5r5e.paths.templates + "items/arcane/arcane-sheet.html",
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "attributes" }],
width: 500,
height: 400,
});
}

View File

@@ -1,137 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheetL5r5e}
*/
export class ArmyCohortSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "army-cohort"],
template: CONFIG.l5r5e.paths.templates + "items/army-cohort/army-cohort-sheet.html",
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "infos" }],
dragDrop: [{ dragSelector: ".item", dropSelector: null }],
});
}
/** @override */
constructor(options = {}) {
super(options);
this._initialize();
}
/**
* Initialize once
* @private
*/
_initialize() {
const data = this.object.system;
// update linked actor datas
if (data.leader_actor_id) {
const actor = game.actors.get(data.leader_actor_id);
if (actor) {
this._updateLinkedActorData(actor);
} else {
this._removeLinkedActor();
}
}
}
/**
* @return {Object|Promise}
*/
async getData(options = {}) {
const sheetData = await super.getData(options);
// Editors enrichment
sheetData.data.enrichedHtml.abilities = await foundry.applications.ux.TextEditor.implementation.enrichHTML(sheetData.data.system.abilities, {
async: true,
});
return sheetData;
}
/**
* Activate a named TinyMCE text editor
* @param {string} name The named data field which the editor modifies.
* @param {object} options TinyMCE initialization options passed to TextEditor.create
* @param {string} initialContent Initial text content for the editor area.
* @override
*/
activateEditor(name, options = {}, initialContent = "") {
// Symbols Compatibility with old compendium modules (PRE l5r v1.7.2)
if (name === "system.abilities" && initialContent) {
initialContent = game.l5r5e.HelpersL5r5e.convertSymbols(initialContent, false);
}
return super.activateEditor(name, options, initialContent);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
*/
activateListeners(html) {
super.activateListeners(html);
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Delete the linked Actor
html.find(".actor-remove-control").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
this._removeLinkedActor();
});
}
/**
* Handle dropped Item data on the Actor sheet (cohort, fortification)
* @param {DragEvent} event
*/
async _onDrop(event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
const droppedActor = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
return this._updateLinkedActorData(droppedActor);
}
/**
* Update actor datas for this army sheet
* @param {ActorL5r5e} actor actor object
* @return {Promise<abstract.Document>}
* @private
*/
async _updateLinkedActorData(actor) {
if (!actor || actor.documentName !== "Actor" || !actor.isCharacterType) {
console.warn("L5R5E | Army Cohort | Wrong actor type", actor?.type, actor);
return;
}
return this.object.update({
img: actor.img,
system: {
leader: actor.name,
leader_actor_id: actor._id,
},
});
}
/**
* Remove the linked actor (commander/warlord)
* @return {Promise<void>}
* @private
*/
async _removeLinkedActor() {
return this.object.update({
system: {
leader_actor_id: null,
},
});
}
}

View File

@@ -1,14 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheetL5r5e}
*/
export class ArmyFortificationSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "army-fortification"],
template: CONFIG.l5r5e.paths.templates + "items/army-fortification/army-fortification-sheet.html",
});
}
}

View File

@@ -1,14 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class BondSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "bond"],
template: CONFIG.l5r5e.paths.templates + "items/bond/bond-sheet.html",
});
}
}

View File

@@ -1,128 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class ItemPatternSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "item-pattern"],
template: CONFIG.l5r5e.paths.templates + "items/item-pattern/item-pattern-sheet.html",
});
}
/**
* @return {Object|Promise}
*/
async getData(options = {}) {
const sheetData = await super.getData(options);
// Linked Property
sheetData.data.linkedProperty = await this.getLinkedProperty(sheetData);
return sheetData;
}
/**
* Get the linked property name
* @param sheetData
* @return {Promise<null|{name, id}>}
*/
async getLinkedProperty(sheetData) {
if (sheetData.data.system.linked_property_id) {
const linkedProperty = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({
id: sheetData.data.system.linked_property_id,
type: "Item",
});
if (linkedProperty) {
return {
id: linkedProperty._id,
name: linkedProperty.name,
};
}
}
return null;
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
*/
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// Delete the linked property
html.find(`.linked-property-delete`).on("click", this._deleteLinkedProperty.bind(this));
}
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @private
*/
async _onDrop(event) {
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// Only property allowed here
let item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || item.documentName !== "Item" || item.type !== "property") {
return;
}
// Set the new property, and update
this.document.system.linked_property_id = item.id;
this.document.update({
system: {
linked_property_id: this.document.system.linked_property_id,
},
});
}
/**
* Remove the link to a property for the current item
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _deleteLinkedProperty(event) {
event.preventDefault();
event.stopPropagation();
let name;
const linkedProperty = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({
id: this.document.system.linked_property_id,
type: "Item",
});
if (linkedProperty) {
name = linkedProperty.name;
}
const callback = async () => {
this.document.system.linked_property_id = null;
this.document.update({
system: {
linked_property_id: this.document.system.linked_property_id,
},
});
};
// Holing Ctrl = without confirm
if (event.ctrlKey || !name) {
return callback();
}
game.l5r5e.HelpersL5r5e.confirmDeleteDialog(
game.i18n.format("l5r5e.global.delete_confirm", { name }),
callback
);
}
}

View File

@@ -108,19 +108,12 @@ export class ItemSheetL5r5e extends BaseItemSheetL5r5e {
}
// If we are a property, the child id need to be different to parent
// (property type removed — guard kept for legacy data safety)
if (this.item.type === "property" && this.item.id === item._id) {
return;
}
// Specific ItemPattern's drop, get the associated props instead
if (item.type === "item_pattern" && item.system.linked_property_id) {
item = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({
id: item.system.linked_property_id,
type: "Item",
});
}
// Final object has to be a property
// Final object has to be a property (type removed — no more drops possible)
if (item.type !== "property") {
return;
}

View File

@@ -0,0 +1,59 @@
import { BaseItemSheetL5r5e } from "./base-item-sheet.js";
/** Mode Invocation values per invocation type */
const INVOCATION_MODE = {
general: 3,
neutre: 0,
precis: -3,
};
/**
* Sheet for Mot d'Invocation items (Chiaroscuro).
* @extends {BaseItemSheetL5r5e}
*/
export class MotInvocationSheetL5r5e extends BaseItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "mot-invocation"],
template: CONFIG.l5r5e.paths.templates + "items/mot_invocation/mot-invocation-sheet.html",
width: 500,
height: 360,
});
}
/** @override */
async getData(options = {}) {
const sheetData = await super.getData(options);
// Build invocation types list from i18n
const invTypes = game.l5r5e.HelpersL5r5e.getLocalizedRawObject("l5r5e.chiaroscuro.technique.invocation_types") ?? {};
sheetData.data.invocationTypesList = [{ id: "", label: "—" }].concat(
Object.entries(invTypes).map(([id, label]) => ({ id, label }))
);
sheetData.data.enrichedHtml = {
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
sheetData.data.system.description ?? "",
{ async: true }
),
};
return sheetData;
}
/** @override */
activateListeners(html) {
super.activateListeners(html);
if (!this.isEditable) return;
html.find("#mot_invocation_type").on("change", async (event) => {
const type = event.target.value;
const mode = INVOCATION_MODE[type] ?? 0;
// Update stored value and refresh display
await this.object.update({ system: { invocation_type: type, mode_invocation: mode } });
this.render(true);
});
}
}

View File

@@ -10,7 +10,8 @@ export class MystereSheetL5r5e extends BaseItemSheetL5r5e {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "mystere"],
template: CONFIG.l5r5e.paths.templates + "items/mystere/mystere-sheet.html",
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "attributes" }],
width: 500,
height: 380,
});
}

View File

@@ -1,14 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class PropertySheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "property"],
template: CONFIG.l5r5e.paths.templates + "items/property/property-sheet.html",
});
}
}

View File

@@ -1,14 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class SignatureScrollSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "signature-scroll"],
template: CONFIG.l5r5e.paths.templates + "items/signature-scroll/signature-scroll-sheet.html",
});
}
}

View File

@@ -0,0 +1,31 @@
import { BaseItemSheetL5r5e } from "./base-item-sheet.js";
/**
* Sheet for Technique École items (Chiaroscuro).
* @extends {BaseItemSheetL5r5e}
*/
export class TechniqueEcoleSheetL5r5e extends BaseItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "technique-ecole"],
template: CONFIG.l5r5e.paths.templates + "items/technique_ecole/technique-ecole-sheet.html",
width: 500,
height: 380,
});
}
/** @override */
async getData(options = {}) {
const sheetData = await super.getData(options);
sheetData.data.enrichedHtml = {
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
sheetData.data.system.description ?? "",
{ async: true }
),
};
return sheetData;
}
}

View File

@@ -1,195 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class TechniqueSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "technique"],
template: CONFIG.l5r5e.paths.templates + "items/technique/technique-sheet.html",
});
}
/** @override */
async getData(options = {}) {
const sheetData = await super.getData(options);
// List all available techniques type
const types = ["core", "school", "title", "chiaroscuro"];
if (game.settings.get(CONFIG.l5r5e.namespace, "techniques-customs")) {
types.push("custom");
}
sheetData.data.techniquesList = game.l5r5e.HelpersL5r5e.getTechniquesList({ types });
// Invocation sub-type fields (visible only for mot_invocation)
sheetData.data.isMotInvocation = sheetData.data.system.technique_type === "mot_invocation";
sheetData.data.invocationTypes = [
{ id: "general", label: game.i18n.localize("chiaroscuro.technique.invocation_types.general") },
{ id: "neutre", label: game.i18n.localize("chiaroscuro.technique.invocation_types.neutre") },
{ id: "precis", label: game.i18n.localize("chiaroscuro.technique.invocation_types.precis") },
];
sheetData.data.modeInvocationValues = [
{ id: "-3", label: "-3" },
{ id: "0", label: "0" },
{ id: "3", label: "+3" },
];
// Convert mode_invocation to string for selectOptions matching
sheetData.data.system.mode_invocation_str = String(sheetData.data.system.mode_invocation ?? 0);
// Sanitize Difficulty and Skill list
sheetData.data.system.difficulty = TechniqueSheetL5r5e.formatDifficulty(sheetData.data.system.difficulty);
sheetData.data.system.skill = TechniqueSheetL5r5e.translateSkillsList(
TechniqueSheetL5r5e.formatSkillList(sheetData.data.system.skill.split(",")),
false
).join(", ");
return sheetData;
}
/**
* This method is called upon form submission after form data is validated
* @param {Event} event The initial triggering submission event
* @param {Object} formData The object of validated form data with which to update the object
* @returns {Promise} A Promise which resolves once the update operation has completed
* @override
*/
async _updateObject(event, formData) {
// Change the image according to the type if this is already the case
if (
formData["system.technique_type"] &&
formData.img === `${CONFIG.l5r5e.paths.assets}icons/techs/${this.object.system.technique_type}.svg`
) {
formData.img = `${CONFIG.l5r5e.paths.assets}icons/techs/${formData["system.technique_type"]}.svg`;
}
// Sanitize Difficulty and Skill list
formData["system.difficulty"] = TechniqueSheetL5r5e.formatDifficulty(formData["system.difficulty"]);
formData["system.skill"] = TechniqueSheetL5r5e.formatSkillList(
TechniqueSheetL5r5e.translateSkillsList(formData["system.skill"].split(","), true)
).join(",");
// Convert mode_invocation_str back to number
if ("system.mode_invocation_str" in formData) {
formData["system.mode_invocation"] = parseInt(formData["system.mode_invocation_str"] ?? "0", 10);
delete formData["system.mode_invocation_str"];
}
return super._updateObject(event, formData);
}
/**
* Listen to html elements
* @param {jQuery} html HTML content of the sheet.
* @override
*/
activateListeners(html) {
super.activateListeners(html);
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Autocomplete
game.l5r5e.HelpersL5r5e.autocomplete(
html,
"system.difficulty",
[
"@T:intrigueRank",
"@T:focus",
"@T:martialRank",
"@T:statusRank|max",
"@T:strife.value|max",
"@T:vigilance",
"@T:vigilance|max",
"@T:vigilance|min",
"@T:vigilance|max(@T:statusRank)",
],
","
);
game.l5r5e.HelpersL5r5e.autocomplete(
html,
"system.skill",
Object.values(TechniqueSheetL5r5e.getSkillsTranslationMap(false)),
","
);
}
/**
* Sanitize the technique difficulty
* @param {string} str
* @return {string}
*/
static formatDifficulty(str) {
if (str && !Number.isNumeric(str) && !CONFIG.l5r5e.regex.techniqueDifficulty.test(str)) {
return "";
}
return str;
}
/**
* Get a flat map for skill translation
* @param {boolean} bToSkillId if true flip props/values
* @return {Object}
*/
static getSkillsTranslationMap(bToSkillId) {
return Array.from(CONFIG.l5r5e.skills).reduce((acc, [id, cat]) => {
if (bToSkillId) {
acc[game.l5r5e.HelpersL5r5e.normalize(game.i18n.localize(`l5r5e.skills.${cat}.${id}`))] = id;
acc[game.l5r5e.HelpersL5r5e.normalize(game.i18n.localize(`l5r5e.skills.${cat}.title`))] = cat;
} else {
acc[id] = game.i18n.localize(`l5r5e.skills.${cat}.${id}`);
acc[cat] = game.i18n.localize(`l5r5e.skills.${cat}.title`);
}
return acc;
}, {});
}
/**
* Translate a list of skill and category
* @param {string[]} aIn
* @param {boolean} bToSkillId
* @return {string[]}
*/
static translateSkillsList(aIn, bToSkillId) {
const map = TechniqueSheetL5r5e.getSkillsTranslationMap(bToSkillId);
return aIn.map((skill) => map[game.l5r5e.HelpersL5r5e.normalize(skill)]);
}
/**
* Sanitize the technique skill list
* @param {string[]} skillList
* @return {string[]}
*/
static formatSkillList(skillList) {
if (!skillList) {
return "";
}
const categories = game.l5r5e.HelpersL5r5e.getCategoriesSkillsList();
// List categories
const unqCatList = new Set();
skillList.forEach((s) => {
s = s?.trim();
if (!!s && categories.has(s)) {
unqCatList.add(s);
}
});
// List skill (not include in cat)
const unqSkillList = new Set();
skillList.forEach((s) => {
s = s?.trim();
if (!!s && CONFIG.l5r5e.skills.has(s)) {
const cat = CONFIG.l5r5e.skills.get(s);
if (!unqCatList.has(cat)) {
unqSkillList.add(s);
}
}
});
return [...unqCatList, ...unqSkillList];
}
}

View File

@@ -1,149 +0,0 @@
import { ItemSheetL5r5e } from "./item-sheet.js";
/**
* @extends {ItemSheet}
*/
export class TitleSheetL5r5e extends ItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "title"],
template: CONFIG.l5r5e.paths.templates + "items/title/title-sheet.html",
});
}
/**
* @return {Object|Promise}
*/
async getData(options = {}) {
const sheetData = await super.getData(options);
// Prepare OwnedItems
sheetData.data.embedItemsList = this._prepareEmbedItems(sheetData.data.system.items);
// Automatically compute the total xp cost (full price) and XP in title (cursus, some halved prices)
const { xp_used_total, xp_used } = game.l5r5e.HelpersL5r5e.getItemsXpCost(sheetData.data.embedItemsList);
sheetData.data.system.xp_used_total = xp_used_total;
sheetData.data.system.xp_used = xp_used;
return sheetData;
}
/**
* Prepare Embed items
* @param {[]|Map} itemsMap
* @return {[]}
* @private
*/
_prepareEmbedItems(itemsMap) {
let itemsList = itemsMap;
if (itemsMap instanceof Map) {
itemsList = Array.from(itemsMap).map(([id, item]) => item);
}
// Sort by rank desc
itemsList.sort((a, b) => (b.system.rank || 0) - (a.system.rank || 0));
return itemsList;
}
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @private
*/
async _onDrop(event) {
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// Check item type and subtype
let item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || item.documentName !== "Item" || !["technique", "advancement"].includes(item.type)) {
return;
}
const data = item.toObject(false);
// Check xp for techs
if (item.type === "technique") {
data.system.xp_cost = data.system.xp_cost > 0 ? data.system.xp_cost : CONFIG.l5r5e.xp.techniqueCost;
data.system.xp_used = data.system.xp_cost;
}
this.document.addEmbedItem(data);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
*/
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// *** Sub-Items management ***
html.find(".item-add").on("click", this._addSubItem.bind(this));
html.find(`.item-edit`).on("click", this._editSubItem.bind(this));
html.find(`.item-delete`).on("click", this._deleteSubItem.bind(this));
html.find(`.item-curriculum`).on("click", this._switchSubItemCurriculum.bind(this));
}
/**
* Display a dialog to choose what Item to add, and add it on this Item
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _addSubItem(event) {
event.preventDefault();
event.stopPropagation();
// Show Dialog
const selectedType = await game.l5r5e.HelpersL5r5e.showSubItemDialog(["advancement", "technique"]);
if (!selectedType) {
return;
}
// Create the new Item
const itemId = await this.document.addEmbedItem(
new game.l5r5e.ItemL5r5e({
name: game.i18n.localize(`TYPES.Item.${selectedType.toLowerCase()}`),
type: selectedType,
img: `${CONFIG.l5r5e.paths.assets}icons/items/${selectedType}.svg`,
})
);
// Get the store object and display it
const item = this.document.items.get(itemId);
if (item) {
item.sheet.render(true);
}
}
/**
* Toogle the curriculum for this embed item
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _switchSubItemCurriculum(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.document.getEmbedItem(itemId);
if (!item) {
return;
}
// Switch the state and update
item.system.in_curriculum = !item.system.in_curriculum;
return this.document.updateEmbedItem(item);
}
}

View File

@@ -15,18 +15,21 @@ export class WeaponSheetL5r5e extends ItemSheetL5r5e {
async getData(options = {}) {
const sheetData = await super.getData(options);
// Martial skills only
// Only these four skills are relevant for weapons
const allowedSkills = ["archery", "unarmed", "melee", "invocation"];
sheetData.data.skills = Array.from(CONFIG.l5r5e.skills)
.filter(([id, cat]) => cat === "martial")
.filter(([id]) => allowedSkills.includes(id))
.map(([id, cat]) => ({
id,
label: "l5r5e.skills." + cat.toLowerCase() + "." + id.toLowerCase(),
}));
// Weapon categories (Chiaroscuro)
// Weapon categories (Chiaroscuro) — sorted alphabetically
const catObj = game.l5r5e.HelpersL5r5e.getLocalizedRawObject("chiaroscuro.weapon.categories") ?? {};
sheetData.data.weaponCategories = [{ id: "", label: "—" }].concat(
Object.entries(catObj).map(([id, label]) => ({ id, label }))
Object.entries(catObj)
.map(([id, label]) => ({ id, label }))
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }))
);
return sheetData;

View File

@@ -10,7 +10,6 @@ import HooksL5r5e from "./hooks.js";
import { ActorL5r5e } from "./actor.js";
import { CharacterSheetL5r5e } from "./actors/character-sheet.js";
import { NpcSheetL5r5e } from "./actors/npc-sheet.js";
import { ArmySheetL5r5e } from "./actors/army-sheet.js";
import { RulerL5r5e, TokenRulerL5r5e } from "./tatical-grid-rulers.js";
// Dice and rolls
import { L5rBaseDie } from "./dice/dietype/l5r-base-die.js";
@@ -26,19 +25,13 @@ import { ItemL5r5e } from "./item.js";
import { ItemSheetL5r5e } from "./items/item-sheet.js";
import { ArmorSheetL5r5e } from "./items/armor-sheet.js";
import { WeaponSheetL5r5e } from "./items/weapon-sheet.js";
import { TechniqueSheetL5r5e } from "./items/technique-sheet.js";
import { PropertySheetL5r5e } from "./items/property-sheet.js";
import { AdvancementSheetL5r5e } from "./items/advancement-sheet.js";
import { PeculiaritySheetL5r5e } from "./items/peculiarity-sheet.js";
import { TitleSheetL5r5e } from "./items/title-sheet.js";
import { BondSheetL5r5e } from "./items/bond-sheet.js";
import { SignatureScrollSheetL5r5e } from "./items/signature-scroll-sheet.js";
import { ItemPatternSheetL5r5e } from "./items/item-pattern-sheet.js";
import { ArcaneSheetL5r5e } from "./items/arcane-sheet.js";
import { EtatSheetL5r5e } from "./items/etat-sheet.js";
import { MystereSheetL5r5e } from "./items/mystere-sheet.js";
import { ArmyCohortSheetL5r5e } from "./items/army-cohort-sheet.js";
import { ArmyFortificationSheetL5r5e } from "./items/army-fortification-sheet.js";
import { TechniqueEcoleSheetL5r5e } from "./items/technique-ecole-sheet.js";
import { MotInvocationSheetL5r5e } from "./items/mot-invocation-sheet.js";
// JournalEntry
import { JournalL5r5e } from "./journal.js";
import { BaseJournalSheetL5r5e } from "./journals/base-journal-sheet.js";
@@ -46,8 +39,6 @@ import { BaseJournalSheetL5r5e } from "./journals/base-journal-sheet.js";
import { CompendiumDirectoryL5r5e } from "./compendium/l5r5e-compendium-directory.js";
// Specific
import { MigrationL5r5e } from "./migration.js";
import { GmToolbox } from "./gm/gm-toolbox.js";
import { GmMonitor } from "./gm/gm-monitor.js";
import { Storage } from "./storage.js";
// Misc
import { L5r5eHtmlMultiSelectElement } from "./misc/l5r5e-multiselect.js";
@@ -123,8 +114,6 @@ Hooks.once("init", async () => {
DicePickerDialog,
RollnKeepDialog,
ChiaroscuroDiceDialog,
GmToolbox,
GmMonitor,
storage: new Storage(),
sockets: new SocketHandlerL5r5e(),
migrations: MigrationL5r5e,
@@ -155,11 +144,6 @@ Hooks.once("init", async () => {
label: "TYPES.Actor.npc",
makeDefault: true,
});
fdc.Actors.registerSheet(L5R5E.namespace, ArmySheetL5r5e, {
types: ["army"],
label: "TYPES.Actor.army",
makeDefault: true,
});
// Items
fdc.Items.unregisterSheet("core", fav1s.ItemSheet);
@@ -178,16 +162,6 @@ Hooks.once("init", async () => {
label: "TYPES.Item.weapon",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, TechniqueSheetL5r5e, {
types: ["technique"],
label: "TYPES.Item.technique",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, PropertySheetL5r5e, {
types: ["property"],
label: "TYPES.Item.property",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, PeculiaritySheetL5r5e, {
types: ["peculiarity"],
label: "TYPES.Item.peculiarity",
@@ -198,36 +172,6 @@ Hooks.once("init", async () => {
label: "TYPES.Item.advancement",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, TitleSheetL5r5e, {
types: ["title"],
label: "TYPES.Item.title",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, BondSheetL5r5e, {
types: ["bond"],
label: "TYPES.Item.bond",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, SignatureScrollSheetL5r5e, {
types: ["signature_scroll"],
label: "TYPES.Item.signature_scroll",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, ItemPatternSheetL5r5e, {
types: ["item_pattern"],
label: "TYPES.Item.item_pattern",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, ArmyCohortSheetL5r5e, {
types: ["army_cohort"],
label: "TYPES.Item.army_cohort",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, ArmyFortificationSheetL5r5e, {
types: ["army_fortification"],
label: "TYPES.Item.army_fortification",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, ArcaneSheetL5r5e, {
types: ["arcane"],
label: "TYPES.Item.arcane",
@@ -243,6 +187,16 @@ Hooks.once("init", async () => {
label: "TYPES.Item.mystere",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, TechniqueEcoleSheetL5r5e, {
types: ["technique_ecole"],
label: "TYPES.Item.technique_ecole",
makeDefault: true,
});
fdc.Items.registerSheet(L5R5E.namespace, MotInvocationSheetL5r5e, {
types: ["mot_invocation"],
label: "TYPES.Item.mot_invocation",
makeDefault: true,
});
// Journal
fdc.Journal.unregisterSheet("core", fav1s.JournalSheet);

View File

@@ -8,10 +8,10 @@ export const PreloadTemplates = async function () {
`${tpl}actors/character/aspects.html`,
`${tpl}actors/character/attributes.html`,
`${tpl}actors/character/category.html`,
`${tpl}actors/character/conflict.html`,
`${tpl}actors/character/experience.html`,
`${tpl}actors/character/identity.html`,
`${tpl}actors/character/identity-text.html`,
`${tpl}actors/character/notes.html`,
`${tpl}actors/character/inventory.html`,
`${tpl}actors/character/invocations.html`,
`${tpl}actors/character/narrative.html`,
@@ -20,6 +20,7 @@ export const PreloadTemplates = async function () {
`${tpl}actors/character/skill.html`,
`${tpl}actors/character/social.html`,
`${tpl}actors/character/stance.html`,
`${tpl}actors/character/stance-simple.html`,
`${tpl}actors/character/techniques.html`,
`${tpl}actors/character/twenty-questions-item.html`,
// *** Actors : Npc ***
@@ -33,20 +34,12 @@ export const PreloadTemplates = async function () {
`${tpl}actors/npc/social.html`,
`${tpl}actors/npc/skill.html`,
`${tpl}actors/npc/techniques.html`,
// *** Actors : Army ***
`${tpl}actors/army/army.html`,
`${tpl}actors/army/cohort.html`,
`${tpl}actors/army/fortification.html`,
`${tpl}actors/army/others.html`,
// *** Items ***
`${tpl}items/advancement/advancement-entry.html`,
`${tpl}items/advancement/advancement-sheet.html`,
`${tpl}items/armor/armors.html`,
`${tpl}items/armor/armor-entry.html`,
`${tpl}items/armor/armor-sheet.html`,
`${tpl}items/bond/bond-entry.html`,
`${tpl}items/bond/bond-sheet.html`,
`${tpl}items/bond/bond-text.html`,
`${tpl}items/item/items.html`,
`${tpl}items/item/item-entry.html`,
`${tpl}items/item/item-value.html`,
@@ -54,29 +47,14 @@ export const PreloadTemplates = async function () {
`${tpl}items/item/item-infos.html`,
`${tpl}items/item/item-text-partial-reference.html`,
`${tpl}items/item/item-text.html`,
`${tpl}items/item-pattern/item-pattern-entry.html`,
`${tpl}items/item-pattern/item-pattern-sheet.html`,
`${tpl}items/item-pattern/item-pattern-text.html`,
`${tpl}items/peculiarity/peculiarity-entry.html`,
`${tpl}items/peculiarity/peculiarity-sheet.html`,
`${tpl}items/peculiarity/peculiarity-text.html`,
`${tpl}items/property/properties.html`,
`${tpl}items/property/property-entry.html`,
`${tpl}items/property/property-sheet.html`,
`${tpl}items/signature-scroll/signature-scroll-entry.html`,
`${tpl}items/signature-scroll/signature-scroll-sheet.html`,
`${tpl}items/signature-scroll/signature-scroll-text.html`,
`${tpl}items/technique/technique-entry.html`,
`${tpl}items/technique/technique-sheet.html`,
`${tpl}items/technique/technique-text.html`,
`${tpl}items/title/title-entry.html`,
`${tpl}items/title/title-sheet.html`,
`${tpl}items/title/title-text.html`,
`${tpl}items/weapon/weapons.html`,
`${tpl}items/weapon/weapon-entry.html`,
`${tpl}items/weapon/weapon-sheet.html`,
`${tpl}items/army-cohort/army-cohort-entry.html`,
`${tpl}items/army-fortification/army-fortification-entry.html`,
`${tpl}items/technique_ecole/technique-ecole-sheet.html`,
`${tpl}items/mot_invocation/mot-invocation-sheet.html`,
`${tpl}dice/chiaroscuro-chat-roll.html`,
]);
};