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(); }); }