// SPDX-FileCopyrightText: 2022 Johannes Loher // // SPDX-License-Identifier: MIT import { packageId } from '../constants'; import { getGame } from '../helpers'; export const registerDS4SpecificFunctionality = () => { if (CONFIG.tickwerk === undefined) CONFIG.tickwerk = {}; foundry.utils.mergeObject(CONFIG.tickwerk, { getTiebreaker, getInitiativeFormula }); registerRollItemSetting(); Hooks.on('ds4.rollItem', onRollItem); }; /** @type {import("../data/documents/combatant").GetTiebreaker} */ const getTiebreaker = async (combatant, combatants) => { if (combatants.length === 0) return 0; /** @type {number[]} */ const lowerBounds = []; /** @type {number[]} */ const upperBounds = []; /** @type {number[]} */ const equals = []; for (const other of combatants) { const tiebreaker = other._newTiebreaker ?? other.getFlag(packageId, 'tiebreaker') ?? 0; if (getInitiative(other) > getInitiative(combatant)) { lowerBounds.push(tiebreaker); } else if (getInitiative(other) < getInitiative(combatant)) { upperBounds.push(tiebreaker); } else { equals.push(tiebreaker); } } equals.sort(); const positionAmongEquals = Math.floor(twist.random() * (equals.length + 1)); equals.forEach((equal, index) => { if (index < positionAmongEquals) { lowerBounds.push(equal); } else { upperBounds.push(equal); } }); const lowerBound = Math.max(...lowerBounds); const upperBound = Math.min(...upperBounds); if (lowerBound === -Infinity) { if (upperBound === Infinity) { return 0; } else { return upperBound - 1; } } else { if (upperBound === Infinity) { return lowerBound + 1; } else { return (lowerBound + upperBound) / 2; } } }; /** * Get the initiative formula for a combatant. * @param {TickwerkCombatant} combatant The combatant for which to get the initiative formula * @returns {string} The initiative formula */ const getInitiativeFormula = (combatant) => { const started = combatant.combat?.started ?? false; if (!started) return '-@combatValues.initiative.total'; const tickValue = combatant.combat?.round ?? 0; return `max(${tickValue} + 10 - @combatValues.initiative.total, ${tickValue})`; }; /** * Get the initiative for a combatant. * @param {TickwerkCombatant} combatant The combatant for which to get the initiative * @returns {number} */ const getInitiative = (combatant) => { return combatant.actor?.system.combatValues.initiative.total ?? -Infinity; }; /** * React to an item roll by prompting the user to advance ticks. * @param {Item} item The item that has been rolled */ const onRollItem = (item) => { const game = getGame(); if (game.settings.get(packageId, 'ds4.reactToRollItemHook')) { if (['weapon', 'spell'].includes(item.type) && item.actor?.id) { const combatants = item.actor .getActiveTokens(false, true) .map((token) => game.combat?.getCombatantByToken(token.id ?? '')); for (const combatant of combatants) { if (combatant?.parent?.started) combatant?.advanceTicksDialog(); } } } }; const registerRollItemSetting = () => { getGame().settings.register(packageId, 'ds4.reactToRollItemHook', { name: 'TICKWERK.SettingDS4ReactToRollItemHookName', hint: 'TICKWERK.SettingDS4ReactToRollItemHookHint', scope: 'client', config: true, type: Boolean, default: true, }); };