From f9313f91b174e943ec70d0ad1bd6cad852aba3cf Mon Sep 17 00:00:00 2001 From: Werner Date: Sun, 22 Jan 2023 20:55:00 +0100 Subject: [PATCH 1/2] add fail button to ability, save and skill rolls --- css/lmrtfy.css | 29 +++++++- lang/en.json | 6 +- src/lmrtfy.js | 26 +++++++ src/requestor.js | 5 +- src/roller.js | 155 +++++++++++++++++++++++++++++++++++++++--- templates/roller.html | 8 ++- 6 files changed, 216 insertions(+), 13 deletions(-) diff --git a/css/lmrtfy.css b/css/lmrtfy.css index 04d404d..8ca08bb 100644 --- a/css/lmrtfy.css +++ b/css/lmrtfy.css @@ -130,8 +130,11 @@ form .form-group .lmrtfy-skill-checks { } .lmrtfy-ability-check, +.lmrtfy-ability-check-fail, .lmrtfy-ability-save, +.lmrtfy-ability-save-fail, .lmrtfy-skill-check, +.lmrtfy-skill-check-fail, .lmrtfy-custom-formula, .lmrtfy-initiative, .lmrtfy-death-save, @@ -142,8 +145,11 @@ form .form-group .lmrtfy-skill-checks { margin-top: 4px; } .lmrtfy-parchment .lmrtfy-ability-check, +.lmrtfy-parchment .lmrtfy-ability-check-fail, .lmrtfy-parchment .lmrtfy-ability-save, +.lmrtfy-parchment .lmrtfy-ability-save-fail, .lmrtfy-parchment .lmrtfy-skill-check, +.lmrtfy-parchment .lmrtfy-skill-check-fail, .lmrtfy-parchment .lmrtfy-custom-formula, .lmrtfy-parchment .lmrtfy-initiative, .lmrtfy-parchment .lmrtfy-death-save, @@ -156,6 +162,15 @@ form .form-group .lmrtfy-skill-checks { height: 78px; } +.enable-lmrtfy-ability-check-fail, +.enable-lmrtfy-ability-save-fail, +.enable-lmrtfy-skill-check-fail { + width: 10%; + position: absolute !important; + margin-top: -57px !important; + right: 27px; +} + .lmrtfy-roller button:disabled { opacity: 0.5; } @@ -210,7 +225,8 @@ form .form-group .lmrtfy-skill-checks { transition: all ease-in-out 0.25s; position: relative; margin: 6px; - border-radius: 5px + border-radius: 5px; + cursor: pointer; } .lmrtfy-bonus-button { @@ -396,3 +412,14 @@ form .form-group .lmrtfy-skill-checks { .system-pf1 form .form-group .lmrtfy-skill-checks { max-height: 600px; } + +.disabled-button { + cursor: default !important; + filter: opacity(0.5) !important; +} + +.disabled-button:hover { + color: #000 !important; + border-color: #000 !important; + box-shadow: 0 0 6px inset #8d9ea7 !important; +} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 76532a2..04bdabf 100644 --- a/lang/en.json +++ b/lang/en.json @@ -59,5 +59,9 @@ "LMRTFY.SelectedNotification": "The selected user can only be used with 'Save Request as Macro'", "LMRTFY.Macros": "For Macros", "LMRTFY.Users": "Users", - "LMRTFY.NoSelectedToken": "Token(s) needs to be selected before the macro can be run" + "LMRTFY.NoSelectedToken": "Token(s) needs to be selected before the macro can be run", + "LMRTFY.SavingThrowFail": "FAIL Saving Throw: ", + "LMRTFY.AbilityCheckFail": "FAIL Ability Check: ", + "LMRTFY.SkillCheckFail": "FAIL Skill Check: ", + "LMRTFY.EnableChooseFail": "Enable Fail Button" } diff --git a/src/lmrtfy.js b/src/lmrtfy.js index a8b3a58..69f2007 100644 --- a/src/lmrtfy.js +++ b/src/lmrtfy.js @@ -66,6 +66,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.DND5E.abilityAbbreviations; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = true; break; case 'pf1': @@ -82,6 +83,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.PF1.abilitiesShort; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'pf2e': @@ -98,6 +100,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.PF2E.abilities; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'D35E': @@ -114,6 +117,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.D35E.abilityAbbreviations; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'cof': @@ -129,6 +133,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.COF.statAbbreviations; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'coc': @@ -144,6 +149,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = CONFIG.COC.statAbbreviations; LMRTFY.modIdentifier = 'mod'; LMRTFY.abilityModifiers = LMRTFY.parseAbilityModifiers(); + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'demonlord': @@ -162,6 +168,7 @@ class LMRTFY { LMRTFY.abilityAbbreviations = abilities; LMRTFY.modIdentifier = 'modifier'; LMRTFY.abilityModifiers = {}; + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'ose': @@ -180,6 +187,7 @@ class LMRTFY { LMRTFY.specialRolls = {}; LMRTFY.modIdentifier = 'modifier'; LMRTFY.abilityModifiers = {}; + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; case 'foundry-chromatic-dungeons': @@ -191,6 +199,7 @@ class LMRTFY { LMRTFY.skills = {}; LMRTFY.saves = CONFIG.CHROMATIC.saves; LMRTFY.specialRolls = {}; + LMRTFY.canFailChecks = false; // unsure if how and if system could handle this break; default: @@ -198,6 +207,23 @@ class LMRTFY { } + LMRTFY.d20Svg = '' + + '' + + '' + + '' + + '' + if (game.settings.get('lmrtfy', 'deselectOnRequestorRender')) { Hooks.on("renderLMRTFYRequestor", () => { canvas.tokens.releaseAll(); diff --git a/src/requestor.js b/src/requestor.js index 710fc70..a1724cf 100644 --- a/src/requestor.js +++ b/src/requestor.js @@ -330,9 +330,9 @@ class LMRTFYRequestor extends FormApplication { let dc = undefined; if (game.system.id === 'pf2e') { - if (Number.isInteger(formData.dc)) { + if (Number.isInteger(parseInt(formData.dc))) { dc = { - value: formData.dc, + value: parseInt(formData.dc), visibility: formData.visibility } } @@ -354,6 +354,7 @@ class LMRTFYRequestor extends FormApplication { perception: formData['extra-perception'], tables: tables, chooseOne: formData['choose-one'], + canFailChecks: LMRTFY.canFailChecks, } if (game.system.id === 'pf2e' && dc) { socketData['dc'] = dc; diff --git a/src/roller.js b/src/roller.js index 34f87f2..96b2f1d 100644 --- a/src/roller.js +++ b/src/roller.js @@ -28,6 +28,45 @@ class LMRTFYRoller extends Application { SKILL: "skill", PERCEPTION: "perception", } + + Handlebars.registerHelper('canFailAbilityChecks', function (name, ability) { + if (LMRTFY.canFailChecks) { + return `
` + + `` + + `
` + + `${LMRTFY.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); + + Handlebars.registerHelper('canFailSaveChecks', function (name, ability) { + if (LMRTFY.canFailChecks) { + return `
` + + `` + + `
` + + `${LMRTFY.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); + + Handlebars.registerHelper('canFailSkillChecks', function (name, skill) { + if (LMRTFY.canFailChecks) { + return `
` + + `` + + `
` + + `${LMRTFY.d20Svg}` + + `
` + + `
`; + } else { + return ''; + } + }); } static get defaultOptions() { @@ -135,6 +174,15 @@ class LMRTFYRoller extends Application { if(LMRTFY.specialRolls['perception']) { this.element.find(".lmrtfy-perception").click(this._onPerception.bind(this)) } + + this.element.find(".enable-lmrtfy-ability-check-fail").click(this._onToggleFailAbilityRoll.bind(this)); + this.element.find(".lmrtfy-ability-check-fail").click(this._onFailAbilityCheck.bind(this)); + + this.element.find(".enable-lmrtfy-ability-save-fail").click(this._onToggleFailSaveRoll.bind(this)); + this.element.find(".lmrtfy-ability-save-fail").click(this._onFailAbilitySave.bind(this)); + + this.element.find(".enable-lmrtfy-skill-check-fail").click(this._onToggleFailSkillRoll.bind(this)); + this.element.find(".lmrtfy-skill-check-fail").click(this._onFailSkillCheck.bind(this)); } _checkClose() { @@ -143,7 +191,39 @@ class LMRTFYRoller extends Application { } } - async _makeRoll(event, rollMethod, ...args) { + _disableButtons(event) { + event.currentTarget.disabled = true; + + const buttonSelector = `${event.currentTarget.className}`; + let oppositeSelector = ""; + let dataSelector = ""; + + if ( + event.currentTarget.className.indexOf('ability-check') > 0 || + event.currentTarget.className.indexOf('ability-save') > 0 + ) { + dataSelector = `[data-ability *= '${event?.currentTarget?.dataset?.ability}']`; + } else { + dataSelector = `[data-skill *= '${event?.currentTarget?.dataset?.skill}']`; + } + + if (event.currentTarget.className.indexOf('fail') > 0) { + oppositeSelector = event.currentTarget.className.substring(0, event.currentTarget.className.indexOf('fail') - 1); + } else { + oppositeSelector = `${event.currentTarget.className}-fail`; + } + + const enableButton = document.querySelector(`.enable-${buttonSelector}${dataSelector}`); + if (enableButton) { + enableButton.disabled = true; + enableButton.classList.add('disabled-button'); + } + + const oppositeButton = document.querySelector(`.${oppositeSelector}${dataSelector}`); + if (oppositeButton) oppositeButton.disabled = true; + } + + async _makeRoll(event, rollMethod, failRoll, ...args) { let fakeEvent = {} switch(this.advantage) { case -1: @@ -184,7 +264,7 @@ class LMRTFYRoller extends Application { case this.pf2eRollFor.SKILL: // system specific roll handling - const skill = actor.system.data.skills[args[0]]; + const skill = actor.system.skills[args[0]]; // roll lore skills only for actors who have them ... if (!skill) continue; @@ -194,7 +274,7 @@ class LMRTFYRoller extends Application { case this.pf2eRollFor.PERCEPTION: const precOptions = actor.getRollOptions(['all', 'wis-based', 'perception']); - actor.system.data.attributes.perception.roll({ event, precOptions, dc: this.dc }); + actor.perception.roll({ event, precOptions, dc: this.dc }); break; } @@ -225,14 +305,20 @@ class LMRTFYRoller extends Application { } default: { - await actor[rollMethod].call(actor, ...args, { event: fakeEvent }); + const options = { + event: fakeEvent, + } + if (failRoll) { + options["parts"] = [-100]; + } + await actor[rollMethod].call(actor, ...args, options); } } } game.settings.set("core", "rollMode", rollMode); - event.currentTarget.disabled = true; + this._disableButtons(event); this._checkClose(); } @@ -383,21 +469,42 @@ class LMRTFYRoller extends Application { event.preventDefault(); const ability = event.currentTarget.dataset.ability; if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.ABILITY; - this._makeRoll(event, LMRTFY.abilityRollMethod, ability); + this._makeRoll(event, LMRTFY.abilityRollMethod, false, ability); + } + + _onFailAbilityCheck(event) { + event.preventDefault(); + const ability = event.currentTarget.dataset.ability; + if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.ABILITY; + this._makeRoll(event, LMRTFY.abilityRollMethod, true, ability); } _onAbilitySave(event) { event.preventDefault(); const saves = event.currentTarget.dataset.ability; if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.SAVE; - this._makeRoll(event, LMRTFY.saveRollMethod, saves); + this._makeRoll(event, LMRTFY.saveRollMethod, false, saves); + } + + _onFailAbilitySave(event) { + event.preventDefault(); + const saves = event.currentTarget.dataset.ability; + if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.SAVE; + this._makeRoll(event, LMRTFY.saveRollMethod, true, saves); } _onSkillCheck(event) { event.preventDefault(); const skill = event.currentTarget.dataset.skill; if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.SKILL; - this._makeRoll(event, LMRTFY.skillRollMethod, skill); + this._makeRoll(event, LMRTFY.skillRollMethod, false, skill); + } + + _onFailSkillCheck(event) { + event.preventDefault(); + const skill = event.currentTarget.dataset.skill; + if (game.system.id === 'pf2e') this.pf2Roll = this.pf2eRollFor.SKILL; + this._makeRoll(event, LMRTFY.skillRollMethod, true, skill); } async _onCustomFormula(event) { @@ -454,4 +561,36 @@ class LMRTFYRoller extends Application { this._drawTable(event, table); } + _onToggleFailAbilityRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.lmrtfy-ability-check-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.lmrtfy-ability-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } + + _onToggleFailSaveRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.lmrtfy-ability-save-fail[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.lmrtfy-ability-save[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } + + _onToggleFailSkillRoll(event) { + event.preventDefault(); + if (event.currentTarget.classList.contains('disabled-button')) return; + + const failButton = document.querySelector(`.lmrtfy-skill-check-fail[data-skill *= '${event?.currentTarget?.dataset?.skill}']`); + if (failButton) failButton.disabled = !failButton.disabled; + + const normalButton = document.querySelector(`.lmrtfy-skill-check[data-ability *= '${event?.currentTarget?.dataset?.ability}']`); + if (normalButton) normalButton.disabled = !normalButton.disabled; + } } diff --git a/templates/roller.html b/templates/roller.html index 9c97be1..60a9ac0 100644 --- a/templates/roller.html +++ b/templates/roller.html @@ -28,16 +28,22 @@
+ {{#canFailAbilityChecks name ability}} + {{/canFailAbilityChecks}} {{/each}} {{#each saves as |name ability|}}
+ {{#canFailSaveChecks name ability}} + {{/canFailSaveChecks}} {{/each}} {{#each skills as |name skill|}}
+ {{#canFailSkillChecks name skill}} + {{/canFailSkillChecks}} {{/each}} {{#if customFormula}}
@@ -65,5 +71,5 @@
{{/each}} - {{/if}} + {{/if}} From b4395d7152c465514bc237d38ce4ff524ffdab07 Mon Sep 17 00:00:00 2001 From: Werner Date: Sun, 22 Jan 2023 21:17:40 +0100 Subject: [PATCH 2/2] make sure to only do other button disable if we can do fail checks --- src/roller.js | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/roller.js b/src/roller.js index 96b2f1d..bae0792 100644 --- a/src/roller.js +++ b/src/roller.js @@ -194,33 +194,35 @@ class LMRTFYRoller extends Application { _disableButtons(event) { event.currentTarget.disabled = true; - const buttonSelector = `${event.currentTarget.className}`; - let oppositeSelector = ""; - let dataSelector = ""; - - if ( - event.currentTarget.className.indexOf('ability-check') > 0 || - event.currentTarget.className.indexOf('ability-save') > 0 - ) { - dataSelector = `[data-ability *= '${event?.currentTarget?.dataset?.ability}']`; - } else { - dataSelector = `[data-skill *= '${event?.currentTarget?.dataset?.skill}']`; - } + if (LMRTFY.canFailChecks) { + const buttonSelector = `${event.currentTarget.className}`; + let oppositeSelector = ""; + let dataSelector = ""; + + if ( + event.currentTarget.className.indexOf('ability-check') > 0 || + event.currentTarget.className.indexOf('ability-save') > 0 + ) { + dataSelector = `[data-ability *= '${event?.currentTarget?.dataset?.ability}']`; + } else { + dataSelector = `[data-skill *= '${event?.currentTarget?.dataset?.skill}']`; + } - if (event.currentTarget.className.indexOf('fail') > 0) { - oppositeSelector = event.currentTarget.className.substring(0, event.currentTarget.className.indexOf('fail') - 1); - } else { - oppositeSelector = `${event.currentTarget.className}-fail`; - } + if (event.currentTarget.className.indexOf('fail') > 0) { + oppositeSelector = event.currentTarget.className.substring(0, event.currentTarget.className.indexOf('fail') - 1); + } else { + oppositeSelector = `${event.currentTarget.className}-fail`; + } - const enableButton = document.querySelector(`.enable-${buttonSelector}${dataSelector}`); - if (enableButton) { - enableButton.disabled = true; - enableButton.classList.add('disabled-button'); - } + const enableButton = document.querySelector(`.enable-${buttonSelector}${dataSelector}`); + if (enableButton) { + enableButton.disabled = true; + enableButton.classList.add('disabled-button'); + } - const oppositeButton = document.querySelector(`.${oppositeSelector}${dataSelector}`); - if (oppositeButton) oppositeButton.disabled = true; + const oppositeButton = document.querySelector(`.${oppositeSelector}${dataSelector}`); + if (oppositeButton) oppositeButton.disabled = true; + } } async _makeRoll(event, rollMethod, failRoll, ...args) {