Skip to content

Commit

Permalink
Merge pull request Z3nner#52 from cirrahn/feature/actor-custom-effects
Browse files Browse the repository at this point in the history
Allow effects folders to have an actor assigned
  • Loading branch information
Z3nner authored Aug 1, 2024
2 parents 42813a1 + 7447554 commit b6ee292
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 73 deletions.
21 changes: 21 additions & 0 deletions css/effects-manager.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
font-weight: 700;
text-transform: uppercase;
border: 0;
text-align: left;
}

.lancer.app input.lwfx-effects-manager__ipt-folder-name[type="text"]::placeholder {
Expand All @@ -44,6 +45,26 @@
font-weight: initial;
}

.lwfx-effects-manager__wrp-actor-link {
border: 1px solid var(--darken-3);
border-radius: 5px;
padding: .25em;
background: var(--light-gray-color);
color: var(--dark-text);
}

a.lwfx-effects-manager__actor-link {
padding: 1px calc(0.5 * var(--lwfx-spacer));
flex: unset;
color: var(--light-text);
border-color: var(--darken-3);
background: var(--dark-gray-color);
}

a.lwfx-effects-manager__actor-link i {
color: var(--dark-text);
}

/* -------------------------------------------- */

.lwfx-effects-manager__effects-uncategorized-empty {
Expand Down
13 changes: 13 additions & 0 deletions css/helpers.css
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,20 @@
margin-right: calc(0.5 * var(--lwfx-spacer)) !important;
}

.lwfx__mx-2 {
margin-left: var(--lwfx-spacer) !important;
margin-right: var(--lwfx-spacer) !important;
}

.lwfx__my-1 {
margin-top: calc(0.5 * var(--lwfx-spacer)) !important;
margin-bottom: calc(0.5 * var(--lwfx-spacer)) !important;
}

.lwfx__mr-0 {
margin-right: 0 !important;
}

.lwfx__mr-1 {
margin-right: calc(0.5 * var(--lwfx-spacer)) !important;
}
Expand All @@ -150,6 +159,10 @@
margin-left: calc(0.5 * var(--lwfx-spacer)) !important;
}

.lwfx__ml-2 {
margin-left: var(--lwfx-spacer) !important;
}

.lwfx__pr-2 {
padding-right: var(--lwfx-spacer) !important;
}
Expand Down
4 changes: 3 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"Multiple Effects Item Name Hint": "Multiple effects exist with this item name!",
"Multiple Effects Lancer ID Hint": "Multiple effects exist with this Lancer ID!",
"Start Tour": "Start Tour",
"Usage Hint": "Drag-and-drop an Item or Macro to create a new effect."
"Usage Hint": "Drag-and-drop an Item or Macro to create a new effect.",
"Folder No Actor Hint": "Effects in this folder will be used by every actor. To instead restrict the use of these effects to a single actor, drag-and-drop an Actor to this folder.",
"Unlink Actor": "Unlink Actor"
},

