Skip to content

Commit

Permalink
Fixes and additions
Browse files Browse the repository at this point in the history
  • Loading branch information
Haxxer committed May 17, 2024
1 parent 46f07c8 commit d0fdb13
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 111 deletions.
4 changes: 1 addition & 3 deletions scripts/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,7 @@ export async function revertMinions(actors) {
* @returns {boolean}
*/
export function isMinion(target) {
target = target?.actor ?? target;
const minionFeatureName = getSetting(CONSTANTS.SETTING_KEYS.MINION_FEATURE_NAME).toLowerCase();
return target.items.some(item => item.name.toLowerCase() === minionFeatureName);
return lib.hasActorItemNamed(target, getSetting(CONSTANTS.SETTING_KEYS.MINION_FEATURE_NAME), true)
}

/**
Expand Down
177 changes: 94 additions & 83 deletions scripts/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const CONSTANTS = {
GROUP_NUMBER: `${FLAG}.groupNumber`,
DELETE_GROUP_NUMBER: `${FLAG}.-=groupNumber`,
MIDI_GROUP_ATTACK: "flags.midiProperties.grpact",
MINION_FEATURE: `${FLAG}.minionfeature.`
},
MODULES: {
MIDI: false
Expand All @@ -40,98 +41,108 @@ CONSTANTS["SETTING_KEYS"] = {
ENABLE_MINION_FEATURE_AUTOMATION: "enableMinionFeatureAutomation"
}

CONSTANTS["SETTINGS"] = {
[CONSTANTS.SETTING_KEYS.DEBUG]: {
name: "MINIONMANAGER.Settings.Debug.Title",
hint: "MINIONMANAGER.Settings.Debug.Hint",
scope: "client",
config: true,
default: false,
type: Boolean
},
/**
* @returns {{
* Object
* }}
* @constructor
*/
CONSTANTS["SETTINGS"] = () => {

[CONSTANTS.SETTING_KEYS.ENABLE_OVERKILL_DAMAGE]: {
name: "MINIONMANAGER.Settings.EnableOverkillDamage.Title",
hint: "MINIONMANAGER.Settings.EnableOverkillDamage.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
return {

[CONSTANTS.SETTING_KEYS.ENABLE_RANGED_OVERKILL]: {
name: "MINIONMANAGER.Settings.EnableRangedOverkill.Title",
hint: "MINIONMANAGER.Settings.EnableRangedOverkill.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.DEBUG]: {
name: "MINIONMANAGER.Settings.Debug.Title",
hint: "MINIONMANAGER.Settings.Debug.Hint",
scope: "client",
config: true,
default: false,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.ENABLE_SPELL_OVERKILL]: {
name: "MINIONMANAGER.Settings.EnableSpellOverkill.Title",
hint: "MINIONMANAGER.Settings.EnableSpellOverkill.Hint",
scope: "world",
config: true,
default: false,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.ENABLE_OVERKILL_DAMAGE]: {
name: "MINIONMANAGER.Settings.EnableOverkillDamage.Title",
hint: "MINIONMANAGER.Settings.EnableOverkillDamage.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.ENABLE_OVERKILL_MESSAGE]: {
name: "MINIONMANAGER.Settings.EnableOverkillMessage.Title",
hint: "MINIONMANAGER.Settings.EnableOverkillMessage.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.ENABLE_RANGED_OVERKILL]: {
name: "MINIONMANAGER.Settings.EnableRangedOverkill.Title",
hint: "MINIONMANAGER.Settings.EnableRangedOverkill.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACKS]: {
name: "MINIONMANAGER.Settings.EnableGroupAttacks.Title",
hint: "MINIONMANAGER.Settings.EnableGroupAttacks.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.ENABLE_SPELL_OVERKILL]: {
name: "MINIONMANAGER.Settings.EnableSpellOverkill.Title",
hint: "MINIONMANAGER.Settings.EnableSpellOverkill.Hint",
scope: "world",
config: true,
default: false,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACK_BONUS]: {
name: "MINIONMANAGER.Settings.EnableGroupAttackBonus.Title",
hint: "MINIONMANAGER.Settings.EnableGroupAttackBonus.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.ENABLE_OVERKILL_MESSAGE]: {
name: "MINIONMANAGER.Settings.EnableOverkillMessage.Title",
hint: "MINIONMANAGER.Settings.EnableOverkillMessage.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.ENABLE_MINION_SUPER_SAVE]: {
name: "MINIONMANAGER.Settings.EnableMinionSuperSave.Title",
hint: "MINIONMANAGER.Settings.EnableMinionSuperSave.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},
[CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACKS]: {
name: "MINIONMANAGER.Settings.EnableGroupAttacks.Title",
hint: "MINIONMANAGER.Settings.EnableGroupAttacks.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.MINION_FEATURE_NAME]: {
name: "MINIONMANAGER.Settings.MinionFeatureName.Title",
hint: "MINIONMANAGER.Settings.MinionFeatureName.Hint",
scope: "world",
config: true,
default: "Minion",
required: true,
type: String
},
[CONSTANTS.SETTING_KEYS.ENABLE_GROUP_ATTACK_BONUS]: {
name: "MINIONMANAGER.Settings.EnableGroupAttackBonus.Title",
hint: "MINIONMANAGER.Settings.EnableGroupAttackBonus.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.MINION_FEATURE_DESCRIPTION]: {
name: "MINIONMANAGER.Settings.MinionFeatureDescription.Title",
hint: "MINIONMANAGER.Settings.MinionFeatureDescription.Hint",
scope: "world",
config: true,
default: "If the minion takes damage from an attack or as the result of a failed saving throw, their hit points are reduced to 0. If the minion takes damage from another effect, they die if the damage equals or exceeds their hit point maximum, otherwise they take no damage.",
required: true,
type: String
},
[CONSTANTS.SETTING_KEYS.ENABLE_MINION_SUPER_SAVE]: {
name: "MINIONMANAGER.Settings.EnableMinionSuperSave.Title",
hint: "MINIONMANAGER.Settings.EnableMinionSuperSave.Hint",
scope: "world",
config: true,
default: true,
type: Boolean
},

[CONSTANTS.SETTING_KEYS.MINION_FEATURE_NAME]: {
name: "MINIONMANAGER.Settings.MinionFeatureName.Title",
hint: "MINIONMANAGER.Settings.MinionFeatureName.Hint",
scope: "world",
config: true,
default: "Minion",
required: true,
type: String
},

[CONSTANTS.SETTING_KEYS.MINION_FEATURE_DESCRIPTION]: {
name: "MINIONMANAGER.Settings.MinionFeatureDescription.Title",
hint: "MINIONMANAGER.Settings.MinionFeatureDescription.Hint",
scope: "world",
config: true,
default: "If the minion takes damage from an attack or as the result of a failed saving throw, their hit points are reduced to 0. If the minion takes damage from another effect, they die if the damage equals or exceeds their hit point maximum, otherwise they take no damage.",
required: true,
type: String
}
}
}


Expand Down
18 changes: 17 additions & 1 deletion scripts/initiative.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,24 @@ export function initializeInitiative() {

let tokenBeingDeleted = false;
Hooks.on("preDeleteToken", (doc) => {
const groupNumber = getProperty(doc, CONSTANTS.FLAGS.GROUP_NUMBER);
if (!groupNumber || !game.combats.viewed) return true;
const tokenCombatant = game.combats.viewed.combatants.find(combatant => getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER) === groupNumber);
if(!tokenCombatant) return true;
tokenBeingDeleted = doc.id;
})
});

Hooks.on("deleteToken", async (doc) => {
if(doc.id !== tokenBeingDeleted) return;
const groupNumber = getProperty(doc, CONSTANTS.FLAGS.GROUP_NUMBER);
const tokenCombatant = game.combats.viewed.combatants.find(combatant => getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER) === groupNumber);
const subCombatants = foundry.utils.deepClone(getProperty(tokenCombatant, CONSTANTS.FLAGS.COMBATANTS) ?? [])
subCombatants.splice(subCombatants.indexOf(doc.uuid), 1);
await tokenCombatant.update({
[CONSTANTS.FLAGS.COMBATANTS]: subCombatants
});
ui.combat.render(true);
});

