mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-28 08:57:32 +01:00
Merge pull request #614 from danielyxie/active-scripts-ui-and-implementation-rework
Active scripts ui and implementation rework
This commit is contained in:
commit
8a00e6e532
126
css/activescripts.scss
Normal file
126
css/activescripts.scss
Normal file
@ -0,0 +1,126 @@
|
||||
@import "theme";
|
||||
|
||||
.active-scripts-list {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
|
||||
> p {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.accordion-header {
|
||||
> pre {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&.active, &:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active {
|
||||
&:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
|
||||
div, ul, ul > li {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
border: none;
|
||||
color: var(--my-font-color);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
outline: none;
|
||||
padding: 4px 25px 4px 10px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: auto;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
&.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-panel {
|
||||
background-color: #555;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0 18px;
|
||||
width: auto;
|
||||
|
||||
pre, h2, ul, li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%;
|
||||
}
|
||||
}
|
@ -18,126 +18,6 @@
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* Active scripts */
|
||||
.active-scripts-list {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#active-scripts-container {
|
||||
position: fixed;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#active-scripts-text,
|
||||
#active-scripts-total-prod {
|
||||
width: 70%;
|
||||
margin: 6px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.active-scripts-server-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
width: 60%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active,
|
||||
.active-scripts-server-header:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.active-scripts-server-header:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.active-scripts-server-header.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
color: #fff;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.active-scripts-server-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 55%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active-scripts-server-panel div,
|
||||
.active-scripts-server-panel ul,
|
||||
.active-scripts-server-panel ul > li {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.active-scripts-script-header {
|
||||
background-color: #555;
|
||||
color: var(--my-font-color);
|
||||
padding: 4px 25px 4px 10px;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '\02795'; /* "plus" sign (+) */
|
||||
font-size: $defaultFontSize * 0.8125;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 var(--my-font-color);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
&.active:after {
|
||||
content: "\2796"; /* "minus" sign (-) */
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.active-scripts-script-panel {
|
||||
padding: 0 18px;
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
display: none;
|
||||
margin-bottom: 6px;
|
||||
|
||||
p, h2, ul, li {
|
||||
background-color: #555;
|
||||
width: auto;
|
||||
color: #fff;
|
||||
margin-left: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
/* World */
|
||||
#world-container {
|
||||
position: fixed;
|
||||
|
@ -243,8 +243,8 @@ a:visited {
|
||||
/* Accordion menus (Header with collapsible panel) */
|
||||
.accordion-header {
|
||||
background-color: #444;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
color: #fff;
|
||||
font-size: $defaultFontSize * 1.25;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
|
@ -1,337 +0,0 @@
|
||||
// TODO - Convert this to React
|
||||
import { workerScripts, killWorkerScript } from "./NetscriptWorker";
|
||||
import { Player } from "./Player";
|
||||
import { getServer } from "./Server/ServerHelpers";
|
||||
|
||||
import { Page, routing } from "./ui/navigationTracking";
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../utils/DialogBox";
|
||||
import { logBoxCreate } from "../utils/LogBox";
|
||||
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
|
||||
import { arrayToString } from "../utils/helpers/arrayToString";
|
||||
import { createProgressBarText } from "../utils/helpers/createProgressBarText";
|
||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
||||
import { roundToTwo } from "../utils/helpers/roundToTwo";
|
||||
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
|
||||
import { createElement } from "../utils/uiHelpers/createElement";
|
||||
import { getElementById } from "../utils/uiHelpers/getElementById";
|
||||
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
||||
|
||||
|
||||
/**
|
||||
* {
|
||||
* serverName: {
|
||||
* header: Server Header Element
|
||||
* panel: Server Panel List (ul) element
|
||||
* scripts: {
|
||||
* script id: Ref to Script information
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
*/
|
||||
const ActiveScriptsUI = {};
|
||||
const ActiveScriptsTasks = []; // Sequentially schedule the creation/deletion of UI elements
|
||||
|
||||
const getHeaderHtml = (server) => {
|
||||
// TODO: calculate the longest hostname length rather than hard coding it
|
||||
const longestHostnameLength = 18;
|
||||
const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength));
|
||||
const barOptions = {
|
||||
progress: server.ramUsed / server.maxRam,
|
||||
totalTicks: 30
|
||||
};
|
||||
return `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' ');
|
||||
};
|
||||
|
||||
const updateHeaderHtml = (server) => {
|
||||
const accordion = ActiveScriptsUI[server.hostname];
|
||||
if (accordion === null || accordion === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert it to a string, as that's how it's stored it will come out of the data attributes
|
||||
const ramPercentage = '' + roundToTwo(server.ramUsed / server.maxRam);
|
||||
if (accordion.header.dataset.ramPercentage !== ramPercentage) {
|
||||
accordion.header.dataset.ramPercentage = ramPercentage;
|
||||
accordion.header.innerHTML = getHeaderHtml(server);
|
||||
}
|
||||
}
|
||||
|
||||
function createActiveScriptsServerPanel(server) {
|
||||
let hostname = server.hostname;
|
||||
|
||||
var activeScriptsList = document.getElementById("active-scripts-list");
|
||||
|
||||
let res = createAccordionElement({
|
||||
hdrText: getHeaderHtml(server)
|
||||
});
|
||||
let li = res[0];
|
||||
var hdr = res[1];
|
||||
let panel = res[2];
|
||||
|
||||
if (ActiveScriptsUI[hostname] != null) {
|
||||
console.log("WARNING: Tried to create already-existing Active Scripts Server panel. This is most likely fine. It probably means many scripts just got started up on a new server. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var panelScriptList = createElement("ul");
|
||||
panel.appendChild(panelScriptList);
|
||||
activeScriptsList.appendChild(li);
|
||||
|
||||
ActiveScriptsUI[hostname] = {
|
||||
header: hdr,
|
||||
panel: panel,
|
||||
panelList: panelScriptList,
|
||||
scripts: {}, // Holds references to li elements for each active script
|
||||
scriptHdrs: {}, // Holds references to header elements for each active script
|
||||
scriptStats: {}, // Holds references to the p elements containing text for each active script
|
||||
};
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the info for a particular server (Dropdown header + Panel with all info)
|
||||
* in the Active Scripts page if it exists
|
||||
*/
|
||||
function deleteActiveScriptsServerPanel(server) {
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
console.log("WARNING: Tried to delete non-existent Active Scripts Server panel. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure it's empty
|
||||
if (Object.keys(ActiveScriptsUI[hostname].scripts).length > 0) {
|
||||
console.warn("Tried to delete Active Scripts Server panel that still has scripts. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
removeElement(ActiveScriptsUI[hostname].panel);
|
||||
removeElement(ActiveScriptsUI[hostname].header);
|
||||
delete ActiveScriptsUI[hostname];
|
||||
}
|
||||
|
||||
function addActiveScriptsItem(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.warn("Invalid server IP for workerscript in addActiveScriptsItem()");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
|
||||
ActiveScriptsTasks.push(function(workerscript, hostname) {
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
createActiveScriptsServerPanel(server);
|
||||
}
|
||||
|
||||
// Create the unique identifier (key) for this script
|
||||
var itemNameArray = ["active", "scripts", hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
let res = createAccordionElement({hdrText:workerscript.name});
|
||||
let li = res[0];
|
||||
let hdr = res[1];
|
||||
let panel = res[2];
|
||||
|
||||
hdr.classList.remove("accordion-header");
|
||||
hdr.classList.add("active-scripts-script-header");
|
||||
panel.classList.remove("accordion-panel");
|
||||
panel.classList.add("active-scripts-script-panel");
|
||||
|
||||
/**
|
||||
* Handle the constant elements on the panel that don't change after creation:
|
||||
* Threads, args, kill/log button
|
||||
*/
|
||||
panel.appendChild(createElement("p", {
|
||||
innerHTML: "Threads: " + workerscript.scriptRef.threads + "<br>" +
|
||||
"Args: " + arrayToString(workerscript.args)
|
||||
}));
|
||||
var panelText = createElement("p", {
|
||||
innerText: "Loading...",
|
||||
fontSize: "14px",
|
||||
});
|
||||
panel.appendChild(panelText);
|
||||
panel.appendChild(createElement("br"));
|
||||
panel.appendChild(createElement("span", {
|
||||
innerText: "Log",
|
||||
class: "accordion-button",
|
||||
margin: "4px",
|
||||
padding: "4px",
|
||||
clickListener: () => {
|
||||
logBoxCreate(workerscript.scriptRef);
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
panel.appendChild(createElement("span", {
|
||||
innerText: "Kill Script",
|
||||
class: "accordion-button",
|
||||
margin: "4px",
|
||||
padding: "4px",
|
||||
clickListener: () => {
|
||||
killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.server);
|
||||
dialogBoxCreate("Killing script, may take a few minutes to complete...");
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
// Append element to list
|
||||
ActiveScriptsUI[hostname]["panelList"].appendChild(li);
|
||||
ActiveScriptsUI[hostname].scripts[itemName] = li;
|
||||
ActiveScriptsUI[hostname].scriptHdrs[itemName] = hdr;
|
||||
ActiveScriptsUI[hostname].scriptStats[itemName] = panelText;
|
||||
}.bind(null, workerscript, hostname));
|
||||
}
|
||||
|
||||
function deleteActiveScriptsItem(workerscript) {
|
||||
ActiveScriptsTasks.push(function(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
throw new Error("ERROR: Invalid server IP for workerscript. This most likely occurred because " +
|
||||
"you tried to delete a large number of scripts and also deleted servers at the " +
|
||||
"same time. It's not a big deal, just save and refresh the game.");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
console.log("ERROR: Trying to delete Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
var itemNameArray = ["active", "scripts", server.hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
let li = ActiveScriptsUI[hostname].scripts[itemName];
|
||||
if (li == null) {
|
||||
console.log("ERROR: Cannot find Active Script UI element for workerscript: ");
|
||||
console.log(workerscript);
|
||||
return;
|
||||
}
|
||||
removeElement(li);
|
||||
delete ActiveScriptsUI[hostname].scripts[itemName];
|
||||
delete ActiveScriptsUI[hostname].scriptHdrs[itemName];
|
||||
delete ActiveScriptsUI[hostname].scriptStats[itemName];
|
||||
if (Object.keys(ActiveScriptsUI[hostname].scripts).length === 0) {
|
||||
deleteActiveScriptsServerPanel(server);
|
||||
}
|
||||
}.bind(null, workerscript));
|
||||
}
|
||||
|
||||
function updateActiveScriptsItems(maxTasks=150) {
|
||||
/**
|
||||
* Run tasks that need to be done sequentially (adding items, creating/deleting server panels)
|
||||
* We'll limit this to 150 at a time for performance (in case someone decides to start a
|
||||
* bunch of scripts all at once...)
|
||||
*/
|
||||
const numTasks = Math.min(maxTasks, ActiveScriptsTasks.length);
|
||||
for (let i = 0; i < numTasks; ++i) {
|
||||
let task = ActiveScriptsTasks.shift();
|
||||
try {
|
||||
task();
|
||||
} catch(e) {
|
||||
exceptionAlert(e);
|
||||
console.log(task);
|
||||
}
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
try {
|
||||
total += updateActiveScriptsItemContent(workerScripts[i]);
|
||||
} catch(e) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!routing.isOn(Page.ActiveScripts)) { return total; }
|
||||
getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total);
|
||||
getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug);
|
||||
getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000));
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function updateActiveScriptsItemContent(workerscript) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.log("ERROR: Invalid server IP for workerscript in updateActiveScriptsItemContent().");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null) {
|
||||
return; // Hasn't been created yet. We'll skip it
|
||||
}
|
||||
|
||||
updateHeaderHtml(server);
|
||||
|
||||
var itemNameArray = ["active", "scripts", server.hostname, workerscript.name];
|
||||
for (var i = 0; i < workerscript.args.length; ++i) {
|
||||
itemNameArray.push(String(workerscript.args[i]));
|
||||
}
|
||||
var itemName = itemNameArray.join("-");
|
||||
|
||||
if (ActiveScriptsUI[hostname].scriptStats[itemName] == null) {
|
||||
return; // Hasn't been fully added yet. We'll skip it
|
||||
}
|
||||
var item = ActiveScriptsUI[hostname].scriptStats[itemName];
|
||||
|
||||
// Update the text if necessary. This fn returns the online $/s production
|
||||
return updateActiveScriptsText(workerscript, item, itemName);
|
||||
}
|
||||
|
||||
function updateActiveScriptsText(workerscript, item, itemName) {
|
||||
var server = getServer(workerscript.serverIp);
|
||||
if (server == null) {
|
||||
console.log("ERROR: Invalid server IP for workerscript for updateActiveScriptsText()");
|
||||
return;
|
||||
}
|
||||
let hostname = server.hostname;
|
||||
if (ActiveScriptsUI[hostname] == null || ActiveScriptsUI[hostname].scriptHdrs[itemName] == null) {
|
||||
console.log("ERROR: Trying to update Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
updateHeaderHtml(server);
|
||||
var onlineMps = workerscript.scriptRef.onlineMoneyMade / workerscript.scriptRef.onlineRunningTime;
|
||||
|
||||
// Only update if the item is visible
|
||||
if (ActiveScriptsUI[hostname].header.classList.contains("active") === false) {return onlineMps;}
|
||||
if (ActiveScriptsUI[hostname].scriptHdrs[itemName].classList.contains("active") === false) {return onlineMps;}
|
||||
|
||||
removeChildrenFromElement(item);
|
||||
|
||||
var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3);
|
||||
var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3);
|
||||
|
||||
// Online
|
||||
var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade);
|
||||
var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " ");
|
||||
|
||||
var onlineMpsText = "Online production rate: " + numeralWrapper.formatMoney(onlineMps) + " / second";
|
||||
var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime;
|
||||
var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " ");
|
||||
|
||||
// Offline
|
||||
var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade);
|
||||
var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " ");
|
||||
|
||||
var offlineMps = workerscript.scriptRef.offlineMoneyMade / workerscript.scriptRef.offlineRunningTime;
|
||||
var offlineMpsText = "Offline production rate: " + numeralWrapper.formatMoney(offlineMps) + " / second";
|
||||
var offlineEps = workerscript.scriptRef.offlineExpGained / workerscript.scriptRef.offlineRunningTime;
|
||||
var offlineEpsText = (Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " ");
|
||||
|
||||
item.innerHTML = onlineTime + "<br>" + offlineTime + "<br>" + onlineTotalMoneyMade + "<br>" + onlineTotalExpEarned + "<br>" +
|
||||
onlineMpsText + "<br>" + onlineEpsText + "<br>" + offlineTotalMoneyMade + "<br>" + offlineTotalExpEarned + "<br>" +
|
||||
offlineMpsText + "<br>" + offlineEpsText + "<br>";
|
||||
return onlineMps;
|
||||
}
|
||||
|
||||
export {addActiveScriptsItem, deleteActiveScriptsItem, updateActiveScriptsItems};
|
@ -32,6 +32,11 @@ export class WorkerScript {
|
||||
*/
|
||||
delay: number | null = null;
|
||||
|
||||
/**
|
||||
* Holds the Promise resolve() function for when the script is "blocked" by an async op
|
||||
*/
|
||||
delayResolve?: () => void;
|
||||
|
||||
/**
|
||||
* Stores names of all functions that have logging disabled
|
||||
*/
|
||||
|
6
src/Netscript/WorkerScriptStartStopEventEmitter.ts
Normal file
6
src/Netscript/WorkerScriptStartStopEventEmitter.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Event emitter that triggers when scripts are started/stopped
|
||||
*/
|
||||
import { EventEmitter } from "../utils/EventEmitter";
|
||||
|
||||
export const WorkerScriptStartStopEventEmitter = new EventEmitter();
|
6
src/Netscript/WorkerScripts.ts
Normal file
6
src/Netscript/WorkerScripts.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Global pool of all active scripts (scripts that are currently running)
|
||||
*/
|
||||
import { WorkerScript } from "./WorkerScript";
|
||||
|
||||
export const workerScripts: WorkerScript[] = [];
|
127
src/Netscript/killWorkerScript.ts
Normal file
127
src/Netscript/killWorkerScript.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Stops an actively-running script (represented by a WorkerScript object)
|
||||
* and removes it from the global pool of active scripts.
|
||||
*/
|
||||
import { WorkerScript } from "./WorkerScript";
|
||||
import { workerScripts } from "./WorkerScripts";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import { RunningScript } from "../Script/RunningScript";
|
||||
import { AllServers } from "../Server/AllServers";
|
||||
|
||||
import { compareArrays } from "../../utils/helpers/compareArrays";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
|
||||
export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string): boolean;
|
||||
export function killWorkerScript(workerScript: WorkerScript): boolean;
|
||||
export function killWorkerScript(script: RunningScript | WorkerScript, serverIp?: string): boolean {
|
||||
if (script instanceof WorkerScript) {
|
||||
script.env.stopFlag = true;
|
||||
killNetscriptDelay(script);
|
||||
removeWorkerScript(script);
|
||||
|
||||
return true;
|
||||
} else if (script instanceof RunningScript && typeof serverIp === "string") {
|
||||
for (let i = 0; i < workerScripts.length; i++) {
|
||||
if (workerScripts[i].name == script.filename && workerScripts[i].serverIp == serverIp &&
|
||||
compareArrays(workerScripts[i].args, script.args)) {
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
killNetscriptDelay(workerScripts[i]);
|
||||
removeWorkerScript(workerScripts[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
console.error(`killWorkerScript() called with invalid argument:`);
|
||||
console.error(script);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that removes the script being killed from the global pool.
|
||||
* Also handles other cleanup-time operations
|
||||
*
|
||||
* @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or
|
||||
* its index in the global workerScripts array
|
||||
*/
|
||||
function removeWorkerScript(id: WorkerScript | number): void {
|
||||
// Get a reference to the WorkerScript and its index in the global pool
|
||||
let workerScript: WorkerScript;
|
||||
let index: number | null = null;
|
||||
|
||||
if (typeof id === "number") {
|
||||
if (id < 0 || id >= workerScripts.length) {
|
||||
console.error(`Too high of an index passed into removeWorkerScript(): ${id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
workerScript = workerScripts[id];
|
||||
index = id;
|
||||
} else if (id instanceof WorkerScript) {
|
||||
workerScript = id;
|
||||
for (let i = 0; i < workerScripts.length; ++i) {
|
||||
if (workerScripts[i] == id) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == null) {
|
||||
console.error(`Could not find WorkerScript in global pool:`);
|
||||
console.error(workerScript);
|
||||
}
|
||||
} else {
|
||||
console.error(`Invalid argument passed into removeWorkerScript(): ${id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const ip = workerScript.serverIp;
|
||||
const name = workerScript.name;
|
||||
|
||||
// Get the server on which the script runs
|
||||
const server = AllServers[ip];
|
||||
if (server == null) {
|
||||
console.error(`Could not find server on which this script is running: ${ip}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate ram used on that server
|
||||
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
|
||||
if (server.ramUsed < 0) {
|
||||
console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
|
||||
server.ramUsed = 0;
|
||||
}
|
||||
|
||||
// Delete the RunningScript object from that server
|
||||
for (let i = 0; i < server.runningScripts.length; ++i) {
|
||||
const runningScript = server.runningScripts[i];
|
||||
if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
|
||||
server.runningScripts.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete script from global pool (workerScripts)
|
||||
workerScripts.splice(<number>index, 1);
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that interrupts a script's delay if it is in the middle of a
|
||||
* timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to
|
||||
* be killed immediately even if they're in the middle of one of those long operations
|
||||
*/
|
||||
function killNetscriptDelay(workerScript: WorkerScript) {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
clearTimeout(workerScript.delay);
|
||||
if (workerScript.delayResolve) {
|
||||
workerScript.delayResolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,9 @@
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { getServer } from "./Server/ServerHelpers";
|
||||
|
||||
import { setTimeoutRef } from "./utils/SetTimeoutRef";
|
||||
import { parse, Node } from "../utils/acorn";
|
||||
|
||||
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
|
||||
import { isString } from "../utils/helpers/isString";
|
||||
|
||||
export function killNetscriptDelay(workerScript) {
|
||||
if (workerScript instanceof WorkerScript) {
|
||||
if (workerScript.delay) {
|
||||
clearTimeout(workerScript.delay);
|
||||
workerScript.delayResolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function netscriptDelay(time, workerScript) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
workerScript.delay = setTimeoutRef(() => {
|
||||
|
@ -3,7 +3,6 @@ const vsprintf = require("sprintf-js").vsprintf;
|
||||
|
||||
import { getRamCost } from "./Netscript/RamCostGenerator";
|
||||
|
||||
import { updateActiveScriptsItems } from "./ActiveScriptsUI";
|
||||
import { Augmentation } from "./Augmentation/Augmentation";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
@ -120,11 +119,11 @@ import {
|
||||
} from "./NetscriptBladeburner";
|
||||
import * as nsGang from "./NetscriptGang";
|
||||
import {
|
||||
workerScripts,
|
||||
killWorkerScript,
|
||||
NetscriptPorts,
|
||||
runScriptFromScript,
|
||||
} from "./NetscriptWorker";
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import {
|
||||
makeRuntimeRejectMsg,
|
||||
netscriptDelay,
|
||||
|
@ -2,20 +2,17 @@
|
||||
* Functions for handling WorkerScripts, which are the underlying mechanism
|
||||
* that allows for scripts to run
|
||||
*/
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
|
||||
|
||||
import {
|
||||
addActiveScriptsItem,
|
||||
deleteActiveScriptsItem,
|
||||
updateActiveScriptsItems
|
||||
} from "./ActiveScriptsUI";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Engine } from "./engine";
|
||||
import { Interpreter } from "./JSInterpreter";
|
||||
import {
|
||||
isScriptErrorMessage,
|
||||
makeRuntimeRejectMsg,
|
||||
killNetscriptDelay
|
||||
} from "./NetscriptEvaluator";
|
||||
import { NetscriptFunctions } from "./NetscriptFunctions";
|
||||
import { executeJSScript } from "./NetscriptJSEvaluator";
|
||||
@ -42,9 +39,7 @@ import { isString } from "../utils/StringHelperFunctions";
|
||||
|
||||
const walk = require("acorn/dist/walk");
|
||||
|
||||
//Array containing all scripts that are running across all servers, to easily run them all
|
||||
export const workerScripts = [];
|
||||
|
||||
// Netscript Ports are instantiated here
|
||||
export const NetscriptPorts = [];
|
||||
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
||||
NetscriptPorts.push(new NetscriptPort());
|
||||
@ -52,10 +47,9 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
|
||||
|
||||
export function prestigeWorkerScripts() {
|
||||
for (var i = 0; i < workerScripts.length; ++i) {
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
// TODO Signal event emitter
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
}
|
||||
updateActiveScriptsItems(5000); //Force UI to update
|
||||
workerScripts.length = 0;
|
||||
}
|
||||
|
||||
@ -141,7 +135,7 @@ function startNetscript2Script(workerScript) {
|
||||
}
|
||||
|
||||
function startNetscript1Script(workerScript) {
|
||||
var code = workerScript.code;
|
||||
const code = workerScript.code;
|
||||
workerScript.running = true;
|
||||
|
||||
//Process imports
|
||||
@ -413,164 +407,103 @@ function processNetscript1Imports(code, workerScript) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Loop through workerScripts and run every script that is not currently running
|
||||
export function runScriptsLoop() {
|
||||
let scriptDeleted = false;
|
||||
|
||||
// Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
|
||||
for (let i = workerScripts.length - 1; i >= 0; i--) {
|
||||
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
|
||||
scriptDeleted = true;
|
||||
// Delete script from the runningScripts array on its host serverIp
|
||||
const ip = workerScripts[i].serverIp;
|
||||
const name = workerScripts[i].name;
|
||||
|
||||
// Recalculate ram used
|
||||
AllServers[ip].ramUsed = 0;
|
||||
for (let j = 0; j < workerScripts.length; j++) {
|
||||
if (workerScripts[j].serverIp !== ip) {
|
||||
continue;
|
||||
}
|
||||
if (j === i) { // not this one
|
||||
continue;
|
||||
}
|
||||
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
|
||||
}
|
||||
|
||||
// Delete script from Active Scripts
|
||||
deleteActiveScriptsItem(workerScripts[i]);
|
||||
|
||||
for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
|
||||
if (AllServers[ip].runningScripts[j].filename == name &&
|
||||
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
|
||||
AllServers[ip].runningScripts.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete script from workerScripts
|
||||
workerScripts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
|
||||
|
||||
|
||||
// Run any scripts that haven't been started
|
||||
for (let i = 0; i < workerScripts.length; i++) {
|
||||
// If it isn't running, start the script
|
||||
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) {
|
||||
let p = null; // p is the script's result promise.
|
||||
if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) {
|
||||
p = startNetscript2Script(workerScripts[i]);
|
||||
} else {
|
||||
p = startNetscript1Script(workerScripts[i]);
|
||||
if (!(p instanceof Promise)) { continue; }
|
||||
}
|
||||
|
||||
// Once the code finishes (either resolved or rejected, doesnt matter), set its
|
||||
// running status to false
|
||||
p.then(function(w) {
|
||||
console.log("Stopping script " + w.name + " because it finished running naturally");
|
||||
w.running = false;
|
||||
w.env.stopFlag = true;
|
||||
w.scriptRef.log("Script finished running");
|
||||
}).catch(function(w) {
|
||||
if (w instanceof Error) {
|
||||
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
||||
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
|
||||
return;
|
||||
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
|
||||
// Script ends with a return statement
|
||||
console.log("Script returning with value: " + w[1]);
|
||||
// TODO maybe do something with this in the future
|
||||
return;
|
||||
} else if (w instanceof WorkerScript) {
|
||||
if (isScriptErrorMessage(w.errorMessage)) {
|
||||
var errorTextArray = w.errorMessage.split("|");
|
||||
if (errorTextArray.length != 4) {
|
||||
console.log("ERROR: Something wrong with Error text in evaluator...");
|
||||
console.log("Error text: " + errorText);
|
||||
return;
|
||||
}
|
||||
var serverIp = errorTextArray[1];
|
||||
var scriptName = errorTextArray[2];
|
||||
var errorMsg = errorTextArray[3];
|
||||
|
||||
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
|
||||
"<br>Script name: " + scriptName +
|
||||
"<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg);
|
||||
w.scriptRef.log("Script crashed with runtime error");
|
||||
} else {
|
||||
w.scriptRef.log("Script killed");
|
||||
}
|
||||
w.running = false;
|
||||
w.env.stopFlag = true;
|
||||
} else if (isScriptErrorMessage(w)) {
|
||||
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
||||
console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
|
||||
return;
|
||||
} else {
|
||||
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
|
||||
console.log(w);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTimeoutRef(runScriptsLoop, 3e3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a script to be killed by setting its stop flag to true. This
|
||||
* kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
|
||||
* prevents any further execution of Netscript functions.
|
||||
* The runScriptsLoop() handles the actual deletion of the WorkerScript
|
||||
*/
|
||||
export function killWorkerScript(runningScriptObj, serverIp) {
|
||||
for (var i = 0; i < workerScripts.length; i++) {
|
||||
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
|
||||
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
|
||||
workerScripts[i].env.stopFlag = true;
|
||||
killNetscriptDelay(workerScripts[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a RunningScript object, queues that script to be run
|
||||
* Start a script
|
||||
*
|
||||
* Given a RunningScript object, constructs a corresponding WorkerScript,
|
||||
* adds it to the global 'workerScripts' pool, and begins executing it.
|
||||
* @param {RunningScript} runningScriptObj - Script that's being run
|
||||
* @param {Server} server - Server on which the script is to be run
|
||||
*/
|
||||
export function addWorkerScript(runningScriptObj, server) {
|
||||
var filename = runningScriptObj.filename;
|
||||
const filename = runningScriptObj.filename;
|
||||
|
||||
//Update server's ram usage
|
||||
var threads = 1;
|
||||
// Update server's ram usage
|
||||
let threads = 1;
|
||||
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
|
||||
threads = runningScriptObj.threads;
|
||||
} else {
|
||||
runningScriptObj.threads = 1;
|
||||
}
|
||||
var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
|
||||
var ramAvailable = server.maxRam - server.ramUsed;
|
||||
const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads);
|
||||
const ramAvailable = server.maxRam - server.ramUsed;
|
||||
if (ramUsage > ramAvailable) {
|
||||
dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " +
|
||||
arrayToString(runningScriptObj.args) + ". This likely occurred because you re-loaded " +
|
||||
"the game and the script's RAM usage increased (either because of an update to the game or " +
|
||||
"your changes to the script.)");
|
||||
dialogBoxCreate(
|
||||
`Not enough RAM to run script ${runningScriptObj.filename} with args ` +
|
||||
`${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
|
||||
`the game and the script's RAM usage increased (either because of an update to the game or ` +
|
||||
`your changes to the script.)`
|
||||
);
|
||||
return;
|
||||
}
|
||||
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
|
||||
|
||||
//Create the WorkerScript
|
||||
var s = new WorkerScript(runningScriptObj, NetscriptFunctions);
|
||||
// Create the WorkerScript
|
||||
const s = new WorkerScript(runningScriptObj, NetscriptFunctions);
|
||||
s.ramUsage = ramUsage;
|
||||
|
||||
//Add the WorkerScript to the Active Scripts list
|
||||
addActiveScriptsItem(s);
|
||||
// Start the script's execution
|
||||
let p = null; // Script's resulting promise
|
||||
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
|
||||
p = startNetscript2Script(s);
|
||||
} else {
|
||||
p = startNetscript1Script(s);
|
||||
if (!(p instanceof Promise)) { return; }
|
||||
}
|
||||
|
||||
//Add the WorkerScript
|
||||
workerScripts.push(s);
|
||||
// Once the code finishes (either resolved or rejected, doesnt matter), set its
|
||||
// running status to false
|
||||
p.then(function(w) {
|
||||
console.log("Stopping script " + w.name + " because it finished running naturally");
|
||||
killWorkerScript(s);
|
||||
w.scriptRef.log("Script finished running");
|
||||
}).catch(function(w) {
|
||||
if (w instanceof Error) {
|
||||
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
||||
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
|
||||
return;
|
||||
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
|
||||
// Script ends with a return statement
|
||||
console.log("Script returning with value: " + w[1]);
|
||||
// TODO maybe do something with this in the future
|
||||
return;
|
||||
} else if (w instanceof WorkerScript) {
|
||||
if (isScriptErrorMessage(w.errorMessage)) {
|
||||
var errorTextArray = w.errorMessage.split("|");
|
||||
if (errorTextArray.length != 4) {
|
||||
console.log("ERROR: Something wrong with Error text in evaluator...");
|
||||
console.log("Error text: " + errorText);
|
||||
return;
|
||||
}
|
||||
var serverIp = errorTextArray[1];
|
||||
var scriptName = errorTextArray[2];
|
||||
var errorMsg = errorTextArray[3];
|
||||
|
||||
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
|
||||
"<br>Script name: " + scriptName +
|
||||
"<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg);
|
||||
w.scriptRef.log("Script crashed with runtime error");
|
||||
} else {
|
||||
w.scriptRef.log("Script killed");
|
||||
return; // Already killed, so stop here
|
||||
}
|
||||
w.running = false;
|
||||
w.env.stopFlag = true;
|
||||
} else if (isScriptErrorMessage(w)) {
|
||||
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
|
||||
console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
|
||||
return;
|
||||
} else {
|
||||
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
|
||||
console.log(w);
|
||||
}
|
||||
|
||||
killWorkerScript(s);
|
||||
});
|
||||
|
||||
// Add the WorkerScript to the global pool
|
||||
workerScripts.push(s);
|
||||
WorkerScriptStartStopEventEmitter.emitEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { deleteActiveScriptsItem } from "./ActiveScriptsUI";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
augmentationExists,
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Class representing a Script instance that is actively running.
|
||||
// A Script can have multiple active instances
|
||||
/**
|
||||
* Class representing a Script instance that is actively running.
|
||||
* A Script can have multiple active instances
|
||||
*/
|
||||
import { Script } from "./Script";
|
||||
import { FconfSettings } from "../Fconf/FconfSettings";
|
||||
import { Settings } from "../Settings/Settings";
|
||||
@ -22,10 +24,8 @@ export class RunningScript {
|
||||
// Script arguments
|
||||
args: any[] = [];
|
||||
|
||||
// Holds a map of servers hacked, where server = key and the value for each
|
||||
// server is an array of four numbers. The four numbers represent:
|
||||
// [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
|
||||
// This data is used for offline progress
|
||||
// Map of [key: server ip] -> Hacking data. Used for offline progress calculations.
|
||||
// Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
|
||||
dataMap: IMap<number[]> = {};
|
||||
|
||||
// Script filename
|
||||
|
@ -1,6 +1,9 @@
|
||||
// Class representing a script file
|
||||
// This does NOT represent a script that is actively running and
|
||||
// being evaluated. See RunningScript for that
|
||||
/**
|
||||
* Class representing a script file.
|
||||
*
|
||||
* This does NOT represent a script that is actively running and
|
||||
* being evaluated. See RunningScript for that
|
||||
*/
|
||||
import { calculateRamUsage } from "./RamCalculations";
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
|
||||
@ -34,7 +37,6 @@ export class Script {
|
||||
// IP of server that this script is on.
|
||||
server: string = "";
|
||||
|
||||
|
||||
constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) {
|
||||
this.filename = fn;
|
||||
this.code = code;
|
||||
@ -44,6 +46,9 @@ export class Script {
|
||||
if (this.code !== "") { this.updateRamUsage(otherScripts); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Download the script as a file
|
||||
*/
|
||||
download(): void {
|
||||
const filename = this.filename + ".js";
|
||||
const file = new Blob([this.code], {type: 'text/plain'});
|
||||
@ -63,10 +68,14 @@ export class Script {
|
||||
}
|
||||
}
|
||||
|
||||
// Save a script FROM THE SCRIPT EDITOR
|
||||
/**
|
||||
* Save a script from the script editor
|
||||
* @param {string} code - The new contents of the script
|
||||
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
||||
*/
|
||||
saveScript(code: string, serverIp: string, otherScripts: Script[]): void {
|
||||
if (routing.isOn(Page.ScriptEditor)) {
|
||||
//Update code and filename
|
||||
// Update code and filename
|
||||
this.code = code.replace(/^\s+|\s+$/g, '');
|
||||
|
||||
const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement;
|
||||
@ -75,18 +84,16 @@ export class Script {
|
||||
return;
|
||||
}
|
||||
this.filename = filenameElem!.value;
|
||||
|
||||
// Server
|
||||
this.server = serverIp;
|
||||
|
||||
//Calculate/update ram usage, execution time, etc.
|
||||
this.updateRamUsage(otherScripts);
|
||||
|
||||
this.module = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the script's RAM usage based on its code
|
||||
/**
|
||||
* Calculates and updates the script's RAM usage based on its code
|
||||
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
|
||||
*/
|
||||
async updateRamUsage(otherScripts: Script[]) {
|
||||
var res = await calculateRamUsage(this.code, otherScripts);
|
||||
if (res > 0) {
|
||||
|
@ -53,7 +53,8 @@ import {
|
||||
import { showLiterature } from "./Literature";
|
||||
import { Message } from "./Message/Message";
|
||||
import { showMessage } from "./Message/MessageHelpers";
|
||||
import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
|
||||
import { addWorkerScript } from "./NetscriptWorker";
|
||||
import { killWorkerScript } from "./Netscript/killWorkerScript";
|
||||
import { Player } from "./Player";
|
||||
import { hackWorldDaemon } from "./RedPill";
|
||||
import { RunningScript } from "./Script/RunningScript";
|
||||
|
@ -1,9 +1,13 @@
|
||||
/**
|
||||
* Game engine. Handles the main game loop as well as the main UI pages
|
||||
*
|
||||
* TODO: Separate UI functionality into its own component
|
||||
*/
|
||||
import {
|
||||
convertTimeMsToTimeElapsedString,
|
||||
replaceAt
|
||||
} from "../utils/StringHelperFunctions";
|
||||
import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox";
|
||||
import { updateActiveScriptsItems } from "./ActiveScriptsUI";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import {
|
||||
initAugmentations,
|
||||
@ -41,9 +45,9 @@ import { LocationName } from "./Locations/data/LocationNames";
|
||||
import { LocationRoot } from "./Locations/ui/Root";
|
||||
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
|
||||
import { inMission, currMission } from "./Missions";
|
||||
import { workerScripts } from "./Netscript/WorkerScripts";
|
||||
import {
|
||||
loadAllRunningScripts,
|
||||
runScriptsLoop,
|
||||
updateOnlineScriptTimes,
|
||||
} from "./NetscriptWorker";
|
||||
import { Player } from "./Player";
|
||||
@ -87,6 +91,8 @@ import { displayCharacterInfo } from "./ui/displayCharacterInfo";
|
||||
import { Page, routing } from "./ui/navigationTracking";
|
||||
import { numeralWrapper } from "./ui/numeralFormat";
|
||||
import { setSettingsLabels } from "./ui/setSettingsLabels";
|
||||
|
||||
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
|
||||
import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers";
|
||||
import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links";
|
||||
|
||||
@ -259,7 +265,10 @@ const Engine = {
|
||||
Engine.hideAllContent();
|
||||
Engine.Display.activeScriptsContent.style.display = "block";
|
||||
routing.navigateTo(Page.ActiveScripts);
|
||||
updateActiveScriptsItems();
|
||||
ReactDOM.render(
|
||||
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
||||
Engine.Display.activeScriptsContent
|
||||
)
|
||||
MainMenuLinks.ActiveScripts.classList.add("active");
|
||||
},
|
||||
|
||||
@ -471,7 +480,10 @@ const Engine = {
|
||||
Engine.Display.terminalContent.style.display = "none";
|
||||
Engine.Display.characterContent.style.display = "none";
|
||||
Engine.Display.scriptEditorContent.style.display = "none";
|
||||
|
||||
Engine.Display.activeScriptsContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
|
||||
|
||||
clearHacknetNodesUI();
|
||||
Engine.Display.createProgramContent.style.display = "none";
|
||||
|
||||
@ -802,13 +814,14 @@ const Engine = {
|
||||
}
|
||||
|
||||
if (Engine.Counters.updateActiveScriptsDisplay <= 0) {
|
||||
// Always update, but make the interval longer if the page isn't active
|
||||
updateActiveScriptsItems();
|
||||
if (routing.isOn(Page.ActiveScripts)) {
|
||||
Engine.Counters.updateActiveScriptsDisplay = 5;
|
||||
} else {
|
||||
Engine.Counters.updateActiveScriptsDisplay = 10;
|
||||
ReactDOM.render(
|
||||
<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />,
|
||||
Engine.Display.activeScriptsContent
|
||||
)
|
||||
}
|
||||
|
||||
Engine.Counters.updateActiveScriptsDisplay = 5;
|
||||
}
|
||||
|
||||
if (Engine.Counters.updateDisplays <= 0) {
|
||||
@ -1515,9 +1528,6 @@ const Engine = {
|
||||
start: function() {
|
||||
// Run main loop
|
||||
Engine.idleTimer();
|
||||
|
||||
// Script-processing loop
|
||||
runScriptsLoop();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import "../css/characteroverview.scss";
|
||||
import "../css/terminal.scss";
|
||||
import "../css/scripteditor.scss";
|
||||
import "../css/codemirror-overrides.scss";
|
||||
import "../css/activescripts.scss";
|
||||
import "../css/hacknetnodes.scss";
|
||||
import "../css/menupages.scss";
|
||||
import "../css/augmentations.scss";
|
||||
|
38
src/ui/ActiveScripts/Root.tsx
Normal file
38
src/ui/ActiveScripts/Root.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Root React Component for the "Active Scripts" UI page. This page displays
|
||||
* and provides information about all of the player's scripts that are currently running
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { ScriptProduction } from "./ScriptProduction";
|
||||
import { ServerAccordions } from "./ServerAccordions";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export class ActiveScriptsRoot extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
This page displays a list of all of your scripts that are currently
|
||||
running across every machine. It also provides information about each
|
||||
script's production. The scripts are categorized by the hostname of
|
||||
the servers on which they are running.
|
||||
</p>
|
||||
|
||||
<ScriptProduction {...this.props} />
|
||||
<ServerAccordions {...this.props} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
46
src/ui/ActiveScripts/ScriptProduction.tsx
Normal file
46
src/ui/ActiveScripts/ScriptProduction.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* React Component for displaying the total production and production rate
|
||||
* of scripts on the 'Active Scripts' UI page
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
type IProps = {
|
||||
p: IPlayer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export function ScriptProduction(props: IProps): React.ReactElement {
|
||||
const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000);
|
||||
|
||||
let onlineProduction = 0;
|
||||
for (const ws of props.workerScripts) {
|
||||
onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime);
|
||||
}
|
||||
|
||||
return (
|
||||
<p id="active-scripts-total-prod">
|
||||
Total online production of Active scripts:
|
||||
<span className="money-gold">
|
||||
<span id="active-scripts-total-production-active">
|
||||
{numeralWrapper.formatMoney(onlineProduction)}
|
||||
</span> / sec
|
||||
</span><br />
|
||||
|
||||
Total online production since last Aug installation:
|
||||
<span id="active-scripts-total-prod-aug-total" className="money-gold">
|
||||
{numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)}
|
||||
</span>
|
||||
|
||||
(<span className="money-gold">
|
||||
<span id="active-scripts-total-prod-aug-avg" className="money-gold">
|
||||
{numeralWrapper.formatMoney(prodRateSinceLastAug)}
|
||||
</span> / sec
|
||||
</span>)
|
||||
</p>
|
||||
)
|
||||
}
|
49
src/ui/ActiveScripts/ServerAccordion.tsx
Normal file
49
src/ui/ActiveScripts/ServerAccordion.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* React Component for rendering the Accordion element for a single
|
||||
* server in the 'Active Scripts' UI page
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { WorkerScriptAccordion } from "./WorkerScriptAccordion";
|
||||
import { Accordion } from "../React/Accordion";
|
||||
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
|
||||
type IProps = {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
export function ServerAccordion(props: IProps): React.ReactElement {
|
||||
const server = props.server;
|
||||
|
||||
// Accordion's header text
|
||||
// TODO: calculate the longest hostname length rather than hard coding it
|
||||
const longestHostnameLength = 18;
|
||||
const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength));
|
||||
const barOptions = {
|
||||
progress: server.ramUsed / server.maxRam,
|
||||
totalTicks: 30
|
||||
};
|
||||
const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`;
|
||||
|
||||
const scripts = props.workerScripts.map((ws) => {
|
||||
return (
|
||||
<WorkerScriptAccordion key={`${ws.name}_${ws.args}`} workerScript={ws} />
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerContent={
|
||||
<pre>{headerTxt}</pre>
|
||||
}
|
||||
panelContent={
|
||||
<ul>{scripts}</ul>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
109
src/ui/ActiveScripts/ServerAccordions.tsx
Normal file
109
src/ui/ActiveScripts/ServerAccordions.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* React Component for rendering the Accordion elements for all servers
|
||||
* on which scripts are running
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { ServerAccordion } from "./ServerAccordion";
|
||||
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter";
|
||||
import { getServer } from "../../Server/ServerHelpers";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
|
||||
// Map of server hostname -> all workerscripts on that server for all active scripts
|
||||
interface IServerData {
|
||||
server: BaseServer;
|
||||
workerScripts: WorkerScript[];
|
||||
}
|
||||
|
||||
interface IServerToScriptsMap {
|
||||
[key: string]: IServerData;
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
workerScripts: WorkerScript[];
|
||||
};
|
||||
|
||||
type IState = {
|
||||
rerenderFlag: boolean;
|
||||
}
|
||||
|
||||
|
||||
const subscriberId = "ActiveScriptsUI";
|
||||
|
||||
export class ServerAccordions extends React.Component<IProps, IState> {
|
||||
serverToScriptMap: IServerToScriptsMap = {};
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rerenderFlag: false,
|
||||
}
|
||||
|
||||
this.updateServerToScriptsMap();
|
||||
|
||||
this.rerender = this.rerender.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
WorkerScriptStartStopEventEmitter.addSubscriber({
|
||||
cb: this.rerender,
|
||||
id: subscriberId,
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId);
|
||||
}
|
||||
|
||||
updateServerToScriptsMap(): void {
|
||||
const map: IServerToScriptsMap = {};
|
||||
|
||||
for (const ws of this.props.workerScripts) {
|
||||
const server = getServer(ws.serverIp);
|
||||
if (server == null) {
|
||||
console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (map[server.hostname] == null) {
|
||||
map[server.hostname] = {
|
||||
server: server,
|
||||
workerScripts: [],
|
||||
};
|
||||
}
|
||||
|
||||
map[server.hostname].workerScripts.push(ws);
|
||||
}
|
||||
|
||||
this.serverToScriptMap = map;
|
||||
}
|
||||
|
||||
rerender() {
|
||||
this.updateServerToScriptsMap();
|
||||
this.setState((prevState) => {
|
||||
return { rerenderFlag: !prevState.rerenderFlag }
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const elems = Object.keys(this.serverToScriptMap).map((serverName) => {
|
||||
const data = this.serverToScriptMap[serverName];
|
||||
return (
|
||||
<ServerAccordion
|
||||
key={serverName}
|
||||
server={data.server}
|
||||
workerScripts={data.workerScripts}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<ul className="active-scripts-list" id="active-scripts-list">
|
||||
{elems}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
}
|
77
src/ui/ActiveScripts/WorkerScriptAccordion.tsx
Normal file
77
src/ui/ActiveScripts/WorkerScriptAccordion.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* React Component for displaying a single WorkerScript's info as an
|
||||
* Accordion element
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
import { numeralWrapper } from "../numeralFormat";
|
||||
|
||||
import { Accordion } from "../React/Accordion";
|
||||
import { AccordionButton } from "../React/AccordionButton";
|
||||
|
||||
import { killWorkerScript } from "../../Netscript/killWorkerScript";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { logBoxCreate } from "../../../utils/LogBox";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
import { arrayToString } from "../../../utils/helpers/arrayToString";
|
||||
|
||||
type IProps = {
|
||||
workerScript: WorkerScript;
|
||||
}
|
||||
|
||||
export function WorkerScriptAccordion(props: IProps): React.ReactElement {
|
||||
const workerScript = props.workerScript;
|
||||
const scriptRef = workerScript.scriptRef;
|
||||
|
||||
|
||||
const logClickHandler = logBoxCreate.bind(null, scriptRef);
|
||||
const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server);
|
||||
|
||||
function killScriptClickHandler() {
|
||||
killScript();
|
||||
dialogBoxCreate("Killing script");
|
||||
}
|
||||
|
||||
// Calculations for script stats
|
||||
const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime;
|
||||
const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime;
|
||||
const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime;
|
||||
const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime;
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
headerClass="active-scripts-script-header"
|
||||
headerContent={
|
||||
<>{props.workerScript.name}</>
|
||||
}
|
||||
panelClass="active-scripts-script-panel"
|
||||
panelContent={
|
||||
<>
|
||||
<pre>Threads: {props.workerScript.scriptRef.threads}</pre>
|
||||
<pre>Args: {arrayToString(props.workerScript.args)}</pre>
|
||||
<pre>Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}</pre>
|
||||
<pre>Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}</pre>
|
||||
<pre>Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}</pre>
|
||||
<pre>{(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp")}</pre>
|
||||
<pre>Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second</pre>
|
||||
<pre>{(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second")}</pre>
|
||||
<pre>Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}</pre>
|
||||
<pre>{(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp")}</pre>
|
||||
<pre>Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second</pre>
|
||||
<pre>{(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second")}</pre>
|
||||
|
||||
<AccordionButton
|
||||
onClick={logClickHandler}
|
||||
text="Log"
|
||||
/>
|
||||
<AccordionButton
|
||||
onClick={killScriptClickHandler}
|
||||
text="Kill Script"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
@ -4,9 +4,12 @@
|
||||
import * as React from "react";
|
||||
|
||||
type IProps = {
|
||||
headerClass?: string; // Override default class
|
||||
headerContent: React.ReactElement;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
panelInitiallyOpened?: boolean;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
type IState = {
|
||||
@ -44,12 +47,21 @@ export class Accordion extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "accordion-header";
|
||||
if (typeof this.props.headerClass === "string") {
|
||||
className = this.props.headerClass;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className={"accordion-header"} onClick={this.handleHeaderClick}>
|
||||
<button className={className} onClick={this.handleHeaderClick}>
|
||||
{this.props.headerContent}
|
||||
</button>
|
||||
<AccordionPanel opened={this.state.panelOpened} panelContent={this.props.panelContent} />
|
||||
<AccordionPanel
|
||||
opened={this.state.panelOpened}
|
||||
panelClass={this.props.panelClass}
|
||||
panelContent={this.props.panelContent}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -57,6 +69,7 @@ export class Accordion extends React.Component<IProps, IState> {
|
||||
|
||||
type IPanelProps = {
|
||||
opened: boolean;
|
||||
panelClass?: string; // Override default class
|
||||
panelContent: React.ReactElement;
|
||||
}
|
||||
|
||||
@ -66,8 +79,13 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = "accordion-panel"
|
||||
if (typeof this.props.panelClass === "string") {
|
||||
className = this.props.panelClass;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"accordion-panel"}>
|
||||
<div className={className}>
|
||||
{this.props.panelContent}
|
||||
</div>
|
||||
)
|
||||
|
52
src/ui/React/AccordionButton.tsx
Normal file
52
src/ui/React/AccordionButton.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Basic stateless button that uses the 'accordion-button' css class.
|
||||
* This class has a black background so that it does not clash with the default
|
||||
* accordion coloring
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
interface IProps {
|
||||
addClasses?: string;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
onClick?: (e: React.MouseEvent<HTMLElement>) => any;
|
||||
style?: object;
|
||||
text: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
type IInnerHTMLMarkup = {
|
||||
__html: string;
|
||||
}
|
||||
|
||||
export function AccordionButton(props: IProps): React.ReactElement {
|
||||
const hasTooltip = props.tooltip != null && props.tooltip !== "";
|
||||
|
||||
// TODO Add a disabled class for accordion buttons?
|
||||
let className = "accordion-button";
|
||||
if (hasTooltip) {
|
||||
className += " tooltip";
|
||||
}
|
||||
|
||||
if (typeof props.addClasses === "string") {
|
||||
className += ` ${props.addClasses}`;
|
||||
}
|
||||
|
||||
// Tooltip will be set using inner HTML
|
||||
let tooltipMarkup: IInnerHTMLMarkup | null;
|
||||
if (hasTooltip) {
|
||||
tooltipMarkup = {
|
||||
__html: props.tooltip!
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} id={props.id} onClick={props.onClick} style={props.style}>
|
||||
{props.text}
|
||||
{
|
||||
hasTooltip &&
|
||||
<span className={"tooltiptext"} dangerouslySetInnerHTML={tooltipMarkup!}></span>
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
50
src/utils/EventEmitter.ts
Normal file
50
src/utils/EventEmitter.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Generic Event Emitter class following a subscribe/publish paradigm.
|
||||
*/
|
||||
import { IMap } from "../types";
|
||||
|
||||
type cbFn = (...args: any[]) => any;
|
||||
|
||||
export interface ISubscriber {
|
||||
/**
|
||||
* Callback function that will be run when an event is emitted
|
||||
*/
|
||||
cb: cbFn;
|
||||
|
||||
/**
|
||||
* Name/identifier for this subscriber
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class EventEmitter {
|
||||
/**
|
||||
* Map of Subscriber name -> Callback function
|
||||
*/
|
||||
subscribers: IMap<cbFn> = {};
|
||||
|
||||
constructor(subs?: ISubscriber[]) {
|
||||
if (Array.isArray(subs)) {
|
||||
for (const s of subs) {
|
||||
this.addSubscriber(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSubscriber(s: ISubscriber) {
|
||||
this.subscribers[s.id] = s.cb;
|
||||
}
|
||||
|
||||
emitEvent(...args: any[]): void {
|
||||
for (const s in this.subscribers) {
|
||||
const cb = this.subscribers[s];
|
||||
|
||||
cb(args);
|
||||
}
|
||||
}
|
||||
|
||||
removeSubscriber(id: string) {
|
||||
delete this.subscribers[id];
|
||||
}
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import {killWorkerScript} from "../src/NetscriptWorker";
|
||||
import {clearEventListeners} from "./uiHelpers/clearEventListeners";
|
||||
import {arrayToString} from "./helpers/arrayToString";
|
||||
|
||||
$(document).keydown(function(event) {
|
||||
if (logBoxOpened && event.keyCode == 27) {
|
||||
logBoxClose();
|
||||
}
|
||||
});
|
||||
|
||||
function logBoxInit() {
|
||||
var closeButton = document.getElementById("log-box-close");
|
||||
logBoxClose();
|
||||
|
||||
//Close Dialog box
|
||||
closeButton.addEventListener("click", function() {
|
||||
logBoxClose();
|
||||
return false;
|
||||
});
|
||||
document.getElementById("log-box-text-header").style.display = "inline-block";
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", logBoxInit, false);
|
||||
|
||||
function logBoxClose() {
|
||||
logBoxOpened = false;
|
||||
var logBox = document.getElementById("log-box-container");
|
||||
logBox.style.display = "none";
|
||||
}
|
||||
|
||||
function logBoxOpen() {
|
||||
logBoxOpened = true;
|
||||
|
||||
var logBox = document.getElementById("log-box-container");
|
||||
logBox.style.display = "block";
|
||||
}
|
||||
|
||||
|
||||
var logBoxOpened = false;
|
||||
var logBoxCurrentScript = null;
|
||||
function logBoxCreate(script) {
|
||||
logBoxCurrentScript = script;
|
||||
var killScriptBtn = clearEventListeners("log-box-kill-script");
|
||||
killScriptBtn.addEventListener("click", ()=>{
|
||||
killWorkerScript(script, script.server);
|
||||
return false;
|
||||
});
|
||||
document.getElementById('log-box-kill-script').style.display = "inline-block";
|
||||
logBoxOpen();
|
||||
document.getElementById("log-box-text-header").innerHTML =
|
||||
logBoxCurrentScript.filename + " " + arrayToString(logBoxCurrentScript.args) + ":<br><br>";
|
||||
logBoxCurrentScript.logUpd = true;
|
||||
logBoxUpdateText();
|
||||
}
|
||||
|
||||
function logBoxUpdateText() {
|
||||
var txt = document.getElementById("log-box-text");
|
||||
if (logBoxCurrentScript && logBoxOpened && txt && logBoxCurrentScript.logUpd) {
|
||||
txt.innerHTML = "";
|
||||
for (var i = 0; i < logBoxCurrentScript.logs.length; ++i) {
|
||||
txt.innerHTML += logBoxCurrentScript.logs[i];
|
||||
txt.innerHTML += "<br>";
|
||||
}
|
||||
logBoxCurrentScript.logUpd = false;
|
||||
}
|
||||
}
|
||||
|
||||
export {logBoxCreate, logBoxUpdateText, logBoxOpened, logBoxCurrentScript};
|
106
utils/LogBox.ts
Normal file
106
utils/LogBox.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { killWorkerScript } from "../src/Netscript/killWorkerScript";
|
||||
import { RunningScript } from "../src/Script/RunningScript";
|
||||
|
||||
import { clearEventListeners } from "./uiHelpers/clearEventListeners";
|
||||
import { arrayToString } from "./helpers/arrayToString";
|
||||
|
||||
import { KEY } from "./helpers/keyCodes";
|
||||
|
||||
document.addEventListener("keydown", function(event: KeyboardEvent) {
|
||||
if (logBoxOpened && event.keyCode == KEY.ESC) {
|
||||
logBoxClose();
|
||||
}
|
||||
});
|
||||
|
||||
let logBoxContainer: HTMLElement | null;
|
||||
let textHeader: HTMLElement | null;
|
||||
let logText: HTMLElement | null;
|
||||
|
||||
function logBoxInit(): void {
|
||||
// Initialize Close button click listener
|
||||
const closeButton = document.getElementById("log-box-close");
|
||||
if (closeButton == null) {
|
||||
console.error(`Could not find LogBox's close button`);
|
||||
return;
|
||||
}
|
||||
|
||||
closeButton.addEventListener("click", function() {
|
||||
logBoxClose();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Initialize text header
|
||||
textHeader = document.getElementById("log-box-text-header");
|
||||
if (textHeader instanceof HTMLElement) {
|
||||
textHeader.style.display = "inline-block";
|
||||
}
|
||||
|
||||
// Initialize references to other DOM elements
|
||||
logBoxContainer = document.getElementById("log-box-container");
|
||||
logText = document.getElementById("log-box-text");
|
||||
|
||||
logBoxClose();
|
||||
|
||||
document.removeEventListener("DOMContentLoaded", logBoxInit);
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", logBoxInit);
|
||||
|
||||
function logBoxClose() {
|
||||
logBoxOpened = false;
|
||||
if (logBoxContainer instanceof HTMLElement) {
|
||||
logBoxContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function logBoxOpen() {
|
||||
logBoxOpened = true;
|
||||
|
||||
if (logBoxContainer instanceof HTMLElement) {
|
||||
logBoxContainer.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export let logBoxOpened = false;
|
||||
let logBoxCurrentScript: RunningScript | null = null;
|
||||
export function logBoxCreate(script: RunningScript) {
|
||||
logBoxCurrentScript = script;
|
||||
|
||||
const killScriptBtn = clearEventListeners("log-box-kill-script");
|
||||
if (killScriptBtn == null) {
|
||||
console.error(`Could not find LogBox's 'Kill Script' button`);
|
||||
return;
|
||||
}
|
||||
|
||||
killScriptBtn.addEventListener("click", () => {
|
||||
killWorkerScript(script, script.server);
|
||||
return false;
|
||||
});
|
||||
|
||||
killScriptBtn.style.display = "inline-block";
|
||||
|
||||
logBoxOpen();
|
||||
|
||||
if (textHeader instanceof HTMLElement) {
|
||||
textHeader.innerHTML = `${logBoxCurrentScript.filename} ${arrayToString(logBoxCurrentScript.args)}:<br><br>`;
|
||||
} else {
|
||||
console.warn(`LogBox's Text Header DOM element is null`);
|
||||
}
|
||||
|
||||
logBoxCurrentScript.logUpd = true;
|
||||
logBoxUpdateText();
|
||||
}
|
||||
|
||||
export function logBoxUpdateText() {
|
||||
if (!(logText instanceof HTMLElement)) { return; }
|
||||
|
||||
if (logBoxCurrentScript && logBoxOpened && logBoxCurrentScript.logUpd) {
|
||||
logText.innerHTML = "";
|
||||
for (let i = 0; i < logBoxCurrentScript.logs.length; ++i) {
|
||||
logText.innerHTML += logBoxCurrentScript.logs[i];
|
||||
logText.innerHTML += "<br>";
|
||||
}
|
||||
logBoxCurrentScript.logUpd = false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user