feat: add color-coded movement ruler for tactical gameplay

This commit is contained in:
Alexander Minges 2025-07-13 16:12:04 +02:00
parent 9e4dcee3c3
commit 7faadf6583
Signed by: Athemis
GPG key ID: 31FBDEF92DDB162B
8 changed files with 145 additions and 1 deletions

View file

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2025 Alexander Minges
*
* SPDX-License-Identifier: MIT
*/
/* Token Ruler Waypoint Styling with Color-Coded Movement Ranges */
:root {
--ds4-movement-normal: inherit;
--ds4-movement-dash: #ffcc00;
--ds4-movement-impossible: #e83031;
}
.system-ds4 .waypoint-label {
.distance {
&.move-range {
color: var(--ds4-movement-normal);
}
&.dash-range {
color: var(--ds4-movement-dash);
font-weight: bold;
}
&.out-of-range {
color: var(--ds4-movement-impossible);
font-weight: bold;
}
}
.delta {
opacity: 0.8;
font-size: 0.9em;
}
.elevation {
font-style: italic;
opacity: 0.9;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--ds4-movement-dash: #ffd700;
--ds4-movement-impossible: #ff4444;
}
}

View file

@ -19,6 +19,7 @@
@import "components/shared/embedded_document_list";
@import "components/shared/form_group";
@import "components/shared/rollable_image";
@import "components/shared/ruler";
@import "components/shared/sheet_body";
@import "components/shared/sheet_form";
@import "components/shared/sheet_tab_nav";

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2025 Alexander Minges
//
// SPDX-License-Identifier: MIT
/**
* DS4 Token Ruler implementation with color-coded movement ranges
* Based on actor movement combat value
*/
export class DS4TokenRuler extends foundry.canvas.placeables.tokens.TokenRuler {
static WAYPOINT_LABEL_TEMPLATE = "systems/ds4/templates/partials/waypoint-label.hbs";
/**
* Enhance waypoint label context with movement range information
* @param {object} waypoint - The waypoint data
* @param {object} state - The current ruler state
* @returns {object} Enhanced context with range class
*/
_getWaypointLabelContext(waypoint, state) {
const context = super._getWaypointLabelContext(waypoint, state);
// Only apply movement coloring for distance measurements in meters
if (context?.cost?.units === "m" || context?.distance?.units === "m") {
const movement = this.token?.actor?.system?.combatValues?.movement?.total;
if (movement) {
const total = Number(context.cost?.total || context.distance?.total || 0);
// DS4 movement rules:
// - Normal movement: up to movement value
// - Dash: up to 2x movement value (requires action)
// - Beyond 2x: impossible in single turn
if (total > 2 * movement) {
context.rangeClass = "out-of-range";
} else if (total <= movement) {
context.rangeClass = "move-range";
} else {
context.rangeClass = "dash-range";
}
}
}
return context;
}
}

View file

@ -492,7 +492,7 @@ export class DS4Actor extends Actor {
}),
ok: {
label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (_event, button, _dialog) => {
callback: (_event, button) => {
const selectedAttribute = button.form.elements[attributeIdentifier].value;
if (!isAttribute(selectedAttribute)) {
throw new Error(

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
// SPDX-FileCopyrightText: 2025 Alexander Minges
//
// SPDX-License-Identifier: MIT
@ -55,6 +56,7 @@ export async function registerHandlebarsPartials() {
"systems/ds4/templates/sheets/shared/components/add-button.hbs",
"systems/ds4/templates/sheets/shared/components/control-button-group.hbs",
"systems/ds4/templates/sheets/shared/components/rollable-image.hbs",
"systems/ds4/templates/partials/waypoint-label.hbs",
];
await foundry.applications.handlebars.loadTemplates(templatePaths);
}

View file

@ -9,6 +9,8 @@ import { registerForReadyHook } from "./ready";
import { registerForRenderHooks } from "./render";
import { registerForSetupHook } from "./setup";
export function registerForHooks(): void {
registerForHotbarDropHook();
registerForPreCreateItemHook();

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
// SPDX-FileCopyrightText: 2025 Alexander Minges
//
// SPDX-License-Identifier: MIT
@ -8,6 +9,7 @@ import { DS4ActiveEffectConfig } from "../apps/active-effect-config";
import { DS4CharacterActorSheet } from "../apps/actor/character-sheet";
import { DS4CreatureActorSheet } from "../apps/actor/creature-sheet";
import { DS4ItemSheet } from "../apps/item-sheet";
import { DS4TokenRuler } from "../apps/ruler/token-ruler";
import { DS4 } from "../config";
import { DS4Check } from "../dice/check";
import { createCheckRoll } from "../dice/check-factory";
@ -51,6 +53,8 @@ async function init() {
CONFIG.ChatMessage.documentClass = DS4ChatMessage;
CONFIG.Token.documentClass = DS4TokenDocument;
CONFIG.Token.rulerClass = DS4TokenRuler;
CONFIG.ActiveEffect.legacyTransferral = false;
CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;

View file

@ -0,0 +1,39 @@
{{!--
SPDX-FileCopyrightText: 2025 Alexander Minges
SPDX-License-Identifier: MIT
--}}
{{!--
!-- Waypoint label template with color-coded movement ranges
!-- Based on DS4 movement combat values
--}}
<div class="waypoint-label {{rangeClass}}">
{{#if action.icon}}
<i class="{{action.icon}}"></i>
{{else if action.label}}
{{localize action.label}}
{{/if}}
{{#if cost}}
<span class="distance {{rangeClass}}">{{cost.total}} {{cost.units}}</span>
{{#if cost.delta}}
<span class="delta">({{cost.delta}})</span>
{{/if}}
{{else}}
<span class="distance {{rangeClass}}">{{distance.total}} {{units}}</span>
{{#if distance.delta}}
<span class="delta">({{distance.delta}})</span>
{{/if}}
{{/if}}
{{#if (and elevation (not elevation.hidden))}}
<span class="elevation">
{{elevation.total}} {{units}}
{{#if elevation.delta}}
({{elevation.delta}})
{{/if}}
</span>
{{/if}}
{{#if secret}}
<i class="fas fa-eye-slash"></i>
{{/if}}
</div>