diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py
index 3bb471c9..0a77094f 100644
--- a/qt_ui/widgets/map/mapmodel.py
+++ b/qt_ui/widgets/map/mapmodel.py
@@ -154,6 +154,8 @@ class GroundObjectJs(QObject):
positionChanged = Signal()
samThreatRangesChanged = Signal()
samDetectionRangesChanged = Signal()
+ categoryChanged = Signal()
+ deadChanged = Signal()
def __init__(self, tgo: TheaterGroundObject, game: Game) -> None:
super().__init__()
@@ -189,6 +191,10 @@ class GroundObjectJs(QObject):
def name(self) -> str:
return self.tgo.name
+ @Property(str, notify=categoryChanged)
+ def category(self) -> str:
+ return self.tgo.category
+
def make_unit_name(self, unit: Unit, dead: bool) -> str:
dead_label = " [DEAD]" if dead else ""
unit_display_name = unit.type
@@ -225,6 +231,12 @@ class GroundObjectJs(QObject):
ll = self.theater.point_to_ll(self.tgo.position)
return [ll.latitude, ll.longitude]
+ @Property(bool, notify=deadChanged)
+ def dead(self) -> bool:
+ if not self.tgo.groups:
+ return all(b.is_dead for b in self.buildings)
+ return not any(g.units for g in self.tgo.groups)
+
@Property(list, notify=samThreatRangesChanged)
def samThreatRanges(self) -> List[float]:
if not self.tgo.might_have_aa:
diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js
index 4a00a964..857d98d9 100644
--- a/resources/ui/map/map.js
+++ b/resources/ui/map/map.js
@@ -18,6 +18,81 @@ const Colors = Object.freeze({
Highlight: "#ffff00",
});
+const Categories = Object.freeze([
+ "aa",
+ "allycamp",
+ "ammo",
+ "armor",
+ "coastal",
+ "comms",
+ "derrick",
+ "factory",
+ "farp",
+ "fob",
+ "fuel",
+ "ewr",
+ "missile",
+ "oil",
+ "ship",
+ "power",
+ "village",
+ "ware",
+ "ww2bunker",
+]);
+
+function makeTgoIcons(player) {
+ const icons = {};
+ const playerSuffix = player ? "_blue" : "";
+ Categories.forEach((category) => {
+ const iconName = `${category}${playerSuffix}`;
+ icons[category] = new L.Icon({
+ iconUrl: `../ground_assets/${iconName}.png`,
+ });
+ if (!icons[category]) {
+ console.log(`Failed to load icon for ${iconName}`);
+ }
+ });
+
+ return Object.freeze(icons);
+}
+
+function makeIcons(player) {
+ const color = player ? "blue" : "red";
+ const playerSuffix = player ? "_blue" : "";
+ const icons = {
+ ControlPoint: new L.Icon({
+ iconUrl: `https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-${color}.png`,
+ shadowUrl:
+ "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png",
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ shadowSize: [41, 41],
+ }),
+
+ Objectives: makeTgoIcons(player),
+
+ Destroyed: new L.Icon({
+ iconUrl: "../ground_assets/destroyed.png",
+ }),
+
+ NoThreat: new L.icon({
+ iconUrl: `../ground_assets/nothreat${playerSuffix}.png`,
+ }),
+ };
+
+ return Object.freeze(icons);
+}
+
+const Icons = Object.freeze({
+ Friendly: makeIcons(true),
+ Enemy: makeIcons(false),
+
+ for(player) {
+ return player ? this.Friendly : this.Enemy;
+ },
+});
+
function metersToNauticalMiles(meters) {
return meters * 0.000539957;
}
@@ -91,28 +166,6 @@ L.control
)
.addTo(map);
-const friendlyCpIcon = new L.Icon({
- iconUrl:
- "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png",
- shadowUrl:
- "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png",
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- shadowSize: [41, 41],
-});
-
-const enemyCpIcon = new L.Icon({
- iconUrl:
- "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png",
- shadowUrl:
- "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png",
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- shadowSize: [41, 41],
-});
-
let game;
new QWebChannel(qt.webChannelTransport, function (channel) {
game = channel.objects.game;
@@ -130,14 +183,6 @@ function recenterMap(center) {
map.setView(center, 8, { animate: true, duration: 1 });
}
-function iconFor(player) {
- if (player) {
- return friendlyCpIcon;
- } else {
- return enemyCpIcon;
- }
-}
-
const SHOW_BASE_NAME_AT_ZOOM = 8;
class ControlPoint {
@@ -156,6 +201,10 @@ class ControlPoint {
this.cp.destinationChanged.connect(() => this.onDestinationChanged());
}
+ icon() {
+ return Icons.for(this.cp.blue).ControlPoint;
+ }
+
hasDestination() {
return this.cp.destination.length > 0;
}
@@ -256,7 +305,7 @@ class ControlPoint {
// markers are helpful so we want to keep them, but make sure the CP is
// always the clickable thing.
return L.marker(location, {
- icon: iconFor(this.cp.blue),
+ icon: this.icon(),
zIndexOffset: 1000,
draggable: this.cp.mobile,
autoPan: true,
@@ -280,7 +329,7 @@ class ControlPoint {
makeSecondaryMarker() {
return L.marker(this.cp.position, {
- icon: iconFor(this.cp.blue),
+ icon: this.icon(),
zIndexOffset: 1000,
});
}
@@ -327,31 +376,67 @@ function drawControlPoints() {
});
}
-function drawSamThreatsAt(tgo) {
- const detectionLayer = tgo.blue
- ? blueSamDetectionLayer
- : redSamDetectionLayer;
- const threatLayer = tgo.blue ? blueSamThreatLayer : redSamThreatLayer;
- const threatColor = tgo.blue ? Colors.Blue : Colors.Red;
- const detectionColor = tgo.blue ? "#bb89ff" : "#eee17b";
+class TheaterGroundObject {
+ constructor(tgo) {
+ this.tgo = tgo;
+ }
- tgo.samDetectionRanges.forEach((range) => {
- L.circle(tgo.position, {
- radius: range,
- color: detectionColor,
- fill: false,
- weight: 1,
- }).addTo(detectionLayer);
- });
+ samIsThreat() {
+ for (const range of this.tgo.samThreatRanges) {
+ if (range > 0) {
+ return true;
+ }
+ }
- tgo.samThreatRanges.forEach((range) => {
- L.circle(tgo.position, {
- radius: range,
- color: threatColor,
- fill: false,
- weight: 2,
- }).addTo(threatLayer);
- });
+ return false;
+ }
+
+ icon() {
+ const iconSet = Icons.for(this.tgo.blue);
+ if (this.tgo.category == "aa" && !this.samIsThreat()) {
+ return iconSet.NoThreat;
+ } else if (this.tgo.dead) {
+ return iconSet.Destroyed;
+ } else {
+ return iconSet.Objectives[this.tgo.category];
+ }
+ }
+
+ drawSamThreats() {
+ const detectionLayer = this.tgo.blue
+ ? blueSamDetectionLayer
+ : redSamDetectionLayer;
+ const threatLayer = this.tgo.blue ? blueSamThreatLayer : redSamThreatLayer;
+ const threatColor = this.tgo.blue ? Colors.Blue : Colors.Red;
+ const detectionColor = this.tgo.blue ? "#bb89ff" : "#eee17b";
+
+ this.tgo.samDetectionRanges.forEach((range) => {
+ L.circle(this.tgo.position, {
+ radius: range,
+ color: detectionColor,
+ fill: false,
+ weight: 1,
+ }).addTo(detectionLayer);
+ });
+
+ this.tgo.samThreatRanges.forEach((range) => {
+ L.circle(this.tgo.position, {
+ radius: range,
+ color: threatColor,
+ fill: false,
+ weight: 2,
+ }).addTo(threatLayer);
+ });
+ }
+
+ draw() {
+ L.marker(this.tgo.position, { icon: this.icon() })
+ .bindTooltip(`${this.tgo.name}
${this.tgo.units.join("
")}`)
+ .on("click", () => this.tgo.showInfoDialog())
+ .on("contextmenu", () => this.tgo.showPackageDialog())
+ .addTo(groundObjectsLayer);
+ this.drawSamThreats();
+ }
}
function drawGroundObjects() {
@@ -361,16 +446,7 @@ function drawGroundObjects() {
blueSamThreatLayer.clearLayers();
redSamThreatLayer.clearLayers();
game.groundObjects.forEach((tgo) => {
- L.marker(tgo.position, { icon: iconFor(tgo.blue) })
- .bindTooltip(`${tgo.name}
${tgo.units.join("
")}`)
- .on("click", function () {
- tgo.showInfoDialog();
- })
- .on("contextmenu", function () {
- tgo.showPackageDialog();
- })
- .addTo(groundObjectsLayer);
- drawSamThreatsAt(tgo);
+ new TheaterGroundObject(tgo).draw();
});
}