Skip to content

Commit

Permalink
Merge pull request #66 from kaelad02/system-concentration-compat
Browse files Browse the repository at this point in the history
Support the system's new Concentration roll
  • Loading branch information
kaelad02 authored Apr 5, 2024
2 parents 77d117d + 040ff81 commit 0d82189
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# upcoming

- feature: Support the system's new concentration rolls with sources and messages (system already handles advantage/disadvantage)

# 3.4.1

- feature: [#64](https://github.com/kaelad02/adv-reminder/pull/64) Add pt-BR translation, thanks @Kharmans
Expand Down
15 changes: 14 additions & 1 deletion src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ class BaseMessage {
addMessage(options) {
debug("checking for message effects");

// get any existing messages
const messages = getProperty(options, "dialogOptions.adv-reminder.messages") ?? [];

// get messages from the actor and merge
const keys = this.messageKeys;
const messages = this.changes
const actorMessages = this.changes
.filter((change) => keys.includes(change.key))
.map((change) => change.value);
messages.push(...actorMessages);

// get messages from the target and merge
const targetKeys = this.targetKeys;
Expand Down Expand Up @@ -114,6 +119,14 @@ export class AbilitySaveMessage extends AbilityBaseMessage {
}
}

export class ConcentrationMessage extends AbilityBaseMessage {
/** @override */
get messageKeys() {
// don't call super since the system will trigger a saving throw
return ["flags.adv-reminder.message.ability.concentration"];
}
}

export class SkillMessage extends AbilityCheckMessage {
constructor(actor, abilityId, skillId) {
super(actor, abilityId);
Expand Down
24 changes: 17 additions & 7 deletions src/reminders.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ class BaseReminder {

/**
* An accumulator that looks for matching keys and tracks advantage/disadvantage.
* @param {Object} options
* @param {boolean} options.advantage initial value for advantage
* @param {boolean} options.disadvantage initial value for disadvantage
*/
_accumulator() {
let advantage;
let disadvantage;

_accumulator({advantage, disadvantage} = {}) {
return {
advantage,
disadvantage,
add: (actorFlags, advKeys, disKeys) => {
advantage = advKeys.reduce((accum, curr) => accum || actorFlags[curr], advantage);
disadvantage = disKeys.reduce((accum, curr) => accum || actorFlags[curr], disadvantage);
Expand All @@ -38,8 +40,16 @@ class BaseReminder {
update: (options) => {
debug(`updating options with {advantage: ${advantage}, disadvantage: ${disadvantage}}`);
// only set if adv or dis, the die roller doesn't handle when both are true correctly
if (advantage && !disadvantage) options.advantage = true;
else if (!advantage && disadvantage) options.disadvantage = true;
if (advantage && !disadvantage) {
options.advantage = true;
if (options.disadvantage) options.disadvantage = false;
} else if (!advantage && disadvantage) {
if (options.advantage) options.advantage = false;
options.disadvantage = true;
} else {
if (options.advantage) options.advantage = false;
if (options.disadvantage) options.disadvantage = false;
}
},
};
}
Expand Down Expand Up @@ -121,7 +131,7 @@ class AbilityBaseReminder extends BaseReminder {
debug("advKeys", advKeys, "disKeys", disKeys);

// find matching keys and update options
const accumulator = this._accumulator();
const accumulator = options.isConcentration ? this._accumulator(options) : this._accumulator();
accumulator.add(this.actorFlags, advKeys, disKeys);
accumulator.update(options);
}
Expand Down
13 changes: 13 additions & 0 deletions src/rollers/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AbilityCheckMessage,
AbilitySaveMessage,
AttackMessage,
ConcentrationMessage,
DamageMessage,
DeathSaveMessage,
SkillMessage,
Expand All @@ -19,6 +20,7 @@ import {
AbilityCheckSource,
AbilitySaveSource,
AttackSource,
ConcentrationSource,
CriticalSource,
DeathSaveSource,
SkillSource,
Expand Down Expand Up @@ -47,6 +49,7 @@ export default class CoreRollerHooks {
// register all the dnd5e.pre hooks
Hooks.on("dnd5e.preRollAttack", this.preRollAttack.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));
Expand Down Expand Up @@ -86,6 +89,16 @@ export default class CoreRollerHooks {
new AbilitySaveReminder(actor, abilityId).updateOptions(config);
}

preRollConcentration(actor, options) {
debug("preRollConcentration hook called");

if (this.isFastForwarding(options)) return;

new ConcentrationMessage(actor, options.ability).addMessage(options);
if (showSources) new ConcentrationSource(actor, options.ability).updateOptions(options);
// don't need a reminder, the system will set advantage/disadvantage
}

preRollAbilityTest(actor, config, abilityId) {
debug("preRollAbilityTest hook called");

Expand Down
11 changes: 11 additions & 0 deletions src/rollers/midi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
AbilityCheckMessage,
AbilitySaveMessage,
AttackMessage,
ConcentrationMessage,
DamageMessage,
DeathSaveMessage,
SkillMessage,
Expand All @@ -10,6 +11,7 @@ import {
AbilityCheckSource,
AbilitySaveSource,
AttackSource,
ConcentrationSource,
CriticalSource,
DeathSaveSource,
SkillSource,
Expand Down Expand Up @@ -45,6 +47,15 @@ export default class MidiRollerHooks extends CoreRollerHooks {
if (showSources) new AbilitySaveSource(actor, abilityId).updateOptions(config);
}

preRollConcentration(actor, options) {
debug("preRollConcentration hook called");

if (this.isFastForwarding(options)) return;

new ConcentrationMessage(actor, options.ability).addMessage(options);
if (showSources) new ConcentrationSource(actor, options.ability).updateOptions(options);
}

preRollAbilityTest(actor, config, abilityId) {
debug("preRollAbilityTest hook called");

Expand Down
12 changes: 12 additions & 0 deletions src/rollers/rsr.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AbilityCheckMessage,
AbilitySaveMessage,
AttackMessage,
ConcentrationMessage,
DamageMessage,
DeathSaveMessage,
SkillMessage,
Expand All @@ -19,6 +20,7 @@ import {
AbilityCheckSource,
AbilitySaveSource,
AttackSource,
ConcentrationSource,
CriticalSource,
DeathSaveSource,
SkillSource,
Expand Down Expand Up @@ -69,6 +71,16 @@ export default class ReadySetRollHooks extends CoreRollerHooks {
if (this._doReminder(config)) new AbilitySaveReminder(actor, abilityId).updateOptions(config);
}

preRollConcentration(actor, options) {
debug("preRollConcentration hook called");

if (this._doMessages(options)) {
new ConcentrationMessage(actor, options.ability).addMessage(options);
if (showSources) new ConcentrationSource(actor, options.ability).updateOptions(options);
}
// don't need a reminder, the system will set advantage/disadvantage
}

preRollAbilityTest(actor, config, abilityId) {
debug("preRollAbilityTest hook called");

Expand Down
28 changes: 28 additions & 0 deletions src/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const SourceMixin = (superclass) =>
if (changes[key]) disadvantageLabels.push(...changes[key]);
});
},
advantage: (label) => {
if (label) advantageLabels.push(label);
},
disadvantage: (label) => {
if (label) disadvantageLabels.push(label);
},
Expand All @@ -70,6 +73,31 @@ export class AttackSource extends SourceMixin(AttackReminder) {}

export class AbilitySaveSource extends SourceMixin(AbilitySaveReminder) {}

export class ConcentrationSource extends SourceMixin(Object) {
constructor(actor, abilityId) {
super();

/** @type {object} */
this.conc = actor.system.attributes?.concentration;
/** @type {string} */
this.abilityId = abilityId;
}

updateOptions(options) {
this._message();

const modes = CONFIG.Dice.D20Roll.ADV_MODE;
const source = () =>
game.i18n.localize("DND5E.Concentration") + " " + game.i18n.localize("DND5E.AdvantageMode");

// check Concentration's roll mode to look for advantage/disadvantage
const accumulator = this._accumulator();
if (this.conc.roll.mode === modes.ADVANTAGE) accumulator.advantage(source());
else if (this.conc.roll.mode === modes.DISADVANTAGE) accumulator.disadvantage(source());
accumulator.update(options);
}
}

export class AbilityCheckSource extends SourceMixin(AbilityCheckReminder) {}

export class SkillSource extends SourceMixin(SkillReminder) {}
Expand Down
13 changes: 13 additions & 0 deletions test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ export default function commonTestInit() {
lastObj[lastProp] = value;
};

globalThis.getProperty = (object, key) => {
if (!key) return undefined;
if (key in object) return object[key];
let target = object;
for (let p of key.split(".")) {
getType(target);
if (!(typeof target === "object")) return undefined;
if (p in target) target = target[p];
else return undefined;
}
return target;
};

globalThis.flattenObject = (obj, _d = 0) => {
const flat = {};
if (_d > 100) {
Expand Down
21 changes: 6 additions & 15 deletions test/messages.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
import { beforeAll, describe, expect, jest, test } from "@jest/globals";
import {
AbilityCheckMessage,
AbilitySaveMessage,
Expand All @@ -7,20 +7,7 @@ import {
DeathSaveMessage,
SkillMessage,
} from "../src/messages";

// fakes
globalThis.setProperty = (object, key, value) => {
// split the key into parts, removing the last one
const parts = key.split(".");
const lastProp = parts.pop();
// recursively create objects out the key parts
const lastObj = parts.reduce((obj, prop) => {
if (!obj.hasOwnProperty(prop)) obj[prop] = {};
return obj[prop];
}, object);
// set the value using the last key part
lastObj[lastProp] = value;
};
import commonTestInit from "./common.js";

function createActorWithEffects(...keyValuePairs) {
const appliedEffects = keyValuePairs.map(createEffect);
Expand Down Expand Up @@ -52,6 +39,10 @@ function createItem(actionType, abilityMod) {
};
}

beforeAll(() => {
commonTestInit();
});

describe("AttackMessage no legit active effects", () => {
test("attack with no active effects should not add a message", () => {
const actor = createActorWithEffects();
Expand Down

0 comments on commit 0d82189

Please sign in to comment.