refactor: cleanup

This commit is contained in:
Johannes Loher 2022-05-16 03:46:53 +02:00
parent ddcbcffe4c
commit 2d9e418ba7
9 changed files with 118 additions and 79 deletions

View file

@ -4,5 +4,8 @@
"TICKWERK.Tick": "Tick", "TICKWERK.Tick": "Tick",
"TICKWERK.Wait": "Abwarten", "TICKWERK.Wait": "Abwarten",
"TICKWERK.Waiting": "Abwarten", "TICKWERK.Waiting": "Abwarten",
"TICKWERK.WarningCannotAdvanceWhileWaiting": "Während des Abwartens ist es nicht möglich, auf der Tickleiste vorzurücken.",
"TICKWERK.WarningCannotAdvanceWithoutStartedCombat": "Solange der Kampf nicht gestartet ist es nicht möglich, auf der Tickleiste vorzurücken.",
"TICKWERK.WarningCannotAdvanceWithoutTickValue": "Ohne Tickwert ist es nicht möglich, auf der Tickleiste vorzurücken.",
"TICKWERK.WarningCannotStartCombat": "Der Kampf kann nur begonnen werden, wenn mindestens ein Kampfteilnehmer einen Tickwert hat." "TICKWERK.WarningCannotStartCombat": "Der Kampf kann nur begonnen werden, wenn mindestens ein Kampfteilnehmer einen Tickwert hat."
} }

View file

@ -4,5 +4,8 @@
"TICKWERK.Tick": "Tick", "TICKWERK.Tick": "Tick",
"TICKWERK.Wait": "Wait", "TICKWERK.Wait": "Wait",
"TICKWERK.Waiting": "Waiting", "TICKWERK.Waiting": "Waiting",
"TICKWERK.WarningCannotAdvanceWhileWaiting": "Cannot advance while waiting.",
"TICKWERK.WarningCannotAdvanceWithoutStartedCombat": "Cannot advance without the combat being started.",
"TICKWERK.WarningCannotAdvanceWithoutTickValue": "Cannot advance without having a tick value.",
"TICKWERK.WarningCannotStartCombat": "In order to start the combat, there needs to be at least one combatant with a tick value." "TICKWERK.WarningCannotStartCombat": "In order to start the combat, there needs to be at least one combatant with a tick value."
} }

View file

