NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Exploration Assistant // @version 1.5.3.3.1 // @author Apache1990 // @namespace Apache1990Userscript // @updateURL https://openuserjs.org/meta/Apache1990/Exploration_Assistant.meta.js // @downloadURL https://openuserjs.org/install/Apache1990/Exploration_Assistant.user.js // @copyright 2023, Apache1990 (https://openuserjs.org/users/Apache1990) // @license MIT // @include /^https:\/\/[0-z]*\.atmoburn\.com\/fleet.php\?\S*fleet=[0-9]\S*/ // @include /^https:\/\/[0-z]*\.atmoburn\.com\/fleet\/[0-9]\S*/ // @grant unsafeWindow // ==/UserScript== function byId(ele) { //getElementById shortcut function (Atmoburn uses this too, but part of this script runs before that copy has loaded) return document.getElementById(ele); } function goToNextExplorer(fromIgnoreOrLoad = false) { let nextFleetIgnored = true; if (byId("coordinatorListing")) { //Find next non-ignored explorer fleet page for (var nextFleet = 0; nextFleet < (byId("coordinatorListing").querySelectorAll("div a").length); nextFleet++) { //If there is no cookie set to ignore the fleet, break out of the loop if (!asstCheckLocalStorage(byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href.match(/(?<=fleet\/)[0-9]*/))) { if (byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href.match(/(?<=fleet\/)[0-9]*/) !== null) { nextFleetIgnored = false; break; } } } if (nextFleet == byId("coordinatorListing").querySelectorAll("div a").length) nextFleet--; console.log("nextFleet: " + nextFleet); } if (byId("coordinatorListing") && /(?<=fleet\/)[0-9]*/.test(byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href)) console.log("Next fleet ID: " + byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href.match(/(?<=fleet\/)[0-9]*/) + " nextFleetIgnored: " + nextFleetIgnored + " fromIgnoreOrLoad: " + fromIgnoreOrLoad); //Load next non-ignored explorer fleet page if (byId("coordinatorListing") && /fleet\//.test(byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href) && (!nextFleetIgnored || fromIgnoreOrLoad)) { console.log("Attempting to navigate fleet: " + byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href); window.location.assign(byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href.replace(/#/g, '')); } else { console.log("Attempting refresh"); window.location.reload(); //Reload the page if there is no valid next explorer (to reload the Exploration Coordinator list) } } function explorationClicked() { //Function sends fleet to first unexplored planet in local targets, alerts about wormholes, and opens starmap if system is fully explored //If parking mode is active and fleet is at system entrance, Parks the fleet and proceeds to next fleet if (asstCheckLocalStorage("asstParkingModeActive")) { if (byId("positionRight").querySelectorAll("div")[1]) { if (/0, 40, 10 local/.test(byId("positionRight").querySelectorAll("div")[1].innerText)) { parkClicked(); return; } } } let assistanceSuccess = false; let wormholeDetected = false; if (byId('mLocalTargets').length > 0) { //Don't run this if there are no local targets. for (let spob of byId("mLocalTargets").querySelectorAll('optgroup')) { if (spob && /Wormholes/.test(spob.label)) { wormholeDetected = true; nextButton.innerHTML = "Ne<u>x</u>t (WORMHOLE DETECTED)"; if (!confirm("Wormhole detected in-system! Continue?")) return; break; } } for (let spob of byId("mLocalTargets").querySelectorAll('option')) { if (spob && (/\(unexplored\)/.test(spob.innerHTML) || /\(empire explored\)/.test(spob.innerHTML))) { assistanceSuccess = true; byId("nextButton").style.visibility = "hidden"; byId("ignoreButton").style.visibility = "hidden"; spob.selected = true; verifyMission(byId("mLocalTargets").value); launchMission(); break; } } } if (!assistanceSuccess) { //If no unexplored planets, bring up starmap (also check fleet range, ask if we should ignore when fuel is critical) let currentFleetRange = byId("fleetRange").innerHTML; if (currentFleetRange == "") return; if (currentFleetRange.replace(/,/g, '') > parseInt(asstSettings.criticalFuelLevel)) { window.location.assign(byId("positionRight").querySelectorAll("span ~ a")[0].href); setTimeout(pointClosestSystem, 2000); } else { if (!confirm("Current fleet range " + currentFleetRange + "; continue exploring? (Fleet ignored " + asstSettings.ignoreDuration + "h on cancel)")) ignoreClicked(true); else { window.location.assign(byId("positionRight").querySelectorAll("span ~ a")[0].href); setTimeout(pointClosestSystem, 2000); } } } } function entrancePark() { //Sends fleet to system entrance if not yet there, otherwise does nothing. if (byId('mLocalTargets').length > 0) { //Don't try to do anything if there are no local targets let spob = byId("mLocalTargets").querySelectorAll('option')[1]; if (/Entry/.test(spob.innerHTML) && !/0, 40, 10 local/.test(byId("positionRight").querySelectorAll("div")[1].innerText)) { byId("nextButton").style.visibility = "hidden"; byId("parkButton").style.visibility = "hidden"; spob.selected = true; byId("mObjective").querySelectorAll('option')[0].selected = true; verifyMission(byId("mLocalTargets").value); launchMission(true); } else if (/0, 40, 10 local/.test(byId("positionRight").querySelectorAll("div")[1].innerText)) { byId("nextButton").style.visibility = "hidden"; byId("parkButton").style.visibility = "hidden"; goToNextExplorer(true); } } } function parkClicked() { if (byId("ignoreButton")) { //Don't try if ignore button doesn't exist (how did you get here?) //Function handles fleet 'parking' (moves to system entrance, with short 1 hour ignore added) let asstCurrentFleet = window.location.href.match(/(?<=fleet\/)[0-9]*|(?<=fleet=)[0-9]*/); asstSetLocalStorage(asstCurrentFleet, 1, 1); byId("ignoreButton").innerHTML = "Remove igno<u>r</u>e?"; entrancePark(); } } function ignoreClicked(fromRangeCheck = false) { //Function handles fleet ignoring/unignoring if (byId("ignoreButton")) { //Don't try if ignore/unignore button doesn't exist (how did you get here?) if (!(/Remove/.test(byId("ignoreButton").innerHTML))) { //Adds to ignore list if not presently on it if (!fromRangeCheck) //Only pops up confirm window if called directly (failing a fleet range check already pops up an earlier confirm) var confirmIgnore = window.confirm("Ignore current fleet for " + asstSettings.ignoreDuration + " hours?"); if (confirmIgnore || fromRangeCheck) { let asstCurrentFleet = window.location.href.match(/(?<=fleet\/)[0-9]*|(?<=fleet=)[0-9]*/); asstSetLocalStorage(asstCurrentFleet, 1, 1 * asstSettings.ignoreDuration); byId("ignoreButton").innerHTML = "Remove igno<u>r</u>e?"; entrancePark(); } } else { //Removes from ignore list if on it if (window.confirm("Remove fleet from ignore list?")) { let asstCurrentFleet = window.location.href.match(/(?<=fleet\/)[0-9]*|(?<=fleet=)[0-9]*/); asstSetLocalStorage(asstCurrentFleet, 1, 0); byId("ignoreButton").innerHTML = "Igno<u>r</u>e " + asstSettings.ignoreDuration + "h"; byId("nextButton").style.visibility = ""; byId("parkButton").style.visibility = ""; } } } } function parkingModeClicked() { //Activates parking mode for 2 hours if not yet active, otherwise ends it immediately if (byId("parkingModeButton")) { //Don't try if the parking mode button doesn't exist (how did you get here?) if (!asstCheckLocalStorage("asstParkingModeActive")) { asstBlinkEffect("#003300", "#008800", "parkingModeButton"); asstSetLocalStorage("asstParkingModeActive", 1, 2); } else { asstSetLocalStorage("asstParkingModeActive", 1, 0); if (blinkInterval) clearInterval(blinkInterval); byId("parkingModeButton").style = ""; } } } function explorationAssistant() { //Function creates the new buttons used by the userscript, other prepwork if (/Explorer/.test(byId("pageSubContainer").childNodes[0].innerHTML)) { //This checks that the fleet is an Explorer, probably unneeded as this function won't be setup in initiation if not console.log("Initiating assistant..."); let isFleetIgnored = asstCheckLocalStorage(window.location.href.match(/(?<=fleet\/)[0-9]*|(?<=fleet=)[0-9]*/)); if (!asstCheckLocalStorage("asstSettings")) { asstSettings = { criticalFuelLevel: 40000000, cautionFuelLevel: 80000000, ignoreDuration: 48 }; asstSetLocalStorage("asstSettings", JSON.stringify(asstSettings), null); } else asstSettings = asstGetLocalStorage("asstSettings"); var navigateFleetAnyway = false; if (!byId("nextButton")) { let nextButton = document.createElement('button'); nextButton.innerHTML = "Ne<u>x</u>t"; nextButton.id = "nextButton"; nextButton.className = "submit greenbutton darkbutton"; nextButton.style.visibility = isFleetIgnored ? "hidden" : ""; //Hide if fleet ignored nextButton.addEventListener("click", explorationClicked); nextButton.title = "Explores next unexplored planet in system, if none remain, opens Starmap."; byId("missionSelectionLocal").append(nextButton); } if (!byId("parkButton")) { let parkButton = document.createElement('button'); parkButton.innerHTML = "<u>P</u>ark"; parkButton.id = "parkButton"; parkButton.className = "submit darkbutton"; parkButton.style.visibility = isFleetIgnored ? "hidden" : ""; //Hide if fleet ignored parkButton.addEventListener("click", parkClicked); parkButton.title = "Sends fleet to system entrance (if not already there), then ignores for 1 hour."; byId("missionSelectionLocal").append(parkButton); } if (!byId("ignoreButton")) { let ignoreButton = document.createElement('button'); ignoreButton.innerHTML = isFleetIgnored ? "Remove igno<u>r</u>e?" : "Igno<u>r</u>e " + asstSettings.ignoreDuration + "h"; ignoreButton.id = "ignoreButton"; ignoreButton.className = "darkbutton dangerbutton"; ignoreButton.addEventListener("click", ignoreClicked); ignoreButton.title = "Sends fleet to system entrance (if not already there), then ignores for " + asstSettings.ignoreDuration + " hours."; byId("missionSelectionLocal").append(ignoreButton); } if (!byId("settingsButton")) { let settingsButton = document.createElement('button'); settingsButton.innerHTML = "Settings"; settingsButton.id = "settingsButton"; settingsButton.className = "submit darkbutton"; settingsButton.addEventListener("click", asstSettingsClicked); settingsButton.title = "Allows customization of fuel warning levels, and duration of fleet ignore."; byId("missionSelectionLocal").append(settingsButton); } if (!byId("parkingModeButton")) { let parkingModeButton = document.createElement('button'); parkingModeButton.innerHTML = "Parking Mode"; parkingModeButton.id = "parkingModeButton"; parkingModeButton.className = "submit darkbutton"; parkingModeButton.addEventListener("click", parkingModeClicked); parkingModeButton.title = "When enabled, changes function of Next button. If fleet is at system entrance, it is Parked; if at a planet, continues to explore as normal. Lasts 2 hours, or until disabled."; byId("missionSelectionLocal").append(parkingModeButton); } //Pulses Parking Mode button if the mode is active if (asstCheckLocalStorage("asstParkingModeActive")) { asstBlinkEffect("#003300", "#008800", "parkingModeButton"); } if (isFleetIgnored && !navigateFleetAnyway && !readyKeyboardShortcuts) { if (!window.confirm("This fleet is currently ignored, navigate anyway?")) { goToNextExplorer(true); } navigateFleetAnyway = true; } //Colors fleet range depending on remaining fuel, based on caution and critical fuel levels //Critical will ask if you want to place the fleet on ignore before bringing up starmap for a new system let currentFleetRange = byId("fleetRange").innerHTML.replace(/,/g, ''); if (!isNaN(parseInt(currentFleetRange))) { if (currentFleetRange <= parseInt(asstSettings.criticalFuelLevel)) byId("fleetRange").parentElement.style.color = "red"; else if (currentFleetRange <= parseInt(asstSettings.cautionFuelLevel)) byId("fleetRange").parentElement.style.color = "yellow"; } } readyKeyboardShortcuts = true; } function asstBlinkEffect(color1, color2, fieldName) { //Modified version of Atmoburn blinkEffect() function that works on buttons. var blinkField = document.getElementById(fieldName); blinkField.style.transition = "background-color 0.5s"; var isBlinked = false; function blinkToggle() { if (!isBlinked) { isBlinked = true; blinkField.style.background = color1; } else { isBlinked = false; blinkField.style.background = color2; } } if (blinkInterval) clearInterval(blinkInterval); blinkInterval = setInterval(blinkToggle, 1000); } function asstSettingsClicked() { //Displays Exploration Assistant settings window (using Atmoburn genericPopup() function) let settingsForm = "Ignore Duration: <input id='asstIgnoreSetting' onclick='this.select();' onkeyup='asstSettingsSave()' class='darkinput' type='text' size='3' value='" + asstSettings.ignoreDuration + "' /><br />" + "Caution Fuel Level: <input id='asstCautionSetting' onclick='this.select();' onkeyup='asstSettingsSave();' class='darkinput' type='text' size='10' value='" + asstSettings.cautionFuelLevel + "' /><br />" + "Critical Fuel Level: <input id='asstCriticalSetting' onclick='this.select();' onkeyup='asstSettingsSave();' class='darkinput' type='text' size='10' value='" + asstSettings.criticalFuelLevel + "' /><br />" + "<span class='smalltext'>Invalid inputs will be saved as default settings.</span>"; genericPopup({ content: settingsForm }); } function asstSettingsSave() { //Saves Exploration Assistant custom settings let ignoreInput = byId("asstIgnoreSetting").value.replace(/[^0-9]+/g, ''); let cautionInput = byId("asstCautionSetting").value.replace(/[^0-9]+/g, ''); let criticalInput = byId("asstCriticalSetting").value.replace(/[^0-9]+/g, ''); if (isNaN(parseInt(ignoreInput))) ignoreInput = 48; if (isNaN(parseInt(cautionInput))) cautionInput = 80000000; if (isNaN(parseInt(criticalInput))) criticalInput = 40000000; asstSettings.ignoreDuration = ignoreInput; asstSettings.cautionFuelLevel = cautionInput; asstSettings.criticalFuelLevel = criticalInput; byId("asstIgnoreSetting").value = asstSettings.ignoreDuration; byId("asstCautionSetting").value = asstSettings.cautionFuelLevel; byId("asstCriticalSetting").value = asstSettings.criticalFuelLevel; let isFleetIgnored = asstCheckLocalStorage(window.location.href.match(/(?<=fleet\/)[0-9]*|(?<=fleet=)[0-9]*/)); ignoreButton.innerHTML = isFleetIgnored ? "Remove igno<u>r</u>e?" : "Igno<u>r</u>e " + asstSettings.ignoreDuration + "h"; //Colors fleet range depending on remaining fuel, based on caution and critical fuel levels //Critical will ask if you want to place the fleet on ignore before bringing up starmap for a new system let currentFleetRange = byId("fleetRange").innerHTML.replace(/,/g, ''); if (currentFleetRange <= parseInt(asstSettings.criticalFuelLevel)) byId("fleetRange").parentElement.style.color = "red"; else if (currentFleetRange <= parseInt(asstSettings.cautionFuelLevel)) byId("fleetRange").parentElement.style.color = "yellow"; else byId("fleetRange").parentElement.style.color = ""; asstSetLocalStorage("asstSettings", JSON.stringify(asstSettings), null); } //Return all fleet missions function getSystemsBeingExplored() { var missions = []; for (var ID in mapWindow.mapMeta.f) { if (mapWindow.mapMeta.f[ID][13] !== '') { missions.push(mapWindow.mapMeta.f[ID][13]); } } return missions; } //Pass list of missions and target system, return true if there is a mission targeting system coordinates. function isTargetInMissions(missions, target) { for (var mission in missions) { if (missions[mission][3] === target[3] && missions[mission][4] === target[4] && missions[mission][5] === target[5]) { return true; } } return false; } //Find closest system, then draw a line to it (tells the starmap that your current fleet is on an assault mission to said system, creating a semi-persistent line until it refreshes the fleet data function pointClosestSystem() { let closestSys = 0, forceRender; function acquireClosest() { if (mapWindow && typeof mapWindow.mapMeta !== 'undefined' && mapWindow.mapMeta && mapWindow.mapMeta.f[fleetID]) { //Does the map window exist? Is mapMeta a thing? Does your fleet exist in mapMeta yet? let distance = Infinity, thisDistance, myFleet = mapWindow.mapMeta.f[fleetID]; let missions = getSystemsBeingExplored(); for (var ID in mapWindow.mapMeta.s) { thisDistance = mapWindow.getDistance(myFleet, mapWindow.mapMeta.s[ID]); if (thisDistance < distance && typeof mapWindow.mapMeta.s[ID][8] !== 'undefined' && mapWindow.mapMeta.s[ID][8] == 0 && !isTargetInMissions(missions, mapWindow.mapMeta.s[ID])) { distance = thisDistance; closestSys = ID; } } clearInterval(acqInt); forceRender = setInterval(renderFunction, 1000); //Keep the line being redrawn every second until fleet window is changed renderFunction(); } } function renderFunction() { if (mapWindow && typeof mapWindow.mapMeta !== 'undefined' && !mapWindow.isRendering && mapWindow.mapMeta && mapWindow.mapMeta.f[fleetID] && closestSys && mapWindow.mapMeta.s[closestSys]) { //Added way too many checks here to stop this from throwing errors if something doesn't exist for no good reason try { mapWindow.updateRenderItem(mapWindow.mapMeta.f[fleetID], 13, [-99, 0, "g", mapWindow.mapMeta.s[closestSys][3], mapWindow.mapMeta.s[closestSys][4], mapWindow.mapMeta.s[closestSys][5], -1, "assault"]); mapWindow.render(); console.log("Exploration Assistant force render. Fleet #" + fleetID + " object: " + typeof mapWindow.mapMeta.f[fleetID] + " [" + mapWindow.mapMeta.f[fleetID].join() + "], Nearest system coords: " + mapWindow.mapMeta.s[closestSys][3] + ", " + mapWindow.mapMeta.s[closestSys][4] + ", " + mapWindow.mapMeta.s[closestSys][5] + ""); } catch (error) { console.log("Exploration Assistant force render failed, reason: " + error + " ... Fleet #" + fleetID + " object: " + typeof mapWindow.mapMeta.f[fleetID] + " [" + mapWindow.mapMeta.f[fleetID].join() + "], Nearest system coords: " + mapWindow.mapMeta.s[closestSys][3] + ", " + mapWindow.mapMeta.s[closestSys][4] + ", " + mapWindow.mapMeta.s[closestSys][5] + ""); clearInterval(forceRender); alert(error + "...See console log for details. Also yell at Apache1990, this should never appear under any circumstances now."); } } else { console.log("Exploration Assistant force render delayed (mapWindow open: " + (!mapWindow.closed) + ", isRendering: " + mapWindow.isRendering + ")"); } if (mapWindow.closed) { clearInterval(forceRender); console.log("Map window closed, shutting down pointing function."); } } let acqInt = setInterval(acquireClosest, 250); } //Set of functions that handle Local Storage (setting, reading, and checking for the existence of) function asstSetLocalStorage(key, value, exhours) { if (exhours === null) { localStorage.setItem(key, value); } else { const expires = new Date(); expires.setTime(expires.getTime() + (exhours * 60 * 60 * 1000)); const item = { value: value, expires: expires.toUTCString() }; localStorage.setItem(key, JSON.stringify(item)); } } function asstGetLocalStorage(key) { const itemString = localStorage.getItem(key); if (itemString) { const item = JSON.parse(itemString); if (item.expires) { const expires = new Date(item.expires); if (expires > new Date()) { return item.value; } else { localStorage.removeItem(key); } } else { return item; } } return ""; } function asstCheckLocalStorage(key) { return asstGetLocalStorage(key) !== ""; } //Only do any of this if the selected fleet is an Explorer if (/Explorer/.test(byId("pageSubContainer").childNodes[0].innerHTML)) { //Eval all script functions so they remain available after the pageload unsafeWindow.eval("var asstSettings;"); unsafeWindow.eval("var readyKeyboardShortcuts = false;"); unsafeWindow.eval(byId.toString()); unsafeWindow.eval(explorationAssistant.toString()); unsafeWindow.eval(goToNextExplorer.toString()); unsafeWindow.eval(asstSetLocalStorage.toString()); unsafeWindow.eval(asstGetLocalStorage.toString()); unsafeWindow.eval(asstCheckLocalStorage.toString()); unsafeWindow.eval(explorationClicked.toString()); unsafeWindow.eval(entrancePark.toString()); unsafeWindow.eval(asstSettingsClicked.toString()); unsafeWindow.eval(asstSettingsSave.toString()); unsafeWindow.eval(asstBlinkEffect.toString()); unsafeWindow.eval(parkClicked.toString()); unsafeWindow.eval(parkingModeClicked.toString()); unsafeWindow.eval(ignoreClicked.toString()); unsafeWindow.eval(getSystemsBeingExplored.toString()); unsafeWindow.eval(isTargetInMissions.toString()); unsafeWindow.eval(pointClosestSystem.toString()); unsafeWindow.eval("explorationAssistant();"); //Adds call to explorationAssistant() to the end of Atmoburn's setInterfaceNoMission() let newFleetData = unsafeWindow.setInterfaceNoMission.toString().slice(0, -1) + "\n\texplorationAssistant();\n}"; unsafeWindow.eval(newFleetData); //Adds call to goToNextExplorer() to return function of Atmoburn's launchMission() let newLaunchMission = unsafeWindow.launchMission.toString().slice(0, 23) + "ignoreOrLoad = false" + unsafeWindow.launchMission.toString().slice(23, -25) + "\n\t\tgoToNextExplorer(ignoreOrLoad);\n" + unsafeWindow.launchMission.toString().slice(-25); unsafeWindow.eval(newLaunchMission); //Update exploration coordinator if (byId("coordinatorListing")) { for (var nextFleet = 0; nextFleet < byId("coordinatorListing").querySelectorAll("div a").length; nextFleet++) { //If there is a cookie set to ignore the fleet, mark red if (asstCheckLocalStorage(byId("coordinatorListing").querySelectorAll("div a")[nextFleet].href.match(/(?<=fleet\/)[0-9]*/))) byId("coordinatorListing").querySelectorAll("div a")[nextFleet].classList.add("dangerbutton"); } } //Keyboard shortcuts section document.onkeydown = function (e) { e = e || window.event; var keycode = e.which || e.keyCode; var ctrlPressed = e.ctrlKey || e.metaKey; //Is Ctrl key pressed? if (!ctrlPressed && document.activeElement.tagName == "BODY" && unsafeWindow.readyKeyboardShortcuts) { //skip if Ctrl key is pressed, activeElement used to skip if user has a text input active, don't do anything before page is loaded if (keycode == 88) { //'88' is the keycode for "x" e.preventDefault(); if (byId("mLocalTargets").offsetParent != null && byId("nextButton") && byId("nextButton").style.visibility != "hidden") //skip if local navigation is hidden or nextButton hidden unsafeWindow.explorationClicked(); else unsafeWindow.goToNextExplorer(); } else if (keycode == 82) { //'82' is the keycode for "r" e.preventDefault(); if (byId("mLocalTargets").offsetParent != null && byId("ignoreButton") && byId("ignoreButton").style.visibility != "hidden") //skip if local navigation is hidden or nextButton hidden unsafeWindow.ignoreClicked(); } else if (keycode == 80) { //'80' is the keycode for "p" e.preventDefault(); if (byId("mLocalTargets").offsetParent != null && byId("parkButton") && byId("parkButton").style.visibility != "hidden") //skip if local navigation is hidden or nextButton hidden unsafeWindow.parkClicked(); } } }; }