Skip to content

Commit

Permalink
Vanilla
Browse files Browse the repository at this point in the history
  • Loading branch information
Haxxer committed Oct 5, 2023
1 parent ae707c7 commit b7c8254
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 175 deletions.
9 changes: 3 additions & 6 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"title": "Minion Manager",
"description": "This module helps you manage minions, specifically MCDM style minions from Flee, Mortals!",
"version": "#{VERSION}#",
"library": "false",
"library": false,
"socket": true,
"manifestPlusVersion": "1.2.0",
"compatibility": {
"minimum": 10,
Expand All @@ -30,11 +31,7 @@
"minimum": "2.0.3"
}
}
],
"requires": [{
"id": "midi-qol",
"type": "module"
}]
]
},
"esmodules": [
"scripts/module.js"
Expand Down
3 changes: 3 additions & 0 deletions scripts/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const CONSTANTS = {
GROUP_NUMBER: `${FLAG}.groupNumber`,
DELETE_GROUP_NUMBER: `${FLAG}.-=groupNumber`,
MIDI_GROUP_ATTACK: "flags.midiProperties.grpact",
},
MODULES: {
MIDI: false
}
}

Expand Down
166 changes: 7 additions & 159 deletions scripts/minion.js
Original file line number Diff line number Diff line change
@@ -1,167 +1,15 @@
import * as api from "./api.js";
import * as lib from "./lib.js";
import CONSTANTS from "./constants.js";
import midiqol from "./plugins/midiqol.js";
import vanilla from "./plugins/vanilla.js";

