added DP on technique (wip)

This commit is contained in:
Vlyan
2022-02-10 14:30:27 +01:00
parent 1afd85d3e9
commit b35fb852af
31 changed files with 1030 additions and 706 deletions

View File

@@ -279,4 +279,37 @@ export class ActorL5r5e extends Actor {
return isPrepared;
}
/**
* Return the Status Rank of this actor
* @return {number|null}
*/
get statusRank() {
if (!["character", "npc"].includes(this.data.type)) {
return null;
}
return Math.floor(this.data.data.social.status / 10);
}
/**
* Return the Intrigue Rank of this actor
* @return {number|null}
*/
get intrigueRank() {
if (!["character", "npc"].includes(this.data.type)) {
return null;
}
return this.data.type === "npc" ? this.data.data.conflict_rank.social : this.data.data.identity.school_rank;
}
/**
* Return the Martial Rank of this actor
* @return {number|null}
*/
get martialRank() {
if (!["character", "npc"].includes(this.data.type)) {
return null;
}
return this.data.type === "npc" ? this.data.data.conflict_rank.martial : this.data.data.identity.school_rank;
}
}

View File

@@ -277,33 +277,14 @@ export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
return;
}
// *** Dice event on Skills clic ***
html.find(".dice-picker").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
const li = $(event.currentTarget);
let skillId = li.data("skill") || null;
// Dice event on Skills clic
html.find(".dice-picker").on("click", this._openDicePickerForSkill.bind(this));
const weaponId = li.data("weapon-id") || null;
if (weaponId) {
skillId = this._getWeaponSkillId(weaponId);
}
new game.l5r5e.DicePickerDialog({
ringId: li.data("ring") || null,
skillId: skillId,
skillCatId: li.data("skillcat") || null,
isInitiativeRoll: li.data("initiative") || false,
actor: this.actor,
}).render(true);
});
// Dice event on Technique clic
html.find(".dice-picker-tech").on("click", this._openDicePickerForTechnique.bind(this));
// Prepared (Initiative)
html.find(".prepared-control").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
this._switchPrepared();
});
html.find(".prepared-control").on("click", this._switchPrepared.bind(this));
// Equipped / Readied
html.find(".equip-readied-control").on("click", this._switchEquipReadied.bind(this));
@@ -317,9 +298,13 @@ export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
/**
* Switch the state "prepared" (initiative)
* @param {Event} event
* @private
*/
_switchPrepared() {
_switchPrepared(event) {
event.preventDefault();
event.stopPropagation();
this.actor.data.data.prepared = !this.actor.data.data.prepared;
this.actor.update({
data: {
@@ -594,4 +579,68 @@ export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
}
return null;
}
/**
* Open the dice-picker for this skill
* @param {Event} event
* @private
*/
_openDicePickerForSkill(event) {
event.preventDefault();
event.stopPropagation();
const li = $(event.currentTarget);
let skillId = li.data("skill") || null;
const weaponId = li.data("weapon-id") || null;
if (weaponId) {
skillId = this._getWeaponSkillId(weaponId);
}
new game.l5r5e.DicePickerDialog({
ringId: li.data("ring") || null,
skillId: skillId,
skillCatId: li.data("skillcat") || null,
isInitiativeRoll: li.data("initiative") || false,
actor: this.actor,
}).render(true);
}
/**
* Open the dice-picker for this technique
* @param {Event} event
* @private
*/
_openDicePickerForTechnique(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id") || null;
const item = this.actor.items.get(itemId);
if (!item || item.type !== "technique" || !item.data.data.skill) {
return;
}
const itemData = item.data.data;
const difficulties = game.l5r5e.DicePickerDialog.parseDifficulty(this.actor, itemData.difficulty);
if (!difficulties) {
// do not block if no target or not found
difficulties.difficulty = null;
difficulties.difficultyHidden = null;
return;
}
const skills = game.l5r5e.DicePickerDialog.parseSkills(itemData.skill);
if (!skills) {
return;
}
console.log(difficulties, skills); // todo tmp
new game.l5r5e.DicePickerDialog({
actor: this.actor,
ringId: itemData.ring || null,
...difficulties,
...skills,
}).render(true);
}
}

View File

@@ -21,11 +21,11 @@ export class CharacterGeneratorDialog extends FormApplication {
generate: {
attributes: true,
demeanor: true,
identity: true,
items: true,
name: true,
narrative: true,
peculiarities: true,
social: true,
techniques: true,
},
};
@@ -40,7 +40,7 @@ export class CharacterGeneratorDialog extends FormApplication {
classes: ["l5r5e", "character-generator-dialog"],
template: CONFIG.l5r5e.paths.templates + "actors/character-generator-dialog.html",
title: game.i18n.localize("l5r5e.char_generator.title"),
// width: 600,
width: 450,
// height: 360,
resizable: false,
closeOnSubmit: false,
@@ -72,13 +72,9 @@ export class CharacterGeneratorDialog extends FormApplication {
const actorDatas = this.actor.data.data;
// Identity
//this.object.age = actorDatas.age;
this.object.clan = actorDatas.identity.clan || "random";
this.object.gender = actorDatas.identity.female
? "female"
: actorDatas.identity.female === false
? "male"
: "random";
this.object.gender =
actorDatas.identity.female === null ? "random" : actorDatas.identity.female ? "female" : "male";
// Rings
this.object.avgRings = CharacterGenerator.sanitizeMinMax(
@@ -106,8 +102,8 @@ export class CharacterGeneratorDialog extends FormApplication {
clanList: [{ id: "random", label: game.i18n.localize("l5r5e.random") }, ...clans],
genderList: [
{ id: "random", label: game.i18n.localize("l5r5e.random") },
{ id: "male", label: game.i18n.localize("l5r5e.gender.male") },
{ id: "female", label: game.i18n.localize("l5r5e.gender.female") },
{ id: "male", label: game.i18n.localize("l5r5e.social.gender.male") },
{ id: "female", label: game.i18n.localize("l5r5e.social.gender.female") },
],
data: this.object,
};

View File

@@ -87,9 +87,6 @@ export class CharacterGenerator {
family: "",
gender: "male",
age: 15,
honor: 30,
glory: 30,
status: 30,
maritalStatus: "",
};
@@ -114,27 +111,24 @@ export class CharacterGenerator {
this.data.clan = clanName;
this.data.family = CharacterGenerator._getRandomFamily(clanName);
this.data.gender = gender;
this.data.age = CharacterGenerator._randomInt(15, this.data.avgRingsValue * 10 + 15);
this.genSocialStanding();
this.data.maritalStatus = this.genMaritalStatus();
}
/**
* Return true if the gender is male
* Return true if the gender is Female
* @return {boolean}
*/
get isMale() {
return this.data.gender === "male";
get isFemale() {
return this.data.gender === "female";
}
/**
* Return a random value for this array
* @param array
* @return {*}
* @return {String}
* @private
*/
static _getRandomArrayValue(array) {
return array[Math.floor(Math.random() * array.length)];
return array[Math.floor(Math.random() * array.length)] ?? "";
}
/**
@@ -203,30 +197,63 @@ export class CharacterGenerator {
* @return {Promise<string>}
*/
async getRandomizedName() {
let table = `Japanese names (${this.isMale ? "Male" : "Female"})`;
// switch (this.data.clan) {
// case "ivory_kingdoms": table = 'Ivory kingdoms names'; break;
// case "qamarist": table = 'Qamarist names'; break;
// case "ujik": table = 'Ujik names'; break;
// }
let table = `Japanese names (${this.isFemale ? "Female" : "Male"})`;
switch (this.data.clan) {
case "ivory_kingdoms":
table = "Ivory Kingdoms names";
break;
case "qamarist":
table = "Qamarist names";
break;
case "ujik":
table = "Ujik names";
break;
}
const randomNames = await game.l5r5e.HelpersL5r5e.drawManyFromPack("l5r5e.core-name-tables", table, 1, {
displayChat: false,
});
return this.data.family + " " + randomNames?.results[0]?.data.text || "";
return this.data.family + " " + (randomNames?.results[0]?.data.text || "");
}
/**
* Generate the actor age
* @param {number} avgRingsValue
* @return {number}
*/
static genAge(avgRingsValue) {
return CharacterGenerator._randomInt(15, avgRingsValue * 10 + 15);
}
/**
* Return the marriage state
* @param {number} age
* @return {string} unmarried|betrothed|married|widowed
*/
static genMaritalStatus(age) {
const rng = Math.random();
if (age < 20) {
return rng < 0.1 ? "married" : rng < 0.4 ? "betrothed" : "unmarried";
}
if (age < 30) {
return rng < 0.4 ? "married" : rng < 0.7 ? "betrothed" : "unmarried";
}
return rng < 0.8 ? "married" : rng < 0.9 ? "widowed" : "unmarried";
}
/**
* Generate Honor, Glory and Status values
* @param {number} age
* @param {string} clan
* @return {{honor: number, glory: number, status: number}}
*/
genSocialStanding() {
static genSocialStanding(age, clan) {
const karma = Math.random() < 0.66 ? 1 : -1;
const rng = (initial, variation) => {
return initial + CharacterGenerator._randomInt(5, variation) * karma;
};
let honor = rng(34, this.data.age / 2);
switch (this.data.clan) {
let honor = rng(34, age / 2);
switch (clan) {
case "lion":
honor += 10;
break;
@@ -234,24 +261,11 @@ export class CharacterGenerator {
honor -= 10;
break;
}
this.data.honor = honor;
this.data.glory = rng(40, this.data.age / 3);
this.data.status = rng(30, this.data.age / 4);
}
/**
* Return the marriage state
* @return {string} unmarried|betrothed|married|widowed
*/
genMaritalStatus() {
const rng = Math.random();
if (this.data.age < 20) {
return rng < 0.1 ? "married" : rng < 0.4 ? "betrothed" : "unmarried";
}
if (this.data.age < 30) {
return rng < 0.4 ? "married" : rng < 0.7 ? "betrothed" : "unmarried";
}
return rng < 0.8 ? "married" : rng < 0.9 ? "widowed" : "unmarried";
return {
honor,
glory: rng(40, age / 3),
status: rng(30, age / 4),
};
}
/**
@@ -260,8 +274,8 @@ export class CharacterGenerator {
* @param {ActorL5r5e} actor Actor object
* @param {Object} generate
* @param {boolean} generate.name If true generate a new name
* @param {boolean} generate.attributes If true generate rings, attributes, skills and confrontation ranks
* @param {boolean} generate.social If true generate Social Standing
* @param {boolean} generate.identity If true generate Clan, Gender, Age, Marital status
* @param {boolean} generate.attributes If true generate Rings, attributes, skills and confrontation ranks
* @param {boolean} generate.demeanor If true generate Demeanor and rings affinities
* @param {boolean} generate.peculiarities If true generate Advantage and Disadvantage
* @param {boolean} generate.items If true generate Armor, Weapons and Items
@@ -274,7 +288,7 @@ export class CharacterGenerator {
generate = {
name: true,
attributes: true,
social: true,
identity: true,
demeanor: true,
peculiarities: true,
items: true,
@@ -284,36 +298,45 @@ export class CharacterGenerator {
) {
const actorDatas = actor.data.data;
console.log(generate); // TODO tmp
// Need to set some required values
this.data.age = actorDatas.identity.age || CharacterGenerator.genAge(this.data.avgRingsValue);
this.data.maritalStatus =
actorDatas.identity.marital_status || CharacterGenerator.genMaritalStatus(this.data.age);
actorDatas.identity.clan = this.data.clan;
actorDatas.identity.family = this.data.family;
actorDatas.identity.female = this.isFemale;
// Name
const newName = generate.name ? await this.getRandomizedName() : actor.data.name;
actorDatas.identity.age = this.data.age;
actorDatas.identity.clan = this.data.clan;
actorDatas.identity.family = this.data.family;
actorDatas.identity.female = this.data.gender === "female";
// Identity
if (generate.identity) {
actorDatas.identity.age = CharacterGenerator.genAge(this.data.avgRingsValue);
actorDatas.identity.marital_status = CharacterGenerator.genMaritalStatus(this.data.age);
this._generateNotes(actorDatas);
}
// Img (only if default)
// Img (only if system defaults)
const folder = "systems/l5r5e/assets/icons/actors";
const newImg = [
`${folder}/npc.svg`,
`${folder}/traditional-japanese-man.svg`,
`${folder}/traditional-japanese-woman.svg`,
].includes(actor.data.img)
? `${folder}/traditional-japanese-${this.isMale ? "man" : "woman"}.svg`
? `${folder}/traditional-japanese-${this.isFemale ? "woman" : "man"}.svg`
: actor.data.img;
// Generate attributes (rings, attributes, skills, confrontation ranks)
// Generate attributes & Social Standing
if (generate.attributes) {
// Generate attributes (rings, attributes, skills, confrontation ranks)
this._generateAttributes(actorDatas);
}
// Social Standing
if (generate.social) {
actorDatas.social.honor = this.data.honor;
actorDatas.social.glory = this.data.glory;
actorDatas.social.status = this.data.status;
// Social Standing
const social = CharacterGenerator.genSocialStanding(this.data.age, this.data.clan);
actorDatas.social.honor = social.honor;
actorDatas.social.glory = social.glory;
actorDatas.social.status = social.status;
}
// Demeanor (npc only)
@@ -321,7 +344,7 @@ export class CharacterGenerator {
this._generateDemeanor(actorDatas);
}
// Items types
// Item types
if (generate.peculiarities || generate.items || generate.techniques) {
const newItemsData = [];
@@ -346,7 +369,7 @@ export class CharacterGenerator {
}
}
// Narrative
// TODO Narrative
if (generate.narrative) {
this._generateNarrative(actorDatas);
}
@@ -358,8 +381,8 @@ export class CharacterGenerator {
data: actorDatas,
};
}
//<editor-fold desc="toActor generators">
/**
* Generate attributes (rings, attributes, skills, confrontation ranks)
* @param {DocumentData.data} actorDatas
@@ -386,9 +409,11 @@ export class CharacterGenerator {
(skillName) => (actorDatas.skills[skillName] = Math.floor(Math.random() * stats.max))
);
// Confrontation ranks
actorDatas.conflict_rank.martial = this.data.avgRingsValue + actorDatas.skills.martial;
actorDatas.conflict_rank.social = this.data.avgRingsValue + actorDatas.skills.social;
// Confrontation ranks (npc only)
if (actorDatas.conflict_rank) {
actorDatas.conflict_rank.martial = this.data.avgRingsValue + actorDatas.skills.martial;
actorDatas.conflict_rank.social = this.data.avgRingsValue + actorDatas.skills.social;
}
}
/**
@@ -594,27 +619,30 @@ export class CharacterGenerator {
} // fr techCfg
}
/**
* Fill notes with some values that don't appear in sheet
* @param {DocumentData.data} actorDatas
* @private
*/
_generateNotes(actorDatas) {
actorDatas.notes =
`<p>${game.i18n.localize("l5r5e.social.age")}: ${this.data.age}</p>` +
`<p>${game.i18n.localize("l5r5e.social.gender.title")}: ${game.i18n.localize(
"l5r5e.social.gender." + this.data.gender
)}</p>` +
`<p>${game.i18n.localize("l5r5e.clan")}: ${game.i18n.localize("l5r5e.clans." + this.data.clan)}</p>` +
`<p>${game.i18n.localize("l5r5e.social.marital_status.title")}: ${game.i18n.localize(
"l5r5e.social.marital_status." + this.data.maritalStatus
)}</p>`;
}
/**
* Generate Narrative fluff
* @param {DocumentData.data} actorDatas
* @private
*/
_generateNarrative(actorDatas) {
// Fill notes with some values that don't appear in sheet
actorDatas.notes =
`<p>${game.i18n.localize("l5r5e.char_generator.age")}: ${this.data.age}</p>` +
`<p>${game.i18n.localize("l5r5e.gender.title")}: ${game.i18n.localize(
"l5r5e.gender." + this.data.gender
)}</p>` +
`<p>${game.i18n.localize("l5r5e.clan")}: ${game.i18n.localize("l5r5e.clans." + this.data.clan)}</p>` +
`<p>${game.i18n.localize("l5r5e.char_generator.marital_status.title")}: ${game.i18n.localize(
"l5r5e.char_generator.marital_status." + this.data.maritalStatus
)}</p>`;
// data: {
// "notes": "",
// "description": "",
// },
// actorDatas.description = '';
}
//</editor-fold>
}

View File

@@ -140,3 +140,7 @@ L5R5E.families.set("oriole", ["Tsi"]);
L5R5E.families.set("ox", ["Morito"]);
L5R5E.families.set("sparrow", ["Suzume"]);
L5R5E.families.set("tortoise", ["Kasuga"]);
// External
L5R5E.families.set("ivory_kingdoms", []);
L5R5E.families.set("qamarist", []);
L5R5E.families.set("ujik", []);

View File

@@ -11,10 +11,14 @@ export class DicePickerDialog extends FormApplication {
_actor = null;
/**
* If GM as set to hidden, lock the player choice so he cannot look the TN
* @type {boolean}
* If GM or Constructor set to hidden, lock the player choice, so he cannot look the TN
* @type {{gm: boolean, option: boolean}}
* @private
*/
_difficultyHiddenIsLock = false;
_difficultyHiddenIsLock = {
gm: false,
option: false,
};
/**
* Payload Object
@@ -135,6 +139,9 @@ export class DicePickerDialog extends FormApplication {
}
// DifficultyHidden
if (options.difficultyHidden) {
this._difficultyHiddenIsLock.option = true;
}
this.difficultyHidden = !!options.difficultyHidden;
// InitiativeRoll
@@ -229,8 +236,8 @@ export class DicePickerDialog extends FormApplication {
* @param difficulty
*/
set difficulty(difficulty) {
difficulty = parseInt(difficulty) || null;
if (difficulty < 0) {
difficulty = parseInt(difficulty);
if (isNaN(difficulty) || difficulty < 0) {
difficulty = 2;
}
this.object.difficulty.value = difficulty;
@@ -242,8 +249,8 @@ export class DicePickerDialog extends FormApplication {
*/
set difficultyHidden(isHidden) {
// If GM hide, then player choice don't matter
this._difficultyHiddenIsLock = game.settings.get("l5r5e", "initiative-difficulty-hidden");
if (this._difficultyHiddenIsLock) {
this._difficultyHiddenIsLock.gm = game.settings.get("l5r5e", "initiative-difficulty-hidden");
if (this._difficultyHiddenIsLock.gm || this._difficultyHiddenIsLock.option) {
isHidden = true;
}
this.object.difficulty.hidden = !!isHidden;
@@ -274,7 +281,7 @@ export class DicePickerDialog extends FormApplication {
canUseVoidPoint:
this.object.difficulty.addVoidPoint || !this._actor || this._actor.data.data.void_points.value > 0,
disableSubmit: this.object.skill.value < 1 && this.object.ring.value < 1,
difficultyHiddenIsLock: this._difficultyHiddenIsLock,
difficultyHiddenIsLock: this._difficultyHiddenIsLock.gm || this._difficultyHiddenIsLock.option,
};
}
@@ -553,4 +560,150 @@ export class DicePickerDialog extends FormApplication {
return game.user.assignHotbarMacro(macro, "auto"); // 1st available
}
/**
* Parse the difficulty from technique
*
* Exemples :
* "@T:vigilance"
* "@T:vigilance|min"
* "@T:vigilance|max"
* "@T:vigilance|max(statusRank)"
* "@T:intrigueRank"
* "@T:martialRank"
* "@T:statusRank|max"
* "@T:strife.value|max"
*
* @param {string|number} difficulty
* @return {{difficulty: number, difficultyHidden: boolean}|boolean}
*/
static parseDifficulty(actor, difficulty) {
const out = {
difficulty: null,
difficultyHidden: null,
};
// Macro style
if (!Number.isNumeric(difficulty) && difficulty.startsWith("@")) {
// 0: "@T:vigilance|max(statusRank)"
// 1: "T" // Meaning : S(elf), T(arget)
// 2: "vigilance"
// 3: "max"
// 4: "statusRank"
const infos = difficulty.match(/^@([TS]):([^|]+?)(?:\|(min|max)(?:\(([^)]+?)\))?)?$/);
// Search for reference actor
if (!infos || ((infos[1] === "T" || !!infos[3]) && game.user.targets.size < 1)) {
// no target set, do manual TN
return out;
}
// Define which actor is needed for the difficulty
let targetActor;
if (infos[1] === "S") {
targetActor = actor;
} else {
// Between the targets
targetActor = DicePickerDialog._getTargetActorFromSelection(
infos[4] || infos[2],
!infos[3] ? null : infos[3] === "min"
);
}
if (!targetActor) {
console.log("L5R5E | Fail to get actor from target selection");
return out;
}
// Check in actor.<prop> or actor.data.data.<prop>
difficulty = targetActor[infos[2]] || targetActor.data.data[infos[2]] || null;
if (difficulty < 1) {
console.log("L5R5E | Fail to parse difficulty from target");
return out;
}
// Hide npc stats on target
if (infos[1] === "T") {
out.difficultyHidden = true;
}
}
// fallback
out.difficulty = parseInt(difficulty);
if (isNaN(out.difficulty) || out.difficulty < 0) {
out.difficulty = null;
}
return out;
}
/**
* Parse Skills from technique
* @param {string} skills
* @return {{skillId: number, skillCatId: number}|boolean}
*/
static parseSkills(skills) {
// Check category
const categories = game.l5r5e.HelpersL5r5e.getCategoriesSkillsList();
const categories2 = game.l5r5e.HelpersL5r5e.getSkillsList(true);
// Expand category (social) to it's skillname (command,courtesy...)
// const skillList = [];
// skills.split(',').forEach(e => {
// if () {
// table.push(e);
// }
// });
console.log(categories, categories2);
// Check skill
return {
skillId: skills,
// skillCatId,
};
}
/**
* Return the actor who have the min/max value for this property
* @param {string} property Property name (vigilance, strife.value)
* @param {boolean|null} isMin Null: single target, Min/Max: get the actor who have the max value
* @return {null|*}
* @private
*/
static _getTargetActorFromSelection(property, isMin = null) {
if (game.user.targets.size < 1) {
return null;
}
let targetActor;
if (isMin === null) {
// only one target, get the first element
targetActor = Array.from(game.user.targets).values().next()?.value.document.actor;
} else {
// Group (Min/Max)
const targetGrp = Array.from(game.user.targets).reduce(
(acc, tgt) => {
const targetActor = tgt.document.actor;
if (!["character", "npc"].includes(targetActor.type)) {
return acc;
}
const targetData = targetActor.data.data;
const value = targetActor[property] || targetData[property] || null;
if (!value) {
return acc;
}
if ((isMin && value < acc.value) || (!isMin && value > acc.value)) {
acc.actor = targetActor;
acc.value = value;
}
return acc;
},
{ actor: null, value: 0 }
);
targetActor = targetGrp.actor;
}
return targetActor;
}
}

View File

@@ -2,42 +2,46 @@
* Custom Handlebars for L5R5e
*/
export const RegisterHandlebars = function () {
const sanitizeIfFail = (str) => {
return str.indexOf("l5r5e.") !== -1 && str.indexOf("undefined") ? "" : str;
};
/* ------------------------------------ */
/* Localizations */
/* ------------------------------------ */
Handlebars.registerHelper("localizeSkill", function (categoryId, skillId) {
const key = "l5r5e.skills." + categoryId.toLowerCase() + "." + skillId.toLowerCase();
return game.i18n.localize(key);
return sanitizeIfFail(game.i18n.localize(key));
});
Handlebars.registerHelper("localizeSkillId", function (skillId) {
const key = "l5r5e.skills." + CONFIG.l5r5e.skills.get(skillId.toLowerCase()) + "." + skillId.toLowerCase();
return game.i18n.localize(key);
return sanitizeIfFail(game.i18n.localize(key));
});
Handlebars.registerHelper("localizeRing", function (ringId) {
const key = "l5r5e.rings." + ringId.toLowerCase();
return game.i18n.localize(key);
return sanitizeIfFail(game.i18n.localize(key));
});
Handlebars.registerHelper("localizeStanceTip", function (ringId) {
const key = "l5r5e.conflict.stances." + ringId.toLowerCase() + "tip";
return game.i18n.localize(key);
return sanitizeIfFail(game.i18n.localize(key));
});
Handlebars.registerHelper("localizeTechnique", function (techniqueName) {
return game.i18n.localize("l5r5e.techniques." + techniqueName.toLowerCase());
return sanitizeIfFail(game.i18n.localize("l5r5e.techniques." + techniqueName.toLowerCase()));
});
Handlebars.registerHelper("localizeYesNo", function (isYes) {
return game.i18n.localize(isYes ? "Yes" : "No");
return sanitizeIfFail(game.i18n.localize(isYes ? "Yes" : "No"));
});
/* ------------------------------------ */
/* Dice */
/* ------------------------------------ */
Handlebars.registerHelper("getDiceFaceUrl", function (diceClass, faceId) {
return game.l5r5e[diceClass].getResultSrc(faceId);
return sanitizeIfFail(game.l5r5e[diceClass].getResultSrc(faceId));
});
/* ------------------------------------ */

View File

@@ -43,6 +43,21 @@ export class HelpersL5r5e {
return skills;
}
/**
* Return Categories and Skill names in it
* @return {Map}
*/
static getCategoriesSkillsList() {
return Array.from(CONFIG.l5r5e.skills).reduce((acc, [id, cat]) => {
if (acc.has(cat)) {
acc.set(cat, [...acc.get(cat), id]);
} else {
acc.set(cat, [id]);
}
return acc;
}, new Map());
}
/**
* Get Techniques for List / Select
* @param types core|school|title|custom