"fields": {
Expand Down
109 changes: 96 additions & 13 deletions scripts/effectManager/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,18 @@ export class EffectManagerApp extends FormApplication {
getData(options = {}) {
const dataModel = this._datamodel.toObject();

const { effectCountsName, effectCountsLancerId } = this._getData_getEffectCounts({ dataModel });
const effectCounts = this._getData_getEffectCounts({ dataModel });

const effects = Object.entries(dataModel.effects).map(([id, effect]) => ({
id,
...effect,
isDuplicate: this._getData_isEffectDuplicate({ effect, effectCountsName, effectCountsLancerId }),
isDuplicate: this._getData_isEffectDuplicate({ dataModel, effect, effectCounts }),
}));

const folders = Object.entries(dataModel.folders).map(([id, folder]) => ({
id,
...folder,
actorLink: this._getData_getActorLinkHtml({ actorUuid: folder.actorUuid }),
effects: effects.filter(effect => effect.folderId === id),
}));

Expand Down Expand Up @@ -214,23 +215,53 @@ export class EffectManagerApp extends FormApplication {
};
}

_getData_getActorLinkHtml({ actorUuid }) {
if (actorUuid == null) return null;

// Fake a regex match
const lnk = TextEditor._createContentLink([
null, // m0, full match
"UUID", // m1, type
actorUuid, // m2, target
]);
if (!lnk) return null;

lnk.classList.add("lwfx-effects-manager__actor-link");

return lnk.outerHTML;
}

_getData_getEffectCountPath({ dataModel, effect, searchName }) {
const actorUuid = dataModel.folders[effect.folderId]?.actorUuid || "_";
return ["name", actorUuid, effect.mode, searchName].join(".");
}

_getData_getEffectCounts({ dataModel }) {
const effectCountsName = {};
const effectCountsLancerId = {};
const effectCounts = {};

Object.values(dataModel.effects).forEach(effect => {
switch (effect.mode) {
case CUSTOM_EFFECT_MODE_NAME: {
const searchName = getSearchString(effect.itemName);
if (!searchName) return;
effectCountsName[searchName] = (effectCountsName[searchName] || 0) + 1;
const propPath = this._getData_getEffectCountPath({ dataModel, effect, searchName });
foundry.utils.setProperty(
effectCounts,
propPath,
(foundry.utils.getProperty(effectCounts, propPath) || 0) + 1,
);
return;
}

case CUSTOM_EFFECT_MODE_LID: {
const searchName = getSearchString(effect.itemLid);
if (!searchName) return;
effectCountsLancerId[searchName] = (effectCountsLancerId[searchName] || 0) + 1;
const propPath = this._getData_getEffectCountPath({ dataModel, effect, searchName });
foundry.utils.setProperty(
effectCounts,
propPath,
(foundry.utils.getProperty(effectCounts, propPath) || 0) + 1,
);
return;
}

Expand All @@ -239,16 +270,28 @@ export class EffectManagerApp extends FormApplication {
}
});

return { effectCountsName, effectCountsLancerId };
return effectCounts;
}

_getData_isEffectDuplicate({ effect, effectCountsName, effectCountsLancerId }) {
_getData_isEffectDuplicate({ dataModel, effect, effectCounts }) {
switch (effect.mode) {
case CUSTOM_EFFECT_MODE_NAME:
return effectCountsName[getSearchString(effect.itemName)] > 1;
case CUSTOM_EFFECT_MODE_NAME: {
const propPath = this._getData_getEffectCountPath({
dataModel,
effect,
searchName: getSearchString(effect.itemName),
});
return foundry.utils.getProperty(effectCounts, propPath) > 1;
}

case CUSTOM_EFFECT_MODE_LID:
return effectCountsLancerId[getSearchString(effect.itemLid)] > 1;
case CUSTOM_EFFECT_MODE_LID: {
const propPath = this._getData_getEffectCountPath({
dataModel,
effect,
searchName: getSearchString(effect.itemLid),
});
return foundry.utils.getProperty(effectCounts, propPath) > 1;
}

default:
throw new Error(`Unknown mode: ${effect.mode}`);
Expand All @@ -270,6 +313,7 @@ export class EffectManagerApp extends FormApplication {

$html.on("click", `[name="btn-folder-expand-collapse"]`, this._handleClick_folderExpandCollapse.bind(this));
$html.on("click", `[name="btn-folder-create-effect"]`, this._handleClick_folderCreateEffect.bind(this));
$html.on("click", `[data-name="btn-folder-actor-unlink"]`, this._handleClick_folderActorUnlink.bind(this));
$html.on("click", `[name="btn-folder-delete"]`, this._handleClick_folderDelete.bind(this));

$html.on("click", `[name="btn-effect-play"]`, this._handleClick_playPreview.bind(this));
Expand Down Expand Up @@ -432,6 +476,19 @@ export class EffectManagerApp extends FormApplication {
});
}

async _handleClick_folderActorUnlink(evt) {
evt.preventDefault();
evt.stopPropagation();

const folderId = evt.currentTarget.closest("[data-folder-id]").getAttribute("data-folder-id");

await this._updateObject(null, {
[`folders.${folderId}`]: {
actorUuid: null,
},
});
}

async _handleClick_folderDelete(evt) {
const folderId = evt.currentTarget.closest("[data-folder-id]").getAttribute("data-folder-id");

Expand Down Expand Up @@ -540,6 +597,9 @@ export class EffectManagerApp extends FormApplication {

case "Macro":
return this._onDrop_macro({ evt, data });

case "Actor":
return this._onDrop_actor({ evt, data });
}
}

Expand Down Expand Up @@ -622,6 +682,28 @@ export class EffectManagerApp extends FormApplication {
});
}

async _onDrop_actor({ evt, data }) {
const eleFolder = evt.currentTarget.closest("[data-folder-id]");

const actor = await fromUuid(data.uuid);

// If dropped to an existing row, update that row
if (eleFolder) {
const folderId = eleFolder.getAttribute("data-folder-id");

return this._updateObject(null, {
[`folders.${folderId}.actorUuid`]: actor.uuid,
});
}

// Otherwise, create a new row
return this._updateObject(null, {
[`folders.${foundry.utils.randomID()}`]: this._getNewFolder({
actorUuid: actor.uuid,
}),
});
}

/* -------------------------------------------- */

async _createEffectFromDroppedItem({ effectId, folderId, item }) {
Expand Down Expand Up @@ -711,9 +793,10 @@ export class EffectManagerApp extends FormApplication {
};
}

_getNewFolder({ name, isCollapsed } = {}) {
_getNewFolder({ name, actorUuid, isCollapsed } = {}) {
return {
name: name || null,
actorUuid: actorUuid || null,
isCollapsed: isCollapsed || false,
};
}
Expand Down
7 changes: 7 additions & 0 deletions scripts/effectManager/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const schemaFolder = new foundry.data.fields.SchemaField({
label: "Name",
}),

// TODO(v12) switch to `foundry.data.fields.DocumentUUIDField`
actorUuid: new foundry.data.fields.StringField({
type: "Actor",
label: "lancer-weapon-fx.effectManager.fields.actorUuid.label",
nullable: true,
}),

isCollapsed: new foundry.data.fields.BooleanField({
initial: false,
}),
Expand Down
61 changes: 5 additions & 56 deletions scripts/effectResolver/effectResolver.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,7 @@
import { weaponEffects } from "../weaponEffects.js";
import { MODULE_ID, PACK_ID_WEAPONFX } from "../consts.js";
import { SETTING_EFFECTS_MANAGER_STATE } from "../settings.js";
import { PACK_ID_WEAPONFX } from "../consts.js";
import { getSearchString } from "../utils.js";

/* -------------------------------------------- */

const _getCustomMacroUuid_itemLid = ({ itemLid, customEffects }) => {
const itemLidSearch = getSearchString(itemLid);
if (!itemLidSearch) return null;

const byLid = Object.values(customEffects)
// `.filter` instead of `.find` so we can warn if multiple matches
.filter(effect => getSearchString(effect.itemLid) === itemLid && getSearchString(effect.macroUuid));

if (!byLid.length) return null;

const [{ macroUuid }] = byLid;
if (byLid.length === 1) return macroUuid;

ui.notifications.warn(`Multiple custom effects found for Lancer ID "${itemLid}"!`);

return macroUuid;
};

const _getCustomMacroUuid_itemName = ({ itemName, customEffects }) => {
const itemNameSearch = getSearchString(itemName);
if (!itemNameSearch) return null;

const byName = Object.values(customEffects)
// `.filter` instead of `.find` so we can warn if multiple matches
.filter(effect => getSearchString(effect.itemName) === itemNameSearch && getSearchString(effect.macroUuid));

if (!byName.length) return null;

const [{ macroUuid }] = byName;
if (byName.length === 1) return macroUuid;

ui.notifications.warn(`Multiple custom effects found for Item Name "${itemName}"!`);

return macroUuid;
};

const _getCustomMacroUuid = (itemLid, itemName = null) => {
const customEffects = (game.settings.get(MODULE_ID, SETTING_EFFECTS_MANAGER_STATE) || {}).effects;
if (!customEffects || !Object.keys(customEffects).length) return null;

const byLid = _getCustomMacroUuid_itemLid({ itemLid, customEffects });
if (byLid) return byLid;

const byName = _getCustomMacroUuid_itemName({ itemName, customEffects });
if (byName) return byName;

return null;
};
import { getCustomMacroUuid } from "./effectResolverCustom.js";

/* -------------------------------------------- */

Expand All @@ -79,9 +28,9 @@ const _pGetLwfxMacroUuid = async macroName => {

/* -------------------------------------------- */

export const pGetMacroUuid = async (itemLid, itemName, fallbackActionIdentifier) => {
export const pGetMacroUuid = async ({ actorUuid, itemLid, itemName, fallbackActionIdentifier }) => {
// Resolve custom macros first, to allow the user to override the module defaults
const customUuid = await _getCustomMacroUuid(itemLid, itemName);
const customUuid = await getCustomMacroUuid({ actorUuid, itemLid, itemName });
if (customUuid) {
console.log(
`Lancer Weapon FX | Found custom macro "${customUuid}" for Lancer ID "${itemLid}"/Item Name "${itemName}"`,
Expand All @@ -97,7 +46,7 @@ export const pGetMacroUuid = async (itemLid, itemName, fallbackActionIdentifier)
}

// Resolve custom macros bound on fallback "fake LID"s
const customFallbackUuid = await _getCustomMacroUuid(fallbackActionIdentifier, null);
const customFallbackUuid = await getCustomMacroUuid({ actorUuid, itemLid: fallbackActionIdentifier });
if (customFallbackUuid) {
console.log(
`Lancer Weapon FX | Found custom macro "${customFallbackUuid}" for fallback identifier "${fallbackActionIdentifier}"`,
Expand Down
Loading

0 comments on commit b6ee292

Please sign in to comment.