Skip to content

Commit

Permalink
Merge pull request #127 from League-of-Fabulous-Developers/dev
Browse files Browse the repository at this point in the history
 V2.3.10 Update
  • Loading branch information
spyrella authored Jun 10, 2024
2 parents 1912800 + 903c3cc commit d29ceb4
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 169 deletions.
27 changes: 5 additions & 22 deletions module/documents/actors/actor.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FUItem } from '../items/item.mjs';
import { toggleStatusEffect } from '../../helpers/effects.mjs';

/**
* Extend the base Actor document by defining a custom roll data structure
Expand Down Expand Up @@ -130,33 +131,15 @@ export class FUActor extends Actor {
const crisisThreshold = Math.floor(hp.max / 2);
const shouldBeInCrisis = hp.value <= crisisThreshold;
const isInCrisis = this.statuses.has('crisis');

if (shouldBeInCrisis && !isInCrisis) {
await ActiveEffect.create(
{
...CONFIG.statusEffects.find((val) => val.id === 'crisis'),
origin: this.uuid,
},
{ parent: this },
);
} else if (!shouldBeInCrisis && isInCrisis) {
this.effects.filter((effect) => effect.statuses.has('crisis')).forEach((val) => val.delete());
if (shouldBeInCrisis !== isInCrisis) {
await toggleStatusEffect(this, 'crisis');
}

// Handle KO status
const shouldBeKO = hp.value === 0; // KO when HP is 0
const isKO = this.statuses.has('ko');

if (shouldBeKO && !isKO) {
await ActiveEffect.create(
{
...CONFIG.statusEffects.find((val) => val.id === 'ko'),
origin: this.uuid,
},
{ parent: this },
);
} else if (!shouldBeKO && isKO) {
this.effects.filter((effect) => effect.statuses.has('ko')).forEach((val) => val.delete());
if (shouldBeKO !== isKO) {
await toggleStatusEffect(this, 'ko');
}
}
super._onUpdate(changed, options, userId);
Expand Down
2 changes: 0 additions & 2 deletions module/documents/actors/character/character-data-model.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@ export class CharacterDataModel extends foundry.abstract.TypeDataModel {
}

prepareDerivedData() {
this.attributes.handleStatusEffects();
this.affinities.handleGuard();
this.tlTracker = new CharacterSkillTracker(this);
}

Expand Down
7 changes: 0 additions & 7 deletions module/documents/actors/common/affinities-data-model.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,4 @@ export class AffinitiesDataModel extends foundry.abstract.DataModel {
poison: new EmbeddedDataField(AffinityDataModel, {}),
};
}

handleGuard() {
const actor = this.parent.actor;
if (actor.statuses.has('guard')) {
Object.values(this).forEach((value) => value.upgrade());
}
}
}
15 changes: 0 additions & 15 deletions module/documents/actors/common/attributes-data-model.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AttributeDataModel } from './attribute-data-model.mjs';
import { statusEffects } from '../../../helpers/statuses.mjs';

/**
* @property {AttributeDataModel} dex
Expand All @@ -17,18 +16,4 @@ export class AttributesDataModel extends foundry.abstract.DataModel {
wlp: new EmbeddedDataField(AttributeDataModel, {}),
};
}

handleStatusEffects() {
const actor = this.parent.actor;
actor.statuses.forEach((status) => {
const statusDefinition = statusEffects.find((value) => value.statuses.includes(status));
if (statusDefinition && statusDefinition.stats) {
if (statusDefinition.mod > 0) {
statusDefinition.stats.forEach((attribute) => this[attribute].upgrade());
} else {
statusDefinition.stats.forEach((attribute) => this[attribute].downgrade());
}
}
});
}
}
2 changes: 0 additions & 2 deletions module/documents/actors/npc/npc-data-model.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ export class NpcDataModel extends foundry.abstract.TypeDataModel {
prepareBaseData() {}

prepareDerivedData() {
this.attributes.handleStatusEffects();
this.affinities.handleGuard();
this.spTracker = new NpcSkillTracker(this);
}

Expand Down
10 changes: 3 additions & 7 deletions module/helpers/action-handler.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createChatMessage, promptCheck, promptOpenCheck } from './checks.mjs';
import { handleStudyTarget } from './study-roll.mjs';
import { toggleStatusEffect } from './effects.mjs';

export async function actionHandler(life, actionType, isShift) {
const actor = life.actor;
Expand Down Expand Up @@ -81,17 +82,12 @@ export async function createActionMessage(actor, action) {

async function toggleGuardEffect(actor) {
const GUARD_EFFECT_ID = 'guard';
const guardEffect = CONFIG.statusEffects.find((effect) => effect.id === GUARD_EFFECT_ID);

const guardActive = actor.effects.some((effect) => effect.statuses.has('guard'));

if (guardActive) {
const guardActive = await toggleStatusEffect(actor, GUARD_EFFECT_ID);
if (!guardActive) {
// Delete existing guard effects
actor.effects.filter((effect) => effect.statuses.has('guard')).forEach((effect) => effect.delete());
ui.notifications.info('Guard is deactivated.');
} else {
// Create a new guard effect
await ActiveEffect.create(guardEffect, { parent: actor });
ui.notifications.info('Guard is activated.');
createActionMessage(actor, 'guard');
}
Expand Down
17 changes: 8 additions & 9 deletions module/helpers/effects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,20 @@ export function prepareActiveEffectCategories(effects) {
/**
* A helper function to toggle a status effect on an Actor.
* Designed based off TokenDocument#toggleActiveEffect to properly interact with token hud.
* @param {string} statusEffectId The status effect id based on CONFIG.statusEffects
* @returns {Promise<boolean>} Whether the ActiveEffect is now on or off
* @param {FUActor} actor the actor the status should get applied to
* @param {string} statusEffectId The status effect id based on CONFIG.statusEffects
* @param {string} [source] the UUID of the document that caused the effect
* @returns {Promise<boolean>} Whether the ActiveEffect is now on or off
*/
export async function toggleStatusEffect(actor, statusEffectId) {
const existing = actor.effects.reduce((arr, e) => {
if (isActiveEffectForStatusEffectId(e, statusEffectId)) arr.push(e);
return arr;
}, []);
export async function toggleStatusEffect(actor, statusEffectId, source = undefined) {
const existing = actor.effects.filter((effect) => isActiveEffectForStatusEffectId(effect, statusEffectId));
if (existing.length > 0) {
await Promise.all(existing.map((e) => e.delete()));
return false;
} else {
const statusEffect = CONFIG.statusEffects.find((e) => e.statuses.includes(statusEffectId));
const statusEffect = CONFIG.statusEffects.find((e) => e.id === statusEffectId);
if (statusEffect) {
await ActiveEffect.create(statusEffect, { parent: actor });
await ActiveEffect.create({ ...statusEffect, statuses: [statusEffectId], origin: source }, { parent: actor });
}
return true;
}
Expand Down
151 changes: 101 additions & 50 deletions module/helpers/inline-effects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FUItem } from '../documents/items/item.mjs';
import { Flags } from './flags.mjs';
import { FU, SYSTEM } from './config.mjs';
import { FUActiveEffect } from '../documents/effects/active-effect.mjs';
import { toggleStatusEffect } from './effects.mjs';

const INLINE_EFFECT = 'InlineEffect';
const INLINE_EFFECT_CLASS = 'inline-effect';
Expand All @@ -16,46 +17,61 @@ const enricher = {
enricher: inlineEffectEnricher,
};

function createEffectAnchor(effect) {
const anchor = document.createElement('a');
anchor.draggable = true;
anchor.dataset.effect = toBase64(effect);
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
const icon = document.createElement('i');
icon.classList.add('fun', 'fu-aura');
anchor.append(icon);
anchor.append(effect.name);
return anchor;
}

function createBrokenAnchor() {
const anchor = document.createElement('a');
anchor.classList.add('inline', 'broken');
const icon = document.createElement('i');
icon.classList.add('fas', 'fa-chain-broken');
anchor.append(icon);
anchor.append(game.i18n.localize('FU.InlineEffectInvalid'));
return anchor;
}

function createStatusAnchor(effectValue, status) {
const anchor = document.createElement('a');
anchor.draggable = true;
anchor.dataset.status = effectValue;
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
const icon = document.createElement('i');
icon.classList.add('fun', 'fu-aura');
anchor.append(icon);
anchor.append(game.i18n.localize(status.name));
return anchor;
}

/**
* @param text
* @param options
*/
function inlineEffectEnricher(text, options) {
/** @type string */
const effectValue = text[1];
let effect;

if (SUPPORTED_STATUSES.includes(effectValue) || BOONS_AND_BANES.includes(effectValue)) {
const status = statusEffects.find((value) => value.id === effectValue);
if (status) {
status.name = game.i18n.localize(status.name);
effect = status;
return createStatusAnchor(effectValue, status);
}
} else {
const decodedEffect = fromBase64(effectValue);
if (decodedEffect && decodedEffect.name && decodedEffect.changes) {
effect = decodedEffect;
return createEffectAnchor(decodedEffect);
}
}

if (effect) {
const anchor = document.createElement('a');
anchor.draggable = true;
anchor.dataset.effect = toBase64(effect);
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
const icon = document.createElement('i');
icon.classList.add('fun', 'fu-aura');
anchor.append(icon);
anchor.append(effect.name);
return anchor;
} else {
const anchor = document.createElement('a');
anchor.classList.add('inline', 'broken');
const icon = document.createElement('i');
icon.classList.add('fas', 'fa-chain-broken');
anchor.append(icon);
anchor.append(game.i18n.localize('FU.InlineEffectInvalid'));
return anchor;
}
return createBrokenAnchor();
}

/**
Expand All @@ -71,14 +87,14 @@ function determineSource(document, element) {
return document.uuid;
} else if (document instanceof ChatMessage) {
const speakerActor = ChatMessage.getSpeakerActor(document.speaker);
if (speakerActor) {
return speakerActor.uuid;
}
const flagItem = document.getFlag(SYSTEM, Flags.ChatMessage.Item);
if (flagItem && speakerActor) {
const item = speakerActor.items.get(flagItem._id);
return item ? item.uuid : null;
}
if (speakerActor) {
return speakerActor.uuid;
}
}
return null;
}
Expand All @@ -96,6 +112,7 @@ function activateListeners(document, html) {
.on('click', function () {
const source = determineSource(document, this);
const effectData = fromBase64(this.dataset.effect);
const status = this.dataset.status;
const controlledTokens = canvas.tokens.controlled;
let actors = [];

Expand All @@ -110,7 +127,15 @@ function activateListeners(document, html) {
}

if (actors.length > 0) {
actors.forEach((actor) => onApplyEffectToActor(actor, source, effectData));
if (effectData) {
actors.forEach((actor) => onApplyEffectToActor(actor, source, effectData));
} else if (status) {
actors.forEach((actor) => {
if (!actor.statuses.has(status)) {
toggleStatusEffect(actor, status, source);
}
});
}
} else {
ui.notifications.warn(game.i18n.localize('FU.ChatApplyEffectNoActorsSelected'));
}
Expand All @@ -127,26 +152,39 @@ function activateListeners(document, html) {
type: INLINE_EFFECT,
source: source,
effect: fromBase64(this.dataset.effect),
status: this.dataset.status,
};
event.dataTransfer.setData('text/plain', JSON.stringify(data));
event.stopPropagation();
})
.on('contextmenu', function (event) {
event.preventDefault();
event.stopPropagation();
const effectData = fromBase64(this.dataset.effect);
const cls = getDocumentClass('ActiveEffect');
delete effectData.id;
cls.migrateDataSafe(effectData);
cls.cleanData(effectData);
Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true })
.then((value) => {
return cls.create(effectData, { temporary: true, render: true, parent: value });
})
.then((value) => {
const activeEffectConfig = new ActiveEffectConfig(value);
activeEffectConfig.render(true, { editable: false });
});
let effectData;
if (this.dataset.status) {
const status = this.dataset.status;
const statusEffect = CONFIG.statusEffects.find((value) => value.id === status);
if (statusEffect) {
effectData = { ...statusEffect, statuses: [status] };
}
} else {
effectData = fromBase64(this.dataset.effect);
}
if (effectData) {
effectData.origin = determineSource(document, this);
const cls = getDocumentClass('ActiveEffect');
delete effectData.id;
cls.migrateDataSafe(effectData);
cls.cleanData(effectData);
Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true })
.then((value) => {
return cls.create(effectData, { temporary: true, render: true, parent: value });
})
.then((value) => {
const activeEffectConfig = new ActiveEffectConfig(value);
activeEffectConfig.render(true, { editable: false });
});
}
});
}

Expand All @@ -163,16 +201,22 @@ function onApplyEffectToActor(actor, source, effect) {
}
}

function onDropActor(actor, sheet, { type, source, effect }) {
function onDropActor(actor, sheet, { type, source, effect, status }) {
if (type === INLINE_EFFECT) {
ActiveEffect.create(
{
...effect,
origin: source,
flags: foundry.utils.mergeObject(effect.flags ?? {}, { [SYSTEM]: { [FUActiveEffect.TEMPORARY_FLAG]: true } }),
},
{ parent: actor },
);
if (status) {
if (!actor.statuses.has(status)) {
toggleStatusEffect(actor, status, source);
}
} else if (effect) {
ActiveEffect.create(
{
...effect,
origin: source,
flags: foundry.utils.mergeObject(effect.flags ?? {}, { [SYSTEM]: { [FUActiveEffect.TEMPORARY_FLAG]: true } }),
},
{ parent: actor },
);
}
return false;
}
}
Expand Down Expand Up @@ -520,7 +564,14 @@ class InlineEffectConfiguration extends FormApplication {
if (this.object.type === 'custom') {
const cls = getDocumentClass('ActiveEffect');
const tempActor = await Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true });
const tempEffect = await cls.create({ _id: foundry.utils.randomID(), name: game.i18n.localize(this.#defaultName), icon: this.#defaultIcon }, { temporary: true, parent: tempActor });
const tempEffect = await cls.create(
{
_id: foundry.utils.randomID(),
name: game.i18n.localize(this.#defaultName),
icon: this.#defaultIcon,
},
{ temporary: true, parent: tempActor },
);
const activeEffectConfig = new TempActiveEffectConfig(tempEffect);
activeEffectConfig.render(true);
const dispatch = this.#dispatch;
Expand Down
Loading

0 comments on commit d29ceb4

Please sign in to comment.