/** * Extend the base TokenDocument class to implement system-specific HP bar logic. * @extends {TokenDocument} */ export class TokenDocument5e extends TokenDocument { /** @inheritdoc */ getBarAttribute(...args) { const data = super.getBarAttribute(...args); if (data && data.attribute === "attributes.hp") { data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0); data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0); } return data; } } /* -------------------------------------------- */ /** * Extend the base Token class to implement additional system-specific logic. * @extends {Token} */ export class Token5e extends Token { /** @inheritdoc */ _drawBar(number, bar, data) { if (data.attribute === "attributes.hp") return this._drawHPBar(number, bar, data); return super._drawBar(number, bar, data); } /* -------------------------------------------- */ /** * Specialized drawing function for HP bars. * @param {number} number The Bar number * @param {PIXI.Graphics} bar The Bar container * @param {object} data Resource data for this bar * @private */ _drawHPBar(number, bar, data) { // Extract health data let {value, max, temp, tempmax} = this.document.actor.data.data.attributes.hp; temp = Number(temp || 0); tempmax = Number(tempmax || 0); // Differentiate between effective maximum and displayed maximum const effectiveMax = Math.max(0, max + tempmax); let displayMax = max + (tempmax > 0 ? tempmax : 0); // Allocate percentages of the total const tempPct = Math.clamped(temp, 0, displayMax) / displayMax; const valuePct = Math.clamped(value, 0, effectiveMax) / displayMax; const colorPct = Math.clamped(value, 0, effectiveMax) / displayMax; // Determine colors to use const blk = 0x000000; const hpColor = PIXI.utils.rgb2hex([1 - colorPct / 2, colorPct, 0]); const c = CONFIG.SW5E.tokenHPColors; // Determine the container size (logic borrowed from core) const w = this.w; let h = Math.max(canvas.dimensions.size / 12, 8); if (this.data.height >= 2) h *= 1.6; const bs = Math.clamped(h / 8, 1, 2); const bs1 = bs + 1; // Overall bar container bar.clear(); bar.beginFill(blk, 0.5).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, w, h, 3); // Temporary maximum HP if (tempmax > 0) { const pct = max / effectiveMax; bar.beginFill(c.tempmax, 1.0) .lineStyle(1, blk, 1.0) .drawRoundedRect(pct * w, 0, (1 - pct) * w, h, 2); } // Maximum HP penalty else if (tempmax < 0) { const pct = (max + tempmax) / max; bar.beginFill(c.negmax, 1.0) .lineStyle(1, blk, 1.0) .drawRoundedRect(pct * w, 0, (1 - pct) * w, h, 2); } // Health bar bar.beginFill(hpColor, 1.0) .lineStyle(bs, blk, 1.0) .drawRoundedRect(0, 0, valuePct * w, h, 2); // Temporary hit points if (temp > 0) { bar.beginFill(c.temp, 1.0) .lineStyle(0) .drawRoundedRect(bs1, bs1, tempPct * w - 2 * bs1, h - 2 * bs1, 1); } // Set position let posY = number === 0 ? this.h - h : 0; bar.position.set(0, posY); } }