move organization of sheets in preparation for per-actor-type sheets

This commit is contained in:
jalensailin
2025-07-22 12:36:13 -04:00
parent b231a1779b
commit dc0bba7533
3 changed files with 968 additions and 1036 deletions
+961
View File
@@ -0,0 +1,961 @@
import DG from "../config.js";
import {
DGDamageRoll,
DGLethalityRoll,
DGPercentileRoll,
DGSanityDamageRoll,
} from "../roll/roll.js";
import DGSheetMixin from "./base-sheet.js";
const { ActorSheetV2 } = foundry.applications.sheets;
export default class DGActorSheet extends DGSheetMixin(ActorSheetV2) {
/** @override */
static DEFAULT_OPTIONS = /** @type {const} */ ({
position: { width: 750, height: 770 },
actions: {
// Skill/Item actions.
itemAction: this._onItemAction,
typedSkillAction: this._onTypedSkillAction,
specialTrainingAction: this._onSpecialTrainingAction,
roll: this._onRoll,
rollLuck: this._onRollLuck,
// Toggles/resets.
toggleEquipped: this._toggleEquipped,
toggleItemSortMode: this._toggleItemSortMode,
toggleShowUntrained: this._toggleShowUntrained,
toggleLethality: this._toggleLethality,
// Other actions.
browsePack: this._browsePack,
},
});
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
// Make it easy for the sheet handlebars to understand how to sort the skills.
context.sortSkillsSetting = game.settings.get("deltagreen", "sortSkills");
// fill an array that is sorted based on the appropriate localized entry
const sortedSkills = [];
for (const [key, skill] of Object.entries(this.actor.system.skills)) {
skill.key = key;
if (game.i18n.lang === "ja") {
skill.sortLabel = game.i18n.localize(`DG.Skills.ruby.${key}`);
} else {
skill.sortLabel = game.i18n.localize(`DG.Skills.${key}`);
}
if (skill.sortLabel === "" || skill.sortLabel === `DG.Skills.${key}`) {
skill.sortLabel = skill.key;
}
// if the actor is an NPC or Unnatural, and they have 'hide untrained skills' active,
// it will break the sorting logic, so we have to skip over these
if (
!(
(this.actor.type === "npc" || this.actor.type === "unnatural") &&
this.actor.system.showUntrainedSkills &&
skill.proficiency < 1
)
) {
sortedSkills.push(skill);
}
}
sortedSkills.sort((a, b) => {
return a.sortLabel.localeCompare(b.sortLabel, game.i18n.lang);
});
// if sorting by columns, re-arrange the array to be columns first, then rows
if (game.settings.get("deltagreen", "sortSkills")) {
const columnSortedSkills = this.reorderForColumnSorting(sortedSkills, 3);
this.actor.system.sortedSkills = columnSortedSkills;
} else {
this.actor.system.sortedSkills = sortedSkills;
}
// Prepare a simplified version of the special training for display on sheet.
const specialTraining = this.actor.system.specialTraining.map(
(training) => {
const simplifiedTraining = {
name: training.name,
id: training.id,
key: training.attribute,
};
// Convert the machine-readable name to a human-readable one.
switch (true) {
// Stats
case DG.statistics.includes(training.attribute):
simplifiedTraining.attribute = `${training.attribute.toUpperCase()}x5`;
simplifiedTraining.targetNumber =
this.actor.system.statistics[training.attribute].x5;
break;
// Skills
case DG.skills.includes(training.attribute):
simplifiedTraining.attribute =
this.actor.system.skills[training.attribute].label;
simplifiedTraining.targetNumber =
this.actor.system.skills[training.attribute].proficiency;
break;
// Typed Skills
default:
simplifiedTraining.attribute =
this.actor.system.typedSkills[training.attribute].label;
simplifiedTraining.targetNumber =
this.actor.system.typedSkills[training.attribute].proficiency;
break;
}
return simplifiedTraining;
},
);
context.specialTraining = specialTraining;
// try to make a combined array of both typed skills and special trainings,
// so that it can be sorted together neatly on the sheet
const sortedCustomSkills = [];
for (const [key, skill] of Object.entries(this.actor.system.typedSkills)) {
skill.type = "typeSkill";
skill.key = key;
skill.sortLabel = `${skill.group}.${skill.label}`;
skill.sortLabel = skill.sortLabel.toUpperCase();
skill.actorType = this.actor.type;
if (skill.sortLabel === "" || skill.sortLabel === `DG.Skills.${key}`) {
skill.sortLabel = skill.key;
}
sortedCustomSkills.push(skill);
}
for (let i = 0; i < context.specialTraining.length; i++) {
const training = context.specialTraining[i];
training.type = "training";
training.sortLabel = training.name.toUpperCase();
training.actorType = this.actor.type;
sortedCustomSkills.push(training);
}
sortedCustomSkills.sort(function (a, b) {
return a.sortLabel.localeCompare(b.sortLabel, game.i18n.lang);
});
if (game.settings.get("deltagreen", "sortSkills")) {
const columnSortedSkills = this.reorderForColumnSorting(
sortedCustomSkills,
2,
);
this.actor.system.sortedCustomSkills = columnSortedSkills;
} else {
this.actor.system.sortedCustomSkills = sortedCustomSkills;
}
return context;
}
/** @override */
async _onRender(context, options) {
await super._onRender(context, options);
const { element } = this;
this._setRightClickListeners();
if (this.actor.isOwner) {
const handler = (ev) => this._onDragStart(ev);
element.querySelectorAll("li.item").forEach((li) => {
if (li.classList.contains("inventory-header")) return;
li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false);
});
}
}
/** @override - Add buttons to the header controls. */
_getHeaderControls() {
const controls = super._getHeaderControls();
controls.push({
action: "rollLuck",
label: "DG.RollLuck",
icon: "fas fa-dice",
});
return controls;
}
/** @override */
_onDragStart(event) {
// Most of this is the standard Foundry implementation of _onDragStart
const li = event.currentTarget;
if (event.target.classList.contains("content-link")) return;
// Create drag data
let dragData;
// Owned Items
if (li.dataset.itemId) {
const item = this.actor.items.get(li.dataset.itemId);
dragData = item.toDragData();
}
// Active Effect
if (li.dataset.effectId) {
const effect = this.actor.effects.get(li.dataset.effectId);
dragData = effect.toDragData();
}
if (!dragData) return;
// this is custom, grab item data for creating item macros on the hotbar
if (li.dataset.itemId) {
const item = this.actor.items.get(li.dataset.itemId);
dragData.itemData = item;
}
// Set data transfer
event.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
/** @override */
async _onDrop(event) {
super._onDrop(event);
// If alt key is held down, we will delete the original document.
if (event.altKey) {
// This is from Foundry. It will get the item data from the event.
const TextEditor = foundry.applications.ux.TextEditor.implementation;
const dragData = TextEditor.getDragEventData(event);
// Make sure that we are dragging an item, otherwise this doesn't make sense.
if (dragData.type === "Item") {
const item = fromUuidSync(dragData.uuid);
await item.delete();
}
}
}
/**
* Listens for right click events on the actor sheet and executes a regular roll,
* or luck roll, depending on the action.
*
* @returns {void}
*/
_setRightClickListeners() {
const { element } = this;
element.addEventListener("contextmenu", (event) => {
event.preventDefault();
const target = event.target.closest(
"[data-action='roll'],[data-action='rollLuck']",
);
if (!target) return;
// Call _onRollLuck if luckRoll is the dispatched action.
if (target.dataset.action === "rollLuck") {
DGActorSheet._onRollLuck.call(this, event, target);
return;
}
// Otherwise, call _onRoll function.
DGActorSheet._onRoll.call(this, event, target);
});
}
reorderForColumnSorting(arr, numCols) {
const numRows = Math.ceil(arr.length / numCols); // Compute required rows
const reordered = new Array(arr.length);
// Determine how many elements each column gets
const baseRowCount = Math.floor(arr.length / numCols); // Minimum rows per column
const extraColumns = arr.length % numCols; // Some columns get an extra row
const colHeights = new Array(numCols).fill(baseRowCount);
for (let i = 0; i < extraColumns; i++) {
colHeights[i] += 1; // Give extra elements to the first N columns
}
let index = 0; // move through alphabetical array, keeping track of what we've resorted already
for (let col = 0; col < numCols; col++) {
// need to check if this is a column that has more rows than the others or not
const rowCount = colHeights[col];
// loop down the column, filling out it's values from the alphabetical array
for (let row = 0; row < rowCount; row++) {
// calculate the new position for this value by column
const newIndex = numCols * row + col;
if (newIndex < arr.length) {
reordered[newIndex] = arr[index];
index += 1;
}
}
}
return reordered;
}
static _onItemAction(event, target) {
const li = target.closest(".item");
const { itemId } = li.dataset;
const { actionType, itemType } = target.dataset;
switch (actionType) {
case "create":
this._onItemCreate(itemType);
break;
case "edit": {
const item = this.actor.items.get(itemId);
item.sheet.render(true);
break;
}
case "delete": {
this.actor.deleteEmbeddedDocuments("Item", [itemId]);
break;
}
default:
break;
}
}
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {String} type The originating click event
* @private
*/
_onItemCreate(type) {
// Initialize a default name.
const name = game.i18n.format(
game.i18n.translations.DOCUMENT?.New || "DG.FallbackText.newItem",
{
type: game.i18n.localize(`TYPES.Item.${type}`),
},
);
// Prepare the item object.
const itemData = {
name,
type,
system: {},
};
if (type === "weapon") {
// itemData.system.skill = "firearms"; //default skill to firearms, since that will be most common
// itemData.system.expense = "Standard";
} else if (type === "armor") {
// itemData.system.armor = 3;
// itemData.system.expense = "Standard";
} else if (type === "bond") {
// try to default bonds for an agent to their current CHA
itemData.system.score = this.actor.system.statistics.cha.value; // Can vary, but at character creation starting bond score is usually agent's charisma
// itemData.img = "icons/svg/mystery-man.svg"
}
// create the item
return this.actor.createEmbeddedDocuments("Item", [itemData]);
}
static _onTypedSkillAction(event, target) {
const { actionType, typedskill } = target.dataset;
switch (actionType) {
case "create":
this._showNewTypeSkillDialog();
break;
case "edit":
this._showNewEditTypeSkillDialog(typedskill);
break;
case "delete":
this.actor.update({ [`system.typedSkills.-=${typedskill}`]: null });
break;
default:
break;
}
}
_showNewEditTypeSkillDialog(targetSkill) {
// TO DO: BUILD DIALOG TO CAPTURE UPDATED DATA
const { typedSkills } = this.actor.system;
const currentLabel = typedSkills[targetSkill].label;
const currentGroup = typedSkills[targetSkill].group;
let htmlContent = `<div>`;
htmlContent += ` <label>${
game.i18n.translations.DG?.Skills?.SkillGroup ?? "Skill Group"
}:</label>`;
htmlContent += ` <select name="new-type-skill-group" />`;
if (currentGroup === game.i18n.translations.DG?.TypeSkills?.Art ?? "Art") {
htmlContent += ` <option value="Art" selected>${
game.i18n.translations.DG?.TypeSkills?.Art ?? "Art"
}</option>`;
} else {
htmlContent += ` <option value="Art">${
game.i18n.translations.DG?.TypeSkills?.Art ?? "Art"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.Craft ??
"Craft"
) {
htmlContent += ` <option value="Craft" selected>${
game.i18n.translations.DG?.TypeSkills?.Craft ?? "Craft"
}</option>`;
} else {
htmlContent += ` <option value="Craft">${
game.i18n.translations.DG?.TypeSkills?.Craft ?? "Craft"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.ForeignLanguage ??
"Foreign Language"
) {
htmlContent += ` <option value="ForeignLanguage" selected>${
game.i18n.translations.DG?.TypeSkills?.ForeignLanguage ??
"Foreign Language"
}</option>`;
} else {
htmlContent += ` <option value="ForeignLanguage">${
game.i18n.translations.DG?.TypeSkills?.ForeignLanguage ??
"Foreign Language"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.MilitaryScience ??
"Military Science"
) {
htmlContent += ` <option value="MilitaryScience" selected>${
game.i18n.translations.DG?.TypeSkills?.MilitaryScience ??
"Military Science"
}</option>`;
} else {
htmlContent += ` <option value="MilitaryScience">${
game.i18n.translations.DG?.TypeSkills?.MilitaryScience ??
"Military Science"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.Pilot ??
"Pilot"
) {
htmlContent += ` <option value="Pilot" selected>${
game.i18n.translations.DG?.TypeSkills?.Pilot ?? "Pilot"
}</option>`;
} else {
htmlContent += ` <option value="Pilot">${
game.i18n.translations.DG?.TypeSkills?.Pilot ?? "Pilot"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.Science ??
"Science"
) {
htmlContent += ` <option value="Science" selected>${
game.i18n.translations.DG?.TypeSkills?.Science ?? "Science"
}</option>`;
} else {
htmlContent += ` <option value="Science">${
game.i18n.translations.DG?.TypeSkills?.Science ?? "Science"
}</option>`;
}
if (
currentGroup === game.i18n.translations.DG?.TypeSkills?.Other ??
"Other"
) {
htmlContent += ` <option value="Other" selected>${
game.i18n.translations.DG?.TypeSkills?.Other ?? "Other"
}</option>`;
} else {
htmlContent += ` <option value="Other">${
game.i18n.translations.DG?.TypeSkills?.Other ?? "Other"
}</option>`;
}
htmlContent += ` </select>`;
htmlContent += `</div>`;
htmlContent += `<div>`;
htmlContent += ` <label>${
game.i18n.translations.DG?.Skills.SkillName ?? "Skill Name"
}</label>`;
htmlContent += ` <input type="text" name="new-type-skill-label" value="${currentLabel}" />`;
htmlContent += `</div>`;
new Dialog({
content: htmlContent,
title:
game.i18n.translations.DG?.Skills?.EditTypedOrCustomSkill ??
"Edit Typed or Custom Skill",
default: "add",
buttons: {
add: {
label: game.i18n.translations.DG?.Skills?.EditSkill ?? "Edit Skill",
callback: (btn) => {
const newTypeSkillLabel = btn
.find("[name='new-type-skill-label']")
.val();
const newTypeSkillGroup = btn
.find("[name='new-type-skill-group']")
.val();
this._updateTypedSkill(
targetSkill,
newTypeSkillLabel,
newTypeSkillGroup,
);
},
},
},
}).render(true);
}
_showNewTypeSkillDialog() {
let htmlContent = "";
htmlContent += `<div>`;
htmlContent += ` <label>${
game.i18n.translations.DG?.Skills?.SkillGroup ?? "Skill Group"
}:</label>`;
htmlContent += ` <select name="new-type-skill-group" />`;
htmlContent += ` <option value="Art">${
game.i18n.translations.DG?.TypeSkills?.Art ?? "Art"
}</option>`;
htmlContent += ` <option value="Craft">${
game.i18n.translations.DG?.TypeSkills?.Craft ?? "Craft"
}</option>`;
htmlContent += ` <option value="ForeignLanguage">${
game.i18n.translations.DG?.TypeSkills?.ForeignLanguage ??
"Foreign Language"
}</option>`;
htmlContent += ` <option value="MilitaryScience">${
game.i18n.translations.DG?.TypeSkills?.MilitaryScience ??
"Military Science"
}</option>`;
htmlContent += ` <option value="Pilot">${
game.i18n.translations.DG?.TypeSkills?.Pilot ?? "Pilot"
}</option>`;
htmlContent += ` <option value="Science">${
game.i18n.translations.DG?.TypeSkills?.Science ?? "Science"
}</option>`;
htmlContent += ` <option value="Other">${
game.i18n.translations.DG?.TypeSkills?.Other ?? "Other"
}</option>`;
htmlContent += ` </select>`;
htmlContent += `</div>`;
htmlContent += `<div>`;
htmlContent += ` <label>${
game.i18n.translations.DG?.Skills.SkillName ?? "Skill Name"
}</label>`;
htmlContent += ` <input type="text" name="new-type-skill-label" />`;
htmlContent += `</div>`;
new Dialog({
content: htmlContent,
title:
game.i18n.translations.DG?.Skills?.AddTypedOrCustomSkill ??
"Add Typed or Custom Skill",
default: "add",
buttons: {
add: {
label: game.i18n.translations.DG?.Skills?.AddSkill ?? "Add Skill",
callback: (btn) => {
const newTypeSkillLabel = btn
.find("[name='new-type-skill-label']")
.val();
const newTypeSkillGroup = btn
.find("[name='new-type-skill-group']")
.val();
this._addNewTypedSkill(newTypeSkillLabel, newTypeSkillGroup);
},
},
},
}).render(true);
}
_addNewTypedSkill(newSkillLabel, newSkillGroup) {
const updatedData = foundry.utils.duplicate(this.actor.system);
const { typedSkills } = updatedData;
const d = new Date();
const newSkillPropertyName =
d.getFullYear().toString() +
(d.getMonth() + 1).toString() +
d.getDate().toString() +
d.getHours().toString() +
d.getMinutes().toString() +
d.getSeconds().toString();
// console.log(newSkillPropertyName);
typedSkills[newSkillPropertyName] = {
label: newSkillLabel,
group: newSkillGroup,
proficiency: 10,
failure: false,
};
updatedData.typedSkills = typedSkills;
this.actor.update({ system: updatedData });
}
_updateTypedSkill(targetSkill, newSkillLabel, newSkillGroup) {
if (
newSkillLabel !== null &&
newSkillLabel !== "" &&
newSkillGroup !== null &&
newSkillGroup !== ""
) {
const updatedData = foundry.utils.duplicate(this.actor.system);
updatedData.typedSkills[targetSkill].label = newSkillLabel;
updatedData.typedSkills[targetSkill].group = newSkillGroup;
this.actor.update({ system: updatedData });
}
}
static _onSpecialTrainingAction(event, target) {
const { actionType, id } = target.dataset;
switch (actionType) {
case "delete":
{
const specialTrainingArray = foundry.utils.duplicate(
this.actor.system.specialTraining,
);
// Get the index of the training to be deleted
const index = specialTrainingArray.findIndex(
(training) => training.id === id,
);
specialTrainingArray.splice(index, 1);
this.actor.update({ "system.specialTraining": specialTrainingArray });
}
break;
default:
this._showSpecialTrainingDialog(actionType, id);
break;
}
}
async _showSpecialTrainingDialog(action, targetID) {
const specialTraining = this.actor.system.specialTraining.find(
(training) => training.id === targetID,
);
// Define the option groups for our drop-down menu.
const optionGroups = {
stats: game.i18n.localize(
"DG.SpecialTraining.Dialog.DropDown.Statistics",
),
skills: game.i18n.localize("DG.SpecialTraining.Dialog.DropDown.Skills"),
typedSkills: game.i18n.localize(
"DG.SpecialTraining.Dialog.DropDown.CustomSkills",
),
};
// Prepare simplified stat list
const statList = Object.entries(this.actor.system.statistics).map(
([key, stat]) => ({
value: key,
group: optionGroups.stats,
label: game.i18n.localize(`DG.Attributes.${key}`),
targetNumber: stat.value * 5,
}),
);
// Prepare simplified skill list
const skillList = Object.entries(this.actor.system.skills).map(
([key, skill]) => ({
value: key,
group: optionGroups.skills,
label: game.i18n.localize(`DG.Skills.${key}`),
targetNumber: skill.proficiency,
}),
);
// Prepare simplified typed/custom skill list
const typedSkillList = Object.entries(this.actor.system.typedSkills).map(
([key, skill]) => ({
value: key,
group: optionGroups.typedSkills,
label: `${game.i18n.localize(`DG.TypeSkills.${skill.group}`)} (${
skill.label
})`,
targetNumber: skill.proficiency,
}),
);
// Prepare the Select element
const selectElement = foundry.applications.fields.createSelectInput({
name: "special-training-skill",
options: [...statList, ...skillList, ...typedSkillList],
groups: Object.values(optionGroups),
}).outerHTML;
// Prepare the template to feed to Dialog.
const { renderTemplate } = foundry.applications.handlebars;
const content = await renderTemplate(
"systems/deltagreen/templates/dialog/special-training.html",
{
name: specialTraining?.name || "",
selectElement,
currentAttribute: specialTraining?.attribute || "",
statList,
skillList,
typedSkillList,
},
);
const buttonLabel = game.i18n.localize(
`DG.SpecialTraining.Dialog.${action.capitalize()}SpecialTraining`,
);
// Prepare and render dialog with above template.
new Dialog({
content,
title: game.i18n.localize("DG.SpecialTraining.Dialog.Title"),
default: "confirm",
buttons: {
confirm: {
label: buttonLabel,
callback: (btn) => {
const specialTrainingLabel = btn
.find("[name='special-training-label']")
.val();
const specialTrainingAttribute = btn
.find("[name='special-training-skill']")
.val();
if (action === "create")
this._createSpecialTraining(
specialTrainingLabel,
specialTrainingAttribute,
);
if (action === "edit")
this._editSpecialTraining(
specialTrainingLabel,
specialTrainingAttribute,
targetID,
);
},
},
},
}).render(true);
}
_createSpecialTraining(label, attribute) {
const specialTrainingArray = foundry.utils.duplicate(
this.actor.system.specialTraining,
);
specialTrainingArray.push({
name: label,
attribute,
id: foundry.utils.randomID(),
});
this.actor.update({ "system.specialTraining": specialTrainingArray });
}
_editSpecialTraining(label, attribute, id) {
const specialTrainingArray = foundry.utils.duplicate(
this.actor.system.specialTraining,
);
const specialTraining = specialTrainingArray.find(
(training) => training.id === id,
);
specialTraining.name = label;
specialTraining.attribute = attribute;
this.actor.update({ "system.specialTraining": specialTrainingArray });
}
/**
* Handle clickable rolls.
*
* @param {Event} event The originating click event
* @async
* @private
*/
static async _onRoll(event, target) {
if (target.classList.contains("not-rollable") || event.which === 2) return;
const { dataset } = target;
const item = this.actor.items.get(dataset.iid);
const rollOptions = {
rollType: dataset.rolltype,
key: dataset.key,
actor: this.actor,
specialTrainingName: dataset?.name || null, // Only applies to Special Training Rolls
item,
};
// Create a default 1d100 roll just in case.
let roll = new Roll("1d100", {});
switch (dataset.rolltype) {
case "stat":
case "skill":
case "sanity":
case "special-training":
case "weapon":
roll = new DGPercentileRoll("1D100", {}, rollOptions);
break;
case "lethality":
roll = new DGLethalityRoll("1D100", {}, rollOptions);
break;
case "damage": {
let diceFormula = item.system.damage;
const { skill } = item.system;
if (
this.actor.type === "agent" &&
(skill === "unarmed_combat" || skill === "melee_weapons")
) {
diceFormula +=
this.actor.system.statistics.str.meleeDamageBonusFormula;
}
roll = new DGDamageRoll(diceFormula, {}, rollOptions);
break;
}
case "sanity-damage": {
const { successLoss, failedLoss } = this.actor.system.sanity;
const combinedFormula = `{${successLoss}, ${failedLoss}}`;
roll = new DGSanityDamageRoll(combinedFormula, {}, rollOptions);
break;
}
default:
break;
}
this.processRoll(event, roll, rollOptions);
}
// This only exists to give a chance to activate the modifier dialogue if desired
// Cannot seem to trigger the event on a right-click, so unfortunately only applies to a shift-click currently.
static async _onRollLuck(event) {
// probably don't want rolls to trigger from a middle mouse click so just kill it here
if (event.which === 2) return;
const rollOptions = {
rollType: "luck",
key: "luck",
actor: this.actor,
};
// Create a default 1d100 roll just in case.
const roll = new DGPercentileRoll("1D100", {}, rollOptions);
// Open dialog if user requests it.
if (event.shiftKey || event.which === 3) {
const dialogData = await roll.showDialog();
if (!dialogData) return;
roll.modifier += dialogData.targetModifier;
roll.options.rollMode = dialogData.rollMode;
}
// Evaluate the roll.
await roll.evaluate();
// Send the roll to chat.
roll.toChat();
}
/**
* Show a dialog for the roll and then send to chat.
* Broke this logic out from `_onRoll()` so that other files can call it,
* namely the macro logic.
*
* TODO: Move this logic to the roll.js.
*
* @param {Event} event The originating click event
* @param {Event} roll The roll to show a dialog for and then send to chat.
* @async
*/
async processRoll(event, roll) {
// Open dialog if user requests it (no dialog for Sanity Damage rolls)
if (
(event.shiftKey || event.which === 3) &&
!(roll instanceof DGSanityDamageRoll)
) {
const dialogData = await roll.showDialog();
if (!dialogData) return;
if (dialogData.newFormula) {
roll = new DGDamageRoll(dialogData.newFormula, {}, roll.options);
}
roll.modifier += dialogData.targetModifier;
roll.options.rollMode = dialogData.rollMode;
}
// Evaluate the roll.
await roll.evaluate();
// Send the roll to chat.
roll.toChat();
}
static _toggleItemSortMode(event, target) {
const itemType = target.dataset.gearType;
const propString = `${itemType}SortAlphabetical`;
const targetProp = `system.settings.sorting.${propString}`;
const currentValue = foundry.utils.getProperty(this.actor, targetProp);
this.actor.update({
[targetProp]: !currentValue,
});
}
static _toggleShowUntrained() {
const targetProp = "system.showUntrainedSkills";
const currentVal = foundry.utils.getProperty(this.actor, targetProp);
this.actor.update({ [targetProp]: !currentVal });
}
static _toggleLethality(event, target) {
const { itemId } = target.dataset;
const isLethal = target.dataset.isLethal?.length === 0;
const item = this.actor.items.get(itemId);
item.update({ "system.isLethal": !isLethal });
}
static _toggleEquipped(event, target) {
event.preventDefault();
const { id } = target.dataset;
const item = this.actor.items.get(id);
const targetProp = "system.equipped";
const currentVal = foundry.utils.getProperty(item, targetProp);
item.update({ [targetProp]: !currentVal });
}
static _browsePack(event, target) {
const { packType } = target.dataset;
switch (packType) {
case "weapon": {
new Dialog({
title: "Select Compendium",
buttons: {
firearms: {
icon: '<i class="fas fa-crosshairs"></i>',
callback: () =>
game.packs
.find((k) => k.collection === "deltagreen.firearms")
.render(true),
},
melee: {
icon: '<i class="far fa-hand-rock"></i>',
callback: () =>
game.packs
.find(
(k) => k.collection === "deltagreen.hand-to-hand-weapons",
)
.render(true),
},
},
}).render(true);
break;
}
default:
game.packs
.find((k) => k.collection === `deltagreen.${packType}`)
.render(true);
break;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
import DG from "../config.js";
const HbsAppMixin = foundry.applications.api.HandlebarsApplicationMixin;
const DGSheetMixin = (Base) => {
return class DGDocumentSheet extends HbsAppMixin(Base) {
/** @inheritdoc */
static DEFAULT_OPTIONS = /** @type {const} */ ({
window: { resizable: true },
classes: [DG.ID, "sheet"],
form: { submitOnChange: true },
});
/** @inheritdoc */
static TEMPLATE_PATH = /** @type {const} */ (`systems/${DG.ID}/templates`);
static get THEME() {
return game.settings.get(DG.ID, "characterSheetStyle");
}
get THEME() {
return this.constructor.THEME;
}
/** @inheritdoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const docName = this.document.documentName.toLowerCase();
// Add the document to the context under a descriptive name (i.e. "actor", "item")
context[docName] = this.document;
return context;
}
/** @inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
const html = this.element;
html
.querySelector(".window-content")
.classList.add(`${this.THEME}-style`);
}
/** @inheritdoc */
async _onRender(context, options) {
await super._onRender(context, options);
const html = this.element;
// Double-click resize handle to reset sheet to original size.
html
.querySelector(".window-resize-handle")
.addEventListener("dblclick", () => {
this.resetPosition();
});
}
/**
* Resets the window's position to its default, as specified in the Sheet's options.
* @returns {void}
*/
resetPosition() {
this.setPosition(this.options.position);
}
};
};
export default DGSheetMixin;