Added Army/Cohort/Fortification raw sheet and js

This commit is contained in:
Vlyan
2021-10-01 19:51:43 +02:00
parent 0491bcc96e
commit b5a21f950e
30 changed files with 1288 additions and 576 deletions

View File

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

View File

@@ -0,0 +1,78 @@
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: "cohort" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
});
}
/** @inheritdoc */
getData(options = {}) {
const sheetData = super.getData(options);
// Split Items by types
sheetData.data.splitItemsList = this._splitItems(sheetData);
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 data on the Actor sheet
* @param {DragEvent} event
*/
async _onDrop(event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Check item type and subtype
const item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || item.documentName !== "Item" || !["army_cohort", "army_fortification"].includes(item.data.type)) {
console.warn("L5R5E | Wrong type", item.data.type);
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.data.toObject(true);
// Finally create the embed
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
}

View File

@@ -0,0 +1,547 @@
import { BaseSheetL5r5e } from "./base-sheet.js";
/**
* Base Sheet for Character types (Character and Npc)
*/
export class BaseCharacterSheetL5r5e extends BaseSheetL5r5e {
/**
* Commons options
*/
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "actor"],
// template: CONFIG.l5r5e.paths.templates + "actors/character-sheet.html",
width: 600,
height: 800,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
});
}
/** @inheritdoc */
getData(options = {}) {
const sheetData = super.getData(options);
sheetData.data.stances = CONFIG.l5r5e.stances;
sheetData.data.techniquesList = game.l5r5e.HelpersL5r5e.getTechniquesList({ displayInTypes: true });
// Split Techniques by types
sheetData.data.splitTechniquesList = this._splitTechniques(sheetData);
// Split Items by types
sheetData.data.splitItemsList = this._splitItems(sheetData);
return sheetData;
}
/**
* Split Techniques by types for better readability
* @private
*/
_splitTechniques(sheetData) {
const out = {};
const schoolTechniques = Array.from(CONFIG.l5r5e.techniques)
.filter(([id, cfg]) => cfg.type === "school")
.map(([id, cfg]) => id);
// Build the list order
Array.from(CONFIG.l5r5e.techniques)
.filter(([id, cfg]) => cfg.type !== "custom" || game.settings.get("l5r5e", "techniques-customs"))
.forEach(([id, cfg]) => {
out[id] = [];
});
// Add tech the character knows
sheetData.items.forEach((item) => {
switch (item.type) {
case "technique":
if (!out[item.data.technique_type]) {
console.warn(
`L5R5E | Empty or unknown technique type[${item.data.technique_type}] forced to "kata" in item id[${item._id}], name[${item.name}]`
);
item.data.technique_type = "kata";
}
out[item.data.technique_type].push(item);
break;
case "title":
// Embed technique in titles
Array.from(item.data.items).forEach(([id, embedItem]) => {
if (embedItem.data.type === "technique") {
if (!out[embedItem.data.data.technique_type]) {
console.warn(
`L5R5E | Empty or unknown technique type[${embedItem.data.data.technique_type}] forced to "kata" in item id[${id}], name[${embedItem.data.name}], parent: id[${item._id}], name[${item.name}]`
);
embedItem.data.data.technique_type = "kata";
}
out[embedItem.data.data.technique_type].push(embedItem.data);
}
});
// If unlocked, add the "title_ability" as technique (or always displayed for npc)
if (item.data.xp_used >= item.data.xp_cost || this.document.type === "npc") {
out["title_ability"].push(item);
}
break;
} //swi
});
// Remove unused techs
Object.keys(out).forEach((tech) => {
if (out[tech].length < 1 && !sheetData.data.data.techniques[tech] && !schoolTechniques.includes(tech)) {
delete out[tech];
}
});
// Manage school add button
sheetData.data.data.techniques["school_ability"] = out["school_ability"].length === 0;
sheetData.data.data.techniques["mastery_ability"] = out["mastery_ability"].length === 0;
// Always display "school_ability", but display a empty "mastery_ability" field only if rank >= 5
if (sheetData.data.data.identity?.school_rank < 5 && out["mastery_ability"].length === 0) {
delete out["mastery_ability"];
}
return out;
}
/**
* Split Items by types for better readability
* @private
*/
_splitItems(sheetData) {
const out = {
weapon: [],
armor: [],
item: [],
};
sheetData.items.forEach((item) => {
if (["item", "armor", "weapon"].includes(item.type)) {
out[item.type].push(item);
}
});
return out;
}
/**
* Handle dropped data on the Actor sheet
* @param {DragEvent} event
*/
async _onDrop(event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Check item type and subtype
const item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || !["Item", "JournalEntry"].includes(item.documentName) || item.data.type === "property") {
return;
}
// Specific curriculum journal drop
if (item.documentName === "JournalEntry") {
// npc does not have this
if (!this.actor.data.data.identity?.school_curriculum_journal) {
return;
}
this.actor.data.data.identity.school_curriculum_journal = {
id: item.data._id,
name: item.data.name,
pack: item.pack || null,
};
await this.actor.update({
data: {
identity: {
school_curriculum_journal: this.actor.data.data.identity.school_curriculum_journal,
},
},
});
return;
}
// Dropped a item with same "id" as one owned
if (this.actor.data.items) {
// Exit if we already owned exactly this id (drag a personal item on our own sheet)
if (
this.actor.data.items.some((embedItem) => {
// Search in children
if (embedItem.items instanceof Map && embedItem.items.has(item.data._id)) {
return true;
}
return embedItem.data._id === item.data._id;
})
) {
return;
}
// Add quantity instead if they have (id is different so use type and name)
if (item.data.data.quantity) {
const tmpItem = this.actor.data.items.find(
(embedItem) => embedItem.name === item.data.name && embedItem.type === item.data.type
);
if (tmpItem && this._modifyQuantity(tmpItem.id, 1)) {
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.data.toObject(true);
// Item subtype specific
switch (itemData.type) {
case "advancement":
// Specific advancements, remove 1 to selected ring/skill
await this.actor.addBonus(item);
break;
case "title":
// Generate new Ids for the embed items
await item.generateNewIdsForAllEmbedItems();
// Add embed advancements bonus
for (let [embedId, embedItem] of item.data.data.items) {
if (embedItem.data.type === "advancement") {
await this.actor.addBonus(embedItem);
}
}
// refresh data
itemData = item.data.toObject(true);
break;
case "technique":
// School_ability and mastery_ability, allow only 1 per type
if (CONFIG.l5r5e.techniques.get(itemData.data.technique_type)?.type === "school") {
if (
Array.from(this.actor.items).some((e) => {
return (
e.type === "technique" && e.data.data.technique_type === itemData.data.technique_type
);
})
) {
ui.notifications.info(game.i18n.localize("l5r5e.techniques.only_one"));
return;
}
// No cost for schools
itemData.data.xp_cost = 0;
itemData.data.xp_used = 0;
itemData.data.in_curriculum = true;
} else {
// Check if technique is allowed for this character
if (!game.user.isGM && !this.actor.data.data.techniques[itemData.data.technique_type]) {
ui.notifications.info(game.i18n.localize("l5r5e.techniques.not_allowed"));
return;
}
// Verify cost
itemData.data.xp_cost =
itemData.data.xp_cost > 0 ? itemData.data.xp_cost : CONFIG.l5r5e.xp.techniqueCost;
itemData.data.xp_used = itemData.data.xp_cost;
}
break;
}
// Modify the bought at rank to the current actor rank
if (itemData.data.bought_at_rank !== undefined && this.actor.data.data.identity?.school_rank) {
itemData.data.bought_at_rank = this.actor.data.data.identity.school_rank;
}
// Finally create the embed
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
/**
* 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;
}
// *** 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;
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);
});
// Prepared (Initiative)
html.find(".prepared-control").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
this._switchPrepared();
});
// Equipped / Readied
html.find(".equip-readied-control").on("click", this._switchEquipReadied.bind(this));
// Others Advancements
html.find(".item-advancement-choose").on("click", this._showDialogAddSubItem.bind(this));
}
/**
* Switch the state "prepared" (initiative)
* @private
*/
_switchPrepared() {
this.actor.data.data.prepared = !this.actor.data.data.prepared;
this.actor.update({
data: {
prepared: this.actor.data.data.prepared,
},
});
this.render(false);
}
/**
* Add a generic item with sub type
* @param {string} type Item sub type (armor, weapon, bond...)
* @param {boolean} isEquipped For item with prop "Equipped" set the value
* @param {string|null} techniqueType Technique subtype (kata, shuji...)
* @return {Promise<void>}
* @private
*/
async _createSubItem({ type, isEquipped = false, techniqueType = null }) {
if (!type) {
return;
}
const created = await this.actor.createEmbeddedDocuments("Item", [
{
name: game.i18n.localize(`ITEM.Type${type.capitalize()}`),
type: type,
img: `${CONFIG.l5r5e.paths.assets}icons/items/${type}.svg`,
},
]);
if (created?.length < 1) {
return;
}
const item = this.actor.items.get(created[0].id);
// Assign current school rank to the new adv/tech
if (this.actor.data.data.identity?.school_rank) {
item.data.data.bought_at_rank = this.actor.data.data.identity.school_rank;
if (["advancement", "technique"].includes(item.data.type)) {
item.data.data.rank = this.actor.data.data.identity.school_rank;
}
}
switch (item.data.type) {
case "item": // no break
case "armor": // no break
case "weapon":
item.data.data.equipped = isEquipped;
break;
case "technique": {
// If technique, select the current sub-type
if (CONFIG.l5r5e.techniques.get(techniqueType)) {
item.data.name = game.i18n.localize(`l5r5e.techniques.${techniqueType}`);
item.data.img = `${CONFIG.l5r5e.paths.assets}icons/techs/${techniqueType}.svg`;
item.data.data.technique_type = techniqueType;
}
break;
}
}
item.sheet.render(true);
}
/**
* Display a dialog to choose what Item to add
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _showDialogAddSubItem(event) {
game.l5r5e.HelpersL5r5e.showSubItemDialog(["bond", "title", "signature_scroll", "item_pattern"]).then(
(selectedType) => {
this._createSubItem({ type: selectedType });
}
);
}
/**
* Add a generic item with sub type
* @param {Event} event
* @private
*/
async _addSubItem(event) {
event.preventDefault();
event.stopPropagation();
const type = $(event.currentTarget).data("item-type");
if (!type) {
return;
}
const isEquipped = $(event.currentTarget).data("item-equipped") || false;
const techniqueType = $(event.currentTarget).data("tech-type") || null;
return this._createSubItem({ type, isEquipped, techniqueType });
}
/**
* Delete a generic item with sub type
* @param {Event} event
* @private
*/
_deleteSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
if (!itemId) {
return;
}
const tmpItem = this.actor.items.get(itemId);
if (!tmpItem) {
return;
}
// Remove 1 qty if possible
if (tmpItem.data.data.quantity > 1 && this._modifyQuantity(tmpItem.id, -1)) {
return;
}
const callback = async () => {
switch (tmpItem.type) {
case "advancement":
// Remove advancements bonus (1 to selected ring/skill)
await this.actor.removeBonus(tmpItem);
break;
case "title":
// Remove embed advancements bonus
for (let [embedId, embedItem] of tmpItem.data.data.items) {
if (embedItem.data.type === "advancement") {
await this.actor.removeBonus(embedItem);
}
}
break;
}
return this.actor.deleteEmbeddedDocuments("Item", [itemId]);
};
// Holing Ctrl = without confirm
if (event.ctrlKey) {
return callback();
}
game.l5r5e.HelpersL5r5e.confirmDeleteDialog(
game.i18n.format("l5r5e.global.delete_confirm", { name: tmpItem.name }),
callback
);
}
/**
* Switch "in_curriculum"
* @param {Event} event
* @private
*/
_switchSubItemCurriculum(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.items.get(itemId);
if (item.type !== "item") {
item.update({
data: {
in_curriculum: !item.data.data.in_curriculum,
},
});
}
}
/**
* Add or subtract a quantity to a owned item
* @private
*/
_modifyQuantity(itemId, add) {
const tmpItem = this.actor.items.get(itemId);
if (tmpItem) {
tmpItem.data.data.quantity = Math.max(1, tmpItem.data.data.quantity + add);
tmpItem.update({
data: {
quantity: tmpItem.data.data.quantity,
},
});
return true;
}
return false;
}
/**
* Switch Readied state on a weapon
* @param {Event} event
* @private
*/
_switchEquipReadied(event) {
event.preventDefault();
event.stopPropagation();
const type = $(event.currentTarget).data("type");
if (!["equipped", "readied"].includes(type)) {
return;
}
const itemId = $(event.currentTarget).data("item-id");
const tmpItem = this.actor.items.get(itemId);
if (!tmpItem || tmpItem.data.data[type] === undefined) {
return;
}
tmpItem.data.data[type] = !tmpItem.data.data[type];
const data = {
equipped: tmpItem.data.data.equipped,
};
// Only weapons
if (tmpItem.data.data.readied !== undefined) {
data.readied = tmpItem.data.data.readied;
}
tmpItem.update({ data });
}
/**
* Get the skillId for this weaponId
* @private
*/
_getWeaponSkillId(weaponId) {
const item = this.actor.items.get(weaponId);
if (!!item && item.type === "weapon") {
return item.data.data.skill;
}
return null;
}
}

View File

@@ -6,7 +6,7 @@ export class BaseSheetL5r5e extends ActorSheet {
* Commons options
*/
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "actor"],
// template: CONFIG.l5r5e.paths.templates + "actors/character-sheet.html",
width: 600,
@@ -45,114 +45,15 @@ export class BaseSheetL5r5e extends ActorSheet {
const sheetData = super.getData(options);
sheetData.data.dtypes = ["String", "Number", "Boolean"];
sheetData.data.stances = CONFIG.l5r5e.stances;
sheetData.data.techniquesList = game.l5r5e.HelpersL5r5e.getTechniquesList({ displayInTypes: true });
// Sort Items by name
sheetData.items.sort((a, b) => {
return a.name.localeCompare(b.name);
});
// Split Techniques by types
sheetData.data.splitTechniquesList = this._splitTechniques(sheetData);
// Split Items by types
sheetData.data.splitItemsList = this._splitItems(sheetData);
return sheetData;
}
/**
* Split Techniques by types for better readability
* @private
*/
_splitTechniques(sheetData) {
const out = {};
const schoolTechniques = Array.from(CONFIG.l5r5e.techniques)
.filter(([id, cfg]) => cfg.type === "school")
.map(([id, cfg]) => id);
// Build the list order
Array.from(CONFIG.l5r5e.techniques)
.filter(([id, cfg]) => cfg.type !== "custom" || game.settings.get("l5r5e", "techniques-customs"))
.forEach(([id, cfg]) => {
out[id] = [];
});
// Add tech the character knows
sheetData.items.forEach((item) => {
switch (item.type) {
case "technique":
if (!out[item.data.technique_type]) {
console.warn(
`L5R5E | Empty or unknown technique type[${item.data.technique_type}] forced to "kata" in item id[${item._id}], name[${item.name}]`
);
item.data.technique_type = "kata";
}
out[item.data.technique_type].push(item);
break;
case "title":
// Embed technique in titles
Array.from(item.data.items).forEach(([id, embedItem]) => {
if (embedItem.data.type === "technique") {
if (!out[embedItem.data.data.technique_type]) {
console.warn(
`L5R5E | Empty or unknown technique type[${embedItem.data.data.technique_type}] forced to "kata" in item id[${id}], name[${embedItem.data.name}], parent: id[${item._id}], name[${item.name}]`
);
embedItem.data.data.technique_type = "kata";
}
out[embedItem.data.data.technique_type].push(embedItem.data);
}
});
// If unlocked, add the "title_ability" as technique (or always displayed for npc)
if (item.data.xp_used >= item.data.xp_cost || this.document.type === "npc") {
out["title_ability"].push(item);
}
break;
} //swi
});
// Remove unused techs
Object.keys(out).forEach((tech) => {
if (out[tech].length < 1 && !sheetData.data.data.techniques[tech] && !schoolTechniques.includes(tech)) {
delete out[tech];
}
});
// Manage school add button
sheetData.data.data.techniques["school_ability"] = out["school_ability"].length === 0;
sheetData.data.data.techniques["mastery_ability"] = out["mastery_ability"].length === 0;
// Always display "school_ability", but display a empty "mastery_ability" field only if rank >= 5
if (sheetData.data.data.identity?.school_rank < 5 && out["mastery_ability"].length === 0) {
delete out["mastery_ability"];
}
return out;
}
/**
* Split Items by types for better readability
* @private
*/
_splitItems(sheetData) {
const out = {
weapon: [],
armor: [],
item: [],
};
sheetData.items.forEach((item) => {
if (["item", "armor", "weapon"].includes(item.type)) {
out[item.type].push(item);
}
});
return out;
}
/**
* Return a light sheet if in "limited" state
* @override
@@ -196,141 +97,6 @@ export class BaseSheetL5r5e extends ActorSheet {
return super._updateObject(event, formData);
}
/**
* Handle dropped data on the Actor sheet
* @param {DragEvent} event
*/
async _onDrop(event) {
// *** Everything below here is only needed if the sheet is editable ***
if (!this.isEditable) {
return;
}
// Check item type and subtype
const item = await game.l5r5e.HelpersL5r5e.getDragnDropTargetObject(event);
if (!item || !["Item", "JournalEntry"].includes(item.documentName) || item.data.type === "property") {
return;
}
// Specific curriculum journal drop
if (item.documentName === "JournalEntry") {
// npc does not have this
if (!this.actor.data.data.identity?.school_curriculum_journal) {
return;
}
this.actor.data.data.identity.school_curriculum_journal = {
id: item.data._id,
name: item.data.name,
pack: item.pack || null,
};
await this.actor.update({
data: {
identity: {
school_curriculum_journal: this.actor.data.data.identity.school_curriculum_journal,
},
},
});
return;
}
// Dropped a item with same "id" as one owned
if (this.actor.data.items) {
// Exit if we already owned exactly this id (drag a personal item on our own sheet)
if (
this.actor.data.items.some((embedItem) => {
// Search in children
if (embedItem.items instanceof Map && embedItem.items.has(item.data._id)) {
return true;
}
return embedItem.data._id === item.data._id;
})
) {
return;
}
// Add quantity instead if they have (id is different so use type and name)
if (item.data.data.quantity) {
const tmpItem = this.actor.data.items.find(
(embedItem) => embedItem.name === item.data.name && embedItem.type === item.data.type
);
if (tmpItem && this._modifyQuantity(tmpItem.id, 1)) {
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.data.toObject(true);
// Item subtype specific
switch (itemData.type) {
case "advancement":
// Specific advancements, remove 1 to selected ring/skill
await this.actor.addBonus(item);
break;
case "title":
// Generate new Ids for the embed items
await item.generateNewIdsForAllEmbedItems();
// Add embed advancements bonus
for (let [embedId, embedItem] of item.data.data.items) {
if (embedItem.data.type === "advancement") {
await this.actor.addBonus(embedItem);
}
}
// refresh data
itemData = item.data.toObject(true);
break;
case "technique":
// School_ability and mastery_ability, allow only 1 per type
if (CONFIG.l5r5e.techniques.get(itemData.data.technique_type)?.type === "school") {
if (
Array.from(this.actor.items).some((e) => {
return (
e.type === "technique" && e.data.data.technique_type === itemData.data.technique_type
);
})
) {
ui.notifications.info(game.i18n.localize("l5r5e.techniques.only_one"));
return;
}
// No cost for schools
itemData.data.xp_cost = 0;
itemData.data.xp_used = 0;
itemData.data.in_curriculum = true;
} else {
// Check if technique is allowed for this character
if (!game.user.isGM && !this.actor.data.data.techniques[itemData.data.technique_type]) {
ui.notifications.info(game.i18n.localize("l5r5e.techniques.not_allowed"));
return;
}
// Verify cost
itemData.data.xp_cost =
itemData.data.xp_cost > 0 ? itemData.data.xp_cost : CONFIG.l5r5e.xp.techniqueCost;
itemData.data.xp_used = itemData.data.xp_cost;
}
break;
}
// Modify the bought at rank to the current actor rank
if (itemData.data.bought_at_rank !== undefined && this.actor.data.data.identity?.school_rank) {
itemData.data.bought_at_rank = this.actor.data.data.identity.school_rank;
}
// Finally create the embed
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
@@ -346,27 +112,6 @@ export class BaseSheetL5r5e extends ActorSheet {
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;
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);
});
// On focus on one numeric element, select all text for better experience
html.find(".select-on-focus").on("focus", (event) => {
event.preventDefault();
@@ -374,48 +119,19 @@ export class BaseSheetL5r5e extends ActorSheet {
event.target.select();
});
// Prepared (Initiative)
html.find(".prepared-control").on("click", (event) => {
event.preventDefault();
event.stopPropagation();
this._switchPrepared();
});
// Equipped / Readied
html.find(".equip-readied-control").on("click", this._switchEquipReadied.bind(this));
// *** Items : add, edit, delete ***
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));
// Others Advancements
html.find(".item-advancement-choose").on("click", this._showDialogAddSubItem.bind(this));
}
/**
* Switch the state "prepared" (initiative)
* @private
*/
_switchPrepared() {
this.actor.data.data.prepared = !this.actor.data.data.prepared;
this.actor.update({
data: {
prepared: this.actor.data.data.prepared,
},
});
this.render(false);
}
/**
* Add a generic item with sub type
* @param {string} type Item sub type (armor, weapon, bond...)
* @param {boolean} isEquipped For item with prop "Equipped" set the value
* @param {string|null} techniqueType Technique subtype (kata, shuji...)
* @return {Promise<void>}
* @private
*/
async _createSubItem({ type, isEquipped = false, techniqueType = null }) {
async _createSubItem({ type }) {
if (!type) {
return;
}
@@ -432,49 +148,9 @@ export class BaseSheetL5r5e extends ActorSheet {
}
const item = this.actor.items.get(created[0].id);
// Assign current school rank to the new adv/tech
if (this.actor.data.data.identity?.school_rank) {
item.data.data.bought_at_rank = this.actor.data.data.identity.school_rank;
if (["advancement", "technique"].includes(item.data.type)) {
item.data.data.rank = this.actor.data.data.identity.school_rank;
}
}
switch (item.data.type) {
case "item": // no break
case "armor": // no break
case "weapon":
item.data.data.equipped = isEquipped;
break;
case "technique": {
// If technique, select the current sub-type
if (CONFIG.l5r5e.techniques.get(techniqueType)) {
item.data.name = game.i18n.localize(`l5r5e.techniques.${techniqueType}`);
item.data.img = `${CONFIG.l5r5e.paths.assets}icons/techs/${techniqueType}.svg`;
item.data.data.technique_type = techniqueType;
}
break;
}
}
item.sheet.render(true);
}
/**
* Display a dialog to choose what Item to add
* @param {Event} event
* @return {Promise<void>}
* @private
*/
async _showDialogAddSubItem(event) {
game.l5r5e.HelpersL5r5e.showSubItemDialog(["bond", "title", "signature_scroll", "item_pattern"]).then(
(selectedType) => {
this._createSubItem({ type: selectedType });
}
);
}
/**
* Add a generic item with sub type
* @param {Event} event
@@ -489,10 +165,7 @@ export class BaseSheetL5r5e extends ActorSheet {
return;
}
const isEquipped = $(event.currentTarget).data("item-equipped") || false;
const techniqueType = $(event.currentTarget).data("tech-type") || null;
return this._createSubItem({ type, isEquipped, techniqueType });
return this._createSubItem({ type });
}
/**
@@ -530,27 +203,7 @@ export class BaseSheetL5r5e extends ActorSheet {
return;
}
// Remove 1 qty if possible
if (tmpItem.data.data.quantity > 1 && this._modifyQuantity(tmpItem.id, -1)) {
return;
}
const callback = async () => {
switch (tmpItem.type) {
case "advancement":
// Remove advancements bonus (1 to selected ring/skill)
await this.actor.removeBonus(tmpItem);
break;
case "title":
// Remove embed advancements bonus
for (let [embedId, embedItem] of tmpItem.data.data.items) {
if (embedItem.data.type === "advancement") {
await this.actor.removeBonus(embedItem);
}
}
break;
}
return this.actor.deleteEmbeddedDocuments("Item", [itemId]);
};
@@ -564,86 +217,4 @@ export class BaseSheetL5r5e extends ActorSheet {
callback
);
}
/**
* Switch "in_curriculum"
* @param {Event} event
* @private
*/
_switchSubItemCurriculum(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.items.get(itemId);
if (item.type !== "item") {
item.update({
data: {
in_curriculum: !item.data.data.in_curriculum,
},
});
}
}
/**
* Add or subtract a quantity to a owned item
* @private
*/
_modifyQuantity(itemId, add) {
const tmpItem = this.actor.items.get(itemId);
if (tmpItem) {
tmpItem.data.data.quantity = Math.max(1, tmpItem.data.data.quantity + add);
tmpItem.update({
data: {
quantity: tmpItem.data.data.quantity,
},
});
return true;
}
return false;
}
/**
* Switch Readied state on a weapon
* @param {Event} event
* @private
*/
_switchEquipReadied(event) {
event.preventDefault();
event.stopPropagation();
const type = $(event.currentTarget).data("type");
if (!["equipped", "readied"].includes(type)) {
return;
}
const itemId = $(event.currentTarget).data("item-id");
const tmpItem = this.actor.items.get(itemId);
if (!tmpItem || tmpItem.data.data[type] === undefined) {
return;
}
tmpItem.data.data[type] = !tmpItem.data.data[type];
const data = {
equipped: tmpItem.data.data.equipped,
};
// Only weapons
if (tmpItem.data.data.readied !== undefined) {
data.readied = tmpItem.data.data.readied;
}
tmpItem.update({ data });
}
/**
* Get the skillId for this weaponId
* @private
*/
_getWeaponSkillId(weaponId) {
const item = this.actor.items.get(weaponId);
if (!!item && item.type === "weapon") {
return item.data.data.skill;
}
return null;
}
}

View File

@@ -1,10 +1,10 @@
import { BaseSheetL5r5e } from "./base-sheet.js";
import { BaseCharacterSheetL5r5e } from "./base-character-sheet.js";
import { TwentyQuestionsDialog } from "./twenty-questions-dialog.js";
/**
* Actor / Character Sheet
*/
export class CharacterSheetL5r5e extends BaseSheetL5r5e {
export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "actor"],

View File

@@ -1,9 +1,9 @@
import { BaseSheetL5r5e } from "./base-sheet.js";
import { BaseCharacterSheetL5r5e } from "./base-character-sheet.js";
/**
* NPC Sheet
*/
export class NpcSheetL5r5e extends BaseSheetL5r5e {
export class NpcSheetL5r5e extends BaseCharacterSheetL5r5e {
/**
* Sub Types
*/

View File

@@ -478,7 +478,7 @@ export class HelpersL5r5e {
* Get a Item from a Actor Sheet
* @param {Event} event HTML Event
* @param {ActorL5r5e} actor
* @return {ItemL5r5e}
* @return {Promise<ItemL5r5e>}
*/
static async getEmbedItemByEvent(event, actor) {
const current = $(event.currentTarget);

View File

@@ -0,0 +1,17 @@
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",
width: 520,
height: 480,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
});
}
}

View File

@@ -0,0 +1,17 @@
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",
width: 520,
height: 480,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
});
}
}

