Implement Script Editor (mostly)

This commit is contained in:
Daniel Xie 2016-11-24 16:30:33 -06:00
parent 1a600ad560
commit f38fd340f4
7 changed files with 322 additions and 59 deletions

68
css/menupages.css Normal file

@ -0,0 +1,68 @@
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
/* Character Info */
#character-container {
position: fixed;
padding-top: 10px;
padding-left: 10px;
margin-left: 10%;
width: 99%;
}
/* Script Editor */
/* This temp element is used for auto adjusting filename field */
.tmp-element {
visibility: hidden;
white-space: pre;
}
#script-editor-container {
position: fixed;
padding-top: 10px;
padding-left: 10px;
height: 100%;
margin-left: 10%;
width: 99%;
color: #66ff33;
}
#script-editor-filename-row-div {
color: #66ff33;
}
#script-editor-filename-tag {
float: left;
}
#script-editor-filename {
float: left;
resize: none;
color: #66ff33;
width: 100%;
border: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
#script-editor-status {
float: left;
color: #ffffff;
}
#script-editor-text {
color: #66ff33;
width: 90%;
height: 100%;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

@ -13,6 +13,7 @@ p {
color: #66ff33; color: #66ff33;
} }
.mainmenu { .mainmenu {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
@ -46,14 +47,6 @@ p {
color: white; color: white;
} }
#character-container {
position: fixed;
padding-top: 10px;
padding-left: 10px;
margin-left: 10%;
width: 99%;
}
/* Style all the buttons */ /* Style all the buttons */
input[type=button] { input[type=button] {
width: 100px; width: 100px;

@ -5,6 +5,7 @@
<title>Netburner</title> <title>Netburner</title>
<link rel="stylesheet" type="text/css" href="css/styles.css" /> <link rel="stylesheet" type="text/css" href="css/styles.css" />
<link rel="stylesheet" type="text/css" href="css/terminal.css" /> <link rel="stylesheet" type="text/css" href="css/terminal.css" />
<link rel="stylesheet" type="text/css" href="css/menupages.css" />
<!-- We'll add in the jQuery library here - direct from <!-- We'll add in the jQuery library here - direct from
the Google CDN (Content Delivery Network). --> the Google CDN (Content Delivery Network). -->
@ -22,10 +23,10 @@
<script src="src/Faction.js"></script> <script src="src/Faction.js"></script>
<script src="src/Company.js"></script> <script src="src/Company.js"></script>
<script src="src/Terminal.js"></script> <script src="src/Terminal.js"></script>
<script src="src/Script.js"></script>
<script src="src/engine.js"></script> <script src="src/engine.js"></script>
</head> </head>
<body> <body>
<div id="mainmenu-container"> <div id="mainmenu-container">
@ -39,7 +40,7 @@
<a href="#" id="character-menu-link"> Character </a> <a href="#" id="character-menu-link"> Character </a>
</li> </li>
<li class="create-script-tab" style="display:none"> <li class="create-script-tab">
<a href="#" id="create-script-menu-link"> Create Script </a> <a href="#" id="create-script-menu-link"> Create Script </a>
</li> </li>
@ -57,29 +58,35 @@
</ul> </ul>
</div> </div>
<!-- Terminal page -->
<div id="terminal-container"> <div id="terminal-container">
<!-- Terminal -->
<table id="terminal"> <table id="terminal">
<tr id="terminal-input"> <tr id="terminal-input">
<td id="terminal-input-td">$ <input type="text" class="terminal-input"/></td> <td id="terminal-input-td">$ <input type="text" class="terminal-input"/></td>
</tr> </tr>
</table> <!-- End terminal --> </table>
</div> <!-- End terminal-container --> </div>
<!-- Character Info page -->
<div id="character-container"> <div id="character-container">
<p id="character-info"> </p> <p id="character-info"> </p>
</div> <!-- End character-container --> </div>
<!-- Script editor -->
<div id="script-editor-container">
<div id="script-editor-filename-row-div" >
<p id="script-editor-filename-tag"> Script name (edit below): </p>
<input id="script-editor-filename" type="text" maxlength="30"> </input>
<br>
<p id="script-editor-status"> </p>
</div>
<br><br>
<textarea id="script-editor-text" style="border: none" autofocus>
</textarea>
</div>
<!-- Always keep the terminal in focus -->
<script type="text/javascript">
$(document).ready(function() {
$('.terminal-input').focus();
});
$(document).keydown(function() {
$('.terminal-input').focus();
})
</script>
</body> </body>

@ -1,14 +1,77 @@
/* Script.js /* Script.js
* Script object * Script object
*/ */
//Define commands in script editor (ctrl x to close, etc.)
$(document).keydown(function(e) {
if (Engine.currentPage == Engine.Page.ScriptEditor) {
if (e.keyCode == 88 && e.ctrlKey) {
var filename = document.getElementById("script-editor-filename").value;
if (checkValidFilename(filename) == false) {
postScriptEditorStatus("Script filename can contain only alphanumerics, hyphens, and underscores");
return;
}
filename += ".script";
//If the current script matches one thats currently running, throw an error
for (var i = 0; i < Player.currentServer.runningScripts.length; i++) {
if (filename == Player.currentServer.runningScripts[i].filename) {
postScriptEditorStatus("Cannot write to script that is currently running!");
return;
}
}
//If the current script already exists on the server, overwrite it
for (var i = 0; i < Player.currentServer.scripts.length; i++) {
if (filename == Player.currentServer.scripts[i].filename) {
Player.currentServer.scripts[i].saveScript();
Engine.loadTerminalContent();
return;
}
}
//If the current script does NOT exist, create a new one
var script = new Script();
script.saveScript();
Player.currentServer.scripts.push(script);
Engine.loadTerminalContent();
}
}
});
//Checks that the string contains only valid characters for a filename, which are alphanumeric,
// underscores and hyphens
function checkValidFilename(filename) {
var regex = /^[a-zA-Z0-9_-]+$/;
if (filename.match(regex)) {
return true;
}
return false;
}
var ScriptEditorLastStatus = null;
function postScriptEditorStatus(text) {
document.getElementById("script-editor-status").innerHTML = text;
clearTimeout(ScriptEditorLastStatus);
ScriptEditorLastStatus = setTimeout(function() {
document.getElementById("script-editor-status").innerHTML = "";
}, 3000);
}
function Script() { function Script() {
//Function queue that holds the next functions to be //Function queue that holds the next functions to be
//executed in this script. A function from this queue //executed in this script. A function from this queue
//is executed every second (this may change) //is executed every second (this may change)
this.functionQueue = []; this.functionQueue = [];
this.filename = "";
this.code = ""; this.code = "";
this.ramUsage = 0; this.ramUsage = 0;
this.server = null; //Which server this script is on
/* Properties to calculate offline progress. Only applies for infinitely looping scripts */ /* Properties to calculate offline progress. Only applies for infinitely looping scripts */
@ -22,18 +85,31 @@ function Script() {
//Which servers are hacked in one iteration of the script. May contain duplicates //Which servers are hacked in one iteration of the script. May contain duplicates
this.serversHacked = []; this.serversHacked = [];
} };
//Execute the next function in the Script's function queue //Execute the next function in the Script's function queue
Script.prototype.executeNext() { Script.prototype.executeNext = function() {
if (this.functionQueue.length <= 0) {return;} if (this.functionQueue.length <= 0) {return;}
//Shift the next element off ths function queue and then execute it //Shift the next element off ths function queue and then execute it
(this.functionQueue.shift())(); (this.functionQueue.shift())();
} }
Script.prototype.setCode(code) { //Get the script data from the Script Editor and save it to the object
this.code = code; Script.prototype.saveScript = function() {
if (Engine.currentPage == Engine.Page.ScriptEditor) {
//Update code and filename
var code = document.getElementById("script-editor-text").value;
this.code = code;
var filename = document.getElementById("script-editor-filename").value + ".script";
this.filename = filename;
//Server
this.server = Player.currentServer;
//TODO Calculate/update number of instructions, ram usage, execution time, etc.
}
} }
/* Wrapper object that wraps a function with its arguments. /* Wrapper object that wraps a function with its arguments.
@ -51,7 +127,7 @@ Script.prototype.setCode(code) {
* fooObj(); * fooObj();
* *
*/ */
function functionObject = function(fn, context, params) { function functionObject(fn, context, params) {
return function() { return function() {
fn.apply(context, params); fn.apply(context, params);
} }

@ -19,7 +19,6 @@ function Server() {
this.scripts = []; this.scripts = [];
this.runningScripts = []; //Scripts currently being run this.runningScripts = []; //Scripts currently being run
this.scriptEnvs = []; //The environment for all of the running scripts. Matched by index number
this.programs = []; this.programs = [];
/* Hacking information (only valid for "foreign" aka non-purchased servers) */ /* Hacking information (only valid for "foreign" aka non-purchased servers) */

@ -19,20 +19,35 @@ var postNetburnerText = function() {
post("Netburner v1.0"); post("Netburner v1.0");
} }
//command is checked on enter key press //Defines what happens when enter is pressed (keycode 13)
$(document).keyup(function(event) { $(document).keyup(function(event) {
if (event.keyCode == 13) { //Terminal
var command = $('input[class=terminal-input]').val(); if (Engine.currentPage == Engine.Page.Terminal) {
if (command.length > 0) { if (event.keyCode == 13) {
post("> " + command); var command = $('input[class=terminal-input]').val();
if (command.length > 0) {
//TODO Do i have to switch the order of these two? post("> " + command);
Terminal.executeCommand(command);
$('input[class=terminal-input]').val(""); //TODO Do i have to switch the order of these two?
Terminal.executeCommand(command);
$('input[class=terminal-input]').val("");
}
} }
} }
}); });
//Keep terminal in focus
$(document).ready(function() {
if (Engine.currentPage == Engine.Page.Terminal) {
$('.terminal-input').focus();
}
});
$(document).keydown(function() {
if (Engine.currentPage == Engine.Page.Terminal) {
$('.terminal-input').focus();
}
})
var Terminal = { var Terminal = {
//Flags to determine whether the player is currently running a hack or an analyze //Flags to determine whether the player is currently running a hack or an analyze
hackFlag: false, hackFlag: false,
@ -136,6 +151,9 @@ var Terminal = {
switch (commandArray[0]) { switch (commandArray[0]) {
case "analyze": case "analyze":
if (commandArray.length != 1) {
post("Incorrect usage of analyze command. Usage: analyze"); return;
}
//Analyze the current server for information //Analyze the current server for information
console.log("analyze terminal command called"); console.log("analyze terminal command called");
Terminal.analyzeFlag = true; Terminal.analyzeFlag = true;
@ -151,6 +169,9 @@ var Terminal = {
break; break;
case "clear": case "clear":
case "cls": case "cls":
if (commandArray.length != 1) {
post("Incorrect usage of clear/cls command. Usage: clear/cls"); return;
}
console.log("cls/clear terminal command called"); console.log("cls/clear terminal command called");
$("#terminal tr:not(:last)").remove(); $("#terminal tr:not(:last)").remove();
postNetburnerText(); postNetburnerText();
@ -177,12 +198,18 @@ var Terminal = {
post("Host not found"); post("Host not found");
break; break;
case "df": case "df":
if (commandArray.length != 1) {
post("Incorrect usage of df command. Usage: df"); return;
}
console.log("df terminal command called"); console.log("df terminal command called");
post("Total: " + Player.currentServer.maxRam.toString() + " GB"); post("Total: " + Player.currentServer.maxRam.toString() + " GB");
post("Used: " + Player.currentServer.ramUsed.toString() + " GB"); post("Used: " + Player.currentServer.ramUsed.toString() + " GB");
post("Available: " + (Player.currentServer.maxRam - Player.currentServer.ramUsed).toString() + " GB"); post("Available: " + (Player.currentServer.maxRam - Player.currentServer.ramUsed).toString() + " GB");
break; break;
case "hack": case "hack":
if (commandArray.length != 1) {
post("Incorrect usage of hack command. Usage: hack"); return;
}
//Hack the current PC (usually for money) //Hack the current PC (usually for money)
//You can't hack your home pc or servers you purchased //You can't hack your home pc or servers you purchased
if (Player.currentServer.purchasedByPlayer) { if (Player.currentServer.purchasedByPlayer) {
@ -207,10 +234,16 @@ var Terminal = {
//TODO //TODO
break; break;
case "hostname": case "hostname":
if (commandArray.length != 1) {
post("Incorrect usage of hostname command. Usage: hostname"); return;
}
//Print the hostname of current system //Print the hostname of current system
post(Player.currentServer.hostname); post(Player.currentServer.hostname);
break; break;
case "ifconfig": case "ifconfig":
if (commandArray.length != 1) {
post("Incorrect usage of ifconfig command. Usage: ifconfig"); return;
}
//Print the IP address of the current system //Print the IP address of the current system
post(Player.currentServer.ip); post(Player.currentServer.ip);
break; break;
@ -218,6 +251,10 @@ var Terminal = {
//TODO //TODO
break; break;
case "ls": case "ls":
if (commandArray.length != 1) {
post("Incorrect usage of ls command. Usage: ls"); return;
}
//Display all programs and scripts //Display all programs and scripts
var allFiles = []; var allFiles = [];
@ -226,7 +263,7 @@ var Terminal = {
allFiles.push(Player.currentServer.programs[i]); allFiles.push(Player.currentServer.programs[i]);
} }
for (var i = 0; i < Player.currentServer.scripts.length; i++) { for (var i = 0; i < Player.currentServer.scripts.length; i++) {
allFiles.push(Player.currentServer.scripts[i]); allFiles.push(Player.currentServer.scripts[i].filename);
} }
//Sort the files alphabetically then print each //Sort the files alphabetically then print each
@ -236,11 +273,41 @@ var Terminal = {
post(allFiles[i]); post(allFiles[i]);
} }
break; break;
case "nano":
if (commandArray.length != 2) {
post("Incorrect usage of nano command. Usage: nano [scriptname]"); return;
}
var filename = commandArray[1];
//Can only edit script files
if (filename.endsWith(".script") == false) {
post("Error: Only .script files are editable with nano (filename must end with .scrip)"); return;
}
//Script name is the filename without the .script at the end
var scriptname = filename.substr(0, filename.indexOf(".script"));
//Cannot edit scripts that are currently running
for (var i = 0; i < Player.currentServer.runningScripts.length; i++) {
if (filename == Player.currentServer.runningScripts[i].filename) {
post("Cannot open/edit scripts that are currently running!"); return;
}
}
//Check if the script already exists
for (var i = 0; i < Player.currentServer.scripts.length; i++) {
if (filename == Player.currentServer.scripts[i].filename) {
Engine.loadScriptEditorContent(scriptname, Player.currentServer.scripts[i].code);
return;
}
}
Engine.loadScriptEditorContent(scriptname, "");
break;
case "netstat": case "netstat":
case "scan": case "scan":
if (commandArray.length != 1) { if (commandArray.length != 1) {
post("Incorrect usage of netstat/scan command. Usage: netstat/scan"); post("Incorrect usage of netstat/scan command. Usage: netstat/scan"); return;
return;
} }
//Displays available network connections using TCP //Displays available network connections using TCP
console.log("netstat/scan terminal command called"); console.log("netstat/scan terminal command called");
@ -268,6 +335,7 @@ var Terminal = {
entry += hasRoot; entry += hasRoot;
post(entry); post(entry);
} }
break;
case "ps": case "ps":
//TODO //TODO
break; break;

@ -17,35 +17,52 @@ var Engine = {
//Main menu buttons //Main menu buttons
terminalMainMenuButton: null, terminalMainMenuButton: null,
characterMainMenuButton: null, characterMainMenuButton: null,
scriptEditorMainMenuButton: null,
}, },
//Display objects //Display objects
Display: { Display: {
//Progress bar //Progress bar
progress: null, progress: null,
//Display for status text (such as "Saved" or "Loaded") //Display for status text (such as "Saved" or "Loaded")
statusText: null, statusText: null,
hacking_skill: null,
hacking_skill: null,
//Main menu content //Main menu content
terminalContent: null, terminalContent: null,
characterContent: null, characterContent: null,
scriptEditorContent: null,
//Character info //Character info
characterInfo: null, characterInfo: null,
//Script editor text
scriptEditorText: null,
}, },
//Current page status
Page: {
Terminal: "Terminal",
CharacterInfo: "CharacterInfo",
ScriptEditor: "ScriptEditor",
},
currentPage: null,
//Time variables (milliseconds unix epoch time) //Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(), _lastUpdate: new Date().getTime(),
_idleSpeed: 200, //Speed (in ms) at which the main loop is updated _idleSpeed: 200, //Speed (in ms) at which the main loop is updated
//Save function //Save function
saveFile: function() { saveGame: function() {
var tempSaveFile = JSON.stringify(Player); var PlayerSave = JSON.stringify(Player);
var ForeignServersSave = JSON.stringify(ForeignServers);
//TODO Add factions + companies here when they're done
window.localStorage.setItem("netburnerSave", tempSaveFile); window.localStorage.setItem("netburnerPlayerSave", PlayerSave);
window.localStorage.setItem("netburnerForeignServersSave", ForeignServersSave)
Engine.displayStatusText("Saved!"); Engine.displayStatusText("Saved!");
}, },
@ -53,11 +70,16 @@ var Engine = {
//Load saved game function //Load saved game function
loadSave: function() { loadSave: function() {
//Check to see if file exists //Check to see if file exists
if (!window.localStorage.getItem("netburnerSave")) { if (!window.localStorage.getItem("netburnerPlayerSave")) {
Engine.displayStatusText("No save file present for load!"); //TODO Add this displayStatusText function
} else { Engine.displayStatusText("No Player save file present for load!");
var tempSaveFile = window.localStorage.getItem("netburnerSave"); } else if (!window.localStorage.getItem("netburnerForeignServersSave")) {
Player = JSON.parse(tempSaveFile); Engine.displayStatusText("No Foreign Serverssave file present for load!");
} else {
var PlayerSave = window.localStorage.getItem("netburnerPlayerSave");
var ForeignServersSave = window.localStorage.getItem("netburnerForeignServersSave");
Player = JSON.parse(PlayerSave);
ForeignServers = JSON.parse(ForeignServersSave);
Engine.displayStatusText("Loaded successfully!"); Engine.displayStatusText("Loaded successfully!");
} }
}, },
@ -76,18 +98,33 @@ var Engine = {
loadTerminalContent: function() { loadTerminalContent: function() {
Engine.hideAllContent(); Engine.hideAllContent();
Engine.Display.terminalContent.style.visibility = "visible"; Engine.Display.terminalContent.style.visibility = "visible";
Engine.currentPage = Engine.Page.Terminal;
}, },
loadCharacterContent: function() { loadCharacterContent: function() {
Engine.hideAllContent(); Engine.hideAllContent();
Engine.Display.characterContent.style.visibility = "visible"; Engine.Display.characterContent.style.visibility = "visible";
Engine.displayCharacterInfo(); Engine.displayCharacterInfo();
Engine.currentPage = Engine.Page.CharacterInfo;
}, },
loadScriptEditorContent: function(filename = "", code = "") {
Engine.hideAllContent();
Engine.Display.scriptEditorContent.style.visibility = "visible";
if (filename == "") {
document.getElementById("script-editor-filename").value = "untitled";
} else {
document.getElementById("script-editor-filename").value = filename;
}
document.getElementById("script-editor-text").value = code;
Engine.currentPage = Engine.Page.ScriptEditor;
},
//Helper function that hides all content //Helper function that hides all content
hideAllContent: function() { hideAllContent: function() {
Engine.Display.terminalContent.style.visibility = "hidden"; Engine.Display.terminalContent.style.visibility = "hidden";
Engine.Display.characterContent.style.visibility = "hidden"; Engine.Display.characterContent.style.visibility = "hidden";
Engine.Display.scriptEditorContent.style.visibility = "hidden";
}, },
/* Display character info */ /* Display character info */
@ -178,6 +215,9 @@ var Engine = {
/* Initialization */ /* Initialization */
init: function() { init: function() {
//Initialization functions //Initialization functions
//TODO Might need to move these into a new "BeginGame()" function. init() is called
//every time the window is opened and we don't want to do these init() functions every
//time
Player.init(); Player.init();
ForeignServers.init(); ForeignServers.init();
@ -216,14 +256,26 @@ var Engine = {
Engine.loadCharacterContent(); Engine.loadCharacterContent();
return false; return false;
}); });
Engine.Clickables.scriptEditorMainMenuButton = document.getElementById("create-script-menu-link");
Engine.Clickables.scriptEditorMainMenuButton.addEventListener("click", function() {
Engine.loadScriptEditorContent();
return false;
});
Engine.Display.terminalContent = document.getElementById("terminal-container"); Engine.Display.terminalContent = document.getElementById("terminal-container");
Engine.currentPage = Engine.Page.Terminal;
Engine.Display.characterContent = document.getElementById("character-container"); Engine.Display.characterContent = document.getElementById("character-container");
Engine.Display.characterContent.style.visibility = "hidden"; Engine.Display.characterContent.style.visibility = "hidden";
Engine.Display.scriptEditorContent = document.getElementById("script-editor-container");
Engine.Display.scriptEditorContent.style.visibility = "hidden";
//Character info //Character info
Engine.Display.characterInfo = document.getElementById("character-info"); Engine.Display.characterInfo = document.getElementById("character-info");
Engine.displayCharacterInfo(); //Engine.displayCharacterInfo(); - Don't think I need this
//Script editor
Engine.Display.scriptEditorText = document.getElementById("script-editor-text");
//Message at the top of terminal //Message at the top of terminal
postNetburnerText(); postNetburnerText();