Working on initiative, and now TN is global with GM tool
This commit is contained in:
@@ -175,7 +175,6 @@ export class BaseSheetL5r5e extends ActorSheet {
|
|||||||
new game.l5r5e.DicePickerDialog({
|
new game.l5r5e.DicePickerDialog({
|
||||||
skillId: li.data("skill") || null,
|
skillId: li.data("skill") || null,
|
||||||
skillCatId: li.data("skillcat") || null,
|
skillCatId: li.data("skillcat") || null,
|
||||||
difficulty: li.data("diff") || 2,
|
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
}).render(true);
|
}).render(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,61 +1,103 @@
|
|||||||
/**
|
/**
|
||||||
* Roll initiative for one or multiple Combatants within the Combat entity
|
* Extends the actor to process special things from L5R.
|
||||||
* @param {string|string[]} ids A Combatant id or Array of ids for which to roll
|
|
||||||
* @param {string|null} [formula] A non-default initiative formula to roll. Otherwise the system default is used.
|
|
||||||
* @param {boolean} [updateTurn] Update the Combat turn after adding new initiative scores to keep the turn on
|
|
||||||
* the same Combatant.
|
|
||||||
* @param {object} [messageOptions] Additional options with which to customize created Chat Messages
|
|
||||||
* @return {Promise<Combat>} A promise which resolves to the updated Combat entity once updates are complete.
|
|
||||||
*/
|
*/
|
||||||
export async function rollInitiative(ids, { formula = null, updateTurn = true, messageOptions = {} } = {}) {
|
export class CombatL5r5e extends Combat {
|
||||||
if (!Array.isArray(ids)) {
|
// game.combat.settings.resource = "fatigue.value"; // nope :/
|
||||||
ids = [ids];
|
// constructor(...args) {
|
||||||
}
|
// super(...args);
|
||||||
const updatedCombatants = [];
|
// console.log(args);
|
||||||
ids.forEach((combatantId) => {
|
// }
|
||||||
const combatant = game.combat.combatants.find((c) => c._id === combatantId);
|
|
||||||
if (!combatant || !combatant.actor) {
|
/**
|
||||||
return;
|
* Roll initiative for one or multiple Combatants within the Combat entity
|
||||||
}
|
* @param {string|string[]} ids A Combatant id or Array of ids for which to roll
|
||||||
const data = combatant.actor.data.data;
|
* @param {string|null} [formula] A non-default initiative formula to roll. Otherwise the system default is used.
|
||||||
const formula = [`${data.rings[data.stance]}dr`];
|
* @param {boolean} [updateTurn] Update the Combat turn after adding new initiative scores to keep the turn on
|
||||||
const skillValue =
|
* the same Combatant.
|
||||||
combatant.actor.data.type === "npc" ? data.skills["martial"] : data.skills["martial"]["tactics"];
|
* @param {object} [messageOptions] Additional options with which to customize created Chat Messages
|
||||||
if (skillValue > 0) {
|
* @return {Promise<Combat>} A promise which resolves to the updated Combat entity once updates are complete.
|
||||||
formula.push(`${skillValue}ds`);
|
*/
|
||||||
|
async rollInitiative(ids, { formula = null, updateTurn = true, messageOptions = {} } = {}) {
|
||||||
|
if (!Array.isArray(ids)) {
|
||||||
|
ids = [ids];
|
||||||
}
|
}
|
||||||
|
|
||||||
const roll = new game.l5r5e.RollL5r5e(formula.join("+"));
|
// Make combatants array
|
||||||
|
const combatants = [];
|
||||||
roll.actor = combatant.actor;
|
ids.forEach((combatantId) => {
|
||||||
roll.l5r5e.stance = data.stance;
|
const combatant = game.combat.combatants.find((c) => c._id === combatantId);
|
||||||
roll.l5r5e.skillId = "tactics";
|
if (combatant && combatant.actor) {
|
||||||
roll.l5r5e.summary.difficulty = 1;
|
combatants.push(combatant);
|
||||||
|
}
|
||||||
roll.roll();
|
|
||||||
roll.toMessage({ flavor: game.i18n.localize("l5r5e.chatdices.initiative_roll") });
|
|
||||||
|
|
||||||
updatedCombatants.push({
|
|
||||||
_id: combatant._id,
|
|
||||||
initiative: roll.l5r5e.summary.success,
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Update all combatants at once
|
// Get modifiers
|
||||||
await this.updateEmbeddedEntity("Combatant", updatedCombatants);
|
const difficulty = game.settings.get("l5r5e", "initiative.difficulty.value");
|
||||||
return this;
|
const difficultyHidden = game.settings.get("l5r5e", "initiative.difficulty.hidden");
|
||||||
}
|
const skillId = CONFIG.l5r5e.initiativeSkills[game.settings.get("l5r5e", "initiative.encounter")];
|
||||||
|
const skillCat = CONFIG.l5r5e.skills.get(skillId);
|
||||||
|
|
||||||
/**
|
// Get score for each combatant
|
||||||
* Define how the array of Combatants is sorted in the displayed list of the tracker.
|
const updatedCombatants = [];
|
||||||
* This method can be overridden by a system or module which needs to display combatants in an alternative order.
|
combatants.forEach((combatant) => {
|
||||||
* By default sort by initiative, falling back to name
|
const data = combatant.actor.data.data;
|
||||||
* @private
|
|
||||||
*/
|
// A character’s initiative value is based on their state of preparedness when the conflict began.
|
||||||
export function _sortCombatants(a, b) {
|
// If the character was ready for the conflict, their base initiative value is their focus attribute.
|
||||||
// if tie, sort by honor, less honorable first
|
// If the character was unprepared (such as when surprised), their base initiative value is their vigilance attribute.
|
||||||
if (a.initiative === b.initiative) {
|
const isPrepared = true; // TODO in actor ? (pc and npc)
|
||||||
return a.actor.data.data.social.honor - b.actor.data.data.social.honor;
|
let initiative = isPrepared ? data.focus : data.vigilance;
|
||||||
|
|
||||||
|
// PC and Adversary
|
||||||
|
// (minion NPCs can generate initiative value without a check, using their focus or vigilance attribute)
|
||||||
|
if (combatant.actor.data.type !== "npc" || combatant.actor.data.data.type === "minion") {
|
||||||
|
const formula = [`${data.rings[data.stance]}dr`];
|
||||||
|
const skillValue =
|
||||||
|
combatant.actor.data.type === "npc" ? data.skills[skillCat] : data.skills[skillCat][skillId];
|
||||||
|
if (skillValue > 0) {
|
||||||
|
formula.push(`${skillValue}ds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const roll = new game.l5r5e.RollL5r5e(formula.join("+"));
|
||||||
|
|
||||||
|
roll.actor = combatant.actor;
|
||||||
|
roll.l5r5e.stance = data.stance;
|
||||||
|
roll.l5r5e.skillId = skillId;
|
||||||
|
roll.l5r5e.summary.difficulty = difficulty;
|
||||||
|
roll.l5r5e.summary.difficultyHidden = difficultyHidden;
|
||||||
|
|
||||||
|
roll.roll();
|
||||||
|
roll.toMessage({ flavor: game.i18n.localize("l5r5e.chatdices.initiative_roll") });
|
||||||
|
|
||||||
|
// if the character succeeded on their Initiative check, they add 1 to their base initiative value,
|
||||||
|
// plus an additional amount equal to their bonus successes.
|
||||||
|
if (roll.l5r5e.summary.success >= difficulty) {
|
||||||
|
initiative = initiative + 1 + Math.max(roll.l5r5e.summary.success - difficulty, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCombatants.push({
|
||||||
|
_id: combatant._id,
|
||||||
|
initiative: initiative,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update all combatants at once
|
||||||
|
await this.updateEmbeddedEntity("Combatant", updatedCombatants);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define how the array of Combatants is sorted in the displayed list of the tracker.
|
||||||
|
* This method can be overridden by a system or module which needs to display combatants in an alternative order.
|
||||||
|
* By default sort by initiative, falling back to name
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sortCombatants(a, b) {
|
||||||
|
// if tie, sort by honor, less honorable first
|
||||||
|
if (a.initiative === b.initiative) {
|
||||||
|
return a.actor.data.data.social.honor - b.actor.data.data.social.honor;
|
||||||
|
}
|
||||||
|
return b.initiative - a.initiative;
|
||||||
}
|
}
|
||||||
return b.initiative - a.initiative;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,24 @@ L5R5E.paths = {
|
|||||||
templates: `systems/l5r5e/templates/`,
|
templates: `systems/l5r5e/templates/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
L5R5E.money = [50, 10];
|
||||||
L5R5E.stances = ["earth", "air", "water", "fire", "void"];
|
L5R5E.stances = ["earth", "air", "water", "fire", "void"];
|
||||||
L5R5E.techniques = ["kata", "kiho", "invocation", "ritual", "shuji", "maho", "ninjutsu"];
|
L5R5E.techniques = ["kata", "kiho", "invocation", "ritual", "shuji", "maho", "ninjutsu"];
|
||||||
L5R5E.techniques_school = ["school_ability", "mastery_ability"];
|
L5R5E.techniques_school = ["school_ability", "mastery_ability"];
|
||||||
|
|
||||||
L5R5E.xp = {
|
L5R5E.xp = {
|
||||||
costPerRank: [0, 20, 24, 32, 44, 60],
|
costPerRank: [0, 20, 24, 32, 44, 60],
|
||||||
ringCostMultiplier: 3,
|
ringCostMultiplier: 3,
|
||||||
skillCostMultiplier: 2,
|
skillCostMultiplier: 2,
|
||||||
techniqueCost: 3,
|
techniqueCost: 3,
|
||||||
};
|
};
|
||||||
L5R5E.money = [50, 10];
|
|
||||||
|
L5R5E.initiativeSkills = {
|
||||||
|
intrigue: "sentiment",
|
||||||
|
duel: "meditation",
|
||||||
|
skirmish: "tactics",
|
||||||
|
mass_battle: "command",
|
||||||
|
};
|
||||||
|
|
||||||
// Map SkillId - CategoryId
|
// Map SkillId - CategoryId
|
||||||
L5R5E.skills = new Map();
|
L5R5E.skills = new Map();
|
||||||
|
|||||||
@@ -121,14 +121,27 @@ export class DicePickerDialog extends FormApplication {
|
|||||||
// Difficulty
|
// Difficulty
|
||||||
if (options?.difficulty) {
|
if (options?.difficulty) {
|
||||||
this.difficulty = options.difficulty;
|
this.difficulty = options.difficulty;
|
||||||
|
} else {
|
||||||
|
this.difficulty = game.settings.get("l5r5e", "initiative.difficulty.value");
|
||||||
}
|
}
|
||||||
|
|
||||||
// difficultyHidden
|
// difficultyHidden
|
||||||
if (options?.difficultyHidden) {
|
if (options?.difficultyHidden) {
|
||||||
this.difficultyHidden = options.difficultyHidden;
|
this.difficultyHidden = options.difficultyHidden;
|
||||||
|
} else {
|
||||||
|
this.difficultyHidden = game.settings.get("l5r5e", "initiative.difficulty.hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh data (used from socket)
|
||||||
|
*/
|
||||||
|
async refresh() {
|
||||||
|
this.difficulty = game.settings.get("l5r5e", "initiative.difficulty.value");
|
||||||
|
this.difficultyHidden = game.settings.get("l5r5e", "initiative.difficulty.hidden");
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set actor
|
* Set actor
|
||||||
* @param actor
|
* @param actor
|
||||||
|
|||||||
138
system/scripts/dice/gm-tools-dialog.js
Normal file
138
system/scripts/dice/gm-tools-dialog.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* L5R Initiative Roll dialog
|
||||||
|
* @extends {FormApplication}
|
||||||
|
*/
|
||||||
|
export class GmToolsDialog extends FormApplication {
|
||||||
|
/**
|
||||||
|
* Settings
|
||||||
|
*/
|
||||||
|
object = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the default options
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
static get defaultOptions() {
|
||||||
|
const x = $(window).width();
|
||||||
|
const y = $(window).height();
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
id: "l5r5e-gm-tools-dialog",
|
||||||
|
classes: ["l5r5e", "gm-tools-dialog"],
|
||||||
|
template: CONFIG.l5r5e.paths.templates + "dice/gm-tools-dialog.html",
|
||||||
|
title: game.i18n.localize("l5r5e.dicepicker.difficulty_title"),
|
||||||
|
width: 200, // ignored under 200px
|
||||||
|
height: 130, // ignored under 50px
|
||||||
|
scale: 0.5, // so scale /2 :D
|
||||||
|
left: x - 470,
|
||||||
|
top: y - 94,
|
||||||
|
closeOnSubmit: false,
|
||||||
|
submitOnClose: false,
|
||||||
|
submitOnChange: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {ApplicationOptions} options
|
||||||
|
*/
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
this.object = {
|
||||||
|
difficulty: game.settings.get("l5r5e", "initiative.difficulty.value"),
|
||||||
|
difficultyHidden: game.settings.get("l5r5e", "initiative.difficulty.hidden"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent non GM to render this windows
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
render(force = false, options = {}) {
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify difficulty hidden
|
||||||
|
html.find(`.difficulty_hidden`).on("click", (event) => {
|
||||||
|
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(1, this.object.difficulty - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
game.settings.set("l5r5e", "initiative.difficulty.value", this.object.difficulty).then(() => this.submit());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
// Notify the change to other players if they already have opened the DicePicker
|
||||||
|
game.l5r5e.sockets.refreshAppId("l5r5e-dice-picker-dialog");
|
||||||
|
|
||||||
|
// If the current GM also have the DP open
|
||||||
|
const app = Object.values(ui.windows).find((e) => e.id === "l5r5e-dice-picker-dialog");
|
||||||
|
if (app && typeof app.refresh === "function") {
|
||||||
|
app.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
system/scripts/hooks.js
Normal file
191
system/scripts/hooks.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
|
export default class HooksL5r5e {
|
||||||
|
/**
|
||||||
|
* Do anything after initialization but before ready
|
||||||
|
*/
|
||||||
|
static setup() {
|
||||||
|
// Embed Babele compendiums
|
||||||
|
if (
|
||||||
|
typeof Babele !== "undefined" &&
|
||||||
|
Babele.get().modules.every((module) => module.lang !== "fr" || module.module !== "l5r5e-dev")
|
||||||
|
) {
|
||||||
|
Babele.get().register({
|
||||||
|
module: "../systems/l5r5e", // babele only accept modules, so... well :D
|
||||||
|
lang: "fr",
|
||||||
|
dir: "babele/fr-fr",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do anything once the system is ready
|
||||||
|
*/
|
||||||
|
static ready() {
|
||||||
|
// Settings TN and EncounterType
|
||||||
|
if (game.user.isGM) {
|
||||||
|
new game.l5r5e.GmToolsDialog().render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***** UI *****
|
||||||
|
// Add title on button dice icon
|
||||||
|
$(".chat-control-icon")[0].title = game.i18n.localize("l5r5e.chatdices.dicepicker");
|
||||||
|
|
||||||
|
// Open Help dialog on clic on logo
|
||||||
|
$("#logo")
|
||||||
|
.on("click", () => new game.l5r5e.HelpDialog().render(true))
|
||||||
|
.prop("title", game.i18n.localize("l5r5e.logo.alt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SidebarTab
|
||||||
|
*/
|
||||||
|
static renderSidebarTab(app, html, data) {
|
||||||
|
// Add button on dice icon
|
||||||
|
html.find(".chat-control-icon").click(async () => {
|
||||||
|
new game.l5r5e.DicePickerDialog().render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat Message
|
||||||
|
*/
|
||||||
|
static renderChatMessage(message, html, data) {
|
||||||
|
// Add a extra CSS class to roll
|
||||||
|
if (message.isRoll) {
|
||||||
|
html.addClass("roll");
|
||||||
|
html.on("click", ".chat-dice-rnk", game.l5r5e.RollnKeepDialog.onChatAction.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combat tracker
|
||||||
|
*/
|
||||||
|
static async renderCombatTracker(app, html, data) {
|
||||||
|
// TODO do this in partial
|
||||||
|
let bar = "";
|
||||||
|
|
||||||
|
// *** Encounter Type ***
|
||||||
|
const encounterIcons = {
|
||||||
|
intrigue: "i_courtier",
|
||||||
|
duel: "fas fa-tint", // fa-tint / fa-blind
|
||||||
|
skirmish: "i_bushi",
|
||||||
|
mass_battle: "fa fa-users",
|
||||||
|
};
|
||||||
|
const encounterType = game.settings.get("l5r5e", "initiative.encounter");
|
||||||
|
Object.entries(CONFIG.l5r5e.initiativeSkills).forEach(([id, skill]) => {
|
||||||
|
bar =
|
||||||
|
bar +
|
||||||
|
`<a class="encounter encounter-control" data-id="${id}">` +
|
||||||
|
`<i class="${encounterIcons[id]}${id === encounterType ? " active" : ""}" title="${game.i18n.localize(
|
||||||
|
"l5r5e.conflict.initiative." + id
|
||||||
|
)}"></i>` +
|
||||||
|
`</a>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// *** Prepared ***
|
||||||
|
// TODO
|
||||||
|
// const encounterType = game.settings.get("l5r5e", "initiative.prepared");
|
||||||
|
bar =
|
||||||
|
bar +
|
||||||
|
`<a class="encounter prepared-control" data-id="tmp">` +
|
||||||
|
`<i class="fa fa-low-vision" title="npc prepared or not (WIP)"></i>` +
|
||||||
|
`</a>`;
|
||||||
|
|
||||||
|
const elmt = html.find("#l5r5e_encounter");
|
||||||
|
if (elmt.length > 0) {
|
||||||
|
elmt.html(bar);
|
||||||
|
} else {
|
||||||
|
html.find("#combat-round").append(`<nav class="encounters flexrow" id="l5r5e_encounter">${bar}</nav>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons Listener
|
||||||
|
html.find(".encounter-control").on("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const encounter = $(event.currentTarget).data("id");
|
||||||
|
game.settings
|
||||||
|
.set("l5r5e", "initiative.encounter", encounter)
|
||||||
|
.then(() => HooksL5r5e.renderCombatTracker(app, html, data));
|
||||||
|
});
|
||||||
|
|
||||||
|
// html.find(".prepared-control").on("click", (event) => {
|
||||||
|
// event.preventDefault();
|
||||||
|
// event.stopPropagation();
|
||||||
|
// let prepared = $(event.currentTarget).data('id');
|
||||||
|
// // if same, unset it
|
||||||
|
// if (prepared === encounterType) {
|
||||||
|
// prepared = "";
|
||||||
|
// }
|
||||||
|
// game.settings.set("l5r5e", "initiative.prepared", prepared).then(() => HooksL5r5e.renderCombatTracker(app, html, data));
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compendium display
|
||||||
|
*/
|
||||||
|
static async renderCompendium(app, html, data) {
|
||||||
|
// Add Rank & Ring in the compendium
|
||||||
|
if (app.entity === "Item") {
|
||||||
|
const content = await app.getContent();
|
||||||
|
content.forEach((item) => {
|
||||||
|
const tags = [];
|
||||||
|
if (item.data.data.rank) {
|
||||||
|
tags.push("<i>" + game.i18n.localize("l5r5e.rank") + " " + item.data.data.rank + "</i>");
|
||||||
|
}
|
||||||
|
if (item.data.data.ring) {
|
||||||
|
tags.push(`<i class="i_${item.data.data.ring}"></i>`);
|
||||||
|
}
|
||||||
|
if (tags.length > 0) {
|
||||||
|
html.find(`[data-entry-id='${item._id}']`).append(tags.join(" "));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DiceSoNice Hook
|
||||||
|
*/
|
||||||
|
static diceSoNiceReady(dice3d) {
|
||||||
|
const texturePath = `${CONFIG.l5r5e.paths.assets}dices/default/3d/`;
|
||||||
|
|
||||||
|
// dice3d.addSystem({
|
||||||
|
// id: "l5r5e",
|
||||||
|
// name: "Legend of the Five Rings 5E"
|
||||||
|
// }, "force");
|
||||||
|
|
||||||
|
// Rings
|
||||||
|
dice3d.addDicePreset(
|
||||||
|
{
|
||||||
|
name: "L5R Ring Dice",
|
||||||
|
type: "ddr", // don't known why the "dd" prefix is required, term is "r"
|
||||||
|
labels: Object.keys(game.l5r5e.RingDie.FACES).map(
|
||||||
|
(e) => `${texturePath}${game.l5r5e.RingDie.FACES[e].image.replace("ring_", "")}.png`
|
||||||
|
),
|
||||||
|
bumpMaps: Object.keys(game.l5r5e.RingDie.FACES).map(
|
||||||
|
(e) => `${texturePath}${game.l5r5e.RingDie.FACES[e].image.replace("ring_", "")}_bm.png`
|
||||||
|
),
|
||||||
|
colorset: "black",
|
||||||
|
system: "standard",
|
||||||
|
},
|
||||||
|
"d6"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Skills
|
||||||
|
dice3d.addDicePreset(
|
||||||
|
{
|
||||||
|
name: "L5R Skill Dice",
|
||||||
|
type: "dds",
|
||||||
|
labels: Object.keys(game.l5r5e.AbilityDie.FACES).map(
|
||||||
|
(e) => `${texturePath}${game.l5r5e.AbilityDie.FACES[e].image.replace("skill_", "")}.png`
|
||||||
|
),
|
||||||
|
bumpMaps: Object.keys(game.l5r5e.AbilityDie.FACES).map(
|
||||||
|
(e) => `${texturePath}${game.l5r5e.AbilityDie.FACES[e].image.replace("skill_", "")}_bm.png`
|
||||||
|
),
|
||||||
|
colorset: "white",
|
||||||
|
system: "standard",
|
||||||
|
},
|
||||||
|
"d12"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { L5R5E } from "../config.js";
|
|
||||||
import { ItemSheetL5r5e } from "./item-sheet.js";
|
import { ItemSheetL5r5e } from "./item-sheet.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +19,7 @@ export class WeaponSheetL5r5e extends ItemSheetL5r5e {
|
|||||||
const sheetData = await super.getData();
|
const sheetData = await super.getData();
|
||||||
|
|
||||||
// Martial skills only
|
// Martial skills only
|
||||||
sheetData.data.skills = Array.from(L5R5E.skills)
|
sheetData.data.skills = Array.from(CONFIG.l5r5e.skills)
|
||||||
.filter(([id, cat]) => cat === "martial")
|
.filter(([id, cat]) => cat === "martial")
|
||||||
.map(([id, cat]) => id);
|
.map(([id, cat]) => id);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { SocketHandlerL5r5e } from "./socket-handler.js";
|
|||||||
import { RegisterSettings } from "./settings.js";
|
import { RegisterSettings } from "./settings.js";
|
||||||
import { PreloadTemplates } from "./preloadTemplates.js";
|
import { PreloadTemplates } from "./preloadTemplates.js";
|
||||||
import { HelpDialog } from "./help/help-dialog.js";
|
import { HelpDialog } from "./help/help-dialog.js";
|
||||||
|
import HooksL5r5e from "./hooks.js";
|
||||||
// Actors
|
// Actors
|
||||||
import { ActorL5r5e } from "./actor.js";
|
import { ActorL5r5e } from "./actor.js";
|
||||||
import { CharacterSheetL5r5e } from "./actors/character-sheet.js";
|
import { CharacterSheetL5r5e } from "./actors/character-sheet.js";
|
||||||
@@ -15,7 +16,8 @@ import { RingDie } from "./dice/dietype/ring-die.js";
|
|||||||
import { RollL5r5e } from "./dice/roll.js";
|
import { RollL5r5e } from "./dice/roll.js";
|
||||||
import { DicePickerDialog } from "./dice/dice-picker-dialog.js";
|
import { DicePickerDialog } from "./dice/dice-picker-dialog.js";
|
||||||
import { RollnKeepDialog } from "./dice/roll-n-keep-dialog.js";
|
import { RollnKeepDialog } from "./dice/roll-n-keep-dialog.js";
|
||||||
import { _sortCombatants, rollInitiative } from "./combat.js";
|
import { CombatL5r5e } from "./combat.js";
|
||||||
|
import { GmToolsDialog } from "./dice/gm-tools-dialog.js";
|
||||||
// Items
|
// Items
|
||||||
import { ItemL5r5e } from "./item.js";
|
import { ItemL5r5e } from "./item.js";
|
||||||
import { ItemSheetL5r5e } from "./items/item-sheet.js";
|
import { ItemSheetL5r5e } from "./items/item-sheet.js";
|
||||||
@@ -48,6 +50,7 @@ Hooks.once("init", async function () {
|
|||||||
CONFIG.l5r5e = L5R5E;
|
CONFIG.l5r5e = L5R5E;
|
||||||
|
|
||||||
// Assign custom classes and constants here
|
// Assign custom classes and constants here
|
||||||
|
CONFIG.Combat.entityClass = CombatL5r5e;
|
||||||
CONFIG.Actor.entityClass = ActorL5r5e;
|
CONFIG.Actor.entityClass = ActorL5r5e;
|
||||||
CONFIG.Actor.sheetClasses = CharacterSheetL5r5e;
|
CONFIG.Actor.sheetClasses = CharacterSheetL5r5e;
|
||||||
CONFIG.Item.entityClass = ItemL5r5e;
|
CONFIG.Item.entityClass = ItemL5r5e;
|
||||||
@@ -64,10 +67,13 @@ Hooks.once("init", async function () {
|
|||||||
|
|
||||||
// Add some classes in game
|
// Add some classes in game
|
||||||
game.l5r5e = {
|
game.l5r5e = {
|
||||||
|
RingDie,
|
||||||
|
AbilityDie,
|
||||||
HelpersL5r5e,
|
HelpersL5r5e,
|
||||||
RollL5r5e,
|
RollL5r5e,
|
||||||
DicePickerDialog,
|
DicePickerDialog,
|
||||||
RollnKeepDialog,
|
RollnKeepDialog,
|
||||||
|
GmToolsDialog,
|
||||||
HelpDialog,
|
HelpDialog,
|
||||||
sockets: new SocketHandlerL5r5e(),
|
sockets: new SocketHandlerL5r5e(),
|
||||||
};
|
};
|
||||||
@@ -78,11 +84,6 @@ Hooks.once("init", async function () {
|
|||||||
// Preload Handlebars templates
|
// Preload Handlebars templates
|
||||||
await PreloadTemplates();
|
await PreloadTemplates();
|
||||||
|
|
||||||
// ***** Combat *****
|
|
||||||
Combat.prototype.rollInitiative = rollInitiative;
|
|
||||||
Combat.prototype._sortCombatants = _sortCombatants;
|
|
||||||
// game.combat.settings.resource = "fatigue.value"; // nope :/
|
|
||||||
|
|
||||||
// ***** Register custom sheets *****
|
// ***** Register custom sheets *****
|
||||||
// Actors
|
// Actors
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
Actors.unregisterSheet("core", ActorSheet);
|
||||||
@@ -188,100 +189,16 @@ Hooks.once("init", async function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* ------------------------------------ */
|
/* ------------------------------------ */
|
||||||
/* Setup system */
|
/* Hooks Once */
|
||||||
/* ------------------------------------ */
|
/* ------------------------------------ */
|
||||||
Hooks.once("setup", function () {
|
Hooks.once("setup", HooksL5r5e.setup);
|
||||||
// Do anything after initialization but before ready
|
Hooks.once("ready", HooksL5r5e.ready);
|
||||||
// Embed Babele compendiums
|
Hooks.once("diceSoNiceReady", (dice3d) => HooksL5r5e.diceSoNiceReady(dice3d));
|
||||||
/* eslint-disable no-undef */
|
|
||||||
if (
|
|
||||||
typeof Babele !== "undefined" &&
|
|
||||||
Babele.get().modules.every((module) => module.lang !== "fr" || module.module !== "l5r5e-dev")
|
|
||||||
) {
|
|
||||||
Babele.get().register({
|
|
||||||
module: "../systems/l5r5e", // babele only accept modules, so... well :D
|
|
||||||
lang: "fr",
|
|
||||||
dir: "babele/fr-fr",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------ */
|
/* ------------------------------------ */
|
||||||
/* Do anything once the system is ready */
|
/* Hooks On */
|
||||||
/* ------------------------------------ */
|
/* ------------------------------------ */
|
||||||
Hooks.once("ready", function () {
|
Hooks.on("renderSidebarTab", (app, html, data) => HooksL5r5e.renderSidebarTab(app, html, data));
|
||||||
// Add title on button dice icon
|
Hooks.on("renderChatMessage", (message, html, data) => HooksL5r5e.renderChatMessage(message, html, data));
|
||||||
$(".chat-control-icon")[0].title = game.i18n.localize("l5r5e.chatdices.dicepicker");
|
Hooks.on("renderCombatTracker", (app, html, data) => HooksL5r5e.renderCombatTracker(app, html, data));
|
||||||
|
Hooks.on("renderCompendium", async (app, html, data) => HooksL5r5e.renderCompendium(app, html, data));
|
||||||
// Open Help dialog on clic on logo
|
|
||||||
$("#logo")
|
|
||||||
.on("click", () => new game.l5r5e.HelpDialog().render(true))
|
|
||||||
.prop("title", game.i18n.localize("l5r5e.logo.alt"));
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------ */
|
|
||||||
/* SidebarTab */
|
|
||||||
/* ------------------------------------ */
|
|
||||||
Hooks.on("renderSidebarTab", (app, html, data) => {
|
|
||||||
// Add button on dice icon
|
|
||||||
html.find(".chat-control-icon").click(async () => {
|
|
||||||
new game.l5r5e.DicePickerDialog().render();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------ */
|
|
||||||
/* Chat Message */
|
|
||||||
/* ------------------------------------ */
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ------------------------------------ */
|
|
||||||
/* DiceSoNice Hook */
|
|
||||||
/* ------------------------------------ */
|
|
||||||
Hooks.once("diceSoNiceReady", (dice3d) => {
|
|
||||||
const texturePath = `${CONFIG.l5r5e.paths.assets}dices/default/3d/`;
|
|
||||||
|
|
||||||
// dice3d.addSystem({
|
|
||||||
// id: "l5r5e",
|
|
||||||
// name: "Legend of the Five Rings 5E"
|
|
||||||
// }, "force");
|
|
||||||
|
|
||||||
// Rings
|
|
||||||
dice3d.addDicePreset(
|
|
||||||
{
|
|
||||||
name: "L5R Ring Dice",
|
|
||||||
type: "ddr", // don't known why the "dd" prefix is required, term is "r"
|
|
||||||
labels: Object.keys(RingDie.FACES).map(
|
|
||||||
(e) => `${texturePath}${RingDie.FACES[e].image.replace("ring_", "")}.png`
|
|
||||||
),
|
|
||||||
bumpMaps: Object.keys(RingDie.FACES).map(
|
|
||||||
(e) => `${texturePath}${RingDie.FACES[e].image.replace("ring_", "")}_bm.png`
|
|
||||||
),
|
|
||||||
colorset: "black",
|
|
||||||
system: "standard",
|
|
||||||
},
|
|
||||||
"d6"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Skills
|
|
||||||
dice3d.addDicePreset(
|
|
||||||
{
|
|
||||||
name: "L5R Skill Dice",
|
|
||||||
type: "dds",
|
|
||||||
labels: Object.keys(AbilityDie.FACES).map(
|
|
||||||
(e) => `${texturePath}${AbilityDie.FACES[e].image.replace("skill_", "")}.png`
|
|
||||||
),
|
|
||||||
bumpMaps: Object.keys(AbilityDie.FACES).map(
|
|
||||||
(e) => `${texturePath}${AbilityDie.FACES[e].image.replace("skill_", "")}_bm.png`
|
|
||||||
),
|
|
||||||
colorset: "white",
|
|
||||||
system: "standard",
|
|
||||||
},
|
|
||||||
"d12"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,4 +1,36 @@
|
|||||||
|
/**
|
||||||
export const RegisterSettings = function() {
|
* Custom system settings register
|
||||||
// Register any custom system settings here
|
*/
|
||||||
}
|
export const RegisterSettings = function () {
|
||||||
|
/**
|
||||||
|
* Settings set by Initiative Roll Dialog (GM only)
|
||||||
|
*/
|
||||||
|
game.settings.register("l5r5e", "initiative.difficulty.hidden", {
|
||||||
|
name: "Initiative difficulty is hidden",
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
game.settings.register("l5r5e", "initiative.difficulty.value", {
|
||||||
|
name: "Initiative difficulty value",
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: Number,
|
||||||
|
default: 2,
|
||||||
|
});
|
||||||
|
game.settings.register("l5r5e", "initiative.encounter", {
|
||||||
|
name: "Initiative encounter type",
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: String,
|
||||||
|
default: "skirmish",
|
||||||
|
});
|
||||||
|
game.settings.register("l5r5e", "initiative.prepared", {
|
||||||
|
name: "Initiative NPC prepared or not",
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export class SocketHandlerL5r5e {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh a app by it's id, not windowsId (ex "l5r5e-twenty-questions-dialog-kZHczAFghMNYFRWe", not "65")
|
* Refresh a app by it's htmlId, not windowsId (ex "l5r5e-twenty-questions-dialog-kZHczAFghMNYFRWe", not "65")
|
||||||
* usage : game.l5r5e.sockets.refreshAppId(appId);
|
* usage : game.l5r5e.sockets.refreshAppId(appId);
|
||||||
* @param appId
|
* @param appId
|
||||||
*/
|
*/
|
||||||
@@ -60,10 +60,7 @@ export class SocketHandlerL5r5e {
|
|||||||
}
|
}
|
||||||
_onRefreshAppId(data) {
|
_onRefreshAppId(data) {
|
||||||
const app = Object.values(ui.windows).find((e) => e.id === data.appId);
|
const app = Object.values(ui.windows).find((e) => e.id === data.appId);
|
||||||
if (!app) {
|
if (!app || typeof app.refresh !== "function") {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof app.refresh !== "function") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
app.refresh();
|
app.refresh();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -184,3 +184,39 @@
|
|||||||
border: 3px solid green;
|
border: 3px solid green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gm-tools-dialog {
|
||||||
|
bottom: 10px;
|
||||||
|
right: 5px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.window-content {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
.gm-tools-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
|
||||||
|
div {
|
||||||
|
flex: 1;
|
||||||
|
cursor: url("../assets/cursors/pointer.webp"), pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.difficulty {
|
||||||
|
font-size: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 60px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide "search anywhere" draggable icon
|
||||||
|
.window-draggable-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -568,30 +568,22 @@ button {
|
|||||||
h3 {
|
h3 {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dialog
|
.encounter {
|
||||||
.dialo {
|
i {
|
||||||
.logo-dialog {
|
font-size: 23px;
|
||||||
height: 125px;
|
vertical-align: middle;
|
||||||
.dialog-buttons {
|
}
|
||||||
button {
|
|
||||||
line-height: 1rem;
|
.active {
|
||||||
}
|
box-shadow: 0 1px 5px $l5r5e-red;
|
||||||
}
|
}
|
||||||
.dialog-content {
|
|
||||||
height: 1rem;
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
|
|
||||||
#pause {
|
#pause {
|
||||||
img {
|
img {
|
||||||
content: url("../assets/icons/pause.svg");
|
content: url("../assets/icons/pause.svg");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<fieldset class="initiative initiative-wrapper">
|
<fieldset class="initiative initiative-wrapper">
|
||||||
<legend class="section-header">{{ localize 'l5r5e.conflict.initiative.title' }}</legend>
|
<legend class="section-header">{{ localize 'l5r5e.conflict.initiative.title' }}</legend>
|
||||||
<button class="initiative dice-picker" data-skill="sentiment" data-diff="1">{{ localize 'l5r5e.conflict.initiative.intrigue'}}</button>
|
<button class="initiative dice-picker" data-skill="sentiment">{{ localize 'l5r5e.conflict.initiative.intrigue'}}</button>
|
||||||
<button class="initiative dice-picker" data-skill="meditation" data-diff="1">{{ localize 'l5r5e.conflict.initiative.duel'}}</button>
|
<button class="initiative dice-picker" data-skill="meditation">{{ localize 'l5r5e.conflict.initiative.duel'}}</button>
|
||||||
<button class="initiative dice-picker" data-skill="tactics" data-diff="1">{{ localize 'l5r5e.conflict.initiative.skirmish'}}</button>
|
<button class="initiative dice-picker" data-skill="tactics">{{ localize 'l5r5e.conflict.initiative.skirmish'}}</button>
|
||||||
<button class="initiative dice-picker" data-skill="command" data-diff="1">{{ localize 'l5r5e.conflict.initiative.mass_battle'}}</button>
|
<button class="initiative dice-picker" data-skill="command">{{ localize 'l5r5e.conflict.initiative.mass_battle'}}</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="stances-content flexrow">
|
<fieldset class="stances-content flexrow">
|
||||||
<legend class="section-header">{{ localize 'l5r5e.conflict.stance' }}</legend>
|
<legend class="section-header">{{ localize 'l5r5e.conflict.stance' }}</legend>
|
||||||
|
|||||||
11
system/templates/dice/gm-tools-dialog.html
Normal file
11
system/templates/dice/gm-tools-dialog.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<form class="l5r5e gm-tools-dialog" autocomplete="off">
|
||||||
|
<div class="gm-tools-container">
|
||||||
|
<div class="difficulty_hidden">
|
||||||
|
<i class="fa fa-eye{{#if data.difficultyHidden}}-slash{{/if}}"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="difficulty">
|
||||||
|
{{data.difficulty}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
Reference in New Issue
Block a user