Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
856 changes: 856 additions & 0 deletions map/static/map/emst.js

Large diffs are not rendered by default.

63 changes: 55 additions & 8 deletions map/static/map/finditem.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,34 @@ var FindItem = {};
}
}

var catalog = []; // [{kind:"item", label, itemPath}, {kind:"building", label, typePaths, category, subcategory, row}, {kind:"vehicle", label, typePaths, isTrain, iconUrl, row}, {kind:"wildlife", label, iconUrl, row}, {kind:"category", label, iconClassName, rows}, ...]
// Map tools -- things the search bar can OPEN rather than find. They exist
// only here: a planning tool that most sessions never touch does not earn a
// permanent button, but typing its name should always reach it. Unlike
// every other entry these are not rebuilt per save (they describe no save
// data), so they sit in the catalog from the first keystroke, with or
// without a save loaded.
var TOOL_ICON_URL = "data:image/svg+xml," + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="26" height="26">' +
'<path d="M5 18 L12 6 L19 15 M12 6 L6 9" fill="none" stroke="#25e0ff" stroke-width="1.6" stroke-linejoin="round"/>' +
'<circle cx="5" cy="18" r="2.2" fill="#25e0ff"/><circle cx="12" cy="6" r="2.2" fill="#25e0ff"/>' +
'<circle cx="19" cy="15" r="2.2" fill="#25e0ff"/><circle cx="6" cy="9" r="2.2" fill="#25e0ff"/>' +
'</svg>'
);

var TOOL_ENTRIES = [
{
kind: "tool",
label: "Optimal network finder (EMST)",
iconUrl: TOOL_ICON_URL,
open: function() {
if (window.NetworkTool) {
NetworkTool.open();
}
},
},
];