@ -21,11 +21,16 @@ const CombatTrackerMixin = (BaseCombatTracker: typeof CombatTracker) => {
turns: data.turns.map((turn) => ({ ...turn, waiting: this.viewed?.combatants.get(turn.id)?.waiting })), turns: data.turns.map((turn) => ({ ...turn, waiting: this.viewed?.combatants.get(turn.id)?.waiting })),
} as CombatTracker.Data; // TODO: Improve upstream types } as CombatTracker.Data; // TODO: Improve upstream types
} }
override activateListeners(html: JQuery): void { override activateListeners(html: JQuery): void {
super.activateListeners(html); super.activateListeners(html);
html.find('.combatant-control[data-control="toggleWaiting"]').on('click', this._onToggleWaiting.bind(this)); html.find('.combatant-control[data-control="toggleWaiting"]').on('click', this._onToggleWaiting.bind(this));
} }
/**
* Handle clicks on the Combatant waiting control button.
* @param event The originating click event
*/
_onToggleWaiting(event: JQuery.ClickEvent) { _onToggleWaiting(event: JQuery.ClickEvent) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();

View file

@ -15,9 +15,12 @@ const onActiveEffectChanged = (activeEffect: ActiveEffect) => {
const parent = activeEffect.parent; const parent = activeEffect.parent;
const actorId = parent?.id; const actorId = parent?.id;
if (!(parent instanceof Actor) || actorId === null || actorId === undefined) return; if (!(parent instanceof Actor) || actorId === null || actorId === undefined) return;
const statusId = activeEffect.getFlag('core', 'statusId'); const statusId = activeEffect.getFlag('core', 'statusId');
if (statusId === CONFIG.Combat.defeatedStatusId) { if (statusId === CONFIG.Combat.defeatedStatusId) {
const relevantCombats = game.combats?.filter((combat) => combat.getCombatantByActor(actorId) !== undefined) ?? []; const relevantCombats = game.combats?.filter((combat) => combat.getCombatantByActor(actorId) !== undefined) ?? [];
for (const combat of relevantCombats) { for (const combat of relevantCombats) {
combat.setupTurns(); combat.setupTurns();
if (combat === game.combat) { if (combat === game.combat) {

View file

@ -29,10 +29,9 @@ const CombatMixin = (BaseCombat: typeof Combat) => {
return 0; return 0;
} }
override async nextRound(): Promise<never> { /**
throw new Error('Not implemented!'); * The current tick value of the Combat encounter.
} */
get tickValue(): number { get tickValue(): number {
const tickValues = this.combatants const tickValues = this.combatants
.filter((combatant) => !combatant.isDefeated) .filter((combatant) => !combatant.isDefeated)
@ -42,11 +41,15 @@ const CombatMixin = (BaseCombat: typeof Combat) => {
return tickValue === Infinity ? 0 : tickValue; return tickValue === Infinity ? 0 : tickValue;
} }
override async nextRound(): Promise<never> {
throw new Error('Not implemented!');
}
override async nextTurn() { override async nextTurn() {
const game = getGame(); const game = getGame();
const combatant = this.combatant; const combatant = this.combatant;
if (combatant === undefined || combatant.initiative === null || combatant.id === null) { if (combatant === undefined || combatant.id === null) {
return this; return this;
} }
@ -62,10 +65,18 @@ const CombatMixin = (BaseCombat: typeof Combat) => {
}); });
if (ticks !== undefined && ticks !== null) { if (ticks !== undefined && ticks !== null) {
await combatant.update({ initiative: combatant.initiative + ticks }); await combatant.advanceTicks(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
} }
return this;
}
override previousRound(): Promise<never> {
throw new Error('Not implemented!');
}
override previousTurn(): Promise<never> {
throw new Error('Not implemented!');
} }
override async resetAll() { override async resetAll() {
@ -118,8 +129,8 @@ const CombatMixin = (BaseCombat: typeof Combat) => {
const ci = ia - ib; const ci = ia - ib;
if (ci !== 0) return ci; if (ci !== 0) return ci;
const tba = a.getFlag(packageId, 'tieBreaker') ?? 0; const tba = a.getFlag(packageId, 'tiebreaker') ?? 0;
const tbb = b.getFlag(packageId, 'tieBreaker') ?? 0; const tbb = b.getFlag(packageId, 'tiebreaker') ?? 0;
const ctb = tba - tbb; const ctb = tba - tbb;
if (ctb !== 0) return ctb; if (ctb !== 0) return ctb;

View file

@ -17,9 +17,9 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => {
_newInitiative: number | null | undefined; _newInitiative: number | null | undefined;
/** /**
* An temporary property to make changes to the tieBreaker available to other instances in their `_pre…` methods. * An temporary property to make changes to the tiebreaker available to other instances in their `_pre…` methods.
*/ */
_newTieBreaker: number | undefined; _newTiebreaker: number | undefined;
/*** /***
* Is this combatant currently waiting? * Is this combatant currently waiting?
@ -28,31 +28,45 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => {
return this.getFlag(packageId, 'waiting') ?? false; return this.getFlag(packageId, 'waiting') ?? false;
} }
/**
* Toggle the waiting state of this combatant.
*/
toggleWaiting(): Promise<this | undefined> { toggleWaiting(): Promise<this | undefined> {
return this.update({ [`flags.${packageId}.waiting`]: !this.waiting, initiative: this.parent?.round }); const update: Record<string, unknown> = { [`flags.${packageId}.waiting`]: !this.waiting };
if (this.parent?.started && this.waiting) update.initiative = this.parent?.round;
return this.update(update);
} }
protected override async _preCreate(...args: Parameters<Combatant['_preCreate']>): Promise<void> { /**
await super._preCreate(...args); * Advance for the given number of ticks.
await this.#updateTieBreakerData(args[0]); * @param ticks The number of ticks to advance for
*/
async advanceTicks(ticks: number): Promise<void> {
if (this.initiative === null) {
ui.notifications?.warn('TICKWERK.WarningCannotAdvanceWithoutTickValue', { localize: true });
return;
}
if (this.waiting) {
ui.notifications?.warn('TICKWERK.WarningCannotAdvanceWhileWaiting', { localize: true });
return;
}
if (!this.combat?.started) {
ui.notifications?.warn('TICKWERK.WarningCannotAdvanceWithoutStartedCombat', { localize: true });
return;
}
await this.update({ initiative: this.initiative + ticks });
const advanceTime = ticks * CONFIG.time.roundTime;
if (advanceTime !== 0) {
await this.combat?.update(undefined, { diff: false, advanceTime } as DocumentModificationContext);
}
} }
protected override async _preUpdate(...args: Parameters<Combatant['_preUpdate']>): Promise<void> { /**
await super._preUpdate(...args); * Update tiebreaker data for a given creation or update.
await this.#updateTieBreakerData(args[0]); * @param data The data of the creation / update
} */
async #updateTiebreakerData(data: DeepPartial<CombatantDataConstructorData>): Promise<void> {
protected override _onCreate(): void {
this._newInitiative = undefined;
this._newTieBreaker = undefined;
}
protected override _onUpdate(): void {
this._newInitiative = undefined;
this._newTieBreaker = undefined;
}
async #updateTieBreakerData(data: DeepPartial<CombatantDataConstructorData>): Promise<void> {
if ('initiative' in data) { if ('initiative' in data) {
const combatantsWithSameTickValue = const combatantsWithSameTickValue =
this.parent?.combatants.filter((combatant) => { this.parent?.combatants.filter((combatant) => {
@ -60,16 +74,21 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => {
combatant._newInitiative !== undefined ? combatant._newInitiative : combatant.initiative; combatant._newInitiative !== undefined ? combatant._newInitiative : combatant.initiative;
return otherInitiative === data.initiative; return otherInitiative === data.initiative;
}) ?? []; }) ?? [];
const tieBreaker = await this.#getTieBreaker(combatantsWithSameTickValue); const tiebreaker = await this.#getTiebreaker(combatantsWithSameTickValue);
setProperty(data, `flags.${packageId}.tieBreaker`, tieBreaker); setProperty(data, `flags.${packageId}.tiebreaker`, tiebreaker);
this._newInitiative = data.initiative; this._newInitiative = data.initiative;
this._newTieBreaker = tieBreaker; this._newTiebreaker = tiebreaker;
} }
} }
async #getTieBreaker(combatants: TickwerkCombatant[]): Promise<number> { /**
const getTieBreaker = CONFIG.tickwerk?.getTieBreaker ?? defaultGetTieBreaker; * Get a tiebreaker between this combatant and the given other combatants.
return getTieBreaker(this, combatants); * @param combatants The other combatants among which to find a tiebreaker
* @returns A promise that resolves to the tiebreaker
*/
async #getTiebreaker(combatants: TickwerkCombatant[]): Promise<number> {
const getTiebreaker = CONFIG.tickwerk?.getTiebreaker ?? defaultGetTiebreaker;
return getTiebreaker(this, combatants);
} }
protected override _getInitiativeFormula(): string { protected override _getInitiativeFormula(): string {
@ -77,19 +96,39 @@ const CombatantMixin = (BaseCombatant: typeof Combatant) => {
if (getInitiativeFormula) return getInitiativeFormula(this); if (getInitiativeFormula) return getInitiativeFormula(this);
return super._getInitiativeFormula(); return super._getInitiativeFormula();
} }
protected override async _preCreate(...args: Parameters<Combatant['_preCreate']>): Promise<void> {
await super._preCreate(...args);
await this.#updateTiebreakerData(args[0]);
}
protected override async _preUpdate(...args: Parameters<Combatant['_preUpdate']>): Promise<void> {
await super._preUpdate(...args);
await this.#updateTiebreakerData(args[0]);
}
protected override _onCreate(): void {
this._newInitiative = undefined;
this._newTiebreaker = undefined;
}
protected override _onUpdate(): void {
this._newInitiative = undefined;
this._newTiebreaker = undefined;
}
}; };
}; };
const defaultGetTieBreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => { const defaultGetTiebreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => {
if (combatants.length === 0) return 0; if (combatants.length === 0) return 0;
const tieBreakers = combatants.map((combatant) => { const tiebreakers = combatants.map((combatant) => {
return ( return (
(combatant._newTieBreaker !== undefined (combatant._newTiebreaker !== undefined
? combatant._newTieBreaker ? combatant._newTiebreaker
: combatant.getFlag(packageId, 'tieBreaker')) ?? 0 : combatant.getFlag(packageId, 'tiebreaker')) ?? 0
); );
}); });
return Math.max(...tieBreakers) + 1; return Math.max(...tiebreakers) + 1;
}; };
export type TickwerkCombatantConstructor = ReturnType<typeof CombatantMixin>; export type TickwerkCombatantConstructor = ReturnType<typeof CombatantMixin>;
@ -99,7 +138,7 @@ declare global {
interface FlagConfig { interface FlagConfig {
Combatant: { Combatant: {
tickwerk: { tickwerk: {
tieBreaker?: number | undefined; tiebreaker?: number | undefined;
waiting?: boolean | undefined; waiting?: boolean | undefined;
}; };
}; };
@ -111,7 +150,7 @@ declare global {
interface CONFIG { interface CONFIG {
tickwerk?: { tickwerk?: {
getTieBreaker?: (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]) => Promise<number>; getTiebreaker?: (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]) => Promise<number>;
getInitiativeFormula?: (combatant: TickwerkCombatant) => string; getInitiativeFormula?: (combatant: TickwerkCombatant) => string;
}; };
} }

View file

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import { packageId } from './constants';
const loggingContext = packageId;
const loggingSeparator = '|';
type LogLevel = 'debug' | 'info' | 'warning' | 'error';
type LoggingFunction = (...data: unknown[]) => void;
const getLoggingFunction = (type: LogLevel = 'info'): LoggingFunction => {
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
};
const logger = Object.freeze({
debug: getLoggingFunction('debug'),
info: getLoggingFunction('info'),
warn: getLoggingFunction('warning'),
error: getLoggingFunction('error'),
getLoggingFunction,
});
export default logger;

View file

@ -8,19 +8,19 @@ import type { TickwerkCombatant } from '../data/documents/combatant';
export const registerDS4SpecificFunctionality = () => { export const registerDS4SpecificFunctionality = () => {
if (CONFIG.tickwerk === undefined) CONFIG.tickwerk = {}; if (CONFIG.tickwerk === undefined) CONFIG.tickwerk = {};
foundry.utils.mergeObject(CONFIG.tickwerk, { getTieBreaker, getInitiativeFormula }); foundry.utils.mergeObject(CONFIG.tickwerk, { getTiebreaker, getInitiativeFormula });
}; };
const getTieBreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => { const getTiebreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => {
if (combatants.length === 0) return 0; if (combatants.length === 0) return 0;
const tieBreakers = combatants.map((combatant) => { const tiebreakers = combatants.map((combatant) => {
return ( return (
(combatant._newTieBreaker !== undefined (combatant._newTiebreaker !== undefined
? combatant._newTieBreaker ? combatant._newTiebreaker
: combatant.getFlag(packageId, 'tieBreaker')) ?? 0 : combatant.getFlag(packageId, 'tiebreaker')) ?? 0
); );
}); });
return Math.max(...tieBreakers) + 1; return Math.max(...tiebreakers) + 1;
}; };
const getInitiativeFormula = (combatant: TickwerkCombatant) => { const getInitiativeFormula = (combatant: TickwerkCombatant) => {

View file

@ -1,4 +1,5 @@
{{!-- {{!--
SPDX-FileCopyrightText: 2022 Foundry Gaming LLC.
SPDX-FileCopyrightText: 2022 Johannes Loher SPDX-FileCopyrightText: 2022 Johannes Loher
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT