diff --git a/client/src/apiTypes.ts b/client/src/apiTypes.ts index 0345b5d54..21d4e05e2 100644 --- a/client/src/apiTypes.ts +++ b/client/src/apiTypes.ts @@ -856,7 +856,7 @@ export interface ToggleCompositeVariant { shape: GlobalId; variant: GlobalId; } -export interface TypeIdModel {} +export interface TypeIdModel { } export interface ApiLocation { id: number; name: string; @@ -867,6 +867,7 @@ export interface ApiOptionalLocationOptions { unit_size?: number | null; unit_size_unit?: string | null; use_grid?: boolean | null; + use_origin_marker?: boolean | null; full_fow?: boolean | null; fow_opacity?: number | null; fow_los?: boolean | null; @@ -891,6 +892,7 @@ export interface ApiLocationOptions { unit_size: number; unit_size_unit: string; use_grid: boolean; + use_origin_marker: boolean; full_fow: boolean; fow_opacity: number; fow_los: boolean; diff --git a/client/src/fa.ts b/client/src/fa.ts index 3be3155db..28a35acc4 100644 --- a/client/src/fa.ts +++ b/client/src/fa.ts @@ -123,6 +123,7 @@ export function loadFontAwesome(): void { faCopy, faCompass, faCommentDots, + faLocationDot, faCut, faDAndD, faDiceD20, diff --git a/client/src/game/api/events/location.ts b/client/src/game/api/events/location.ts index 77d7a19eb..0e848742b 100644 --- a/client/src/game/api/events/location.ts +++ b/client/src/game/api/events/location.ts @@ -60,6 +60,8 @@ function setLocationOptions(id: number | undefined, options: ApiOptionalLocation locationSettingsSystem.setUnitSize(options.unit_size ?? undefined, id, false); if (overwrite_all || options.unit_size_unit !== undefined) locationSettingsSystem.setUnitSizeUnit(options.unit_size_unit ?? undefined, id, false); + if (overwrite_all || options.use_origin_marker !== undefined) + locationSettingsSystem.setUseOriginMarker(options.use_origin_marker ?? undefined, id, false); if (overwrite_all || options.drop_ratio !== undefined) locationSettingsSystem.setDropRatio(options.drop_ratio ?? undefined, id, false); diff --git a/client/src/game/layers/variants/grid.ts b/client/src/game/layers/variants/grid.ts index 66bf72533..754e59bfe 100644 --- a/client/src/game/layers/variants/grid.ts +++ b/client/src/game/layers/variants/grid.ts @@ -1,6 +1,10 @@ +import { toGP } from "../../../core/geometry"; import { DEFAULT_HEX_RADIUS, DEFAULT_GRID_SIZE, SQRT3, GridType } from "../../../core/grid"; import type { IGridLayer } from "../../interfaces/layers/grid"; +import { FontAwesomeIcon } from "../../shapes/variants/fontAwesomeIcon"; +import type { SvgDisplayOverrides } from "../../shapes/variants/fontAwesomeIcon"; import { floorState } from "../../systems/floors/state"; +import { gameState } from "../../systems/game/state"; import { positionState } from "../../systems/position/state"; import { locationSettingsState } from "../../systems/settings/location/state"; import { playerSettingsState } from "../../systems/settings/players/state"; @@ -8,6 +12,12 @@ import { playerSettingsState } from "../../systems/settings/players/state"; import { Layer } from "./layer"; export class GridLayer extends Layer implements IGridLayer { + displayOverrides: SvgDisplayOverrides = { fill: "rgba(255,0,0,0.4)", stroke: "black", strokeWidth: "10" }; + originIcon: FontAwesomeIcon = new FontAwesomeIcon({ prefix: "fas", iconName: "location-dot" }, toGP(0, 0), 40, { + svgDisplayOverrides: this.displayOverrides, + }); + originIconSize = { width: 30, height: 40 }; + invalidate(): void { this.valid = false; } @@ -19,6 +29,17 @@ export class GridLayer extends Layer implements IGridLayer { draw(_doClear?: boolean): void { if (!this.valid) { + this.clear(); + if (locationSettingsState.raw.useOriginMarker.value && gameState.raw.isDm) { + const ctx = this.ctx; + const state = positionState.readonly; + console.log(state.zoom); + this.originIcon.draw(ctx, false, { + center: toGP(0, -20 / state.zoom), + width: this.originIconSize.width, + height: this.originIconSize.height, + }); + } if (locationSettingsState.raw.useGrid.value) { const activeFowFloor = floorState.currentFloor.value!.id; @@ -28,7 +49,6 @@ export class GridLayer extends Layer implements IGridLayer { this.canvas.style.display = "none"; const ctx = this.ctx; - this.clear(); ctx.beginPath(); const state = positionState.readonly; diff --git a/client/src/game/shapes/variants/fontAwesomeIcon.ts b/client/src/game/shapes/variants/fontAwesomeIcon.ts index b31f24cb6..b76cffbea 100644 --- a/client/src/game/shapes/variants/fontAwesomeIcon.ts +++ b/client/src/game/shapes/variants/fontAwesomeIcon.ts @@ -5,21 +5,33 @@ import type { GlobalId, LocalId } from "../../../core/id"; import type { IAsset } from "../../interfaces/shapes/asset"; import type { SHAPE_TYPE } from "../types"; +export type SvgDisplayOverrides = { + fill: string; + stroke: string; + strokeWidth: string; +}; + import { Asset } from "./asset"; const faBlobs = new Map(); -function getFaBlobUrl(iconDef: IconDefinition): string { - const name = `${iconDef.prefix}-${iconDef.iconName}`; +function getFaBlobUrl( + iconDef: IconDefinition, + displayOverrides: SvgDisplayOverrides = { fill: "white", stroke: "black", strokeWidth: "20" }, +): string { + const name = `${iconDef.prefix}-${iconDef.iconName}-${displayOverrides.fill}-${displayOverrides.stroke}-${displayOverrides.strokeWidth}`; if (faBlobs.has(name)) return faBlobs.get(name)!; const svg = icon(iconDef).node[0] as SVGElement; + svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); const path = svg.firstChild as SVGTextPathElement; - path.setAttribute("fill", "white"); - path.setAttribute("stroke", "black"); - path.setAttribute("stroke-width", "20"); + path.setAttribute("fill", displayOverrides.fill); + path.setAttribute("stroke", displayOverrides.stroke); + path.setAttribute("stroke-width", displayOverrides.strokeWidth); + console.log(svg.outerHTML); const blob = new Blob([svg.outerHTML], { type: "image/svg+xml;charset=utf-8" }); const url = URL.createObjectURL(blob); + console.log(url); faBlobs.set(name, url); return url; } @@ -31,13 +43,18 @@ export class FontAwesomeIcon extends Asset implements IAsset { icon: IconLookup, topleft: GlobalPoint, w: number, - options?: { id?: LocalId; uuid?: GlobalId; isSnappable?: boolean; parentId?: LocalId }, + options?: { + id?: LocalId; + uuid?: GlobalId; + isSnappable?: boolean; + parentId?: LocalId; + svgDisplayOverrides?: SvgDisplayOverrides; + }, ) { const image = new Image(); const iconDef = findIconDefinition(icon); const h = w * (iconDef.icon[1] / iconDef.icon[0]); - image.src = getFaBlobUrl(iconDef); - + image.src = getFaBlobUrl(iconDef, options?.svgDisplayOverrides); super(image, topleft, w, h, { isSnappable: false, ...options }); } } diff --git a/client/src/game/systems/settings/location/index.ts b/client/src/game/systems/settings/location/index.ts index a5bfa9f80..df8872951 100644 --- a/client/src/game/systems/settings/location/index.ts +++ b/client/src/game/systems/settings/location/index.ts @@ -26,6 +26,7 @@ class LocationSettingsSystem implements System { gridType: this.setGridType.bind(this), unitSize: this.setUnitSize.bind(this), unitSizeUnit: this.setUnitSizeUnit.bind(this), + useOriginMarker: this.setUseOriginMarker.bind(this), dropRatio: this.setDropRatio.bind(this), fullFow: this.setFullFow.bind(this), fowLos: this.setFowLos.bind(this), @@ -98,8 +99,6 @@ class LocationSettingsSystem implements System { for (const floor of floorState.raw.floors) { const gridLayer = floorSystem.getGridLayer(floor)!; - if ($.useGrid.value) gridLayer.canvas.style.display = "block"; - else gridLayer.canvas.style.display = "none"; gridLayer.invalidate(); } @@ -138,6 +137,14 @@ class LocationSettingsSystem implements System { if (sync) sendLocationOption("unit_size_unit", unitSizeUnit, location); } + setUseOriginMarker(useOriginMarker: boolean | undefined, location: number | undefined, sync: boolean): void { + if (!this.setValue($.useOriginMarker, useOriginMarker, location)) return; + + floorSystem.invalidateAllFloors(); + + if (sync) sendLocationOption("use_origin_marker", useOriginMarker, location); + } + setDropRatio(dropRatio: number | undefined, location: number | undefined, sync: boolean): void { if (!this.setValue($.dropRatio, dropRatio, location)) return; diff --git a/client/src/game/systems/settings/location/models.ts b/client/src/game/systems/settings/location/models.ts index 31dd8e25d..c989ea317 100644 --- a/client/src/game/systems/settings/location/models.ts +++ b/client/src/game/systems/settings/location/models.ts @@ -17,6 +17,7 @@ export interface LocationOptions { gridType: GridType; unitSize: number; unitSizeUnit: string; + useOriginMarker: boolean; fullFow: boolean; fowOpacity: number; fowLos: boolean; diff --git a/client/src/game/systems/settings/location/state.ts b/client/src/game/systems/settings/location/state.ts index fe38d8bc9..92660449c 100644 --- a/client/src/game/systems/settings/location/state.ts +++ b/client/src/game/systems/settings/location/state.ts @@ -21,6 +21,7 @@ function getInitState(): _S { useGrid: init(false), unitSize: init(5), // gridSize computed is not triggering on setDefault for some reason unitSizeUnit: init("ft"), + useOriginMarker: init(false), visionMaxRange: init(0), visionMinRange: init(0), visionMode: init(""), diff --git a/client/src/game/ui/settings/location/GridSettings.vue b/client/src/game/ui/settings/location/GridSettings.vue index b6a94c15a..cb5d1cb06 100644 --- a/client/src/game/ui/settings/location/GridSettings.vue +++ b/client/src/game/ui/settings/location/GridSettings.vue @@ -18,6 +18,7 @@ const gridType = useLocationSettings("gridType", location); const unitSize = useLocationSettings("unitSize", location); const unitSizeUnit = useLocationSettings("unitSizeUnit", location); +const useOriginMarker = useLocationSettings("useOriginMarker", location); const dropRatio = useLocationSettings("dropRatio", location); @@ -69,6 +70,14 @@ const dropRatio = useLocationSettings("dropRatio", location); + + +
+ +
+