Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions system/src/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,12 @@ SHADOWDARK.TALENT_CLASSES = {
patronBoon: "SHADOWDARK.talent.class.patronBoon",
};

SHADOWDARK.TOKEN_HP_COLORS = {
damage: 0xFF0000,
healing: 0x00FF00,
defeated: 0x000000,
};

SHADOWDARK.WEAPON_BASE_DAMAGE = {
d2: "1d2",
d4: "1d4",
Expand Down
55 changes: 55 additions & 0 deletions system/src/documents/ActorSD.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@ export default class ActorSD extends Actor {
if (abilityScore >= 18) return 4;
}

_animateHpChange(delta) {
const isDamage = delta < 0;
const color = isDamage
? CONFIG.SHADOWDARK.TOKEN_HP_COLORS.damage
: CONFIG.SHADOWDARK.TOKEN_HP_COLORS.healing;

const tokens = this.isToken ? [this.token] : this.getActiveTokens(true, false);
for (const t of tokens) {
// Suppress further effects if the token is marked as defeated in combat tracker
if (t.document.hasStatusEffect(CONFIG.specialStatusEffects.DEFEATED)) continue;

// Flash dynamic ring if enabled
if (t.document.ring.enabled) {
const anim = isDamage ? {
duration: 500,
easing: t.ring.constructor.easeTwoPeaks,
} : {};
t.ring.flashColor(Color.from(color), anim);
}

// Create scrolling combat text for HP delta
const hpPercent = Math.clamp(
Math.abs(delta) / (this.system.attributes.hp.max || 1),
0,
1
);
canvas.interface.createScrollingText(t.center, delta.signedString(), {
anchor: CONST.TEXT_ANCHOR_POINTS.TOP,
fontSize: 16 + (32 * hpPercent),
fill: color,
stroke: 0x000000,
strokeThickness: 4,
jitter: 0.25,
});
}
}

async _applyHpRollToMax(value) {
const currentHpBase = this.system.attributes.hp.base;
Expand Down Expand Up @@ -134,6 +170,16 @@ export default class ActorSD extends Actor {
});
}

async _onUpdate(data, options, userId) {
super._onUpdate(data, options, userId);

// If _preUpdate captured a previous HP value, animate the change
const prev = options?.shadowdark?.prevHpValue;
if (prev !== undefined) {
const delta = this.system.attributes.hp.value - prev;
if (delta !== 0) this._animateHpChange(delta);
}
}

async _playerRollHP(options={}) {
const characterClass = await this.getClass();
Expand Down Expand Up @@ -210,6 +256,15 @@ export default class ActorSD extends Actor {
this._populatePlayerModifiers();
}

async _preUpdate(data, options, userId) {
await super._preUpdate(data, options, userId);

// for HP changes, store a transient value to the update options for use in _onUpdate
const hpValuePath = "system.attributes.hp.value";
if (foundry.utils.hasProperty(data, hpValuePath)) {
(options.shadowdark ??= {}).prevHpValue = this.system.attributes.hp.value;
}
}

abilityModifier(ability) {
if (this.type === "Player") {
Expand Down
11 changes: 11 additions & 0 deletions system/src/hooks/targeting.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ export const TargetingHooks = {
if (game.user.targets.size > 1 && !user.isGM) {
game.user.updateTokenTargets([token.id]);
}

// if a token has a dynamic ring configured, flash using the user's color
if (token.document.ring.enabled) {
const color = Color.from(user.color);
token.ring.flashColor(color,
{
duration: 500,
easing: token.ring.constructor.easeTwoPeaks,
}
);
}
});
},
};