Skip to content

Commit

Permalink
Update 5e class features.
Browse files Browse the repository at this point in the history
  • Loading branch information
krbz999 committed Dec 1, 2024
1 parent 8835769 commit a9c1ab9
Show file tree
Hide file tree
Showing 16 changed files with 574 additions and 575 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// EYES OF NIGHT
// Required modules: warpgate, itemacro

const range = 120;
const targets = game.user.targets;
const mod = Math.max(actor.system.abilities.wis.mod, 1);

if (!targets.size.between(1, mod)) {
ui.notifications.error(`Please target between 1 and ${mod} creatures.`);
return;
}

const updates = {
actor: {"system.attributes.senses.darkvision": range},
token: {"sight.visionMode": "darkvision", "sight.range": range}
};

const options = {
name: `Darkvision (${range}ft)`,
description: `You are being granted ${range} feet of darkvision.`
};

const use = await item.use();
if (!use) return;

for (const target of targets) {
await warpgate.mutate(target.document, updates, {}, options);
}
// EYES OF NIGHT
// Required modules: warpgate, itemacro

const range = 120;
const targets = game.user.targets;
const mod = Math.max(actor.system.abilities.wis.mod, 1);

if (!targets.size.between(1, mod)) {
ui.notifications.error(`Please target between 1 and ${mod} creatures.`);
return;
}

const updates = {
actor: {"system.attributes.senses.darkvision": range},
token: {"sight.visionMode": "darkvision", "sight.range": range}
};

const options = {
name: `Darkvision (${range}ft)`,
description: `You are being granted ${range} feet of darkvision.`
};

const use = await item.use();
if (!use) return;

