176 lines
4.9 KiB
JavaScript
176 lines
4.9 KiB
JavaScript
// 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');
|
|
}
|
|
}
|
|
};
|
|
};
|