Add the ability for the Combat Tracker to make it multiple actor's turns at the same time
Combat Groups
This commit is contained in:
commit
b33d7b59eb
14 changed files with 374 additions and 9 deletions
BIN
assets/turn-marker.png
Normal file
BIN
assets/turn-marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
|
|
@ -16,6 +16,7 @@ export default [
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
CONFIG: `writable`,
|
CONFIG: `writable`,
|
||||||
|
CONST: `readonly`,
|
||||||
game: `readonly`,
|
game: `readonly`,
|
||||||
Handlebars: `readonly`,
|
Handlebars: `readonly`,
|
||||||
Hooks: `readonly`,
|
Hooks: `readonly`,
|
||||||
|
|
@ -31,6 +32,10 @@ export default [
|
||||||
renderTemplate: `readonly`,
|
renderTemplate: `readonly`,
|
||||||
TextEditor: `readonly`,
|
TextEditor: `readonly`,
|
||||||
fromUuid: `readonly`,
|
fromUuid: `readonly`,
|
||||||
|
Combat: `readonly`,
|
||||||
|
Combatant: `readonly`,
|
||||||
|
canvas: `readonly`,
|
||||||
|
Token: `readonly`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export class HeroSummaryCardV1 extends GenericAppMixin(HandlebarsApplicationMixi
|
||||||
ctx.fate.selected = ctx.actor.system.fate;
|
ctx.fate.selected = ctx.actor.system.fate;
|
||||||
ctx.fate.options = [
|
ctx.fate.options = [
|
||||||
{ label: `RipCrypt.common.empty`, v: `` },
|
{ label: `RipCrypt.common.empty`, v: `` },
|
||||||
...gameTerms.FatePath
|
...Object.values(gameTerms.FatePath)
|
||||||
.map(v => ({ label: `RipCrypt.common.path.${v}`, value: v })),
|
.map(v => ({ label: `RipCrypt.common.path.${v}`, value: v })),
|
||||||
];
|
];
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|
|
||||||
37
module/Apps/sidebar/CombatTracker.mjs
Normal file
37
module/Apps/sidebar/CombatTracker.mjs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
const { CombatTracker } = foundry.applications.sidebar.tabs;
|
||||||
|
|
||||||
|
export class RipCryptCombatTracker extends CombatTracker {
|
||||||
|
/**
|
||||||
|
* Changes the way the combat tracker renders combatant rows to account for
|
||||||
|
* multiple combatants being in the same combat "group", thus all going at the
|
||||||
|
* same time.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
async _prepareTurnContext(combat, combatant, index) {
|
||||||
|
const turn = await super._prepareTurnContext(combat, combatant, index);
|
||||||
|
|
||||||
|
turn.hasDecimals = true;
|
||||||
|
turn.initiative = combatant.dynamicInitiative;
|
||||||
|
|
||||||
|
const groupKey = combatant?.groupKey;
|
||||||
|
if (groupKey && combat.started) {
|
||||||
|
turn.active ||= combat.combatant?.groupKey === groupKey;
|
||||||
|
if (turn.active && !turn.css.includes(`active`)) {
|
||||||
|
turn.css += ` active`;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return turn;
|
||||||
|
};
|
||||||
|
|
||||||
|
async _onRender(...args) {
|
||||||
|
await super._onRender(...args);
|
||||||
|
|
||||||
|
// Purge the combat controls that I don't want to exist because they don't
|
||||||
|
// make sense in the system.
|
||||||
|
this.element?.querySelector(`[data-action="resetAll"]`)?.remove();
|
||||||
|
this.element?.querySelector(`[data-action="rollNPC"]`)?.remove();
|
||||||
|
this.element?.querySelector(`[data-action="rollAll"]`)?.remove();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -90,7 +90,7 @@ export class HeroData extends foundry.abstract.TypeDataModel {
|
||||||
trim: true,
|
trim: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
choices: () => {
|
choices: () => {
|
||||||
return gameTerms.FatePath.concat(``);
|
return Object.values(gameTerms.FatePath).concat(``);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
level: new fields.SchemaField({
|
level: new fields.SchemaField({
|
||||||
|
|
|
||||||
139
module/documents/combat.mjs
Normal file
139
module/documents/combat.mjs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
Resources:
|
||||||
|
- Combat : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/documents/combat.mjs
|
||||||
|
- Combatant : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/documents/combatant.mjs
|
||||||
|
- CombatTracker : https://github.com/foundryvtt/dnd5e/blob/4.3.x/module/applications/combat/combat-tracker.mjs
|
||||||
|
*/
|
||||||
|
|
||||||
|
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:
|
||||||
|
* - Distance from the current fate ordinal. (0 -> 3)
|
||||||
|
* - Coin Flip result (if disposition matches flip result, then 0, otherwise, 0.5)
|
||||||
|
*/
|
||||||
|
_sortCombatants(a, b) {
|
||||||
|
const ia = Number.isNumeric(a.dynamicInitiative) ? a.dynamicInitiative : -Infinity;
|
||||||
|
const ib = Number.isNumeric(b.dynamicInitiative) ? b.dynamicInitiative : -Infinity;
|
||||||
|
|
||||||
|
const delta = ia - ib;
|
||||||
|
if (Math.sign(delta) !== 0) {
|
||||||
|
return delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fallback to alphabetical sort
|
||||||
|
return a.name < b.name ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
async nextTurn() {
|
||||||
|
if (this.round === 0) {return this.nextRound()}
|
||||||
|
|
||||||
|
const turn = this.turn ?? -1;
|
||||||
|
|
||||||
|
const groupKey = this.turns[turn]?.groupKey;
|
||||||
|
|
||||||
|
// Determine the next turn number
|
||||||
|
let nextTurn = null;
|
||||||
|
for (let i = turn + 1; i < this.turns.length; i++) {
|
||||||
|
const combatant = this.turns[i];
|
||||||
|
if (combatant.groupKey !== groupKey) {
|
||||||
|
nextTurn = i;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maybe advance to the next round
|
||||||
|
if ((nextTurn === null) || (nextTurn >= this.turns.length)) {return this.nextRound()}
|
||||||
|
|
||||||
|
const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, nextTurn);
|
||||||
|
|
||||||
|
// Update the document, passing data through a hook first
|
||||||
|
const updateData = {round: this.round, turn: nextTurn};
|
||||||
|
const updateOptions = {direction: 1, worldTime: {delta: advanceTime}};
|
||||||
|
Hooks.callAll(`combatTurn`, this, updateData, updateOptions);
|
||||||
|
await this.update(updateData, updateOptions);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
async previousTurn() {
|
||||||
|
if (this.round === 0) { return this }
|
||||||
|
if ((this.turn === 0) || (this.turns.length === 0)) {return this.previousRound()}
|
||||||
|
|
||||||
|
const currentTurn = (this.turn ?? this.turns.length) - 1;
|
||||||
|
let previousTurn = null;
|
||||||
|
const groupKey = this.combatant.groupKey;
|
||||||
|
for (let i = currentTurn; i >= 0; i--) {
|
||||||
|
const combatant = this.turns[i];
|
||||||
|
if (combatant.groupKey !== groupKey) {
|
||||||
|
previousTurn = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousTurn < 0) {
|
||||||
|
if (this.round === 1) {
|
||||||
|
this.round = 0;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
return this.previousRound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const advanceTime = this.getTimeDelta(this.round, this.turn, this.round, previousTurn);
|
||||||
|
|
||||||
|
// Update the document, passing data through a hook first
|
||||||
|
const updateData = {round: this.round, turn: previousTurn};
|
||||||
|
const updateOptions = {direction: -1, worldTime: {delta: advanceTime}};
|
||||||
|
Hooks.callAll(`combatTurn`, this, updateData, updateOptions);
|
||||||
|
await this.update(updateData, updateOptions);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overridden to make it so that there can be multiple tokens with turn markers
|
||||||
|
* visible at the same time.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @internal
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_updateTurnMarkers() {
|
||||||
|
if (!canvas.ready) { return };
|
||||||
|
|
||||||
|
const tokenGroup = this.combatant?.groupKey;
|
||||||
|
for (const token of canvas.tokens.turnMarkers) {
|
||||||
|
const actor = token.actor ?? token.baseActor;
|
||||||
|
if (actor?.groupKey !== tokenGroup) {
|
||||||
|
token.renderFlags.set({refreshTurnMarker: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.active) { return };
|
||||||
|
const currentToken = this.combatant?.token?._object;
|
||||||
|
if (!tokenGroup && currentToken) {
|
||||||
|
currentToken.renderFlags.set({refreshTurnMarker: true});
|
||||||
|
} else {
|
||||||
|
const group = this.groups.get(tokenGroup) ?? [];
|
||||||
|
for (const combatant of group) {
|
||||||
|
combatant.token?._object?.renderFlags.set({ refreshTurnMarker: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
70
module/documents/combatant.mjs
Normal file
70
module/documents/combatant.mjs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { distanceBetweenFates } from "../utils/distanceBetweenFates.mjs";
|
||||||
|
|
||||||
|
export class RipCryptCombatant extends Combatant {
|
||||||
|
|
||||||
|
get disposition() {
|
||||||
|
switch (this.token?.disposition) {
|
||||||
|
case CONST.TOKEN_DISPOSITIONS.HOSTILE:
|
||||||
|
return `hostile`;
|
||||||
|
case CONST.TOKEN_DISPOSITIONS.FRIENDLY:
|
||||||
|
return `friendly`;
|
||||||
|
};
|
||||||
|
return `unknown`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the Combat tracker to order combatants according to their
|
||||||
|
* fate path and the coin flip.
|
||||||
|
*/
|
||||||
|
get dynamicInitiative() {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
const start = game.settings.get(`ripcrypt`, `currentFate`);
|
||||||
|
const end = this.actor?.system?.fate || this.baseActor?.system?.fate;
|
||||||
|
total += distanceBetweenFates(start, end);
|
||||||
|
|
||||||
|
const whoFirst = game.settings.get(`ripcrypt`, `whoFirst`);
|
||||||
|
if (whoFirst) {
|
||||||
|
const disposition = this.disposition;
|
||||||
|
if (disposition === `unknown`) {
|
||||||
|
total += 0.25;
|
||||||
|
} else if (whoFirst !== disposition) {
|
||||||
|
total += 0.5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = this.disposition;
|
||||||
|
|
||||||
|
return `${path}:${disposition}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create the turn marker when the combatant is added if they're in
|
||||||
|
* the group whose turn it is.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_onCreate() {
|
||||||
|
this.token?._object?._refreshTurnMarker();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to remove the turn marker when the combatant is removed from combat
|
||||||
|
* if they had it visible so that it doesn't stick around infinitely.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_onDelete() {
|
||||||
|
this.token?._object?._refreshTurnMarker();
|
||||||
|
};
|
||||||
|
};
|
||||||
34
module/documents/token.mjs
Normal file
34
module/documents/token.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const { TokenTurnMarker } = foundry.canvas.placeables.tokens;
|
||||||
|
|
||||||
|
export class RipCryptToken extends Token {
|
||||||
|
/**
|
||||||
|
* Overridden using a slightly modified implementation in order to make it so
|
||||||
|
* that the turn marker shows up on tokens if they're in the same group as the
|
||||||
|
* currently active combatant
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
_refreshTurnMarker() {
|
||||||
|
// Should a Turn Marker be active?
|
||||||
|
const {turnMarker} = this.document;
|
||||||
|
const markersEnabled = CONFIG.Combat.settings.turnMarker.enabled
|
||||||
|
&& (turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED);
|
||||||
|
const combatant = game.combat?.active ? game.combat.combatant : null;
|
||||||
|
const isTurn = combatant && (combatant.groupKey === this.combatant?.groupKey);
|
||||||
|
const isDefeated = combatant && combatant.isDefeated;
|
||||||
|
const markerActive = markersEnabled && isTurn && !isDefeated;
|
||||||
|
|
||||||
|
// Activate a Turn Marker
|
||||||
|
if (markerActive) {
|
||||||
|
if (!this.turnMarker) {
|
||||||
|
this.turnMarker = this.addChildAt(new TokenTurnMarker(this), 0);
|
||||||
|
};
|
||||||
|
canvas.tokens.turnMarkers.add(this);
|
||||||
|
this.turnMarker.draw();
|
||||||
|
} else if (this.turnMarker) {
|
||||||
|
canvas.tokens.turnMarkers.delete(this);
|
||||||
|
this.turnMarker.destroy();
|
||||||
|
this.turnMarker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -11,12 +11,12 @@ export const gameTerms = Object.preventExtensions({
|
||||||
FLECT: `flect`,
|
FLECT: `flect`,
|
||||||
FRACT: `fract`,
|
FRACT: `fract`,
|
||||||
}),
|
}),
|
||||||
FatePath: [
|
FatePath: Object.freeze({
|
||||||
`North`,
|
NORTH: `North`,
|
||||||
`East`,
|
EAST: `East`,
|
||||||
`South`,
|
SOUTH: `South`,
|
||||||
`West`,
|
WEST: `West`,
|
||||||
],
|
}),
|
||||||
Access: [
|
Access: [
|
||||||
`Common`,
|
`Common`,
|
||||||
`Uncommon`,
|
`Uncommon`,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { CombinedHeroSheet } from "../Apps/ActorSheets/CombinedHeroSheet.mjs";
|
||||||
import { DelveTourApp } from "../Apps/DelveTourApp.mjs";
|
import { DelveTourApp } from "../Apps/DelveTourApp.mjs";
|
||||||
import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs";
|
import { HeroSkillsCardV1 } from "../Apps/ActorSheets/HeroSkillsCardV1.mjs";
|
||||||
import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs";
|
import { HeroSummaryCardV1 } from "../Apps/ActorSheets/HeroSummaryCardV1.mjs";
|
||||||
|
import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs";
|
||||||
|
|
||||||
// Data Models
|
// Data Models
|
||||||
import { AmmoData } from "../data/Item/Ammo.mjs";
|
import { AmmoData } from "../data/Item/Ammo.mjs";
|
||||||
|
|
@ -18,7 +19,10 @@ import { WeaponData } from "../data/Item/Weapon.mjs";
|
||||||
import { CryptDie } from "../dice/CryptDie.mjs";
|
import { CryptDie } from "../dice/CryptDie.mjs";
|
||||||
|
|
||||||
// Documents
|
// Documents
|
||||||
|
import { RipCryptCombat } from "../documents/combat.mjs";
|
||||||
|
import { RipCryptCombatant } from "../documents/combatant.mjs";
|
||||||
import { RipCryptItem } from "../documents/item.mjs";
|
import { RipCryptItem } from "../documents/item.mjs";
|
||||||
|
import { RipCryptToken } from "../documents/token.mjs";
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
import helpers from "../handlebarHelpers/_index.mjs";
|
import helpers from "../handlebarHelpers/_index.mjs";
|
||||||
|
|
@ -32,6 +36,7 @@ import { registerWorldSettings } from "../settings/worldSettings.mjs";
|
||||||
Hooks.once(`init`, () => {
|
Hooks.once(`init`, () => {
|
||||||
Logger.log(`Initializing`);
|
Logger.log(`Initializing`);
|
||||||
|
|
||||||
|
CONFIG.Combat.initiative.decimals = 2;
|
||||||
CONFIG.ui.crypt = DelveTourApp;
|
CONFIG.ui.crypt = DelveTourApp;
|
||||||
|
|
||||||
// #region Settings
|
// #region Settings
|
||||||
|
|
@ -53,6 +58,10 @@ Hooks.once(`init`, () => {
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Class Changes
|
// #region Class Changes
|
||||||
|
CONFIG.ui.combat = RipCryptCombatTracker;
|
||||||
|
CONFIG.Combat.documentClass = RipCryptCombat;
|
||||||
|
CONFIG.Combatant.documentClass = RipCryptCombatant;
|
||||||
|
CONFIG.Token.objectClass = RipCryptToken;
|
||||||
CONFIG.Item.documentClass = RipCryptItem;
|
CONFIG.Item.documentClass = RipCryptItem;
|
||||||
CONFIG.Dice.terms.d = CryptDie;
|
CONFIG.Dice.terms.d = CryptDie;
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
@ -94,6 +103,7 @@ Hooks.once(`init`, () => {
|
||||||
|
|
||||||
// #region Token Attrs
|
// #region Token Attrs
|
||||||
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
|
CONFIG.Actor.trackableAttributes.hero = HeroData.trackableAttributes;
|
||||||
|
// #endregion
|
||||||
|
|
||||||
registerCustomComponents();
|
registerCustomComponents();
|
||||||
Handlebars.registerHelper(helpers);
|
Handlebars.registerHelper(helpers);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { filePath } from "../consts.mjs";
|
||||||
import { Logger } from "../utils/Logger.mjs";
|
import { Logger } from "../utils/Logger.mjs";
|
||||||
|
|
||||||
Hooks.once(`ready`, () => {
|
Hooks.once(`ready`, () => {
|
||||||
|
|
@ -21,4 +22,15 @@ Hooks.once(`ready`, () => {
|
||||||
if (game.settings.get(`ripcrypt`, `showDelveTour`)) {
|
if (game.settings.get(`ripcrypt`, `showDelveTour`)) {
|
||||||
ui.crypt.render({ force: true });
|
ui.crypt.render({ force: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MARK: 1-time updates
|
||||||
|
if (!game.settings.get(`ripcrypt`, `firstLoadFinished`)) {
|
||||||
|
// Update the turnMarker to be the RipCrypt defaults
|
||||||
|
const combatConfig = game.settings.get(`core`, `combatTrackerConfig`);
|
||||||
|
combatConfig.turnMarker.src = filePath(`assets/turn-marker.png`);
|
||||||
|
combatConfig.turnMarker.animation = `spinPulse`;
|
||||||
|
game.settings.set(`core`, `combatTrackerConfig`, combatConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
game.settings.set(`ripcrypt`, `firstLoadFinished`, true);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,35 @@ export function registerMetaSettings() {
|
||||||
ui.crypt.render({ parts: [ `delveConditions` ]});
|
ui.crypt.render({ parts: [ `delveConditions` ]});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register(`ripcrypt`, `currentFate`, {
|
||||||
|
scope: `world`,
|
||||||
|
type: String,
|
||||||
|
config: false,
|
||||||
|
requiresReload: false,
|
||||||
|
onChange: async () => {
|
||||||
|
await ui.crypt.render({ parts: [ `fate` ] });
|
||||||
|
await game.combat.setupTurns();
|
||||||
|
await ui.combat.render({ parts: [ `tracker` ] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(`ripcrypt`, `whoFirst`, {
|
||||||
|
scope: `world`,
|
||||||
|
type: String,
|
||||||
|
config: false,
|
||||||
|
requiresReload: false,
|
||||||
|
initial: `friendly`,
|
||||||
|
onChange: async () => {
|
||||||
|
await game.combat.setupTurns();
|
||||||
|
await ui.combat.render({ parts: [ `tracker` ] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register(`ripcrypt`, `firstLoadFinished`, {
|
||||||
|
scope: `world`,
|
||||||
|
type: Boolean,
|
||||||
|
initial: false,
|
||||||
|
requiresReload: false,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const augmentedProps = new Set([
|
||||||
export const Logger = new Proxy(console, {
|
export const Logger = new Proxy(console, {
|
||||||
get(target, prop, _receiver) {
|
get(target, prop, _receiver) {
|
||||||
if (augmentedProps.has(prop)) {
|
if (augmentedProps.has(prop)) {
|
||||||
return (...args) => target[prop](game.system.id, `|`, ...args);
|
return target[prop].bind(target, game.system.id, `|`);
|
||||||
};
|
};
|
||||||
return target[prop];
|
return target[prop];
|
||||||
},
|
},
|
||||||
|
|
|
||||||
27
module/utils/distanceBetweenFates.mjs
Normal file
27
module/utils/distanceBetweenFates.mjs
Normal file
|
|
@ -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 && b === 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;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue