tickwerk/src/data/documents/combat.ts

140 lines
4.1 KiB
TypeScript

// 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;
}
override async nextRound(): Promise<never> {
throw new Error('Not implemented!');
}
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 nextTurn() {
const game = getGame();
const combatant = this.combatant;
if (combatant === undefined || combatant.initiative === null || combatant.id === null) {
return this;
}
const ticks = await Dialog.prompt({
title: game.i18n.localize('TICKWERK.AdvanceTicks'),
content: '<input name="ticks" type="number" value="5" min="0" />',
label: game.i18n.localize('TICKWERK.AdvanceTicks'),
callback: (html) => {
const ticks = html[0]?.querySelector<HTMLInputElement>('input[name="ticks"]')?.value;
return ticks !== undefined ? parseInt(ticks) : undefined;
},
rejectClose: false,
});
if (ticks !== undefined && ticks !== null) {
await combatant.update({ initiative: combatant.initiative + ticks });
const advanceTime = ticks * CONFIG.time.roundTime;
return this.update(undefined, { diff: false, advanceTime } as DocumentModificationContext); // TODO: improve upstream types to allow this without type assertion
}
}
override async resetAll() {
for (const c of this.combatants) {
c.data.update({ initiative: null });
}
return this.update(
{ turn: 0, combatants: this.combatants.toObject(), 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<this | undefined> {
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;
};
};
}
}