View File

@@ -0,0 +1,166 @@
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class BaseItemSheetL5r5e extends ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["l5r5e", "sheet", "item"],
//template: CONFIG.l5r5e.paths.templates + "items/item/item-sheet.html",
width: 520,
height: 480,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
});
}
/**
* Add the SendToChat button on top of sheet
* @override
*/
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Send To Chat
buttons.unshift({
label: game.i18n.localize("l5r5e.global.send_to_chat"),
class: "send-to-chat",
icon: "fas fa-comment-dots",
onclick: () =>
game.l5r5e.HelpersL5r5e.debounce(
"send2chat-" + this.object.id,
() => game.l5r5e.HelpersL5r5e.sendToChat(this.object),
2000,
true
)(),
});
return buttons;
}
/**
* @return {Object|Promise}
*/
async getData(options = {}) {
const sheetData = await super.getData(options);
sheetData.data.dtypes = ["String", "Number", "Boolean"];
// Fix editable
sheetData.editable = this.isEditable;
sheetData.options.editable = sheetData.editable;
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 = "") {
if (name === "data.description" && initialContent) {
initialContent = game.l5r5e.HelpersL5r5e.convertSymbols(initialContent, false);
}
super.activateEditor(name, options, initialContent);
}
/**
* This method is called upon form submission after form data is validated
* @param event {Event} The initial triggering submission event
* @param formData {Object} 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) {
if (formData["data.description"]) {
// Base links (Journal, compendiums...)
formData["data.description"] = TextEditor.enrichHTML(formData["data.description"]);
// L5R Symbols
formData["data.description"] = game.l5r5e.HelpersL5r5e.convertSymbols(formData["data.description"], true);
}
return super._updateObject(event, formData);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
* @override
*/
activateListeners(html) {
super.activateListeners(html);
// Commons
game.l5r5e.HelpersL5r5e.commonListeners(html, this.actor);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// On focus on one numeric element, select all text for better experience
html.find(".select-on-focus").on("focus", (event) => {
event.preventDefault();
event.stopPropagation();
event.target.select();
});
}
/**
* Add a embed item
* @param {Event} event
* @private
*/
_addSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
console.warn("L5R5E | TODO ItemSheetL5r5e._addSubItem()", itemId); // TODO _addSubItem Currently not used, title override it
}
/**
* Add a embed item
* @param {Event} event
* @private
*/
_editSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.document.items.get(itemId);
if (item) {
item.sheet.render(true);
}
}
/**
* Delete a embed item
* @param {Event} event
* @private
*/
_deleteSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.document.getEmbedItem(itemId);
if (!item) {
return;
}
const callback = async () => {
this.document.deleteEmbedItem(itemId);
};
// Holing Ctrl = without confirm
if (event.ctrlKey) {
return callback();
}
game.l5r5e.HelpersL5r5e.confirmDeleteDialog(
game.i18n.format("l5r5e.global.delete_confirm", { name: item.name }),
callback
);
}
}