var catalog = TOOL_ENTRIES.slice(); // [{kind:"item", label, itemPath}, {kind:"building", label, typePaths, category, subcategory, row}, {kind:"vehicle", label, typePaths, isTrain, iconUrl, row}, {kind:"wildlife", label, iconUrl, row}, {kind:"category", label, iconClassName, rows}, {kind:"tool", label, iconUrl, open}, ...]
var itemCatalogByLabel = {}; // label -> itemPath, for an exact-match Enter on a fully typed item name.
var buildingCatalogByLabel = {}; // label -> building catalog entry, same for a fully typed building name.
var vehicleCatalogByLabel = {}; // label -> vehicle catalog entry, same for a fully typed vehicle name.
Expand Down Expand Up @@ -1312,7 +1339,9 @@ var FindItem = {};
function selectSuggestion(entry) {
searchInput.value = entry.label;
hideSuggestions();
if (entry.kind === "building") {
if (entry.kind === "tool") {
entry.open();
} else if (entry.kind === "building") {
runBuildingSearchFor(entry);
} else if (entry.kind === "vehicle") {
runVehicleSearchFor(entry);
Expand All @@ -1334,8 +1363,12 @@ var FindItem = {};

// Whether this suggestion has any layer to show/hide at all. Everything
// that isn't an item always does; an item only when it's also a world
// pickup carrying its Collectables rows (see FindItem.build).
// pickup carrying its Collectables rows (see FindItem.build). A tool has no
// layer at all -- it opens something.
function entryHasVisibilityToggle(entry) {
if (entry.kind === "tool") {
return false;
}
return entry.kind !== "item" || !!entry.rows;
}

Expand Down Expand Up @@ -1411,10 +1444,12 @@ var FindItem = {};
var img = document.createElement("img");
img.className = "searchSuggestionIcon";
img.alt = "";
if (entry.kind === "vehicle" || entry.kind === "wildlife" || entry.kind === "resource") {
// Vehicle glyphs (icons/vehicles/), creature art (icons/creatures/) and
// a resource's ore icon all arrive as a ready URL on the entry itself
// (see filters.js) -- no ClassName-keyed lookup to do.
if (entry.kind === "vehicle" || entry.kind === "wildlife" || entry.kind === "resource"
|| entry.kind === "tool") {
// Vehicle glyphs (icons/vehicles/), creature art (icons/creatures/), a
// resource's ore icon and a tool's own inline glyph all arrive as a
// ready URL on the entry itself (see filters.js) -- no ClassName-keyed
// lookup to do.
img.onerror = function() { img.onerror = null; img.src = DEFAULT_BUILDING_ICON_URL; };
img.src = entry.iconUrl;
if (entry.kind === "vehicle") {
Expand Down Expand Up @@ -1515,6 +1550,10 @@ var FindItem = {};
["Buildings", matchesOfKind("building")],
["Vehicles", matchesOfKind("vehicle")],
["Wildlife", matchesOfKind("wildlife")],
// Last: a tool is only ever reached by typing most of its name, so it
// never needs to compete for the top of the list with what the query
// more likely means.
["Tools", matchesOfKind("tool")],
];
currentSuggestions = groups.reduce(function(all, group) { return all.concat(group[1]); }, []);

Expand Down Expand Up @@ -1618,6 +1657,13 @@ var FindItem = {};
} else if (layerCatalogByLabel.hasOwnProperty(typedLabel)) {
hideSuggestions();
runLayerSearchFor(layerCatalogByLabel[typedLabel]);
} else {
TOOL_ENTRIES.forEach(function(tool) {
if (tool.label === typedLabel) {
hideSuggestions();
tool.open();
}
});
}
} else if (e.key === "Escape") {
hideSuggestions();
Expand Down Expand Up @@ -1797,7 +1843,8 @@ var FindItem = {};
layerCatalogByLabel = {};
wildlifeEntries.concat(categoryEntries, resourceEntries).forEach(function(entry) { layerCatalogByLabel[entry.label] = entry; });

catalog = itemEntries.concat(buildingEntries, vehicleEntries, wildlifeEntries, categoryEntries, resourceEntries);
catalog = itemEntries.concat(buildingEntries, vehicleEntries, wildlifeEntries, categoryEntries,
resourceEntries, TOOL_ENTRIES);

window.MapApp.currentDepotItems = payload.dimensionalDepot || [];
};
Expand Down
83 changes: 83 additions & 0 deletions map/static/map/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,87 @@
</div>
</div>

<!-- "Optimal network finder (EMST)" (network.js) -- reachable only by typing
its name in the search bar, so a planning tool costs no permanent UI.
Non-modal like #pastePanel: the map stays live underneath because
clicking it is how points get added. -->
<div id="networkPanel" style="display:none">
<div id="networkPanelHeader">
<svg id="networkPanelIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20">
<path d="M5 18 L12 6 L19 15" fill="none" stroke="#25e0ff" stroke-width="1.6" stroke-linejoin="round"/>
<path d="M12 6 L6 9" fill="none" stroke="#25e0ff" stroke-width="1.6"/>
<circle cx="5" cy="18" r="2.2" fill="#25e0ff"/>
<circle cx="12" cy="6" r="2.2" fill="#25e0ff"/>
<circle cx="19" cy="15" r="2.2" fill="#25e0ff"/>
<circle cx="6" cy="9" r="2.2" fill="#25e0ff"/>
</svg>
<div id="networkPanelHeadings">
<span id="networkPanelTitle">Optimal network finder</span>
<span id="networkPanelSubtitle">Shortest links joining every point</span>
</div>
<button id="networkPanelClose" title="Close">&times;</button>
</div>

<!-- Everything below the header scrolls as one region when the window is
too short for it, so no control is ever cut off (see map.css). -->
<div id="networkPanelBody">
<div class="networkSectionLabel">Link style</div>
<div id="networkModes">
<button id="networkModeStraight" class="networkSegment active" type="button">Straight</button>
<button id="networkModeAxis" class="networkSegment" type="button">X / Y only</button>
</div>
<div id="networkModeHint">Shortest point-to-point network (Euclidean MST).</div>

<div class="networkSectionLabel">Points <span id="networkPointCount">none yet</span></div>
<div class="networkButtonRow">
<button id="networkPickBtn" type="button" title="Click the map to add points">Click map to add</button>
<button id="networkSelectionBtn" type="button" disabled>Add selection</button>
</div>
<div id="networkCoordRow">
<input id="networkCoordX" type="number" step="1" placeholder="X (m)" title="World X in metres">
<input id="networkCoordY" type="number" step="1" placeholder="Y (m)" title="World Y in metres">
<button id="networkCoordAdd" type="button" title="Add a point at these coordinates">Add</button>
</div>
<div id="networkPointList" class="networkList"></div>

<!-- Destination: optional. Without one the tool minimises total length and
nothing else; with one, the slider trades that length against how far
every point has to travel through the tree to reach it. -->
<div class="networkSectionLabel">Destination</div>
<div id="networkDestinationRow">
<span id="networkDestinationNone">None — set one from a point above</span>
<span id="networkDestinationName" style="display:none"></span>
<button id="networkDestinationClear" type="button" title="Remove the destination" style="display:none">&times;</button>
</div>
<div id="networkPriority" style="display:none">
<input type="range" id="networkAlpha" min="0" max="100" step="5" value="0"
title="What the network is optimised for">
<div id="networkAlphaEnds">
<span>Shortest network</span>
<span>Shortest trips</span>
</div>
<div id="networkAlphaNote"></div>
</div>

<!-- Summary only: the per-link breakdown lives on the map (hover a link)
and in Copy, not as hundreds of rows eating the panel. -->
<div id="networkResult" style="display:none">
<div id="networkSummary"></div>
</div>
</div>

<!-- Pinned action bar: the buttons that DO something stay put while the
lists above them scroll. -->
<div id="networkPanelFooter">
<div class="networkButtonRow">
<button id="networkComputeBtn" type="button" disabled>Compute network</button>
<button id="networkClearBtn" type="button" title="Remove every point" disabled>Clear</button>
</div>
<button id="networkCopyBtn" type="button" style="display:none">Copy points &amp; links</button>
</div>
</div>
<div id="networkHint" style="display:none">Click the map to add a point &middot; click a building to take its position &middot; click either again to remove it &middot; Esc when done</div>

<!-- Full-screen busy overlay for save-editing operations (copy / paste /
delete / undo / redo / export -- see SaveLoadFlow.showBusy in data.js).
Blocks all input while the WASM worker crunches so it's unmistakable
Expand Down Expand Up @@ -443,6 +524,8 @@
<script src="contextmenu.js"></script>
<script src="save_client.js"></script>
<script src="editor.js"></script>
<script src="emst.js"></script>
<script src="network.js"></script>
<script src="data.js"></script>
</body>
</html>
Loading
Loading