Skip to content

Commit

Permalink
Merge pull request #369 from le-jeu/blockers_layer
Browse files Browse the repository at this point in the history
Blockers layer
  • Loading branch information
le-jeu authored Sep 23, 2023
2 parents 65f2b3a + f2f602d commit bbdfeb4
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 153 deletions.
227 changes: 113 additions & 114 deletions src/code/crosslinks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WasabeePortal, WasabeeMarker, WasabeeBlocker } from "./model";
import { WasabeePortal, WasabeeBlocker } from "./model";
import { getSelectedOperation } from "./selectedOp";

import * as PortalUI from "./ui/portal";
Expand All @@ -7,85 +7,64 @@ import type { IITC } from "../types/iitc";
import type WasabeeLink from "./model/link";

import { greatCircleArcIntersectByLatLngs } from "./geo";
import { WLBlockerLayer } from "./map/blocker";

const cache = new Set<string>();

function testPolyLine(
wasabeeLink: WasabeeLink,
realLink: IITC.Link,
operation: WasabeeOp
) {
if (
greatCircleArcIntersectByLatLngs(
realLink.getLatLngs(),
wasabeeLink.getLatLngs(operation)
)
) {
if (!operation.markers || operation.markers.length == 0) {
return true;
}

for (const marker of operation.markers) {
if (
marker.type == WasabeeMarker.constants.MARKER_TYPE_DESTROY ||
marker.type == WasabeeMarker.constants.MARKER_TYPE_VIRUS ||
marker.type == WasabeeMarker.constants.MARKER_TYPE_DECAY
) {
if (
marker.portalId == realLink.options.data.dGuid ||
marker.portalId == realLink.options.data.oGuid
) {
return false;
}
}
}
return true;
}
return false;
return greatCircleArcIntersectByLatLngs(
realLink.getLatLngs(),
wasabeeLink.getLatLngs(operation)
);
}

