Skip to content

Commit

Permalink
resolves #3 New conditional message feature (#12)
Browse files Browse the repository at this point in the history
* add roll dialog template

* add message check to the reminders

* move message checks into their own classes

instead of including them in the advantage reminder classes

* add unit test for AttackMessage

* show multiple messages instead of just one

* remove sorting

Sorting the effects by priority was an attempt to repurpose that to just show one message. Probably not a good idea to change what priority was meant for, which was for how to apply multiple effects on the same thing. Also not as necessary since showing them all anyways.

* await the addMessage method

Fixes the unit tests and changed it into the module code as well to make sure we wait for setting up the dialog hook before proceeding. It worked fine without it but better to be safe

* add test for two messages and check that the hook was setup

* add test cases for AbilityCheckMessage

* add unit tests for AbilitySaveMessage

* add unit tests for SkillMessage

* add unit tests for DeathSaveMessage

* add DamageMessage implementation and tests

* add sorting back

* remove roll dialog replacement template

Was going to use this to completely replace the core dialog but went with adding to the existing one instead. Don't need this anymore

* flip priority around

to match the order Foundry applies active effect changes

* small CSS tweaks

Some changes inspired by #9. Increases the padding by 2px to get close to what it had. I still think the smaller sides is better. Increased the margin to 8px. While not as large, it's the size of the padding of the dialog box (window-content class)

* use dialogOptions.id to get the message on the right roll dialog

* add nested options in for Damage message

* update readme and changelog

* fix readme type in MRE section

* add links to issues in changelog

just a convenience

* add MRE compatibility with damage rolls

MRE configures it's damage wrapper with libWrapper as MIXED. That means it always runs after WRAPPED methods like this module uses. If we are using MRE, always process it so we can pass critical and messages down to MRE and then it can decide what to do with them.
  • Loading branch information
kaelad02 authored Jan 27, 2022
1 parent f7daacc commit 8453286
Show file tree
Hide file tree
Showing 8 changed files with 1,188 additions and 26 deletions.
17 changes: 11 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# 1.0

