From e302b56a4e9b2ddf293c49256a6c7a6272b5f060 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 14 Feb 2025 00:04:48 -0700 Subject: [PATCH] Get most of the custom Initiative sorting stuff through the door --- eslint.config.mjs | 2 + module/Apps/ActorSheets/HeroSummaryCardV1.mjs | 2 +- module/Apps/sidebar/CombatTracker.mjs | 27 ++++++++++++++ module/data/Actor/Hero.mjs | 2 +- module/documents/combat.mjs | 31 +++++++++++++--- module/documents/combatant.mjs | 37 +++++++++++++++++++ module/gameTerms.mjs | 12 +++--- module/hooks/init.mjs | 6 +++ module/settings/metaSettings.mjs | 10 +++++ module/utils/distanceBetweenFates.mjs | 27 ++++++++++++++ 10 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 module/documents/combatant.mjs create mode 100644 module/utils/distanceBetweenFates.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs index 272ca4a..16a335a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,7 @@ export default [ languageOptions: { globals: { CONFIG: `writable`, + CONST: `readonly`, game: `readonly`, Handlebars: `readonly`, Hooks: `readonly`, @@ -32,6 +33,7 @@ export default [ TextEditor: `readonly`, fromUuid: `readonly`, Combat: `readonly`, + Combatant: `readonly`, }, }, }, diff --git a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs index 16b9895..0dc19bd 100644 --- a/module/Apps/ActorSheets/HeroSummaryCardV1.mjs +++ b/module/Apps/ActorSheets/HeroSummaryCardV1.mjs @@ -116,7 +116,7 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi ctx.fate.selected = ctx.actor.system.fate; ctx.fate.options = [ { label: `RipCrypt.common.empty`, v: `` }, - ...gameTerms.FatePath + ...Object.values(gameTerms.FatePath) .map(v => ({ label: `RipCrypt.common.path.${v}`, value: v })), ]; return ctx; diff --git a/module/Apps/sidebar/CombatTracker.mjs b/module/Apps/sidebar/CombatTracker.mjs index e69de29..05f5f29 100644 --- a/module/Apps/sidebar/CombatTracker.mjs +++ b/module/Apps/sidebar/CombatTracker.mjs @@ -0,0 +1,27 @@ +import { Logger } from "../../utils/Logger.mjs"; + +const { CombatTracker } = foundry.applications.sidebar.tabs; + +export class RipCryptCombatTracker extends CombatTracker { + /** + * @override + */ + async _prepareTurnContext(combat, combatant, index) { + Logger.debug(`_prepareTurnContext`); + const turn = await super._prepareTurnContext(combat, combatant, index); + + // if ( !this.viewed ) return; + + // ! TODO: This is causing an error while the combat is not active, but is fine when it's active, this needs to be fixed + Logger.debug(turn, combatant); + const groupKey = combatant.groupKey; + if (groupKey) { + turn.active ||= combat.combatant.groupKey === groupKey; + if (turn.active && !turn.css.includes(`active`)) { + turn.css += `active`; + }; + }; + + return turn; + } +}; diff --git a/module/data/Actor/Hero.mjs b/module/data/Actor/Hero.mjs index 6f2e3ed..ab5d3d5 100644 --- a/module/data/Actor/Hero.mjs +++ b/module/data/Actor/Hero.mjs @@ -90,7 +90,7 @@ export class HeroData extends foundry.abstract.TypeDataModel { trim: true, nullable: false, choices: () => { - return gameTerms.FatePath.concat(``); + return Object.values(gameTerms.FatePath).concat(``); }, }), level: new fields.SchemaField({ diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index 2a4760c..6be6f22 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -6,6 +6,24 @@ Resources: */ export class RipCryptCombat extends Combat { + + get groups() { + let groups = new Map(); + + for (const combatant of this.combatants) { + const groupKey = combatant.groupKey; + if (!groupKey) { continue }; + + if (groups.has(groupKey)) { + groups.get(groupKey).push(combatant); + } else { + groups.set(groupKey, [combatant]); + }; + }; + + return groups; + }; + /** * @override * Sorts combatants for the combat tracker in the following way: @@ -14,13 +32,14 @@ export class RipCryptCombat extends Combat { */ _sortCombatants(a, b) { // The distance from fate + return super._sortCombatants(a, b) * -1; }; - nextTurn() { - // Make it skip all combatants with the same initiative value - }; + // nextTurn() { + // // Make it skip all combatants with the same initiative value + // }; - previousTurn() { - // Go back a step - }; + // previousTurn() { + // // Go back a step + // }; }; diff --git a/module/documents/combatant.mjs b/module/documents/combatant.mjs new file mode 100644 index 0000000..d925fc6 --- /dev/null +++ b/module/documents/combatant.mjs @@ -0,0 +1,37 @@ +import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs"; + +export class RipCryptCombatant extends Combatant { + + async _preCreate(data, options, user) { + const allowed = await super._preCreate(data, options, user); + if (allowed === false) { return false }; + + const start = game.settings.get(`ripcrypt`, `currentFate`); + const end = this.actor?.system?.fate || this.baseActor?.system?.fate; + const fateDistance = distanceBetweenFates(start, end); + + this.updateSource({ + initiative: fateDistance, + }); + }; + + get groupKey() { + const path = this.token?.actor?.system?.fate; + + // Disallow grouping things that don't have a fate path + if (!path) { return null }; + + // Token Disposition (group into: friendlies, unknown, hostiles) + let disposition = `unknown`; + switch (this.token.disposition) { + case CONST.TOKEN_DISPOSITIONS.HOSTILE: + disposition = `hostile`; + break; + case CONST.TOKEN_DISPOSITIONS.FRIENDLY: + disposition = `friendly`; + break; + }; + + return `${path}:${disposition}`; + }; +}; diff --git a/module/gameTerms.mjs b/module/gameTerms.mjs index de258d9..3e3bfff 100644 --- a/module/gameTerms.mjs +++ b/module/gameTerms.mjs @@ -11,12 +11,12 @@ export const gameTerms = Object.preventExtensions({ FLECT: `flect`, FRACT: `fract`, }), - FatePath: [ - `North`, - `East`, - `South`, - `West`, - ], + FatePath: Object.freeze({ + NORTH: `North`, + EAST: `East`, + SOUTH: `South`, + WEST: `West`, + }), Access: [ `Common`, `Uncommon`, diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 50d4a6b..a6cbf58 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -28,6 +28,9 @@ import { registerDevSettings } from "../settings/devSettings.mjs"; import { registerMetaSettings } from "../settings/metaSettings.mjs"; import { registerUserSettings } from "../settings/userSettings.mjs"; import { registerWorldSettings } from "../settings/worldSettings.mjs"; +import { RipCryptCombat } from "../documents/combat.mjs"; +import { RipCryptCombatant } from "../documents/combatant.mjs"; +import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; Hooks.once(`init`, () => { Logger.log(`Initializing`); @@ -53,6 +56,9 @@ Hooks.once(`init`, () => { // #endregion // #region Class Changes + CONFIG.ui.combat = RipCryptCombatTracker; + CONFIG.Combat.documentClass = RipCryptCombat; + CONFIG.Combatant.documentClass = RipCryptCombatant; CONFIG.Item.documentClass = RipCryptItem; CONFIG.Dice.terms.d = CryptDie; // #endregion diff --git a/module/settings/metaSettings.mjs b/module/settings/metaSettings.mjs index 57580b9..1f2abaf 100644 --- a/module/settings/metaSettings.mjs +++ b/module/settings/metaSettings.mjs @@ -8,4 +8,14 @@ export function registerMetaSettings() { ui.crypt.render({ parts: [ `delveConditions` ]}); }, }); + + game.settings.register(`ripcrypt`, `currentFate`, { + scope: `world`, + type: String, + config: false, + requiresReload: false, + onChange: () => { + ui.crypt.render({ parts: [ `fate` ] }); + }, + }); }; diff --git a/module/utils/distanceBetweenFates.mjs b/module/utils/distanceBetweenFates.mjs new file mode 100644 index 0000000..78a603a --- /dev/null +++ b/module/utils/distanceBetweenFates.mjs @@ -0,0 +1,27 @@ +import { gameTerms } from "../gameTerms.mjs"; +import { Logger } from "./Logger.mjs"; + +const { FatePath } = gameTerms; + +export function isOppositeFates(a, b) { + return a === FatePath.NORTH && b === FatePath.SOUTH + || a === FatePath.EAST && FatePath.WEST; +}; + +export function distanceBetweenFates(start, end) { + if (!start || !end) { + Logger.error(`Start and End must both have a defined value, given`, {start, end}); + return undefined; + }; + + if (isOppositeFates(start, end)) { + return 2; + }; + + let isForward = start === FatePath.SOUTH && end === FatePath.WEST; + isForward ||= start === FatePath.NORTH && end === FatePath.EAST; + if (isForward) { + return 1; + }; + return 3; +};