Skip to content

Commit d29ceb4

Browse files
authored
Merge pull request #127 from League-of-Fabulous-Developers/dev
V2.3.10 Update
2 parents 1912800 + 903c3cc commit d29ceb4

File tree

11 files changed

+292
-169
lines changed

11 files changed

+292
-169
lines changed

module/documents/actors/actor.mjs

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FUItem } from '../items/item.mjs';
2+
import { toggleStatusEffect } from '../../helpers/effects.mjs';
23

34
/**
45
* Extend the base Actor document by defining a custom roll data structure
@@ -130,33 +131,15 @@ export class FUActor extends Actor {
130131
const crisisThreshold = Math.floor(hp.max / 2);
131132
const shouldBeInCrisis = hp.value <= crisisThreshold;
132133
const isInCrisis = this.statuses.has('crisis');
133-
134-
if (shouldBeInCrisis && !isInCrisis) {
135-
await ActiveEffect.create(
136-
{
137-
...CONFIG.statusEffects.find((val) => val.id === 'crisis'),
138-
origin: this.uuid,
139-
},
140-
{ parent: this },
141-
);
142-
} else if (!shouldBeInCrisis && isInCrisis) {
143-
this.effects.filter((effect) => effect.statuses.has('crisis')).forEach((val) => val.delete());
134+
if (shouldBeInCrisis !== isInCrisis) {
135+
await toggleStatusEffect(this, 'crisis');
144136
}
145137

146138
// Handle KO status
147139
const shouldBeKO = hp.value === 0; // KO when HP is 0
148140
const isKO = this.statuses.has('ko');
149-
150-
if (shouldBeKO && !isKO) {
151-
await ActiveEffect.create(
152-
{
153-
...CONFIG.statusEffects.find((val) => val.id === 'ko'),
154-
origin: this.uuid,
155-
},
156-
{ parent: this },
157-
);
158-
} else if (!shouldBeKO && isKO) {
159-
this.effects.filter((effect) => effect.statuses.has('ko')).forEach((val) => val.delete());
141+
if (shouldBeKO !== isKO) {
142+
await toggleStatusEffect(this, 'ko');
160143
}
161144
}
162145
super._onUpdate(changed, options, userId);

module/documents/actors/character/character-data-model.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ export class CharacterDataModel extends foundry.abstract.TypeDataModel {
130130
}
131131

132132
prepareDerivedData() {
133-
this.attributes.handleStatusEffects();
134-
this.affinities.handleGuard();
135133
this.tlTracker = new CharacterSkillTracker(this);
136134
}
137135

module/documents/actors/common/affinities-data-model.mjs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,4 @@ export class AffinitiesDataModel extends foundry.abstract.DataModel {
2626
poison: new EmbeddedDataField(AffinityDataModel, {}),
2727
};
2828
}
29-
30-
handleGuard() {
31-
const actor = this.parent.actor;
32-
if (actor.statuses.has('guard')) {
33-
Object.values(this).forEach((value) => value.upgrade());
34-
}
35-
}
3629
}
Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { AttributeDataModel } from './attribute-data-model.mjs';
2-
import { statusEffects } from '../../../helpers/statuses.mjs';
32

43
/**
54
* @property {AttributeDataModel} dex
@@ -17,18 +16,4 @@ export class AttributesDataModel extends foundry.abstract.DataModel {
1716
wlp: new EmbeddedDataField(AttributeDataModel, {}),
1817
};
1918
}
20-
21-
handleStatusEffects() {
22-
const actor = this.parent.actor;
23-
actor.statuses.forEach((status) => {
24-
const statusDefinition = statusEffects.find((value) => value.statuses.includes(status));
25-
if (statusDefinition && statusDefinition.stats) {
26-
if (statusDefinition.mod > 0) {
27-
statusDefinition.stats.forEach((attribute) => this[attribute].upgrade());
28-
} else {
29-
statusDefinition.stats.forEach((attribute) => this[attribute].downgrade());
30-
}
31-
}
32-
});
33-
}
3419
}

module/documents/actors/npc/npc-data-model.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ export class NpcDataModel extends foundry.abstract.TypeDataModel {
124124
prepareBaseData() {}
125125

126126
prepareDerivedData() {
127-
this.attributes.handleStatusEffects();
128-
this.affinities.handleGuard();
129127
this.spTracker = new NpcSkillTracker(this);
130128
}
131129

module/helpers/action-handler.mjs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createChatMessage, promptCheck, promptOpenCheck } from './checks.mjs';
22
import { handleStudyTarget } from './study-roll.mjs';
3+
import { toggleStatusEffect } from './effects.mjs';
34

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

8283
async function toggleGuardEffect(actor) {
8384
const GUARD_EFFECT_ID = 'guard';
84-
const guardEffect = CONFIG.statusEffects.find((effect) => effect.id === GUARD_EFFECT_ID);
85-
86-
const guardActive = actor.effects.some((effect) => effect.statuses.has('guard'));
87-
88-
if (guardActive) {
85+
const guardActive = await toggleStatusEffect(actor, GUARD_EFFECT_ID);
86+
if (!guardActive) {
8987
// Delete existing guard effects
90-
actor.effects.filter((effect) => effect.statuses.has('guard')).forEach((effect) => effect.delete());
9188
ui.notifications.info('Guard is deactivated.');
9289
} else {
9390
// Create a new guard effect
94-
await ActiveEffect.create(guardEffect, { parent: actor });
9591
ui.notifications.info('Guard is activated.');
9692
createActionMessage(actor, 'guard');
9793
}

module/helpers/effects.mjs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,20 @@ export function prepareActiveEffectCategories(effects) {
7373
/**
7474
* A helper function to toggle a status effect on an Actor.
7575
* Designed based off TokenDocument#toggleActiveEffect to properly interact with token hud.
76-
* @param {string} statusEffectId The status effect id based on CONFIG.statusEffects
77-
* @returns {Promise<boolean>} Whether the ActiveEffect is now on or off
76+
* @param {FUActor} actor the actor the status should get applied to
77+
* @param {string} statusEffectId The status effect id based on CONFIG.statusEffects
78+
* @param {string} [source] the UUID of the document that caused the effect
79+
* @returns {Promise<boolean>} Whether the ActiveEffect is now on or off
7880
*/
79-
export async function toggleStatusEffect(actor, statusEffectId) {
80-
const existing = actor.effects.reduce((arr, e) => {
81-
if (isActiveEffectForStatusEffectId(e, statusEffectId)) arr.push(e);
82-
return arr;
83-
}, []);
81+
export async function toggleStatusEffect(actor, statusEffectId, source = undefined) {
82+
const existing = actor.effects.filter((effect) => isActiveEffectForStatusEffectId(effect, statusEffectId));
8483
if (existing.length > 0) {
8584
await Promise.all(existing.map((e) => e.delete()));
8685
return false;
8786
} else {
88-
const statusEffect = CONFIG.statusEffects.find((e) => e.statuses.includes(statusEffectId));
87+
const statusEffect = CONFIG.statusEffects.find((e) => e.id === statusEffectId);
8988
if (statusEffect) {
90-
await ActiveEffect.create(statusEffect, { parent: actor });
89+
await ActiveEffect.create({ ...statusEffect, statuses: [statusEffectId], origin: source }, { parent: actor });
9190
}
9291
return true;
9392
}

module/helpers/inline-effects.mjs

Lines changed: 101 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FUItem } from '../documents/items/item.mjs';
44
import { Flags } from './flags.mjs';
55
import { FU, SYSTEM } from './config.mjs';
66
import { FUActiveEffect } from '../documents/effects/active-effect.mjs';
7+
import { toggleStatusEffect } from './effects.mjs';
78

89
const INLINE_EFFECT = 'InlineEffect';
910
const INLINE_EFFECT_CLASS = 'inline-effect';
@@ -16,46 +17,61 @@ const enricher = {
1617
enricher: inlineEffectEnricher,
1718
};
1819

20+
function createEffectAnchor(effect) {
21+
const anchor = document.createElement('a');
22+
anchor.draggable = true;
23+
anchor.dataset.effect = toBase64(effect);
24+
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
25+
const icon = document.createElement('i');
26+
icon.classList.add('fun', 'fu-aura');
27+
anchor.append(icon);
28+
anchor.append(effect.name);
29+
return anchor;
30+
}
31+
32+
function createBrokenAnchor() {
33+
const anchor = document.createElement('a');
34+
anchor.classList.add('inline', 'broken');
35+
const icon = document.createElement('i');
36+
icon.classList.add('fas', 'fa-chain-broken');
37+
anchor.append(icon);
38+
anchor.append(game.i18n.localize('FU.InlineEffectInvalid'));
39+
return anchor;
40+
}
41+
42+
function createStatusAnchor(effectValue, status) {
43+
const anchor = document.createElement('a');
44+
anchor.draggable = true;
45+
anchor.dataset.status = effectValue;
46+
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
47+
const icon = document.createElement('i');
48+
icon.classList.add('fun', 'fu-aura');
49+
anchor.append(icon);
50+
anchor.append(game.i18n.localize(status.name));
51+
return anchor;
52+
}
53+
1954
/**
2055
* @param text
2156
* @param options
2257
*/
2358
function inlineEffectEnricher(text, options) {
2459
/** @type string */
2560
const effectValue = text[1];
26-
let effect;
61+
2762
if (SUPPORTED_STATUSES.includes(effectValue) || BOONS_AND_BANES.includes(effectValue)) {
2863
const status = statusEffects.find((value) => value.id === effectValue);
2964
if (status) {
30-
status.name = game.i18n.localize(status.name);
31-
effect = status;
65+
return createStatusAnchor(effectValue, status);
3266
}
3367
} else {
3468
const decodedEffect = fromBase64(effectValue);
3569
if (decodedEffect && decodedEffect.name && decodedEffect.changes) {
36-
effect = decodedEffect;
70+
return createEffectAnchor(decodedEffect);
3771
}
3872
}
3973

40-
if (effect) {
41-
const anchor = document.createElement('a');
42-
anchor.draggable = true;
43-
anchor.dataset.effect = toBase64(effect);
44-
anchor.classList.add('inline', INLINE_EFFECT_CLASS, 'disable-how-to');
45-
const icon = document.createElement('i');
46-
icon.classList.add('fun', 'fu-aura');
47-
anchor.append(icon);
48-
anchor.append(effect.name);
49-
return anchor;
50-
} else {
51-
const anchor = document.createElement('a');
52-
anchor.classList.add('inline', 'broken');
53-
const icon = document.createElement('i');
54-
icon.classList.add('fas', 'fa-chain-broken');
55-
anchor.append(icon);
56-
anchor.append(game.i18n.localize('FU.InlineEffectInvalid'));
57-
return anchor;
58-
}
74+
return createBrokenAnchor();
5975
}
6076

6177
/**
@@ -71,14 +87,14 @@ function determineSource(document, element) {
7187
return document.uuid;
7288
} else if (document instanceof ChatMessage) {
7389
const speakerActor = ChatMessage.getSpeakerActor(document.speaker);
74-
if (speakerActor) {
75-
return speakerActor.uuid;
76-
}
7790
const flagItem = document.getFlag(SYSTEM, Flags.ChatMessage.Item);
7891
if (flagItem && speakerActor) {
7992
const item = speakerActor.items.get(flagItem._id);
8093
return item ? item.uuid : null;
8194
}
95+
if (speakerActor) {
96+
return speakerActor.uuid;
97+
}
8298
}
8399
return null;
84100
}
@@ -96,6 +112,7 @@ function activateListeners(document, html) {
96112
.on('click', function () {
97113
const source = determineSource(document, this);
98114
const effectData = fromBase64(this.dataset.effect);
115+
const status = this.dataset.status;
99116
const controlledTokens = canvas.tokens.controlled;
100117
let actors = [];
101118

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

112129
if (actors.length > 0) {
113-
actors.forEach((actor) => onApplyEffectToActor(actor, source, effectData));
130+
if (effectData) {
131+
actors.forEach((actor) => onApplyEffectToActor(actor, source, effectData));
132+
} else if (status) {
133+
actors.forEach((actor) => {
134+
if (!actor.statuses.has(status)) {
135+
toggleStatusEffect(actor, status, source);
136+
}
137+
});
138+
}
114139
} else {
115140
ui.notifications.warn(game.i18n.localize('FU.ChatApplyEffectNoActorsSelected'));
116141
}
@@ -127,26 +152,39 @@ function activateListeners(document, html) {
127152
type: INLINE_EFFECT,
128153
source: source,
129154
effect: fromBase64(this.dataset.effect),
155+
status: this.dataset.status,
130156
};
131157
event.dataTransfer.setData('text/plain', JSON.stringify(data));
132158
event.stopPropagation();
133159
})
134160
.on('contextmenu', function (event) {
135161
event.preventDefault();
136162
event.stopPropagation();
137-
const effectData = fromBase64(this.dataset.effect);
138-
const cls = getDocumentClass('ActiveEffect');
139-
delete effectData.id;
140-
cls.migrateDataSafe(effectData);
141-
cls.cleanData(effectData);
142-
Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true })
143-
.then((value) => {
144-
return cls.create(effectData, { temporary: true, render: true, parent: value });
145-
})
146-
.then((value) => {
147-
const activeEffectConfig = new ActiveEffectConfig(value);
148-
activeEffectConfig.render(true, { editable: false });
149-
});
163+
let effectData;
164+
if (this.dataset.status) {
165+
const status = this.dataset.status;
166+
const statusEffect = CONFIG.statusEffects.find((value) => value.id === status);
167+
if (statusEffect) {
168+
effectData = { ...statusEffect, statuses: [status] };
169+
}
170+
} else {
171+
effectData = fromBase64(this.dataset.effect);
172+
}
173+
if (effectData) {
174+
effectData.origin = determineSource(document, this);
175+
const cls = getDocumentClass('ActiveEffect');
176+
delete effectData.id;
177+
cls.migrateDataSafe(effectData);
178+
cls.cleanData(effectData);
179+
Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true })
180+
.then((value) => {
181+
return cls.create(effectData, { temporary: true, render: true, parent: value });
182+
})
183+
.then((value) => {
184+
const activeEffectConfig = new ActiveEffectConfig(value);
185+
activeEffectConfig.render(true, { editable: false });
186+
});
187+
}
150188
});
151189
}
152190

@@ -163,16 +201,22 @@ function onApplyEffectToActor(actor, source, effect) {
163201
}
164202
}
165203

166-
function onDropActor(actor, sheet, { type, source, effect }) {
204+
function onDropActor(actor, sheet, { type, source, effect, status }) {
167205
if (type === INLINE_EFFECT) {
168-
ActiveEffect.create(
169-
{
170-
...effect,
171-
origin: source,
172-
flags: foundry.utils.mergeObject(effect.flags ?? {}, { [SYSTEM]: { [FUActiveEffect.TEMPORARY_FLAG]: true } }),
173-
},
174-
{ parent: actor },
175-
);
206+
if (status) {
207+
if (!actor.statuses.has(status)) {
208+
toggleStatusEffect(actor, status, source);
209+
}
210+
} else if (effect) {
211+
ActiveEffect.create(
212+
{
213+
...effect,
214+
origin: source,
215+
flags: foundry.utils.mergeObject(effect.flags ?? {}, { [SYSTEM]: { [FUActiveEffect.TEMPORARY_FLAG]: true } }),
216+
},
217+
{ parent: actor },
218+
);
219+
}
176220
return false;
177221
}
178222
}
@@ -520,7 +564,14 @@ class InlineEffectConfiguration extends FormApplication {
520564
if (this.object.type === 'custom') {
521565
const cls = getDocumentClass('ActiveEffect');
522566
const tempActor = await Actor.create({ name: 'Temp Actor', type: 'character' }, { temporary: true });
523-
const tempEffect = await cls.create({ _id: foundry.utils.randomID(), name: game.i18n.localize(this.#defaultName), icon: this.#defaultIcon }, { temporary: true, parent: tempActor });
567+
const tempEffect = await cls.create(
568+
{
569+
_id: foundry.utils.randomID(),
570+
name: game.i18n.localize(this.#defaultName),
571+
icon: this.#defaultIcon,
572+
},
573+
{ temporary: true, parent: tempActor },
574+
);
524575
const activeEffectConfig = new TempActiveEffectConfig(tempEffect);
525576
activeEffectConfig.render(true);
526577
const dispatch = this.#dispatch;

0 commit comments

Comments
 (0)