View File

@@ -1,8 +1,10 @@
import { BaseItemSheetL5r5e } from "./base-item-sheet.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* Extend BaseItemSheetL5r5e with modifications for objects
* @extends {ItemSheet}
*/
export class ItemSheetL5r5e extends ItemSheet {
export class ItemSheetL5r5e extends BaseItemSheetL5r5e {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
@@ -14,46 +16,17 @@ export class ItemSheetL5r5e extends ItemSheet {
});
}
/**
* Add the SendToChat button on top of sheet
* @override
*/
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Send To Chat
buttons.unshift({
label: game.i18n.localize("l5r5e.global.send_to_chat"),
class: "send-to-chat",
icon: "fas fa-comment-dots",
onclick: () =>
game.l5r5e.HelpersL5r5e.debounce(
"send2chat-" + this.object.id,
() => game.l5r5e.HelpersL5r5e.sendToChat(this.object),
2000,
true
)(),
});
return buttons;
}
/**
* @return {Object|Promise}
*/
async getData(options = {}) {
const sheetData = await super.getData(options);
sheetData.data.dtypes = ["String", "Number", "Boolean"];
sheetData.data.ringsList = game.l5r5e.HelpersL5r5e.getRingsList();
// Prepare Properties (id/name => object)
await this._prepareProperties(sheetData);
// Fix editable
sheetData.editable = this.isEditable;
sheetData.options.editable = sheetData.editable;
return sheetData;
}
@@ -79,37 +52,6 @@ export class ItemSheetL5r5e extends ItemSheet {
}
}
/**
* 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 = "") {
if (name === "data.description" && initialContent) {
initialContent = game.l5r5e.HelpersL5r5e.convertSymbols(initialContent, false);
}
super.activateEditor(name, options, initialContent);
}
/**
* This method is called upon form submission after form data is validated
* @param event {Event} The initial triggering submission event
* @param formData {Object} 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) {
if (formData["data.description"]) {
// Base links (Journal, compendiums...)
formData["data.description"] = TextEditor.enrichHTML(formData["data.description"]);
// L5R Symbols
formData["data.description"] = game.l5r5e.HelpersL5r5e.convertSymbols(formData["data.description"], true);
}
return super._updateObject(event, formData);
}
/**
* Subscribe to events from the sheet.
* @param {jQuery} html HTML content of the sheet.
@@ -118,21 +60,11 @@ export class ItemSheetL5r5e extends ItemSheet {
activateListeners(html) {
super.activateListeners(html);
// Commons
game.l5r5e.HelpersL5r5e.commonListeners(html, this.actor);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) {
return;
}
// On focus on one numeric element, select all text for better experience
html.find(".select-on-focus").on("focus", (event) => {
event.preventDefault();
event.stopPropagation();
event.target.select();
});
// Delete a property
html.find(`.property-delete`).on("click", this._deleteProperty.bind(this));
}
@@ -260,60 +192,4 @@ export class ItemSheetL5r5e extends ItemSheet {
callback
);
}
/**
* Add a embed item
* @param {Event} event
* @private
*/
_addSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
console.warn("L5R5E | TODO ItemSheetL5r5e._addSubItem()", itemId); // TODO _addSubItem Currently not used, title override it
}
/**
* Add a embed item
* @param {Event} event
* @private
*/
_editSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.document.items.get(itemId);
if (item) {
item.sheet.render(true);
}
}
/**
* Delete a embed item
* @param {Event} event
* @private
*/
_deleteSubItem(event) {
event.preventDefault();
event.stopPropagation();
const itemId = $(event.currentTarget).data("item-id");
const item = this.document.getEmbedItem(itemId);
if (!item) {
return;
}
const callback = async () => {
this.document.deleteEmbedItem(itemId);
};
// Holing Ctrl = without confirm
if (event.ctrlKey) {
return callback();
}
game.l5r5e.HelpersL5r5e.confirmDeleteDialog(
game.i18n.format("l5r5e.global.delete_confirm", { name: item.name }),
callback
);
}
}

