Files
DeltaGreen/module/sheets/agent-sheet.js
T

294 lines
9.4 KiB
JavaScript

import { DG, BASE_TEMPLATE_PATH } from "../config.js";
import DGActorSheet from "./base-actor-sheet.js";
const { renderTemplate } = foundry.applications.handlebars;
/** @extends {DGActorSheet} */
export default class DGAgentSheet extends DGActorSheet {
/** @override */
static DEFAULT_OPTIONS = /** @type {const} */ ({
actions: {
// Resets.
clearBondDamage: DGAgentSheet._clearBondDamage,
resetBreakingPoint: DGAgentSheet._resetBreakingPoint,
// Other actions.
applySkillImprovements: DGAgentSheet._applySkillImprovements,
},
});
static TABS = /** @type {const} */ ({
primary: {
initial: "skills",
labelPrefix: "DG.Navigation.Agent",
tabs: [
{ id: "skills" },
{ id: "physical" },
{ id: "motivations" },
{ id: "gear" },
{ id: "bio" },
{ id: "bonds" },
{ id: "about", icon: "fas fa-question-circle", label: "" },
],
},
});
/** @override */
static PARTS = /** @type {const} */ ({
header: this.BASE_PARTS.header,
tabs: this.BASE_PARTS.tabs,
skills: this.BASE_PARTS.skills,
physical: {
template: `${this.TEMPLATE_PATH}/parts/physical-tab.html`,
templates: [
`${this.TEMPLATE_PATH}/partials/attributes-grid-partial.html`,
],
scrollable: [""],
},
motivations: {
template: `${this.TEMPLATE_PATH}/parts/motivations-tab.html`,
},
gear: this.BASE_PARTS.gear,
bio: {
template: `${this.TEMPLATE_PATH}/parts/bio-tab.html`,
templates: [`${this.TEMPLATE_PATH}/partials/notes-partial.html`],
scrollable: [""],
},
bonds: {
template: `${this.TEMPLATE_PATH}/parts/bonds-tab.html`,
scrollable: [""],
},
about: this.BASE_PARTS.about,
});
/* -------------------------------------------- */
/** @override */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const enrichedDescription =
await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.actor.system.physicalDescription,
{
rollData: this.document.getRollData(),
relativeTo: this.document,
},
);
const { HTMLProseMirrorElement } = foundry.applications.elements;
context.descriptionField = HTMLProseMirrorElement.create({
name: "system.physicalDescription",
value: this.actor.system.physicalDescription,
enriched: enrichedDescription,
toggled: true,
}).outerHTML;
return context;
}
/* -------------------------------------------- */
static async _applySkillImprovements() {
const { skills, typedSkills } = this.actor.system;
const failedSkills = Object.values(skills).filter((data) => data.failure);
const failedTypedSkills = Object.values(typedSkills).filter(
(data) => data.failure,
);
const localizedFailedSkills = failedSkills.map((skill) =>
game.i18n.localize(`DG.Skills.${skill.key}`),
);
const localizedFailedTypedSkills = failedTypedSkills.map((skill) => {
const groupKey = `DG.TypeSkills.${skill.group.replace(/\s+/g, "")}`;
const groupLabel = game.i18n.localize(groupKey);
return `${groupLabel} (${skill.label})`;
});
if (failedSkills.length === 0 && failedTypedSkills.length === 0) {
ui.notifications.warn("DG.Skills.ApplySkillImprovements.Warning", {
localize: true,
});
return;
}
const failedSkillNames = [
...localizedFailedSkills,
...localizedFailedTypedSkills,
].join(", ");
const baseRollFormula = game.settings.get(DG.ID, "skillImprovementFormula");
const content = await renderTemplate(
`${BASE_TEMPLATE_PATH}/dialog/apply-skill-improvements.html`,
{ failedSkillNames, baseRollFormula },
);
new Dialog({
content,
title: game.i18n.localize("DG.Skills.ApplySkillImprovements.Title"),
default: "add",
buttons: {
apply: {
label: game.i18n.localize("DG.Skills.Apply"),
callback: (btn) => {
this._applySkillImprovements(
baseRollFormula,
failedSkills,
failedTypedSkills,
);
},
},
},
}).render(true);
}
/** Resets the actor's current breaking point based on their sanity and POW statistics. */
static _resetBreakingPoint() {
const systemData = this.actor.system;
const newBreakingPoint =
systemData.sanity.value - systemData.statistics.pow.value;
const updatedData = foundry.utils.duplicate(systemData);
updatedData.sanity.currentBreakingPoint = newBreakingPoint;
this.actor.update({ system: updatedData });
}
_resetBreakingPoint(event) {
event.preventDefault();
let currentBreakingPoint = 0;
currentBreakingPoint =
this.actor.system.sanity.value - this.actor.system.statistics.pow.value;
if (currentBreakingPoint < 0) {
currentBreakingPoint = 0;
}
const updatedData = foundry.utils.duplicate(this.actor.system);
updatedData.sanity.currentBreakingPoint = currentBreakingPoint;
this.actor.update({ system: updatedData });
}
// For any skills a user has checked off as failed, roll the improvement and update the agent's skills to their new values
async _applySkillImprovements(
baseRollFormula,
failedSkills,
failedTypedSkills,
) {
const actorData = this.actor.system;
const resultList = [];
let rollFormula;
// Define the amount of dice being rolled, if any.
switch (baseRollFormula) {
case "1":
rollFormula = 1;
break;
case "1d3":
rollFormula = `${failedSkills.length + failedTypedSkills.length}d3`;
break;
case "1d4":
case "1d4-1":
rollFormula = `${failedSkills.length + failedTypedSkills.length}d4`;
break;
default:
}
let roll;
if (rollFormula !== 1) {
roll = new Roll(rollFormula, actorData);
await roll.evaluate();
// Put the results into a list.
roll.terms[0].results.forEach((result) =>
resultList.push(
baseRollFormula === "1d4-1" ? result.result - 1 : result.result,
),
);
}
// This will be end up being a list of skills and how much each were improved by. It gets modified in the following loops.
let improvedSkillList = "";
// Get copy of current system data, will update this and then apply all changes at once synchronously at the end.
const updatedData = foundry.utils.duplicate(actorData);
failedSkills.forEach((skill, value) => {
updatedData.skills[skill.key].proficiency += resultList[value] ?? 1; // Increase proficiency by die result or by 1 if there is no dice roll.
updatedData.skills[skill.key].failure = false;
// So we can record the regular skills improved and how much they were increased by in chat.
// The if statement tells us whether to add a comma before the term or not.
if (value === 0) {
improvedSkillList += `${game.i18n.localize(
`DG.Skills.${skill.key}`,
)}: <b>+${resultList[value] ?? 1}%</b>`;
} else {
improvedSkillList += `, ${game.i18n.localize(
`DG.Skills.${skill.key}`,
)}: <b>+${resultList[value] ?? 1}%</b>`;
}
});
failedTypedSkills.forEach((skill, value) => {
// We must increase value in the following line by the length of failedSkills, so that we index the entire resultList.
// Otherwise we would be adding the same die results to regular skills and typed skills.
updatedData.typedSkills[skill.key].proficiency +=
resultList[value + failedSkills.length] ?? 1;
updatedData.typedSkills[skill.key].failure = false;
// So we can record the typed skills improved and how much they were increased by in chat.
// The if statement tells us whether to add a comma before the term or not.
if (value === 0 && improvedSkillList === "") {
improvedSkillList += `${game.i18n.localize(
`DG.TypeSkills.${skill.group.split(" ").join("")}`,
)} (${skill.label}): <b>+${
resultList[value + failedSkills.length] ?? 1
}%</b>`;
} else {
improvedSkillList += `, ${game.i18n.localize(
`DG.TypeSkills.${skill.group.split(" ").join("")}`,
)} (${skill.label}): <b>+${
resultList[value + failedSkills.length] ?? 1
}%</b>`;
}
});
// Probably not worth triggering the update if the user didn't pick any skills
if (improvedSkillList !== "") {
await this.actor.update({ system: updatedData });
}
let html;
html = `<div class="dice-roll">`;
html += ` <div>${improvedSkillList}</div>`;
html += `</div>`;
const chatData = {
speaker: ChatMessage.getSpeaker({
actor: this.actor,
token: this.token,
alias: this.actor.name,
}),
content: html,
flavor: `${game.i18n.localize(
"DG.Skills.ApplySkillImprovements.ChatFlavor",
)} <b>+${baseRollFormula}%</b>:`,
type: baseRollFormula === "1" ? 0 : 5, // 0 = CHAT_MESSAGE_TYPES.OTHER, 5 = CHAT_MESSAGE_TYPES.ROLL
rolls: baseRollFormula === "1" ? [] : [roll], // If adding flat +1, there is no roll.
rollMode: game.settings.get("core", "rollMode"),
};
// Create a message from this roll, if there is one.
if (roll) return roll.toMessage(chatData);
// If no roll, create a chat message directly.
return ChatMessage.create(chatData, {});
}
}