import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs'; export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { static DEFAULT_OPTIONS = { actions: { requestSpotlight: this.requestSpotlight, toggleSpotlight: this.toggleSpotlight, setActionTokens: this.setActionTokens } }; static PARTS = { header: { template: 'systems/daggerheart/templates/ui/combatTracker/combatTrackerHeader.hbs' }, tracker: { template: 'systems/daggerheart/templates/ui/combatTracker/combatTracker.hbs' }, footer: { template: 'systems/daggerheart/templates/ui/combatTracker/combatTrackerFooter.hbs' } }; /** @inheritDoc */ async _preparePartContext(_partId, context, _options) { return context; } async _prepareContext(options) { const context = await super._prepareContext(options); await this._prepareTrackerContext(context, options); await this._prepareCombatContext(context, options); return context; } async _prepareCombatContext(context, options) { await super._prepareCombatContext(context, options); const modifierBP = this.combats .find(x => x.active) ?.system?.extendedBattleToggles?.reduce((acc, toggle) => (acc ?? 0) + toggle.category, null) ?? null; const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length) + modifierBP; const currentBP = AdversaryBPPerEncounter(context.adversaries, context.characters); Object.assign(context, { fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), battlepoints: { max: maxBP, current: currentBP, hasModifierBP: modifierBP !== null } }); } async _prepareTrackerContext(context, options) { await super._prepareTrackerContext(context, options); const adversaries = context.turns?.filter(x => x.isNPC) ?? []; const characters = context.turns?.filter(x => !x.isNPC) ?? []; const spotlightQueueEnabled = game.settings.get( CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue ); const spotlightRequests = characters ?.filter(x => !x.isNPC && spotlightQueueEnabled) .filter(x => x.system.spotlight.requestOrderIndex > 0) .sort((a, b) => { const valueA = a.system.spotlight.requestOrderIndex; const valueB = b.system.spotlight.requestOrderIndex; return valueA - valueB; }); Object.assign(context, { actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens, adversaries, characters: characters ?.filter(x => !x.isNPC) .filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0), spotlightRequests }); } _getCombatContextOptions() { return [ { name: 'COMBAT.ClearMovementHistories', icon: '', condition: () => game.user.isGM && this.viewed?.combatants.size > 0, callback: () => this.viewed.clearMovementHistories() }, { name: 'COMBAT.Delete', icon: '', condition: () => game.user.isGM && !!this.viewed, callback: () => this.viewed.endCombat() } ]; } getDefeatedId(combatant) { if (!combatant.actor) return CONFIG.specialStatusEffects.DEFEATED; const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).defeated; return settings[`${combatant.actor.type}Default`]; } /** @inheritdoc */ async _onToggleDefeatedStatus(combatant) { const isDefeated = !combatant.isDefeated; await combatant.update({ defeated: isDefeated }); await combatant.actor?.toggleStatusEffect(this.getDefeatedId(combatant), { overlay: true, active: isDefeated }); } /** @inheritdoc */ async _prepareTurnContext(combat, combatant, index) { const { id, name, isOwner, isDefeated, hidden, initiative, permission } = combatant; const resource = permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER ? combatant.resource : null; const hasDecimals = Number.isFinite(initiative) && !Number.isInteger(initiative); const turn = { hasDecimals, hidden, id, isDefeated, initiative, isOwner, name, resource, active: index === combat.turn, canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), type: combatant.actor?.system?.type, img: await this._getCombatantThumbnail(combatant) }; turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin( ' ' ); const defeatedId = this.getDefeatedId(combatant); const effects = []; for (const effect of combatant.actor?.temporaryEffects ?? []) { if (effect.statuses.has(defeatedId)) turn.isDefeated = true; else if (effect.img) effects.push({ img: effect.img, name: effect.name }); } turn.effects = { icons: effects, tooltip: this._formatEffectsTooltip(effects) }; return { ...turn, isNPC: combatant.isNPC, system: combatant.system.toObject() }; } async setCombatantSpotlight(combatantId) { const update = { system: { 'spotlight.requesting': false, 'spotlight.requestOrderIndex': 0 } }; const combatant = this.viewed.combatants.get(combatantId); const toggleTurn = this.viewed.combatants.contents .sort(this.viewed._sortCombatants) .map(x => x.id) .indexOf(combatantId); if (this.viewed.turn !== toggleTurn) { const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; if (combatant.actor?.type === 'character') { await updateCountdowns( CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id, CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id ); } else { await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id); } const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints; if (autoPoints) { update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0); } } await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn, round: this.viewed.round + 1 }); await combatant.update(update); } static async requestSpotlight(_, target) { const characters = this.viewed.turns?.filter(x => !x.isNPC) ?? []; const orderValues = characters.map(character => character.system.spotlight.requestOrderIndex); const maxRequestIndex = Math.max(...orderValues); const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {}; const combatant = this.viewed.combatants.get(combatantId); await combatant.update({ 'system.spotlight': { requesting: !combatant.system.spotlight.requesting, requestOrderIndex: !combatant.system.spotlight.requesting ? maxRequestIndex + 1 : 0 } }); this.render(); } static async toggleSpotlight(_, target) { const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {}; await this.setCombatantSpotlight(combatantId); } static async setActionTokens(_, target) { const { combatantId, tokenIndex } = target.closest('[data-combatant-id]')?.dataset ?? {}; const combatant = this.viewed.combatants.get(combatantId); const changeIndex = Number(tokenIndex); const newIndex = combatant.system.actionTokens > changeIndex ? changeIndex : changeIndex + 1; await combatant.update({ 'system.actionTokens': newIndex }); this.render(); } }