function showCrossLink(link: IITC.Link) {
// this should be in static.js or skin
const blocked = L.geodesicPolyline(link.getLatLngs(), {
color: "#d22",
opacity: 0.7,
weight: 5,
interactive: false,
dashArray: [8, 8],
guid: link.options.guid,
});

blocked.addTo(window.plugin.wasabee.crossLinkLayers);
window.plugin.wasabee._crosslinkCache.set(link.options.guid, blocked);
async function addBlockerFromIITC(link: IITC.Link, operation: WasabeeOp) {
let fromPortal =
operation.getPortal(link.options.data.oGuid) ||
PortalUI.get(link.options.data.oGuid);
if (!fromPortal)
fromPortal = WasabeePortal.fake(
(link.options.data.oLatE6 / 1e6).toFixed(6),
(link.options.data.oLngE6 / 1e6).toFixed(6),
link.options.data.oGuid
);
let toPortal =
operation.getPortal(link.options.data.dGuid) ||
PortalUI.get(link.options.data.dGuid);
if (!toPortal)
toPortal = WasabeePortal.fake(
(link.options.data.dLatE6 / 1e6).toFixed(6),
(link.options.data.dLngE6 / 1e6).toFixed(6),
link.options.data.dGuid
);
window.plugin.wasabee.crossLinkLayers.addBlocker(fromPortal, toPortal);
await WasabeeBlocker.addBlocker(
operation,
fromPortal,
toPortal,
link.options.data.team as WasabeeBlocker["team"]
);
}

function testLink(link: IITC.Link, operation: WasabeeOp) {
async function testLink(link: IITC.Link, operation: WasabeeOp) {
// if the crosslink already exists, do not recheck
if (window.plugin.wasabee._crosslinkCache.has(link.options.guid)) {
if (cache.has(link.options.data.oGuid + link.options.data.dGuid)) {
return;
}
cache.add(link.options.data.oGuid + link.options.data.dGuid);

let cross = false;
for (const drawnLink of operation.links) {
if (testPolyLine(drawnLink, link, operation)) {
showCrossLink(link);
let fromPortal =
operation.getPortal(link.options.data.oGuid) ||
PortalUI.get(link.options.data.oGuid);
if (!fromPortal)
fromPortal = WasabeePortal.fake(
(link.options.data.oLatE6 / 1e6).toFixed(6),
(link.options.data.oLngE6 / 1e6).toFixed(6),
link.options.data.oGuid
);
let toPortal =
operation.getPortal(link.options.data.dGuid) ||
PortalUI.get(link.options.data.dGuid);
if (!toPortal)
toPortal = WasabeePortal.fake(
(link.options.data.dLatE6 / 1e6).toFixed(6),
(link.options.data.dLngE6 / 1e6).toFixed(6),
link.options.data.dGuid
);
WasabeeBlocker.addBlocker(operation, fromPortal, toPortal);
break;
if (!cross) {
await addBlockerFromIITC(link, operation);
cross = true;
}
drawnLink.blocked = true;
}
}
}
Expand All @@ -105,88 +84,108 @@ export function testSelfBlock(incoming: WasabeeLink, operation: WasabeeOp) {
return false;
}

// lets see if using a generator makes the GUI more responsive on large ops
// -- yeild doesn't seem to release the main thread, maybe we need to yeild a
// Promise.resolve(window.links[g]) and await it in the for loop?
function* realLinks() {
const guids = Object.getOwnPropertyNames(window.links);
// it is possible that the link was purged while we were yielded
// checking here should reduce the workload while scrolling/zooming
for (const g of guids) {
if (window.links[g] != null) yield window.links[g];
/** Test a Wasabee link against known blockers */
export function testBlocked(
incoming: WasabeeLink,
operation: WasabeeOp,
blockers: WasabeeBlocker[]
) {
for (const b of blockers) {
if (
greatCircleArcIntersectByLatLngs(incoming.getLatLngs(operation), [
b.fromPortal.latLng,
b.toPortal.latLng,
])
) {
return true;
}
}
return false;
}

/** Test a known blocker against all Wasabee links */
export function testBlocker(operation: WasabeeOp, blocker: WasabeeBlocker) {
for (const l of operation.links) {
if (
greatCircleArcIntersectByLatLngs(l.getLatLngs(operation), [
blocker.fromPortal.latLng,
blocker.toPortal.latLng,
])
) {
return true;
}
}
return false;
}

export function checkAllLinks() {
export async function checkAllLinks() {
if (window.isLayerGroupDisplayed("Wasabee Cross Links") === false) return;

const operation = getSelectedOperation();

window.plugin.wasabee.crossLinkLayers.clearLayers();
window.plugin.wasabee._crosslinkCache.clear();

if (!operation.links || operation.links.length == 0) return;
cache.clear();

const linkGenerator = realLinks();
for (const link of linkGenerator) {
testLink(link, operation);
if (!operation.links || operation.links.length == 0) {
WasabeeBlocker.removeBlockers(operation.ID);
window.map.fire("wasabee:crosslinks:done");
return;
}

// re-test known data (link/link, link/blocker)
const blockers = await WasabeeBlocker.getAll(operation);
for (const l of operation.links) {
if (testSelfBlock(l, operation)) {
const blocked = L.geodesicPolyline(
l.getLatLngs(operation),
window.plugin.wasabee.skin.selfBlockStyle
);
blocked.options.interactive = false;
blocked.addTo(window.plugin.wasabee.crossLinkLayers);
l.blocked = testBlocked(l, operation, blockers);
l.selfBlocked = testSelfBlock(l, operation);
}
for (const b of blockers) {
cache.add(b.from + b.to);
if (!testBlocker(operation, b)) {
WasabeeBlocker.removeBlocker(operation, b.from, b.to);
}
}
window.map.fire("wasabee:crosslinks:done");
}

function onLinkAdded(data: EventLinkAdded) {
testLink(data.link, getSelectedOperation());
}
const markers: PortalID[] = [];
for (const marker of operation.markers) {
if (marker.isDestructMarker()) {
markers.push(marker.portalId);
}
}

const links: IITC.Link[] = [];
for (const guid in window.links) {
const l = window.links[guid];
if (
l &&
!markers.includes(l.options.data.oGuid) &&
!markers.includes(l.options.data.dGuid)
)
links.push(l);
}

function onMapDataRefreshStart() {
window.removeHook("linkAdded", onLinkAdded);
// test all intel links
for (const link of links) {
await testLink(link, operation);
}
window.map.fire("wasabee:crosslinks:done");
}

function onMapDataRefreshEnd() {
if (window.isLayerGroupDisplayed("Wasabee Cross Links") === false) return;
window.plugin.wasabee.crossLinkLayers.bringToFront();

checkAllLinks();
window.addHook("linkAdded", onLinkAdded);
}

export function initCrossLinks() {
window.map.on("wasabee:crosslinks", checkAllLinks);
window.plugin.wasabee.crossLinkLayers = new L.FeatureGroup();
window.plugin.wasabee._crosslinkCache = new Map();
window.plugin.wasabee.crossLinkLayers = new WLBlockerLayer();
window.addLayerGroup(
"Wasabee Cross Links",
window.plugin.wasabee.crossLinkLayers,
true
);

// rerun crosslinks on re-enable
window.map.on("layeradd", (obj) => {
if (obj.layer === window.plugin.wasabee.crossLinkLayers) {
window.plugin.wasabee._crosslinkCache = new Map();
checkAllLinks();
}
});

// clear crosslinks on disable
window.map.on("layerremove", (obj) => {
if (obj.layer === window.plugin.wasabee.crossLinkLayers) {
window.plugin.wasabee.crossLinkLayers.clearLayers();
delete window.plugin.wasabee._crosslinkCache;
}
});
window.plugin.wasabee.crossLinkLayers.on("add", checkAllLinks);

window.addHook("mapDataRefreshStart", onMapDataRefreshStart);
window.addHook("mapDataRefreshEnd", onMapDataRefreshEnd);
}
6 changes: 6 additions & 0 deletions src/code/css/wasabee.css
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@
.wasabee-dialog-checklist .wasabee-table .order input {
width: 4rem;
}

.wasabee-dialog-checklist .wasabee-table tr.blocked td,
.wasabee-dialog-checklist .wasabee-table tr.self-blocked td {
background-color: #a007;
}

.wasabee-dialog-perms .add-perm {
display: grid;
grid-template-columns: repeat(4, auto);
Expand Down
40 changes: 34 additions & 6 deletions src/code/dialogs/blockersList.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import { WasabeeBlocker } from "../model";

import * as PortalUI from "../ui/portal";
import statics from "../static";
import { appendFAIcon } from "../auxiliar";

function getFactionCSS(team) {
return team === "E" ? "enl" : team === "R" ? "res" : "mac";
}

const BlockerList = WDialog.extend({
statics: {
Expand Down Expand Up @@ -62,6 +67,7 @@ const BlockerList = WDialog.extend({
buttons[wX("RESET")] = async () => {
const operation = getSelectedOperation();
await WasabeeBlocker.removeBlockers(operation.ID);
operation.resetBlockedLinks();
this.update();
window.map.fire("wasabee:crosslinks");
};
Expand Down Expand Up @@ -122,9 +128,12 @@ const BlockerList = WDialog.extend({
blocker.fromPortal ? blocker.fromPortal.name : blocker.from,
sort: (a, b) => a.localeCompare(b),
format: (row, value, blocker) => {
if (blocker.fromPortal)
row.appendChild(PortalUI.displayFormat(blocker.fromPortal));
else row.textContent = value;
if (blocker.fromPortal) {
const a = row.appendChild(
PortalUI.displayFormat(blocker.fromPortal)
);
if (blocker.team) a.classList.add(getFactionCSS(blocker.team));
} else row.textContent = value;
},
},
{
Expand All @@ -143,9 +152,10 @@ const BlockerList = WDialog.extend({
blocker.toPortal ? blocker.toPortal.name : blocker.to,
sort: (a, b) => a.localeCompare(b),
format: (row, value, blocker) => {
if (blocker.toPortal)
row.appendChild(PortalUI.displayFormat(blocker.toPortal));
else row.textContent = value;
if (blocker.toPortal) {
const a = row.appendChild(PortalUI.displayFormat(blocker.toPortal));
if (blocker.team) a.classList.add(getFactionCSS(blocker.team));
} else row.textContent = value;
},
},
{
Expand All @@ -158,6 +168,24 @@ const BlockerList = WDialog.extend({
},
format: (row, value) => (row.textContent = value),
},
{
name: "",
value: (blocker) => blocker,
sort: null,
format: (cell, blocker) => {
const del = L.DomUtil.create("a", null, cell);
del.href = "#";
del.title = wX("dialog.common.delete");
appendFAIcon("trash", del);
L.DomEvent.on(del, "click", (ev) => {
L.DomEvent.stop(ev);
WasabeeBlocker.removeBlocker(operation, blocker.from, blocker.to);
this.update();
window.map.fire("wasabee:crosslinks");
});
},
smallScreenHide: true,
},
];
content.sortBy = sortBy;
content.sortAsc = sortAsc;
Expand Down
3 changes: 3 additions & 0 deletions src/code/dialogs/checklist.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ const OperationChecklistDialog = WDialog.extend({
if (thing instanceof WasabeeLink) {
cell.appendChild(LinkUI.displayFormat(thing, operation));
cell.colSpan = 2;
const row = cell.closest("tr");
if (row && thing.blocked) row.classList.add("blocked");
if (row && thing.selfBlocked) row.classList.add("self-blocked");
} else {
cell.appendChild(
PortalUI.displayFormat(operation.getPortal(thing.portalId))
Expand Down
2 changes: 1 addition & 1 deletion src/code/dialogs/markerAddDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const MarkerAddDialog = WDialog.extend({
localStorage[window.plugin.wasabee.static.constants.LAST_MARKER_KEY] =
selectedType;
if (WasabeeMarker.isDestructMarkerType(selectedType))
WasabeeBlocker.removeBlocker(operation, PortalUI.getSelected().id);
WasabeeBlocker.removePortal(operation, PortalUI.getSelected().id);
await this.update();
} else displayError(wX("ALREADY_HAS_MARKER"));
},
Expand Down
Loading

0 comments on commit bbdfeb4

Please sign in to comment.