From 835866233b4aae5e9ced5f7bce23fac10a3520e1 Mon Sep 17 00:00:00 2001 From: jalensailin Date: Mon, 18 Sep 2023 18:04:10 -0400 Subject: [PATCH] refactor % roll into separate class; skills and stats function, other rolls need work --- module/actor/actor-sheet.js | 9 +- module/deltagreen.js | 5 +- module/roll/roll.js | 185 +++++++++++++++++++----------------- 3 files changed, 108 insertions(+), 91 deletions(-) diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index a61b0c3..2f85c23 100644 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -1,6 +1,7 @@ /* globals $ game Roll ChatMessage AudioHelper ActorSheet mergeObject Dialog TextEditor ActiveEffect ui duplicate fromUuidSync */ import { + DGPercentileRoll, sendPercentileTestToChat, sendLethalityTestToChat, sendDamageRollToChat, @@ -611,7 +612,8 @@ export class DeltaGreenActorSheet extends ActorSheet { case "sanity": { // const confirmRoll = showModifyPercentileTestDialogue(this.actor); // if (!confirmRoll) return; - sendPercentileTestToChat(dataset.rolltype, dataset.key, this.actor); + const roll = new DGPercentileRoll(dataset.rolltype, dataset.key, this.actor) + sendPercentileTestToChat(roll); break; } @@ -620,13 +622,14 @@ export class DeltaGreenActorSheet extends ActorSheet { return ui.notifications.error("No item id provided.") } const item = this.actor.items.get(dataset.iid); - sendPercentileTestToChat(dataset.rolltype, dataset.key, this.actor, item); + const roll = new DGPercentileRoll(dataset.rolltype, dataset.key, this.actor, item) + sendPercentileTestToChat(roll); break; } case "damage": { let diceFormula = dataset.roll; - let skillType = dataset.skill ? dataset.skill : ''; + let skillType = dataset.key; if(this.actor.type === 'agent' && (skillType === 'unarmed_combat' || skillType === 'melee_weapons')){ diceFormula += this.actor.system.statistics.str.meleeDamageBonusFormula; } diff --git a/module/deltagreen.js b/module/deltagreen.js index 9c90d1f..00b24d2 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -3,7 +3,7 @@ import { DeltaGreenActor } from "./actor/actor.js"; import { DeltaGreenActorSheet } from "./actor/actor-sheet.js"; import { DeltaGreenItem } from "./item/item.js"; import { DeltaGreenItemSheet } from "./item/item-sheet.js"; -import { sendPercentileTestToChat, sendLethalityTestToChat, sendDamageRollToChat } from "./roll/roll.js"; +import { sendPercentileTestToChat, sendLethalityTestToChat, sendDamageRollToChat, DGPercentileRoll } from "./roll/roll.js"; import { registerSystemSettings } from "./settings.js" import { preloadHandlebarsTemplates } from "./templates.js"; import { ParseDeltaGreenStatBlock } from "./other/stat-parser-macro.js"; @@ -29,6 +29,9 @@ Hooks.once('init', async function() { decimals: 0 }; + // Register custom dice rolls + CONFIG.Dice.rolls.push(DGPercentileRoll); + // Register System Settings registerSystemSettings(); diff --git a/module/roll/roll.js b/module/roll/roll.js index 6f52c80..149a645 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -2,119 +2,130 @@ import {localizeWithFallback} from "../other/utility-functions.js" -export class DGRoll extends Roll { - constructor(formula, data, rollType, options) { - super(formula, data, options); +export class DGPercentileRoll extends Roll { + constructor(rollType, key, actor, item, options) { + super("1D100", {}, options); + this.type = rollType; + this.key = key; + this.actor = actor; + this.item = item; + switch (rollType) { + case "stat": + this.rollBasis = actor.system.statistics[key] + this.target = this.rollBasis.x5; + this.localizedKey = key.toUpperCase(); + break; + case "skill": + this.rollBasis = actor.system.skills[key]; + this.target = this.rollBasis.proficiency; + this.localizedKey = game.i18n.localize(`DG.Skills.${key}`); + break; + default: + break; + } + } + + /** + * "Inhuman" stat being rolled, logic is different per page 188 of the Handler's Guide. + * Note - originally implemented by Uriele, but my attempt at merging conficts went poorly, so re-implementing. + * For an inhuman check, the roll succeeds except on a roll of 100 which fails AND fumbles. + * If the roll is a matching digit roll, it is a critical as normal. + * Also, if the roll is below the regular (non-x5) value of the stat, it is a critical. E.g. a CON of 25, a d100 roll of 21 would be a critical. + * + */ + get isInhuman() { + if (this.target > 99 && this.type === "stat") { + return true; + } + return false; + } + + /** + * Determines if a roll result is critical. + * If roll has not been evaluated, return null. + * + * @returns {null|Boolean} + */ + get isCritical() { + // If roll isn't evaluated, return null. + if (!this.total) { + return null; + } + let isCritical = false + + // 1, 100, or any matching dice are a crit, i.e. 11, 22, 33...99. + if (this.total === 1 || this.total === 100 || this.total % 11 === 0) { + // really good, or reeaaaally bad + isCritical = true; + } + + // If inhuman and the roll is below the regular (non-x5) value of the stat, it is a critical. + // E.g. a CON of 25, a d100 roll of 21 would be a critical. + if (this.isInhuman && this.total <= (this.target / 5)) { + isCritical = true; + } + + return isCritical; + } + + /** + * Determines if a roll succeeded. + * If roll has not been evaluated, return null. + * + * @returns {null|Boolean} + */ + get isSuccess() { + // If roll isn't evaluated, return null. + if (!this.total) { + return null; + } + + // A roll of 100 always (critically) fails, even for inhuman rolls. + if (this.total === 100) return false; + return this.total <= this.target; } } -export async function sendPercentileTestToChat(rollType, rollKey, actor, item){ - let roll = new Roll('1D100', actor.system) - +export async function sendPercentileTestToChat(roll){ await roll.evaluate({async: true}); - - const msg = await roll.toMessage({}, {create: false}); - - let total = roll.total; - let isCritical = false; - let isSuccess = false; - let html = ''; - let label = ''; - let resultString = ''; - let styleOverride = ''; - let target; - let rollBasis; - let localizedKey; - - - - switch (rollType) { - case "stat": - rollBasis = actor.system.statistics[rollKey] - target = rollBasis.x5; - localizedKey = rollKey.toUpperCase(); - break; - case "skill": - rollBasis = actor.system.skills[rollKey]; - target = rollBasis.proficiency; - localizedKey = game.i18n.localize(`DG.Skills.${rollKey}`); - break; - default: - break; - } - let rollMode = game.settings.get("core", "rollMode"); // if using private san rolls, must hide any SAN roll unless user is a GM - let setting = false; - - setting = game.settings.get("deltagreen", "keepSanityPrivate"); - - if(setting && (rollKey === 'sanity' || rollKey === 'ritual') && !game.user.isGM){ + const privateSanSetting = game.settings.get("deltagreen", "keepSanityPrivate"); + if (privateSanSetting && (roll.key === 'sanity' || roll.key === 'ritual') && !game.user.isGM){ rollMode = 'blindroll'; } - // "Inhuman" stat being rolled, logic is different per page 188 of the Handler's Guide. - // Note - originally implemented by Uriele, but my attempt at merging conficts went poorly, so re-implementing. - // For an inhuman check, the roll succeeds except on a roll of 100 which fails AND fumbles. - // If the roll is a matching digit roll, it is a critical as normal. - // Also, if the roll is below the regular (non-x5) value of the stat, it is a critical. E.g. a CON of 25, a d100 roll of 21 would be a critical. - if(target > 99 && rollType === "stat"){ - - label = `${game.i18n.localize("DG.Roll.Rolling")} ${localizedKey} [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}] ${game.i18n.localize("DG.Roll.Target")} ${Math.floor(target / 5)}`; - - if(total === 100){ - // only possible fail criteria, and also a fumble. - isSuccess = false; - isCritical = true; - } - else{ - isSuccess = true; - if(total <= (target / 5.0)){ - isCritical = true; - } - else if(skillCheckResultIsCritical(total)){ - isCritical = true; - } - else{ - isCritical = false; - } - } - - } - else{ - - label = `${game.i18n.localize("DG.Roll.Rolling")} ${localizedKey} ${game.i18n.localize("DG.Roll.Target")} ${target}`; - - isCritical = skillCheckResultIsCritical(total); - - if(total <= target){ - isSuccess = true; - } - + let label = ''; + // "Inhuman" stat being rolled. See function for details. + if (roll.isInhuman) { + label = `${game.i18n.localize("DG.Roll.Rolling")} ${roll.localizedKey} [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}] ${game.i18n.localize("DG.Roll.Target")} ${Math.floor(roll.target / 5)}`; + } else { + label = `${game.i18n.localize("DG.Roll.Rolling")} ${roll.localizedKey} ${game.i18n.localize("DG.Roll.Target")} ${roll.target}`; } - if(isCritical){ + let resultString = '', styleOverride = ''; + if (roll.isCritical) { resultString = `${game.i18n.localize("DG.Roll.Critical")} `; } - if(isSuccess){ + if (roll.isSuccess) { resultString += `${game.i18n.localize("DG.Roll.Success")}`; - if(isCritical){ + if (roll.isCritical){ resultString = resultString.toUpperCase() + '!'; styleOverride="color: green"; } - } - else{ + } else { resultString += `${game.i18n.localize("DG.Roll.Failure")}`; - if(isCritical){ + if(roll.isCritical){ resultString = resultString.toUpperCase() + '!'; styleOverride="color: red"; } } + let html = ''; html += `
` html += `
` html += `
${resultString}
` @@ -135,7 +146,7 @@ export async function sendPercentileTestToChat(rollType, rollKey, actor, item){ html += `
` let chatData = { - speaker: ChatMessage.getSpeaker({actor: actor}), + speaker: ChatMessage.getSpeaker({actor: roll.actor}), content: html, flavor: label, type: 5, //CHAT_MESSAGE_TYPES.ROLL,