export function initializeMinions() {

const workflows = {}

Hooks.on("midi-qol.preAttackRoll", async (workflow) => {

if (!lib.getSetting(CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACKS)) return true;

const isGroupAttack = getProperty(workflow.item, CONSTANTS.FLAGS.MIDI_GROUP_ATTACK) ?? false;

if (!api.isMinion(workflow.actor) || !isGroupAttack) return true;

const result = await Dialog.confirm({
title: game.i18n.localize("MINIONMANAGER.Dialogs.MinionAttack.Title"),
content: `
<p>${game.i18n.localize("MINIONMANAGER.Dialogs.MinionAttack.Label")}</p>
<p><input name="numberOfAttacks" type="number" value="1"></p>
`,
yes: (html) => {
return html.find('input[name="numberOfAttacks"]').val()
},
options: { height: "100%" }
})

const numMinionsAttacked = Number(result) || 1;
workflows[workflow.id] = numMinionsAttacked;

if (!lib.getSetting(CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACK_BONUS)) return true;

const attackHookId = Hooks.on("dnd5e.preRollAttack", (rolledItem, rollConfig) => {
if (rolledItem !== workflow.item) return true;
rollConfig.fastForward = true;
rollConfig.parts.push(numMinionsAttacked);
Hooks.off("dnd5e.preRollAttack", attackHookId);
return true;
});
return true;

});

Hooks.on("midi-qol.preDamageRoll", async (workflow) => {

if (!workflows?.[workflow.id]) return true;
if (workflow.item.system?.damage?.parts?.length < 1) return true;

const numMinionsAttacked = workflows[workflow.id];
delete workflows[workflow.id];
const firstDamage = workflow.item.system.damage.parts[0][0];
const newFormula = isNaN(Number(firstDamage))
? firstDamage + " * " + numMinionsAttacked
: Number(firstDamage) * numMinionsAttacked;

const damageType = workflow.item.system.damage.parts[0][1];

const damageHookId = Hooks.on("dnd5e.preRollDamage", (rolledItem, rollConfig) => {
if (rolledItem !== workflow.item) return true;
rollConfig.fastForward = true;
rollConfig.parts[0] = [`${newFormula}${damageType ? `[${damageType}]` : ""}`];
Hooks.off("dnd5e.preRollDamage", damageHookId);
return true;
});

return true;
});

Hooks.on("midi-qol.postCheckSaves", async (workflow) => {
if (!lib.getSetting(CONSTANTS.SETTING_KEYS.ENABLE_MINION_SUPER_SAVE)) return;
for (const savedToken of workflow.saves) {
if (!api.isMinion(savedToken)) continue;
workflow.superSavers.add(savedToken);
}
});

Hooks.on("midi-qol.preDamageRollComplete", async (workflow) => {

if (!lib.getSetting(CONSTANTS.SETTING_KEYS.ENABLE_OVERKILL_DAMAGE)) return true;

// Overkill management
const actionType = workflow.item.system?.actionType;
const isWeaponMeleeAttack = actionType === "mwak";
const isWeaponRangedAttack = actionType === "rwak";

if (!actionType || !(isWeaponMeleeAttack || isWeaponRangedAttack)) return true;
if (!lib.getSetting(CONSTANTS.SETTING_KEYS.ENABLE_RANGED_OVERKILL) && isWeaponRangedAttack) return true;
if (!workflow.hitTargets.size) return true;

const hitTarget = Array.from(workflow.hitTargets)[0]

if (!api.isMinion(hitTarget.actor)) return true;

let damageTotal = workflow.damageTotal;

const minionHP = getProperty(hitTarget.actor, "system.attributes.hp.value");

if ((minionHP - damageTotal) > 0) return true;

damageTotal -= minionHP;

const closestTokens = new Set(canvas.tokens.placeables
.filter(_token => {
const withinRange = canvas.grid.measureDistance(workflow.token, _token) <= workflow.item.system.range.value + 2.5;
return hitTarget.actor.name === _token.actor.name && withinRange;
})
.sort((a, b) => canvas.grid.measureDistance(workflow.token, b) - canvas.grid.measureDistance(workflow.token, a)));

closestTokens.delete(game.user.targets.first());

let maxAdditionalTargets = Math.ceil(damageTotal / minionHP);
Array.from(closestTokens)
.slice(0, maxAdditionalTargets)
.forEach(_token => _token.setTarget(true, { releaseOthers: false }));

const label1Localization = "MINIONMANAGER.Dialogs.OverkillDamage." + (isWeaponRangedAttack ? "RangedLabel1" : "MeleeLabel1");
const label1 = game.i18n.format(label1Localization, {
max_targets: maxAdditionalTargets + 1,
total_targets: game.user.targets.size,
name: hitTarget.actor.name
});

let label2 = game.i18n.format("MINIONMANAGER.Dialogs.OverkillDamage.Label2", {
max_targets: maxAdditionalTargets + 1,
total_targets: game.user.targets.size
});

let targetingHookId = false;
await Dialog.prompt({
"title": game.i18n.localize("MINIONMANAGER.Dialogs.OverkillDamage.Title"),
"content": `
<p style='text-align:center;'>${label1}</p>
<p style='text-align:center;' class="minion-manager-targets">${label2}</p>
<p style='text-align:center;'>${game.i18n.localize("MINIONMANAGER.Dialogs.OverkillDamage.Label3")}</p>
`,
"rejectClose": false,
render: (html) => {
targetingHookId = Hooks.on("targetToken", () => {
html.find(".minion-manager-targets").text(game.i18n.format("MINIONMANAGER.Dialogs.OverkillDamage.Label2", {
max_targets: maxAdditionalTargets + 1,
total_targets: game.user.targets.size
}))
})
},
options: { top: 150 }
});

const userTargets = new Set([...game.user.targets]
.filter(_token => _token.name === hitTarget.name)
);

userTargets.delete(hitTarget);

await MidiQOL.applyTokenDamage(
workflow.damageDetail,
workflow.damageTotal,
new Set([...userTargets].slice(0, maxAdditionalTargets)),
workflow.item
)

return true;

});
if(CONSTANTS.MODULES.MIDI){
midiqol.initializeMinions();
}else{
vanilla.initializeMinions();
}

Hooks.on("preUpdateActor", (doc, change) => {
const actorIsMinion = api.isMinion(doc);
Expand Down
12 changes: 2 additions & 10 deletions scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,19 @@ import { initializeInterface } from "./interface.js";
import { initializeInitiative } from "./initiative.js";
import * as API from "./api.js";

let midiActive = false;

Hooks.on("init", () => {
midiActive = game.modules.get("midi-qol")?.active;
if (!midiActive) return;
CONSTANTS.MODULES.MIDI = game.modules.get("midi-qol")?.active;
initializeSettings();
initializeMinions();
initializeInterface();
initializeInitiative();
});

Hooks.once("ready", () => {
if (!midiActive) {
ui.notifications.error(game.i18n.localize("MINIONMANAGER.Errors.MidiActive"))
return
}
if (midiActive) {
if (CONSTANTS.MODULES.MIDI) {
const flagName = CONSTANTS.FLAGS.MIDI_GROUP_ATTACK.split(".").pop();
CONFIG.DND5E.midiProperties[flagName] = "Group Action";
}

game.modules.get(CONSTANTS.MODULE_NAME).api = API;
});

Expand Down
Loading

0 comments on commit b7c8254

Please sign in to comment.