Merge pull request #207 from reyzor1991/add-marked-skill

add marker skill feature
This commit is contained in:
Jalen
2025-07-18 20:46:34 -04:00
committed by GitHub
15 changed files with 245 additions and 95 deletions
+33 -1
View File
@@ -1235,4 +1235,36 @@ body:not(.no-theme) #interface:not(.theme-light) #action-bar .slot.full:hover {
.target-proficiency-mod {
color: #247c14;
}
}
.rollback-section {
order: 99;
display: flex;
margin-top: 5px;
align-items: center;
justify-content: center;
> i {
margin: 5px;
}
}
[data-action="rollback-skill-failure-state"] {
align-items: center;
display: inline-flex;
justify-content: center;
min-height: unset;
width: 20px;
height: 20px;
margin-left: 5px;
font-size: 12px;
}
[data-tooltip] {
position: relative;
cursor: pointer;
}
.strike {
text-decoration: line-through;
}
+4 -1
View File
@@ -271,5 +271,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Activation Time",
"DG.ItemWindow.Ritual.ActivationCosts": "Activation Costs",
"DG.ItemWindow.Ritual.StudyTime": "Study Time",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage",
"DG.Messages.Skillsmark.Marked": "Aquesta habilitat està marcada",
"DG.Messages.Skillsmark.Tooltip": "Vegeu la p. 29 del Manual de l'agent"
}
+4 -1
View File
@@ -309,5 +309,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "激活耗时",
"DG.ItemWindow.Ritual.ActivationCosts": "激活代价",
"DG.ItemWindow.Ritual.StudyTime": "学习时长",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "学习造成的理智损失"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "学习造成的理智损失",
"DG.Messages.Skillsmark.Marked": "此技能被标记",
"DG.Messages.Skillsmark.Tooltip": "参见第 29 页《代理人手册》"
}
+4 -1
View File
@@ -289,5 +289,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Activation Time",
"DG.ItemWindow.Ritual.ActivationCosts": "Activation Costs",
"DG.ItemWindow.Ritual.StudyTime": "Study Time",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage",
"DG.Messages.Skillsmark.Marked": "Diese Fähigkeit ist gekennzeichnet.",
"DG.Messages.Skillsmark.Tooltip": "Siehe S. 29 des Agentenhandbuchs"
}
+4 -1
View File
@@ -390,5 +390,8 @@
"DG.Settings.hypergeometry.name": "Always Show Hypergeometry Section for Players",
"DG.Settings.hypergeometry.hint": "Always show Hypergeometry item section for players, even if they don't have any items of that type.",
"DG.Settings.landscapes.name": "Show Impossible Landscapes Content",
"DG.Settings.landscapes.hint": "Show Impossible Landscapes-specific fields from character sheets."
"DG.Settings.landscapes.hint": "Show Impossible Landscapes-specific fields from character sheets.",
"DG.Messages.Skillsmark.Marked": "This skill is marked",
"DG.Messages.Skillsmark.Tooltip": "See p. 29 Agent's Handbook"
}
+4 -1
View File
@@ -276,5 +276,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Activation Time",
"DG.ItemWindow.Ritual.ActivationCosts": "Activation Costs",
"DG.ItemWindow.Ritual.StudyTime": "Study Time",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage",
"DG.Messages.Skillsmark.Marked": "Esta habilidad está marcada.",
"DG.Messages.Skillsmark.Tooltip": "Ver pág. 29 del Manual del Agente."
}
+4 -1
View File
@@ -313,5 +313,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Temp d'Activation",
"DG.ItemWindow.Ritual.ActivationCosts": "Coût d'Activation",
"DG.ItemWindow.Ritual.StudyTime": "Temps d'étude",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Dégâts SAN Étude"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Dégâts SAN Étude",
"DG.Messages.Skillsmark.Marked": "Cette compétence est marquée.",
"DG.Messages.Skillsmark.Tooltip": "Voir p. 29 du Manuel de l'agent."
}
+4 -1
View File
@@ -352,5 +352,8 @@
"DG.Skills.ruby.swim": "すいえい",
"DG.Skills.ruby.unarmed_combat": "としゅかくとう",
"DG.Skills.ruby.unnatural": "ひしぜん",
"DG.Skills.ruby.flight": "ひこう"
"DG.Skills.ruby.flight": "ひこう",
"DG.Messages.Skillsmark.Marked": "このスキルはマークされています",
"DG.Messages.Skillsmark.Tooltip": "エージェントハンドブック29ページ参照"
}
+4 -1
View File
@@ -313,5 +313,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Czas Aktywacji",
"DG.ItemWindow.Ritual.ActivationCosts": "Koszt Aktywacji",
"DG.ItemWindow.Ritual.StudyTime": "Czas Nauki",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Utrata PP po Nauce"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Utrata PP po Nauce",
"DG.Messages.Skillsmark.Marked": "Ta umiejętność jest oznaczona.",
"DG.Messages.Skillsmark.Tooltip": "Zobacz str. 29 Podręcznika Agenta."
}
+4 -1
View File
@@ -311,5 +311,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Tempo de Ativação",
"DG.ItemWindow.Ritual.ActivationCosts": "Custos de Ativação",
"DG.ItemWindow.Ritual.StudyTime": "Tempo de Estudo",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Dano de SAN ao Aprender"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Dano de SAN ao Aprender",
"DG.Messages.Skillsmark.Marked": "Esta perícia é marcada",
"DG.Messages.Skillsmark.Tooltip": "Veja pág. 29 do Manual do Agente"
}
+4 -1
View File
@@ -311,5 +311,8 @@
"DG.ItemWindow.Ritual.ActivationTime": "Aktiveringstid",
"DG.ItemWindow.Ritual.ActivationCosts": "Aktiveringskostnader",
"DG.ItemWindow.Ritual.StudyTime": "Studietid",
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage"
"DG.ItemWindow.Ritual.LearnedSanityDamage": "Learn SAN Damage",
"DG.Messages.Skillsmark.Marked": "Denna färdighet är markerad",
"DG.Messages.Skillsmark.Tooltip": "Se s. 29 Agenthandbok"
}
+13
View File
@@ -15,6 +15,7 @@ import {
rollSkillMacro,
rollSkillTestAndDamageForOwnedItem,
} from "./other/macro-functions.js";
import { handleInlineActions } from "./other/inline.js";
Hooks.once("init", async () => {
game.deltagreen = {
@@ -197,3 +198,15 @@ Hooks.on("renderGamePause", function (_, html, options) {
html.querySelector("img").classList.remove("fa-spin"); // I don't like the logo spinning personally
} catch {}
});
Hooks.on("renderChatLog", async (app, element, context, options) => {
element.addEventListener("click", (event) => {
const btnWithAction = event.target.closest("button[data-action]");
const message = event.target.closest("li[data-message-id]")
const { messageId } = message?.dataset || {}
if (btnWithAction && messageId) {
handleInlineActions(btnWithAction, messageId);
}
});
});
+34
View File
@@ -0,0 +1,34 @@
export async function handleInlineActions(btnWithAction, messageId) {
let action = btnWithAction.dataset?.action;
let message = game.messages.get(messageId);
let actor = message?.speakerActor;
if (!actor) return;
if (action === 'rollback-skill-failure-state') {
const rollbackFlag = message.getFlag("deltagreen", "rollbacks");
await actor.update(foundry.utils.deepClone(rollbackFlag));
toggleAllSkillFailures(rollbackFlag)
const label = btnWithAction
.closest(".rollback-section")
?.querySelector("label");
const oldHtml = label.outerHTML;
label.classList.toggle('strike');
message.update({
[`flags.deltagreen.rollbacks`]: rollbackFlag,
content: message.content
.replace(oldHtml, label.outerHTML)
})
}
}
function toggleAllSkillFailures(data) {
for (const skill of Object.values(data.system?.skills || {})) {
skill.failure = !skill.failure;
}
for (const skill of Object.values(data.system?.typedSkills || {})) {
skill.failure = !skill.failure;
}
}
+95 -84
View File
@@ -77,12 +77,13 @@ export class DGPercentileRoll extends DGRoll {
super("1D100", {}, options);
// Set roll info for Skill, Stat, Typed Skill, and non-custom Weapon Percentile rolls.
const { target, localizedKey } = this.getRollInfoFromKey(
const { target, localizedKey, skillPath } = this.getRollInfoFromKey(
this.key,
this.actor.system,
);
this.target = target;
this.localizedKey = localizedKey;
this.skillPath = skillPath;
// Set roll info for other Percentile rolls
switch (this.type) {
@@ -272,50 +273,7 @@ export class DGPercentileRoll extends DGRoll {
this.options.rollMode = "blindroll";
}
let label = `${game.i18n.localize("DG.Roll.Rolling")} <b>${
this.localizedKey
}</b><br> ${game.i18n.localize("DG.Roll.Target")} ${this.effectiveTarget}%`;
// "Inhuman" stat being rolled. See function for details.
if (this.isInhuman) {
label = `${game.i18n.localize("DG.Roll.Rolling")} <b>${
this.localizedKey
} [${game.i18n
.localize("DG.Roll.Inhuman")
.toUpperCase()}]</b> ${game.i18n.localize("DG.Roll.Target")} ${
this.effectiveTarget
}`;
}
let isExhausted = false;
let exhaustedCheckPenalty = -20;
try {
// I suspect (but am not entirely certain) that being tired doesn't make you less lucky)
if (this.type !== "luck") {
isExhausted = this.actor.system.physical.exhausted;
exhaustedCheckPenalty = this.actor.system.physical.exhaustedPenalty;
exhaustedCheckPenalty = -1 * Math.abs(exhaustedCheckPenalty);
}
} catch {
isExhausted = false;
exhaustedCheckPenalty = -20;
}
if (this.modifier || isExhausted) {
label += ` (${this.target}%`;
if (this.modifier) {
label += `${DGUtils.formatStringWithLeadingPlus(this.modifier)}%`;
}
if (isExhausted) {
label += `${DGUtils.formatStringWithLeadingPlus(
exhaustedCheckPenalty,
)}%`;
}
label += `)`;
}
let label = this.createLabel();
let resultString = "";
let styleOverride = "";
@@ -338,31 +296,37 @@ export class DGPercentileRoll extends DGRoll {
}
}
let html = "";
html += `<div class="dice-roll">`;
html += ` <div class="dice-result">`;
html += ` <div style="${styleOverride}" class="dice-formula">${resultString}</div>`;
html += ` <div class="dice-tooltip">`;
html += ` <section class="tooltip-part">`;
html += ` <div class="dice">`;
html += ` <header class="part-header flexrow">`;
html += ` <span class="part-formula">`;
html += ` ${this.formula}`;
html += ` </span>`;
html += ` <span class="part-total">`;
html += ` ${this.total}`;
html += ` </span>`;
html += ` </header>`;
html += ` <ol class="dice-rolls">`;
html += ` <li class="roll die ${this.formula}">${this.total}</li>`;
html += ` </ol>`;
html += ` </div>`;
html += ` </section>`;
html += ` </div>`;
html += ` <h4 class="dice-total">${this.total}</h4>`;
html += `</div>`;
const failureMark = !this.isSuccess && this.skillPath
&& !foundry.utils.getProperty(this.actor, `${this.skillPath}.failure`);
return this.toMessage({ content: html, flavor: label });
const { renderTemplate } = foundry.applications.handlebars;
const html = await renderTemplate(
"systems/deltagreen/templates/roll/percentile-roll.hbs",
{
styleOverride,
resultString,
formula: this.formula,
total: this.total,
failureMark
});
//TODO: add setting for it?
if (failureMark) {
const keyForUpdate = `${this.skillPath}.failure`;
//TODO: auto-update actor or post icon with manual apply
await this.actor.update({
[keyForUpdate]: true
})
return this.toMessage({ flags: { deltagreen: {
rollbacks: {
[keyForUpdate]: false
}
} }, content: html, flavor: label });
} else {
return this.toMessage({ content: html, flavor: label });
}
}
/**
@@ -382,6 +346,7 @@ export class DGPercentileRoll extends DGRoll {
let target = null;
let localizedKey = null;
let skillPath = null; // For optimization of failure checks
if (statKeys.includes(this.key)) {
target = actorData.statistics[this.key].x5;
localizedKey = game.i18n.localize(`DG.Attributes.${this.key}`);
@@ -390,17 +355,76 @@ export class DGPercentileRoll extends DGRoll {
// use calculated target proficiency (effects and etc like aim + 20%)
target = actorData.skills[this.key].targetProficiency || actorData.skills[this.key].proficiency;
localizedKey = game.i18n.localize(`DG.Skills.${this.key}`);
skillPath = `system.skills.${this.key}`;
}
if (typedSkillKeys.includes(this.key)) {
const skill = actorData.typedSkills[this.key];
target = skill.proficiency;
localizedKey = `${skill.group} (${skill.label})`;
skillPath = `system.typedSkills.${this.key}`;
}
if (this.key === "ritual") {
target = actorData.sanity.ritual;
localizedKey = game.i18n.localize(`DG.Skills.ritual`);
}
return { target, localizedKey };
return { target, localizedKey, skillPath };
}
/**
* Create label based on result of roll
*
* todo: do we want make isInhuman more similar to base label?
*
* @returns {string}
*/
createLabel() {
const startOfLabel = game.i18n.localize("DG.Roll.Rolling")
+ ` <b>${this.localizedKey}`
const endOfLabel =`${game.i18n.localize("DG.Roll.Target")} ${this.effectiveTarget}`
let label = this.isInhuman
// "Inhuman" stat being rolled. See function for details.
? `${startOfLabel} [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}]</b> ${endOfLabel}`
: `${startOfLabel}</b><br> ${endOfLabel}%`;
const {isExhausted, exhaustedCheckPenalty} = this.exhausted
if (this.modifier || isExhausted) {
label += ` (${this.target}%`;
if (this.modifier) {
label += `${DGUtils.formatStringWithLeadingPlus(this.modifier)}%`;
}
if (isExhausted) {
label += `${DGUtils.formatStringWithLeadingPlus(
exhaustedCheckPenalty,
)}%`;
}
label += `)`;
}
return label;
}
get exhausted() {
let isExhausted = false;
let exhaustedCheckPenalty = -20;
try {
// I suspect (but am not entirely certain) that being tired doesn't make you less lucky)
if (this.type !== "luck") {
isExhausted = this.actor.system.physical.exhausted;
exhaustedCheckPenalty = this.actor.system.physical.exhaustedPenalty;
exhaustedCheckPenalty = -1 * Math.abs(exhaustedCheckPenalty);
}
} catch {
isExhausted = false;
exhaustedCheckPenalty = -20;
}
return {isExhausted, exhaustedCheckPenalty};
}
/**
@@ -481,20 +505,7 @@ export class DGPercentileRoll extends DGRoll {
get effectiveTarget() {
let target = 1;
let isExhausted = false;
let exhaustedCheckPenalty = -20;
try {
// I suspect (but am not entirely certain) that being tired doesn't make you less lucky)
if (this.type !== "luck") {
isExhausted = this.actor.system.physical.exhausted;
exhaustedCheckPenalty = this.actor.system.physical.exhaustedPenalty;
exhaustedCheckPenalty = -1 * Math.abs(exhaustedCheckPenalty);
}
} catch {
isExhausted = false;
exhaustedCheckPenalty = -20;
}
let {isExhausted, exhaustedCheckPenalty} = this.exhausted
if (!this.target || Number.isNaN(this.target)) {
return null;
+30
View File
@@ -0,0 +1,30 @@
<div class="dice-roll">
<div class="dice-result">
<div style="{{styleOverride}}" class="dice-formula">{{resultString}}</div>
<div class="dice-tooltip">
<section class="tooltip-part">
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span>
</header>
<ol class="dice-rolls">
<li class="roll die {{formula}}">{{total}}</li>
</ol>
</div>
</section>
</div>
<h4 class="dice-total">{{total}}</h4>
</div>
{{#if failureMark}}
<div class="rollback-section">
<label>{{localize 'DG.Messages.Skillsmark.Marked'}}</label>
<i class="fa fa-book" aria-hidden="true" data-tooltip="<p>{{localize 'DG.Messages.Skillsmark.Tooltip'}}</p>"></i>
<button type="button" data-action="rollback-skill-failure-state">
<i class="fa fa-undo" aria-hidden="true"></i>
</button>
</div>
{{/if}}
</div>