View File

@@ -11,6 +11,7 @@ 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";
// Dice and rolls
import { L5rBaseDie } from "./dice/dietype/l5r-base-die.js";
import { AbilityDie } from "./dice/dietype/ability-die.js";
@@ -32,6 +33,8 @@ 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 { ArmyCohortSheetL5r5e } from "./items/army-cohort-sheet.js";
import { ArmyFortificationSheetL5r5e } from "./items/army-fortification-sheet.js";
// JournalEntry
import { JournalL5r5e } from "./journal.js";
import { BaseJournalSheetL5r5e } from "./journals/base-journal-sheet.js";
@@ -106,6 +109,7 @@ Hooks.once("init", async () => {
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("l5r5e", CharacterSheetL5r5e, { types: ["character"], makeDefault: true });
Actors.registerSheet("l5r5e", NpcSheetL5r5e, { types: ["npc"], makeDefault: true });
Actors.registerSheet("l5r5e", ArmySheetL5r5e, { types: ["army"], makeDefault: true });
// Items
Items.unregisterSheet("core", ItemSheet);
@@ -120,6 +124,8 @@ Hooks.once("init", async () => {
Items.registerSheet("l5r5e", BondSheetL5r5e, { types: ["bond"], makeDefault: true });
Items.registerSheet("l5r5e", SignatureScrollSheetL5r5e, { types: ["signature_scroll"], makeDefault: true });
Items.registerSheet("l5r5e", ItemPatternSheetL5r5e, { types: ["item_pattern"], makeDefault: true });
Items.registerSheet("l5r5e", ArmyCohortSheetL5r5e, { types: ["army_cohort"], makeDefault: true });
Items.registerSheet("l5r5e", ArmyFortificationSheetL5r5e, { types: ["army_fortification"], makeDefault: true });
// Journal
Items.unregisterSheet("core", JournalSheet);

View File

@@ -28,6 +28,10 @@ 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/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`,
@@ -57,5 +61,7 @@ export const PreloadTemplates = async function () {
`${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`,
]);
};