- feature: [#3](https://github.com/kaelad02/adv-reminder/issues/3) Messages that show up on the roll dialogs
- feature: improve MRE compatibility to skip when it fast-forwards rolls

# 0.4

* feature: support armor that imposes stealth disadvantage
- feature: [#7](https://github.com/kaelad02/adv-reminder/issues/7) support armor that imposes stealth disadvantage

# 0.3

* bug fix: Active effects from unequipped items weren't being ignored
* documentation: Update readme with module compatibility info
- bug fix: [#2](https://github.com/kaelad02/adv-reminder/issues/2) Active effects from unequipped items weren't being ignored
- documentation: Update readme with module compatibility info

# 0.2

* feature: add Tool checks support
* mark compatible with v9
- feature: add Tool checks support
- mark compatible with v9

# 0.1

* Initial release
- Initial release
47 changes: 37 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ In addition to the active effects, this module supports armor that imposes steal

Supports active effects on the following rolls:

* Attack rolls
* Ability checks
* Saving throws, including auto-fail (e.g. Stunned)
* Skill checks
* Tool checks
* Death saves
* Damage rolls
- Attack rolls
- Ability checks
- Saving throws, including auto-fail (e.g. Stunned)
- Skill checks
- Tool checks
- Death saves
- Damage rolls

## Auto-Fail Rolls

Expand All @@ -26,6 +26,31 @@ There are active effect keys for automatically failing ability checks, saving th

If the player holds down one of the Ctrl/Alt/Shift/Meta keys to fast-forward the die roll (e.g. hold Alt to roll with advantage, skipping the roll dialog) then this module WILL NOT do anything. Holding down one of those keys stops the roll dialog from popping up so it's interpreted as overriding what this module does.

## Messages

In addition to active effects adding advantage or disadvantage, you can also add messages to remind you of conditional bonuses or advantage. For example, features like Dwarven Resilience give advantage on saving throws against poison don't work with the advantage flags since there isn't a way to limit it to poison. Now you can add a message to the dialog right above the buttons to remind you about Dwarven Resilience.

![Saving Throw screenshot with message](screenshot2.png?raw=true)

You have control over when the message appears and what it contains, including HTML formatting. In the screenshot above it just reminds you to roll with advantage on saving throws against poison. You are free to change it to just include `Dwarven Resilience` if that's all the reminder you need or `Advantage against poison` that doesn't mention saving throws since it only appears on CON saving throws.

The active effects keys are listed below and should be set with the change mode of `Custom`.

- `flags.adv-reminder.message.all` for all rolls
- `flags.adv-reminder.message.attack.all` for all Attack rolls
- `flags.adv-reminder.message.attack.mwak/rwak/msak/rsak` for Attack rolls of a specific action type
- `flags.adv-reminder.message.attack.str/dex/con/int/wis/cha` for Attack rolls using a specific ability
- `flags.adv-reminder.message.ability.all` for all Ability checks, Saving throws, Skill checks, and Death saves
- `flags.adv-reminder.message.ability.check.all` for all Ability checks and Skill checks
- `flags.adv-reminder.message.ability.check.str/dex/con/int/wis/cha` for specific Ability checks and Skill checks
- `flags.adv-reminder.message.ability.save.all` for all Saving throws and Death saves
- `flags.adv-reminder.message.ability.save.str/dex/con/int/wis/cha` for specific Saving throws
- `flags.adv-reminder.message.skill.all` for all Skill checks
- `flags.adv-reminder.message.skill.acr/ath/.../sur` for specific Skill checks
- `flags.adv-reminder.message.deathSave` for Death saves
- `flags.adv-reminder.message.damage.all` for all Damage rolls
- `flags.adv-reminder.message.damage.mwak/rwak/msak/rsak` for Damage rolls of a specific action type

## Other Modules

Notes about other modules.
Expand All @@ -44,9 +69,11 @@ Notes about other modules.

### Compatibility Notes

[Better Rolls for 5e](https://foundryvtt.com/packages/betterrolls5e) This module works with Better Rolls, making rolls with advantage and disadvantage with some caviats.
[Better Rolls for 5e](https://foundryvtt.com/packages/betterrolls5e) This module works with Better Rolls, making rolls with advantage and disadvantage with the following known issue(s).

* Active effects for critical hits do not work.
* The "d20 Mode" Better Rolls setting of "Single Roll Upgradeable" does not give the hint in the pop-up asking what kind of roll to perform. It will still apply the active effects though possibly leading to some confusion, especially since advantage and disadvantage will not cancel each other out like they should.
- Active effects for critical hits do not work.
- The "d20 Mode" Better Rolls setting of "Single Roll Upgradeable" does not give the hint in the pop-up asking what kind of roll to perform. It will still apply the active effects though possibly leading to some confusion, especially since advantage and disadvantage will not cancel each other out like they should.

[Midi QOL](https://foundryvtt.com/packages/midi-qol) This module is compatible with Midi QOL. However, if you've enabled Midi QOL's workflow then it is not necessary to use this module as well since Midi QOL will already do this for you.

[Minimal Rolling Enhancements for D&D5e](https://foundryvtt.com/packages/mre-dnd5e) This module works with MRE, making rolls with advantage/disadvantage and showing messages if you hold the "Roll Dialog Modifier Key" (an MRE setting).
13 changes: 13 additions & 0 deletions css/adv-reminder.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@
.dialog .dialog-buttons button.default.critical {
font-weight: bold;
}

.dialog .dialog-content .adv-reminder-messages {
background: rgba(0, 0, 0, 0.05);
padding: 3px 5px;
margin: 8px 0;
color: #191813;
border: 1px solid #7a7971;
border-radius: 3px;
}

.adv-reminder-messages > div:not(:last-child) {
margin-bottom: 10px;
}
Binary file added screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions src/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { debug } from "./util.js";

class BaseMessage {
constructor(actor) {
/** @type {EffectChangeData[]} */
this.changes = actor.effects
.filter((effect) => !effect.isSuppressed && !effect.data.disabled)
.flatMap((effect) => effect.data.changes)
.sort((a, b) => a.priority - b.priority);
}

get messageKeys() {
return ["flags.adv-reminder.message.all"];
}

async addMessage(options) {
const keys = this.messageKeys;
const messages = this.changes
.filter((change) => keys.includes(change.key))
.map((change) => change.value);

if (messages.length > 0) {
// add id to dialogOptions to put the message on the correct roll dialog
const messageId = randomID();
options.dialogOptions = options.dialogOptions || {};
options.dialogOptions.id = messageId;
// build message
const message = await renderTemplate(
"modules/adv-reminder/templates/roll-dialog-messages.hbs",
{ messages }
);
debug("adding hook to renderDialog w/ ", message);
const hookId = Hooks.on("renderDialog", (dialog, html, data) => {
debug("called on hook for renderDialog");
if (dialog.options.id !== messageId) return;
// add message at the end
const formGroups = html.find(".form-group:last");
formGroups.after(message);
// reset dialog height
const position = dialog.position;
position.height = "auto";
dialog.setPosition(position);
});
setTimeout(() => {
Hooks.off("renderDialog", hookId);
}, 10_000);
}

return messages;
}
}

export class AttackMessage extends BaseMessage {
constructor(actor, item) {
super(actor);

/** @type {string} */
this.actionType = item.data.data.actionType;
/** @type {string} */
this.abilityId = item.abilityMod;
}

/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.attack.all",
`flags.adv-reminder.message.attack.${this.actionType}`,
`flags.adv-reminder.message.attack.${this.abilityId}`
);
}
}

class AbilityBaseMessage extends BaseMessage {
constructor(actor, abilityId) {
super(actor);

/** @type {string} */
this.abilityId = abilityId;
}

/** @override */
get messageKeys() {
return super.messageKeys.concat("flags.adv-reminder.message.ability.all");
}
}

export class AbilityCheckMessage extends AbilityBaseMessage {
/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.ability.check.all",
`flags.adv-reminder.message.ability.check.${this.abilityId}`
);
}
}

export class AbilitySaveMessage extends AbilityBaseMessage {
/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.ability.save.all",
`flags.adv-reminder.message.ability.save.${this.abilityId}`
);
}
}

export class SkillMessage extends AbilityCheckMessage {
constructor(actor, skillId) {
super(actor, actor.data.data.skills[skillId].ability);

/** @type {string} */
this.skillId = skillId;
}

/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.skill.all",
`flags.adv-reminder.message.skill.${this.skillId}`
);
}
}

export class DeathSaveMessage extends AbilityBaseMessage {
constructor(actor) {
super(actor, null);
}

/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.ability.save.all",
"flags.adv-reminder.message.deathSave"
);
}
}

export class DamageMessage extends BaseMessage {
constructor(actor, item) {
super(actor);

/** @type {string} */
this.actionType = item.data.data.actionType;
}

/** @override */
get messageKeys() {
return super.messageKeys.concat(
"flags.adv-reminder.message.damage.all",
`flags.adv-reminder.message.damage.${this.actionType}`
);
}

async addMessage(options) {
// Damage options has a nested options variable, add that and pass it to super
options.options = options.options || {};
return super.addMessage(options.options);
}
}
Loading

0 comments on commit 8453286

Please sign in to comment.