Skip to content

Commit

Permalink
Merge pull request #77 from kaelad02/dnd5e-v4
Browse files Browse the repository at this point in the history
System version 4.0 compatibility
  • Loading branch information
kaelad02 authored Sep 17, 2024
2 parents a46487e + e8eff5e commit 009cdf5
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 41 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 4.1.0

- feature: [#77](https://github.com/kaelad02/adv-reminder/pull/77) System version 4.0 compatibility

# 4.0.2

- bug fix: [#74](https://github.com/kaelad02/adv-reminder/pull/74) Fix pt-BR localization file
Expand Down
21 changes: 21 additions & 0 deletions css/adv-reminder.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,24 @@
.adv-reminder-messages a.dialog-roll i {
color: var(--color-text-dark-inactive);
}

/* AppV2 Dialogs */
.dnd5e2.roll-configuration .dialog-buttons button {
flex: 2;
}
.dnd5e2.roll-configuration .dialog-buttons button.default {
flex: 3;
border-color: var(--dnd5e-color-gold);
}
/* Copy the system's .inline-roll styling for .dialog-roll */
.dnd5e2.roll-configuration a.dialog-roll {
background: transparent;
border: none;
text-decoration: underline currentcolor;
text-underline-offset: 2px;
}
.dnd5e2.roll-configuration a.dialog-roll > i {
color: var(--dnd5e-color-text-dark-primary);
opacity: 0.75;
transition: opacity 250ms ease;
}
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"adv-reminder.AutoFail": "Auto fail",
"adv-reminder.Messages": "Messages",
"adv-reminder.ColorMenu": {
"Name": "Roll Dialog Colors",
"Hint": "Change the colors of messages and default buttons on roll dialogs",
Expand Down
6 changes: 3 additions & 3 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
"authors": [{ "name": "kaelad", "discord": "kaelad#1693" }],
"version": "0.1",
"compatibility": {
"minimum": "11.315",
"minimum": "12.331",
"verified": "12"
},
"relationships": {
"systems": [
{
"id": "dnd5e",
"compatibility": {
"minimum": "3.0.0",
"verified": "3.1.2"
"minimum": "4.0.0",
"verified": "4.0.0"
}
},
{
Expand Down
30 changes: 27 additions & 3 deletions src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ class BaseMessage {
return undefined;
}

get prefix() {
return "dialogOptions";
}

addMessage(options) {
debug("checking for message effects");

// get any existing messages
const messages = foundry.utils.getProperty(options, "dialogOptions.adv-reminder.messages") ?? [];
const messages = foundry.utils.getProperty(options, `${this.prefix}.adv-reminder.messages`) ?? [];

// get messages from the actor and merge
const keys = this.messageKeys;
Expand All @@ -50,8 +54,8 @@ class BaseMessage {

if (messages.length > 0) {
debug("messages found:", messages);
foundry.utils.setProperty(options, "dialogOptions.adv-reminder.messages", messages);
foundry.utils.setProperty(options, "dialogOptions.adv-reminder.rollData", this.actor.getRollData());
foundry.utils.setProperty(options, `${this.prefix}.adv-reminder.messages`, messages);
foundry.utils.setProperty(options, `${this.prefix}.adv-reminder.rollData`, this.actor.getRollData());
}
}
}
Expand Down Expand Up @@ -85,6 +89,16 @@ export class AttackMessage extends BaseMessage {
}
}

export class AttackMessageV2 extends AttackMessage {
constructor(actor, targetActor, activity) {
super(actor, targetActor, { system: { actionType: activity.actionType} , abilityMod: activity.ability });
}

get prefix() {
return "options";
}
}

class AbilityBaseMessage extends BaseMessage {
constructor(actor, abilityId) {
super(actor);
Expand Down Expand Up @@ -182,3 +196,13 @@ export class DamageMessage extends BaseMessage {
];
}
}

export class DamageMessageV2 extends DamageMessage {
constructor(actor, targetActor, activity) {
super(actor, targetActor, { system: { actionType: activity.actionType } });
}

get prefix() {
return "options";
}
}
63 changes: 60 additions & 3 deletions src/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,23 @@ function updateConditionEffects() {
ce.advReminderAdvantageAttack = new Set(["hiding", "invisible"]);
ce.advReminderAdvantageDexSave = new Set(["dodging"]);
ce.advReminderDisadvantageAttack = new Set(["blinded", "frightened", "poisoned", "prone", "restrained"]);
ce.advReminderDisadvantageAbility = new Set(["exhaustion-1", "frightened", "poisoned"]);
ce.advReminderDisadvantageSave = new Set(["exhaustion-3"]);
ce.advReminderDisadvantageAbility = new Set(["frightened", "poisoned"]);
ce.advReminderDisadvantageSave = new Set();
ce.advReminderDisadvantageDexSave = new Set(["restrained"]);
ce.advReminderDisadvantagePhysicalRolls = new Set(["heavilyEncumbered"]);
ce.advReminderFailDexSave = new Set(["paralyzed", "petrified", "stunned", "unconscious"]);
ce.advReminderFailStrSave = new Set(["paralyzed", "petrified", "stunned", "unconscious"]);
ce.advReminderGrantAdvantageAttack = new Set(["blinded", "paralyzed", "petrified", "restrained", "stunned", "unconscious"]);
ce.advReminderGrantAdjacentCritical = new Set(["paralyzed", "unconscious"]);
ce.advReminderGrantDisadvantageAttack = new Set(["dodging", "exhaustion-3", "hidden", "invisible"]);
ce.advReminderGrantDisadvantageAttack = new Set(["dodging", "hidden", "invisible"]);
// if adjacent, grant advantage on the attack, else grant disadvantage
ce.advReminderGrantAdjacentAttack = new Set(["prone"]);

if (game.settings.get("dnd5e", "rulesVersion") === "legacy") {
ce.advReminderDisadvantageAbility.add("exhaustion-1");
ce.advReminderDisadvantageSave.add("exhaustion-3");
ce.advReminderGrantDisadvantageAttack.add("exhaustion-3");
}
}

// Add message flags to DAE so it shows them in the AE editor
Expand Down Expand Up @@ -137,9 +143,48 @@ Hooks.on("renderDialog", async (dialog, html, data) => {
}
});

// New roll dialog hook, as of dnd5e v4.0
Hooks.on("renderRollConfigurationDialog", async (dialog, html) => {
debug("renderRollConfigurationDialog hook called");

const message = await prepareMessage(dialog.options);
if (message) {
// put messages inside their own fieldset
const messageFieldset = document.createElement("fieldset");
messageFieldset.innerHTML = message;
const legend = document.createElement("legend");
legend.innerText = game.i18n.localize("adv-reminder.Messages");
messageFieldset.insertBefore(legend, messageFieldset.firstChild);
// add messages right after configuration
const configFieldset = html.querySelector('fieldset[data-application-part="configuration"]');
configFieldset.after(messageFieldset);
// swap "inline-roll" class for "dialog-roll"
const inlineRolls = html.querySelectorAll("a.inline-roll");
inlineRolls.forEach(ir => {
debug("found inline-roll", ir);
ir.classList.remove("inline-roll");
ir.classList.add("dialog-roll");
// add click listener
ir.addEventListener("click", (event) => {
// get the formula from the button
const button = event.currentTarget;
const formula = button.dataset.formula;
debug("adding to input:", formula);
// add the formula to the bonus input
const dialogContent = button.closest(".window-content");
const input = dialogContent.querySelector('.rolls input[name="roll.0.situational"]');
input.value = !!input.value ? `${input.value} + ${formula}` : formula;
// rebuild dialog (i.e. show new die icons)
dialog.rebuild();
});
});
}
});

async function prepareMessage(dialogOptions) {
const opt = dialogOptions["adv-reminder"];
if (!opt) return;
if (opt.rendered) return;

// merge the messages with the advantage/disadvantage from sources
const messages = [...(opt.messages ?? [])];
Expand Down Expand Up @@ -167,6 +212,18 @@ async function prepareMessage(dialogOptions) {
async: true,
});
debug("messages", messages, "enriched", enriched);
opt.rendered = true;
return enriched;
}
}

// set default button on Damage Roll dialog
Hooks.on("renderDamageRollConfigurationDialog", (dialog, html) => {
debug("renderDamageRollConfigurationDialog hook called", dialog);

const isCritical = dialog.rolls[0]?.options?.isCritical;
const selector = `.dialog-buttons button[data-action="${isCritical ? "critical" : "normal"}"]`;
const button = html.querySelector(selector);
button.classList.add("default");
button.focus();
});
12 changes: 12 additions & 0 deletions src/reminders.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ export class AttackReminder extends BaseReminder {
}
}

export class AttackReminderV2 extends AttackReminder {
constructor(actor, targetActor, activity, distanceFn) {
super(actor, targetActor, { system: { actionType: activity.actionType} , abilityMod: activity.ability }, distanceFn);
}
}

class AbilityBaseReminder extends BaseReminder {
constructor(actor, abilityId) {
super(actor);
Expand Down Expand Up @@ -408,3 +414,9 @@ export class CriticalReminder extends BaseReminder {
};
}
}

export class CriticalReminderV2 extends CriticalReminder {
constructor(actor, targetActor, activity, distanceFn) {
super(actor, targetActor, { system: { actionType: activity.actionType } }, distanceFn);
}
}
62 changes: 32 additions & 30 deletions src/rollers/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@ import { AbilitySaveFail } from "../fails.js";
import {
AbilityCheckMessage,
AbilitySaveMessage,
AttackMessage,
AttackMessageV2,
ConcentrationMessage,
DamageMessage,
DamageMessageV2,
DeathSaveMessage,
SkillMessage,
} from "../messages.js";
import {
AttackReminder,
AttackReminderV2,
AbilityCheckReminder,
AbilitySaveReminder,
CriticalReminder,
CriticalReminderV2,
DeathSaveReminder,
SkillReminder,
} from "../reminders.js";
import {
AbilityCheckSource,
AbilitySaveSource,
AttackSource,
AttackSourceV2,
ConcentrationSource,
CriticalSource,
CriticalSourceV2,
DeathSaveSource,
SkillSource,
} from "../sources.js";
Expand All @@ -47,14 +47,14 @@ export default class CoreRollerHooks {
debug("checkArmorStealth", this.checkArmorStealth);

// register all the dnd5e.pre hooks
Hooks.on("dnd5e.preRollAttack", this.preRollAttack.bind(this));
Hooks.on("dnd5e.preRollAttackV2", this.preRollAttackV2.bind(this));
Hooks.on("dnd5e.preRollAbilitySave", this.preRollAbilitySave.bind(this));
Hooks.on("dnd5e.preRollConcentration", this.preRollConcentration.bind(this));
Hooks.on("dnd5e.preRollAbilityTest", this.preRollAbilityTest.bind(this));
Hooks.on("dnd5e.preRollSkill", this.preRollSkill.bind(this));
Hooks.on("dnd5e.preRollToolCheck", this.preRollToolCheck.bind(this));
Hooks.on("dnd5e.preRollDeathSave", this.preRollDeathSave.bind(this));
Hooks.on("dnd5e.preRollDamage", this.preRollDamage.bind(this));
Hooks.on("dnd5e.preRollDamageV2", this.preRollDamageV2.bind(this));
}

/**
Expand All @@ -65,16 +65,17 @@ export default class CoreRollerHooks {
return true;
}

preRollAttack(item, config) {
debug("preRollAttack hook called");
preRollAttackV2(config, dialog, message) {
debug("preRollAttackV2 hook called", config, dialog, message);

if (this.isFastForwarding(config)) return;
if (this.isFastForwarding(config, dialog)) return;
const target = getTarget();
const distanceFn = getDistanceToTargetFn(config.messageData.speaker);
const distanceFn = getDistanceToTargetFn(message.data.speaker);
const activity = config.subject;

new AttackMessage(item.actor, target, item).addMessage(config);
if (showSources) new AttackSource(item.actor, target, item, distanceFn).updateOptions(config);
new AttackReminder(item.actor, target, item, distanceFn).updateOptions(config);
new AttackMessageV2(activity.actor, target, activity).addMessage(dialog);
if (showSources) new AttackSourceV2(activity.actor, target, activity, distanceFn).updateOptions(dialog);
new AttackReminderV2(activity.actor, target, activity, distanceFn).updateOptions(config.rolls[0].options);
}

preRollAbilitySave(actor, config, abilityId) {
Expand Down Expand Up @@ -142,19 +143,18 @@ export default class CoreRollerHooks {
new DeathSaveReminder(actor).updateOptions(config);
}

preRollDamage(item, config) {
debug("preRollDamage hook called");

// damage/healing enricher doesn't have an item, skip
if (!item) return;
preRollDamageV2(config, dialog, message) {
debug("preRollDamageV2 hook called", config, dialog, message);

if (this.isFastForwarding(config)) return;
if (this.isFastForwarding(config, dialog)) return;
const target = getTarget();
const distanceFn = getDistanceToTargetFn(config.messageData.speaker);
const distanceFn = getDistanceToTargetFn(message.data.speaker);
const activity = config.subject;

new DamageMessage(item.actor, target, item).addMessage(config);
if (showSources) new CriticalSource(item.actor, target, item, distanceFn).updateOptions(config);
new CriticalReminder(item.actor, target, item, distanceFn).updateOptions(config);
new DamageMessageV2(activity.actor, target, activity).addMessage(dialog);
if (showSources) new CriticalSourceV2(activity.actor, target, activity, distanceFn).updateOptions(dialog);
const reminder = new CriticalReminderV2(activity.actor, target, activity, distanceFn);
config.rolls.forEach(roll => reminder.updateOptions(roll.options, "isCritical"));
}

/**
Expand All @@ -163,15 +163,17 @@ export default class CoreRollerHooks {
* @param {object} options
* @param {boolean} [options.fastForward] a specific fastForward flag
* @param {Event} [options.event] the triggering event
* @param {object} dialog
* @param {boolean} [dialog.configure] whether or not to show the dialog
* @returns {boolean} true if they are fast-forwarding, false otherwise
*/
isFastForwarding({ fastForward = false, event = {} }) {
isFastForwarding({ fastForward = false, event = {} }, { configure = true } = {}) {
const isFF = !!(
fastForward ||
event?.shiftKey ||
event?.altKey ||
event?.ctrlKey ||
event?.metaKey
!configure ||
dnd5e.utils.areKeysPressed(event, "skipDialogNormal") ||
dnd5e.utils.areKeysPressed(event, "skipDialogAdvantage") ||
dnd5e.utils.areKeysPressed(event, "skipDialogDisadvantage")
);
if (isFF) debug("fast-forwarding the roll, stop processing");
return isFF;
Expand Down
Loading

0 comments on commit 009cdf5

Please sign in to comment.