Hooks.on("preDeleteCombatant", (combatant) => {
if (tokenBeingDeleted !== combatant.tokenId) return true;
Expand Down
22 changes: 16 additions & 6 deletions scripts/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export function initializeInterface() {

Hooks.on("renderCombatTracker", async (app) => {
app.element.find(".combatant").each(function () {
const combatant = game.combats.viewed.combatants.get($(this).data("combatantId"));
const combatant = game.combats.viewed ? game.combats.viewed.combatants.get($(this).data("combatantId")) : null;
if (!combatant) return;
const minionGroup = foundry.utils.deepClone(getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER));
if (!minionGroup) return;
const tokenImageDiv = $("<div class='token-image'></div>");
Expand Down Expand Up @@ -45,7 +46,11 @@ export function initializeInterface() {

const newGroupNumber = Number(index) + 1;

const colorBox = $(`<div class="minion-group"><img src="modules/${CONSTANTS.MODULE_NAME}/assets/${newGroupNumber}.svg"/></div>`);
const groupAlreadyExists = game.combats.viewed ? game.combats.viewed.combatants.some(combatant => {
return getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER) === newGroupNumber;
}) : false;

const colorBox = $(`<div class="minion-group ${groupAlreadyExists ? 'minion-group-used' : ''}" style='background-image: url(modules/${CONSTANTS.MODULE_NAME}/assets/${newGroupNumber}.svg);'></div>`);

colorBox.on("click", async () => {

Expand All @@ -56,9 +61,9 @@ export function initializeInterface() {
return !newTokens.includes(oldToken) && existingGroupNumber && tokenGroupNumber && existingGroupNumber === tokenGroupNumber;
});

const existingCombatantGroup = game.combats.viewed.combatants.find(combatant => {
const existingCombatantGroup = game.combats.viewed ? game.combats.viewed.combatants.find(combatant => {
return existingGroupNumber && getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER) === existingGroupNumber;
});
}) : false;

if (existingCombatantGroup && !tokensKeptInOldGroup.length) {
await existingCombatantGroup.delete()
Expand All @@ -84,9 +89,13 @@ export function initializeInterface() {
[CONSTANTS.FLAGS.GROUP_NUMBER]: newGroupNumber
})));

if (!game.combats.viewed) {
await Combat.create({ scene: canvas.scene.id });
}

const existingCombatantInNewGroup = game.combats.viewed.combatants.find(combatant => {
return getProperty(combatant.token, CONSTANTS.FLAGS.GROUP_NUMBER) === newGroupNumber;
});
})

if (existingCombatantInNewGroup) {
const existingUuids = foundry.utils.deepClone(getProperty(existingCombatantInNewGroup, CONSTANTS.FLAGS.COMBATANTS) ?? []);
Expand Down Expand Up @@ -123,7 +132,8 @@ export function initializeInterface() {
libWrapper.register(CONSTANTS.MODULE_NAME, 'CombatTracker.prototype.getData', async function (wrapped, ...args) {
const data = await wrapped(...args);
for (const turn of data.turns) {
const combatant = game.combats.viewed.combatants.get(turn.id);
const combatant = game.combats.viewed ? game.combats.viewed.combatants.get(turn.id) : false;
if (!combatant) continue;
const subCombatants = foundry.utils.deepClone(getProperty(combatant, CONSTANTS.FLAGS.COMBATANTS) ?? [])
if (!subCombatants.length) continue;
const documents = subCombatants.map((uuid) => fromUuidSync(uuid)).filter(Boolean);
Expand Down
7 changes: 7 additions & 0 deletions scripts/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export function patchItemDamageRollConfig(item){
return `${newFormula}${damageType ? `[${damageType}]` : ""}`
})
}

export function hasActorItemNamed(target, itemName, lowerCase=false){
target = target?.actor ?? target;
return target && target.items.some(item => {
return (lowerCase ? item.name.toLowerCase() : item.name) === (lowerCase ? itemName.toLowerCase() : itemName);
});
}
6 changes: 1 addition & 5 deletions scripts/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ Hooks.on("init", () => {
});

Hooks.once("ready", () => {
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;
});

function initializeSettings() {
for (const [key, setting] of Object.entries(CONSTANTS.SETTINGS)) {
for (const [key, setting] of Object.entries(CONSTANTS.SETTINGS())) {
game.settings.register(CONSTANTS.MODULE_NAME, key, setting);
}
}
4 changes: 2 additions & 2 deletions scripts/plugins/midiqol.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default {
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 && _token?.actor && hitTarget?.actor?.name === _token?.actor?.name && withinRange;
return hitTarget?.actor && _token?.actor && hitTarget.document?.baseActor?.name === _token?.document?.baseActor?.name && withinRange;
})
.sort((a, b) => canvas.grid.measureDistance(workflow.token, a) - canvas.grid.measureDistance(workflow.token, b)));

Expand Down Expand Up @@ -162,7 +162,7 @@ export default {
});

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

userTargets.delete(hitTarget);
Expand Down
20 changes: 9 additions & 11 deletions styles/module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,16 @@ div.token-image > img.minion-group{
margin: 0;
padding: 0;
border-radius: 99px;
background-color: white;
cursor: pointer;
position: relative;
max-width: initial !important;
opacity: 1;
background-color: white;
background-size: calc(100% + 2px) calc(100% + 2px) !important;
background-position: -1px -1px !important;
overflow: visible !important;
}

.grouped-initiative > .minion-group > img {
opacity: 1 !important;
margin: 0 !important;
left: -1px;
top: -1px;
width: 22px;
height: 22px;
position: absolute;
max-width: initial !important;
.grouped-initiative > .minion-group.minion-group-used {
background-color: black;
box-shadow: inset 0px 0px 4px 1px rgba(255,255,255,1);
}

0 comments on commit d0fdb13

Please sign in to comment.