// SPDX-FileCopyrightText: 2022 Johannes Loher // // SPDX-License-Identifier: MIT import { packageId } from '../../constants'; import { getGame } from '../../helpers'; import type { TickwerkCombatant } from './combatant'; export const registerCombatFunctionality = () => { CONFIG.Combat.documentClass = CombatMixin(CONFIG.Combat.documentClass); }; const CombatMixin = (BaseCombat: typeof Combat) => { return class TickwerkCombat extends BaseCombat { override get combatant() { return this.turns[0]; } override get round() { return this.tickValue; } override get started() { return this.turns.length > 0 && (this.getFlag(packageId, 'started') ?? false); } override get turn() { return 0; } /** * The current tick value of the Combat encounter. */ get tickValue(): number { const tickValues = this.combatants .filter((combatant) => !combatant.isDefeated) .map((combatant) => combatant.initiative) .filter((tickValue): tickValue is number => tickValue !== null); const tickValue = Math.min(...tickValues); return tickValue === Infinity ? 0 : tickValue; } override async nextRound(): Promise { throw new Error('Not implemented!'); } override async nextTurn() { const game = getGame(); const combatant = this.combatant; if (combatant === undefined || combatant.id === null) { return this; } const ticks = await Dialog.prompt({ title: game.i18n.localize('TICKWERK.AdvanceTicks'), content: '', label: game.i18n.localize('TICKWERK.AdvanceTicks'), callback: (html) => { const ticks = html[0]?.querySelector('input[name="ticks"]')?.value; return ticks !== undefined ? parseInt(ticks) : undefined; }, rejectClose: false, }); if (ticks !== undefined && ticks !== null) { await combatant.advanceTicks(ticks); } return this; } override previousRound(): Promise { throw new Error('Not implemented!'); } override previousTurn(): Promise { throw new Error('Not implemented!'); } override async resetAll() { for (const c of this.combatants) { c.data.update({ 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(): this['turns'] { 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?.data.tokenId ?? null, }; return (this.turns = turns); } override async startCombat(): Promise { const hasCombatantWithTickValue = this.combatants.find( (combatant) => !combatant.isDefeated && combatant.initiative !== null, ); if (!hasCombatantWithTickValue) { ui.notifications?.warn('TICKWERK.WarningCannotStartCombat', { localize: true }); return this; } return this.setFlag(packageId, 'started', true); } protected override _sortCombatants(a: TickwerkCombatant, b: TickwerkCombatant): number { 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; } }; }; declare global { interface FlagConfig { Combat: { tickwerk?: { started?: boolean; }; }; } }