-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathminimap.ts
167 lines (147 loc) · 6.11 KB
/
minimap.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/// <reference path="interfaces/index.ts" />
/// <reference path="rest/index.ts" />
/// <reference path="map-engine/types.ts" />
/**
* Details for the "ps2map_minimapjump" custom event.
*/
interface MinimapJumpEvent {
target: Point,
}
/**
* Controller for on-screen minimap.
*/
class Minimap {
/**
* DOM element containing the minimap.
* Its height will be set equal to its width.
*/
readonly element: HTMLDivElement;
/** Minimap view box frame element. */
private readonly _viewBoxElement: HTMLDivElement;
/** Size of the map used. Controls view box interpretation. */
private _mapSize: number = 0;
private _baseOutlineSvg: SVGElement | undefined = undefined;
/** CSS size of the minimap. */
private _cssSize: number;
private _polygons: Map<number, SVGPolygonElement> = new Map();
constructor(element: HTMLDivElement) {
// Set up DOM containers
this.element = element;
this.element.classList.add("ps2map__minimap");
this._cssSize = this.element.clientWidth;
this.element.style.height = `${this._cssSize}px`;
this.element.style.fillOpacity = "0.5";
this._viewBoxElement = document.createElement("div");
this._viewBoxElement.classList.add("ps2map__minimap__viewbox");
this.element.appendChild(this._viewBoxElement);
// Attach event listener
this.element.addEventListener(
"mousedown", this._jumpToPosition.bind(this), { passive: true });
const obj = new ResizeObserver(() => {
this._cssSize = this.element.clientWidth;
this.element.style.height = `${this._cssSize}px`;
});
obj.observe(this.element);
}
/** Update the viewBox displayed on the minimap. */
updateViewbox(viewBox: ViewBox): void {
const mapSize = this._mapSize;
// Convert map-coordinate viewBox to percentages
const relViewBox: ViewBox = {
top: (viewBox.top + mapSize * 0.5) / mapSize,
left: (viewBox.left + mapSize * 0.5) / mapSize,
bottom: (viewBox.bottom + mapSize * 0.5) / mapSize,
right: (viewBox.right + mapSize * 0.5) / mapSize,
};
const relHeight = relViewBox.top - relViewBox.bottom;
const relWidth = relViewBox.right - relViewBox.left;
// Project the relative percentages onto the minimap
Object.assign(this._viewBoxElement.style, {
height: `${this._cssSize * relHeight}px`,
width: `${this._cssSize * relWidth}px`,
left: `${this._cssSize * relViewBox.left}px`,
bottom: `${this._cssSize * relViewBox.bottom}px`,
});
}
updateBaseOwnership(map: Map<number, number>): void {
// The CSS variables are not available
map.forEach((factionId, baseId) => {
const polygon = this._polygons.get(baseId);
if (polygon)
polygon.style.fill =
`var(${this._factionIdToCssVar(factionId)})`;
});
}
/**
* Return the CSS variable name for the given faction.
*
* @param factionId - The faction ID to get the colour for.
* @returns The CSS variable name for the faction's colour.
*/
private _factionIdToCssVar(factionId: number): string {
const code = GameData.getInstance().getFaction(factionId).code;
return `--ps2map__faction-${code}-colour`;
}
async switchContinent(continent: Continent): Promise<void> {
// Load the base outline SVG
const svg = await fetchContinentOutlines(continent.code);
this._mapSize = continent.map_size;
// Set minimap background image
this.element.style.backgroundImage =
`url(${UrlGen.mapBackground(continent.code)})`;
// Delete the existing hex layer, if any
if (this._baseOutlineSvg)
this.element.removeChild(this._baseOutlineSvg);
// Delete any existing polygons
this._polygons = new Map();
// Add the new hex layer
svg.classList.add("ps2map__minimap__hexes");
this._baseOutlineSvg = svg;
this.element.appendChild(this._baseOutlineSvg);
// Add the polygons to the local cache
svg.querySelectorAll("polygon").forEach(poly => {
this._polygons.set(parseInt(poly.id, 10), poly);
// Update polygon IDs to be unique
poly.id = this._polygonIdFromBaseId(poly.id);
});
}
private _buildMinimapJumpEvent(target: Point): CustomEvent<MinimapJumpEvent> {
return new CustomEvent("ps2map_minimapjump", {
detail: { target }, bubbles: true, cancelable: true,
});
}
/**
* Event callback for clicking on the minimap.
* @param evtDown Position the mouse was clicked at
*/
private _jumpToPosition(evtDown: MouseEvent): void {
if (this._mapSize === 0 || evtDown.button !== 0)
return;
// Continuous "mousemove" callback
const drag = rafDebounce((evtDrag: MouseEvent) => {
// Get relative cursor position
const rect = this.element.getBoundingClientRect();
const relX = (evtDrag.clientX - rect.left) / (rect.width);
const relY = (evtDrag.clientY - rect.top) / (rect.height);
// Calculate target cursor position
const target: Point = {
x: Math.round(relX * this._mapSize) + this._mapSize * -0.5,
y: Math.round((1 - relY) * this._mapSize) + this._mapSize * -0.5,
};
this.element.dispatchEvent(this._buildMinimapJumpEvent(target));
});
// Global "mouseup" callback
const up = () => {
this.element.removeEventListener("mousemove", drag);
document.removeEventListener("mouseup", up);
};
// Add listeners
document.addEventListener("mouseup", up);
this.element.addEventListener("mousemove", drag, { passive: true });
// Manually invoke the "drag" callback once to handle single click pans
drag(evtDown);
}
private _polygonIdFromBaseId(baseId: number | string): string {
return `minimap-baseId-${baseId}`;
}
}