for (const target of targets) {
await warpgate.mutate(target.document, updates, {}, options);
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,55 @@
// HARNESS DIVINE POWER
// required modules: itemacro

const data = actor.getRollData();
const maxLevel = Math.ceil(data.attributes.prof / 2);
const validLevels = [];
for (let i = 1; i <= maxLevel; i++) {
const key = `spell${i}`;
if ((data.spells[key].value < data.spells[key].max) && (data.spells[key].max > 0)) {
validLevels.push(key);
}
}
const pact = data.spells.pact;
if ((pact.max > 0) && (pact.level <= maxLevel) && (pact.value < pact.max)) validLevels.push("pact");

// bail out if you are not missing any spell slots or if the item has no remaining uses (limited uses and resources).
if (!validLevels.length) return ui.notifications.warn("You are not missing any valid spell slots.");
if (!item.system.uses.value) return ui.notifications.warn("You have no uses left of Harness Divine Power.");

const resourceItem = actor.items.get(item.system.consume.target);
const uses = resourceItem.system.uses;
if (!uses.value) return ui.notifications.warn("You have no uses of Channel Divinity left.");

// define dialog contents
const options = validLevels.reduce((acc, e) => {
const spells = data.spells[e];
const label = e === "pact" ? "Pact Slots" : CONFIG.DND5E.spellLevels[e.at(-1)];
return acc + `<option value="${e}">${label} (${spells.value} / ${spells.max})</option>`;
}, "");

return Dialog.prompt({
title: item.name,
rejectClose: false,
label: "Recover",
content: `
<p>Select a spell slot level.</p>
<form>
<div class="form-group">
<label>Spell Slot:</label>
<div class="form-fields">
<select>${options}</select>
</div>
</div>
</form>`,
callback: async (html) => {
const level = html[0].querySelector("select").value;
const value = data.spells[level].value + 1;
await actor.update({[`system.spells.${level}.value`]: value});
await actor.updateEmbeddedDocuments("Item", [
{_id: resourceItem.id, "system.uses.value": uses.value - 1},
{_id: item.id, "system.uses.value": item.uses.value - 1}
]);
ui.notifications.info("Recovered a spell slot!");
}
});
// HARNESS DIVINE POWER
// required modules: itemacro

const data = actor.getRollData();
const maxLevel = Math.ceil(data.attributes.prof / 2);
const validLevels = [];
for (let i = 1; i <= maxLevel; i++) {
const key = `spell${i}`;
if ((data.spells[key].value < data.spells[key].max) && (data.spells[key].max > 0)) {
validLevels.push(key);
}
}
const pact = data.spells.pact;
if ((pact.max > 0) && (pact.level <= maxLevel) && (pact.value < pact.max)) validLevels.push("pact");

// bail out if you are not missing any spell slots or if the item has no remaining uses (limited uses and resources).
if (!validLevels.length) return ui.notifications.warn("You are not missing any valid spell slots.");
if (!item.system.uses.value) return ui.notifications.warn("You have no uses left of Harness Divine Power.");

const resourceItem = actor.items.get(item.system.consume.target);
const uses = resourceItem.system.uses;
if (!uses.value) return ui.notifications.warn("You have no uses of Channel Divinity left.");

// define dialog contents
const options = validLevels.reduce((acc, e) => {
const spells = data.spells[e];
const label = e === "pact" ? "Pact Slots" : CONFIG.DND5E.spellLevels[e.at(-1)];
return acc + `<option value="${e}">${label} (${spells.value} / ${spells.max})</option>`;
}, "");

return Dialog.prompt({
title: item.name,
rejectClose: false,
label: "Recover",
content: `
<p>Select a spell slot level.</p>
<form>
<div class="form-group">
<label>Spell Slot:</label>
<div class="form-fields">
<select>${options}</select>
</div>
</div>
</form>`,
callback: async (html) => {
const level = html[0].querySelector("select").value;
const value = data.spells[level].value + 1;
await actor.update({[`system.spells.${level}.value`]: value});
await actor.updateEmbeddedDocuments("Item", [
{_id: resourceItem.id, "system.uses.value": uses.value - 1},
{_id: item.id, "system.uses.value": item.uses.value - 1}
]);
ui.notifications.info("Recovered a spell slot!");
}
});
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
// STEPS OF NIGHT
// required modules: itemacro

const id = item.name.slugify({strict: true});
const effect = actor.effects.find(e => e.statuses.has(id));
if (effect) return effect.delete();
const use = await item.use();
if (!use) return;

return actor.createEmbeddedDocuments("ActiveEffect", [{
name: item.name,
changes: [{
key: "system.attributes.movement.fly",
mode: CONST.ACTIVE_EFFECT_MODES.UPGRADE,
value: actor.system.attributes.movement.walk
}],
duration: {seconds: 60},
icon: item.img,
statuses: [id],
description: "<p>You have a flying speed equal to your walking speed.</p>",
"flags.visual-active-effects.data.content": item.system.description.value
}]);
// STEPS OF NIGHT
// required modules: itemacro

const id = item.name.slugify({strict: true});
const effect = actor.effects.find(e => e.statuses.has(id));
if (effect) return effect.delete();
const use = await item.use();
if (!use) return;

return actor.createEmbeddedDocuments("ActiveEffect", [{
name: item.name,
changes: [{
key: "system.attributes.movement.fly",
mode: CONST.ACTIVE_EFFECT_MODES.UPGRADE,
value: actor.system.attributes.movement.walk
}],
duration: {seconds: 60},
icon: item.img,
statuses: [id],
description: "<p>You have a flying speed equal to your walking speed.</p>",
"flags.visual-active-effects.data.content": item.system.description.value
}]);

Original file line number Diff line number Diff line change
@@ -1,62 +1,62 @@
// TWILIGHT SANCTUARY
// required modules: itemacro, sequencer, jb2a-patreon, warpgate

// CONSTS
const file = "jb2a.markers.circle_of_stars.orangepurple";
const error = "Please target a token.";
const target = game.user.targets.first();

// find Sequencer effect
const [effect] = Sequencer.EffectManager.getEffects({name: item.name});

if (!effect) {
const use = await item.use();
if (!use) return;
return new Sequence()
.effect()
.attachTo(token)
.persist()
.name(item.name)
.file(file)
.size(canvas.grid.size * 8)
.scaleIn(0, 800, {ease: "easeOutCubic"})
.rotateIn(180, 1200, {ease: "easeOutCubic"})
.scaleOut(0, 500, {ease: "easeOutCubic"})
.fadeOut(500, {ease: "easeOutCubic"})
.play();
}

new Dialog({
title: item.name,
buttons: {
hp: {
icon: "<i class='fa-solid fa-heart'></i>",
label: "Grant temp HP",
callback: async () => {
if (!target) return ui.notifications.error(error);
const roll = new Roll("1d6 + @classes.cleric.levels", actor.getRollData());
const {total} = await roll.evaluate({async: true});
await roll.toMessage({speaker, flavor: item.name});
const temp = target.actor.system.attributes.hp.temp ?? 0;
const updates = {actor: {"system.attributes.hp.temp": total}};
const config = {permanent: true, name: item.name, description: `You are being granted ${total} temporary hit points.`};
if (total > temp) return warpgate.mutate(target.document, updates, {}, config);
}
},
effect: {
icon: "<i class='fa-solid fa-check'></i>",
label: "End an effect",
callback: async () => {
if (!target) return ui.notifications.error(error);
return ChatMessage.create({speaker, content: `${actor.name} ends the charmed or frightened condition on ${target.name}.`});
}
},
end: {
icon: "<i class='fa-solid fa-times'></i>",
label: "End Sanctuary",
callback: async () => {
return Sequencer.EffectManager.endEffects({name: item.name});
}
}
}
}).render(true);
// TWILIGHT SANCTUARY
// required modules: itemacro, sequencer, jb2a-patreon, warpgate

// CONSTS
const file = "jb2a.markers.circle_of_stars.orangepurple";
const error = "Please target a token.";
const target = game.user.targets.first();

// find Sequencer effect
const [effect] = Sequencer.EffectManager.getEffects({name: item.name});

if (!effect) {
const use = await item.use();
if (!use) return;
return new Sequence()
.effect()
.attachTo(token)
.persist()
.name(item.name)
.file(file)
.size(canvas.grid.size * 8)
.scaleIn(0, 800, {ease: "easeOutCubic"})
.rotateIn(180, 1200, {ease: "easeOutCubic"})
.scaleOut(0, 500, {ease: "easeOutCubic"})
.fadeOut(500, {ease: "easeOutCubic"})
.play();
}

new Dialog({
title: item.name,
buttons: {
hp: {
icon: "<i class='fa-solid fa-heart'></i>",
label: "Grant temp HP",
callback: async () => {
if (!target) return ui.notifications.error(error);
const roll = new Roll("1d6 + @classes.cleric.levels", actor.getRollData());
const {total} = await roll.evaluate({async: true});
await roll.toMessage({speaker, flavor: item.name});
const temp = target.actor.system.attributes.hp.temp ?? 0;
const updates = {actor: {"system.attributes.hp.temp": total}};
const config = {permanent: true, name: item.name, description: `You are being granted ${total} temporary hit points.`};
if (total > temp) return warpgate.mutate(target.document, updates, {}, config);
}
},
effect: {
icon: "<i class='fa-solid fa-check'></i>",
label: "End an effect",
callback: async () => {
if (!target) return ui.notifications.error(error);
return ChatMessage.create({speaker, content: `${actor.name} ends the charmed or frightened condition on ${target.name}.`});
}
},
end: {
icon: "<i class='fa-solid fa-times'></i>",
label: "End Sanctuary",
callback: async () => {
return Sequencer.EffectManager.endEffects({name: item.name});
}
}
}
}).render(true);
Loading

0 comments on commit a9c1ab9

Please sign in to comment.