// SPDX-FileCopyrightText: 2022 Johannes Loher // // SPDX-License-Identifier: MIT import { packageId } from '../../constants'; export const registerCombatFunctionality = () => { CONFIG.Combat.documentClass = CombatMixin(CONFIG.Combat.documentClass); }; /** * Enhance a combat class with functionality for the Tickwerk. * @param {typeof Combat} BaseCombat The combat class to enhance * @returns A combat class, adapted for the Tickwerk */ const CombatMixin = (BaseCombat) => { return class TickwerkCombat extends BaseCombat { /** @override */ get combatant() { return this.turns?.[0]; } /** @override */ get nextCombatant() { return this.turns?.[1]; } /** @override */ get started() { return (this.turns?.length ?? 0) > 0 && (this.getFlag(packageId, 'started') ?? false); } /** * The current tick value of the Combat encounter. * @type {number} */ get tickValue() { const tickValues = this.combatants .filter((combatant) => !combatant.isDefeated && !combatant.waiting) .map((combatant) => combatant.initiative) .filter((tickValue) => tickValue !== null); const tickValue = Math.min(...tickValues); return tickValue === Infinity ? 0 : tickValue; } /** @override */ prepareDerivedData() { super.prepareDerivedData(); this.turn = this.started ? 0 : null; } /** @override */ async nextRound() { throw new Error('Not implemented!'); } /** @override */ async nextTurn() { await this.combatant?.advanceTicksDialog(); return this; } /** @override */ previousRound() { throw new Error('Not implemented!'); } /** @override */ previousTurn() { throw new Error('Not implemented!'); } /** @override */ async resetAll() { for (const c of this.combatants) { c.updateSource({ initiative: null }); } return this.update( { turn: 0, combatants: this.combatants.toObject().map((combatant) => { if (combatant.flags.tickwerk.tiebreaker) delete combatant.flags.tickwerk.tiebreaker; return { ...combatant, [`flags.${packageId}.-=tiebreaker`]: null }; }), flags: { [packageId]: { started: false } }, }, { diff: false }, ); } /** @override */ setupTurns() { const turns = this.combatants.contents.sort(this._sortCombatants); const c = turns[0]; this.current = { round: this.round, turn: 0, combatantId: c?.id ?? null, tokenId: c?.tokenId ?? null, }; return (this.turns = turns); } /** @override */ async startCombat() { const hasCombatantWithTickValue = this.combatants.find( (combatant) => !combatant.isDefeated && combatant.initiative !== null, ); if (!hasCombatantWithTickValue) { ui.notifications?.warn('TICKWERK.WarningCannotStartCombat', { localize: true }); return this; } this._playCombatSound('startEncounter'); const updateData = { flags: { [packageId]: { started: true } } }; Hooks.callAll('combatStart', this, updateData); return this.update(updateData); } /** @override */ _sortCombatants(a, b) { const da = a.isDefeated ? 1 : 0; const db = b.isDefeated ? 1 : 0; const cd = da - db; if (cd !== 0) return cd; const wa = a.waiting ? 1 : 0; const wb = b.waiting ? 1 : 0; const cw = wa - wb; if (cw !== 0) return cw; const ia = a.initiative ?? Infinity; const ib = b.initiative ?? Infinity; const ci = ia - ib; if (ci !== 0) return ci; const tba = a.getFlag(packageId, 'tiebreaker') ?? 0; const tbb = b.getFlag(packageId, 'tiebreaker') ?? 0; const ctb = tba - tbb; if (ctb !== 0) return ctb; return (b.id ?? '') > (a.id ?? '') ? 1 : -1; } /** @override */ async _preUpdate(changed, options, user) { delete changed.round; delete changed.turn; options.tickwerk = { combatantBeforeUpdate: this.combatant?.id, nextCombatantBeforeUpdate: this.nextCombatant?.id, }; return super._preUpdate(changed, options, user); } /** @override */ _onUpdate(data, options, userId) { super._onUpdate(data, options, userId); if (game.user.character) { const { combatantBeforeUpdate, nextCombatantBeforeUpdate } = options.tickwerk ?? {}; if ( combatantBeforeUpdate !== undefined && combatantBeforeUpdate !== this.combatant && this.combatant?.actorId === game.user.character._id ) { this._playCombatSound('yourTurn'); } else if ( nextCombatantBeforeUpdate !== undefined && nextCombatantBeforeUpdate !== this.nextCombatant && this.nextCombatant?.actorId === game.user.character._id ) this._playCombatSound('nextUp'); } } }; };