import {CONSTANTS} from "./Constants.js"; import {Engine} from "./engine.js"; import {displayFactionContent} from "./Faction.js"; import {Player} from "./Player.js"; import {dialogBoxCreate} from "../utils/DialogBox.js"; import {addOffset, getRandomInt, clearEventListenersEl, clearEventListeners} from "../utils/HelperFunctions.js"; import {formatNumber, isString} from "../utils/StringHelperFunctions.js"; import jsplumb from 'jsplumb' let inMission = false; //Flag to denote whether a mission is running let currMission = null; function setInMission(bool, mission) { inMission = bool; if (bool) { currMission = mission; } else { currMission = null; } } //Keyboard shortcuts $(document).keydown(function(e) { if (inMission && currMission && currMission.selectedNode != null) { switch (e.keyCode) { case 65: //a for Attack currMission.actionButtons[0].click(); break; case 83: //s for Scan currMission.actionButtons[1].click(); break; case 87: //w for Weaken currMission.actionButtons[2].click(); break; case 70: //f for Fortify currMission.actionButtons[3].click(); break; case 82: //r for Overflow currMission.actionButtons[4].click(); break; case 68: //d for Detach connection currMission.actionButtons[5].click(); break; default: break; } } }); let NodeTypes = { Core: "CPU Core Node", //All actions available Firewall: "Firewall Node", //No actions available Database: "Database Node", //No actions available Spam: "Spam Node", //No actions Available Transfer: "Transfer Node", //Can Weaken, Scan, Fortify and Overflow Shield: "Shield Node" //Can Fortify } let NodeActions = { Attack: "Attacking", //Damaged based on attack stat + hacking level + opp def Scan: "Scanning", //-Def for target, affected by attack and hacking level Weaken: "Weakening", //-Attack for target, affected by attack and hacking level Fortify: "Fortifying", //+Defense for Node, affected by hacking level Overflow: "Overflowing", //+Attack but -Defense for Node, affected by hacking level } function Node(type, stats) { this.type = type; this.atk = stats.atk ? stats.atk : 0; this.def = stats.def ? stats.def : 0; this.hp = stats.hp ? stats.hp : 0; this.maxhp = this.hp; this.plyrCtrl = false; this.enmyCtrl = false; this.pos = [0, 0]; //x, y this.el = null; //Holds the Node's DOM element this.action = null; //Holds the JsPlumb Connection object for this Node, //where this Node is the Source (since each Node //can only have 1 outgoing Connection) this.conn = null; } Node.prototype.setPosition = function(x, y) { this.pos = [x, y]; } Node.prototype.setControlledByPlayer = function() { this.plyrCtrl = true; this.enmyCtrl = false; if (this.el) { this.el.classList.remove("hack-mission-enemy-node"); this.el.classList.add("hack-mission-player-node"); } } Node.prototype.setControlledByEnemy = function() { this.plyrCtrl = false; this.enmyCtrl = true; if (this.el) { this.el.classList.remove("hack-mission-player-node"); this.el.classList.add("hack-mission-enemy-node"); } } //Sets this node to be the active node Node.prototype.select = function(actionButtons) { if (this.enmyCtrl) {return;} this.el.classList.add("hack-mission-player-node-active"); //Make all buttons inactive for (var i = 0; i < actionButtons.length; ++i) { actionButtons[i].classList.remove("a-link-button"); actionButtons[i].classList.add("a-link-button-inactive"); } switch(this.type) { case NodeTypes.Core: //All buttons active for (var i = 0; i < actionButtons.length; ++i) { actionButtons[i].classList.remove("a-link-button-inactive"); actionButtons[i].classList.add("a-link-button"); } break; case NodeTypes.Transfer: actionButtons[1].classList.remove("a-link-button-inactive"); actionButtons[1].classList.add("a-link-button"); actionButtons[2].classList.remove("a-link-button-inactive"); actionButtons[2].classList.add("a-link-button"); actionButtons[3].classList.remove("a-link-button-inactive"); actionButtons[3].classList.add("a-link-button"); actionButtons[4].classList.remove("a-link-button-inactive"); actionButtons[4].classList.add("a-link-button"); actionButtons[5].classList.remove("a-link-button-inactive"); actionButtons[5].classList.add("a-link-button"); break; default: break; } } Node.prototype.deselect = function(actionButtons) { this.el.classList.remove("hack-mission-player-node-active"); for (var i = 0; i < actionButtons.length; ++i) { actionButtons[i].classList.remove("a-link-button"); actionButtons[i].classList.add("a-link-button-inactive"); } } //Hacking mission instance //Takes in the reputation of the Faction for which the mission is //being conducted function HackingMission(rep, fac) { this.faction = fac; this.started = false; this.time = 120000; //2 minutes, milliseconds this.playerCores = []; this.playerNodes = []; //Non-core nodes this.playerDef = 0; this.enemyCores = []; this.enemyDatabases = []; this.enemyNodes = []; //Non-core nodes this.enemyDef = 0; this.miscNodes = []; this.selectedNode = null; //Which of the player's nodes is currently selected this.actionButtons = []; //DOM buttons for actions this.availablePositions = []; for (var r = 0; r < 8; ++r) { for (var c = 0; c < 8; ++c) { this.availablePositions.push([r, c]); } } this.map = []; for (var i = 0; i < 8; ++i) { this.map.push([null, null, null, null, null, null, null, null]); } this.jsplumbinstance = null; //difficulty capped at 16 this.difficulty = Math.min(16, Math.round(rep / CONSTANTS.HackingMissionRepToDiffConversion) + 1); console.log("difficulty: " + this.difficulty); this.reward = 200 + (rep / CONSTANTS.HackingMissionRepToRewardConversion); } HackingMission.prototype.init = function() { //Create Header DOM this.createPageDom(); //Create player starting nodes var home = Player.getHomeComputer() for (var i = 0; i < home.cpuCores; ++i) { var stats = { atk: (Player.hacking_skill / 10), def: (Player.hacking_skill / 25), hp: (Player.hacking_skill / 5), }; this.playerCores.push(new Node(NodeTypes.Core, stats)); this.playerCores[i].setControlledByPlayer(); this.setNodePosition(this.playerCores[i], 0, i); this.removeAvailablePosition(0, i); } //Randomly generate enemy nodes (CPU and Firewall) based on difficulty var numNodes = getRandomInt(this.difficulty, this.difficulty + 1); var numFirewalls = getRandomInt(this.difficulty, this.difficulty + 2); var numDatabases = getRandomInt(this.difficulty, this.difficulty + 1); var totalNodes = numNodes + numFirewalls + numDatabases; var xlimit = 7 - Math.floor(totalNodes / 8); console.log("numNodes: " + numNodes); console.log("numFirewalls: " + numFirewalls); console.log("numDatabases: " + numDatabases); console.log("totalNodes: " + totalNodes); var randMult = addOffset(this.difficulty, 20); for (var i = 0; i < numNodes; ++i) { var stats = { atk: randMult * getRandomInt(125, 175), def: randMult * getRandomInt(30, 50), hp: randMult * getRandomInt(225, 275) } this.enemyCores.push(new Node(NodeTypes.Core, stats)); this.enemyCores[i].setControlledByEnemy(); this.setNodeRandomPosition(this.enemyCores[i], xlimit); } for (var i = 0; i < numFirewalls; ++i) { var stats = { atk: randMult * getRandomInt(10, 25), def: randMult * getRandomInt(50, 75), hp: randMult * getRandomInt(175, 200) } this.enemyNodes.push(new Node(NodeTypes.Firewall, stats)); this.enemyNodes[i].setControlledByEnemy(); this.setNodeRandomPosition(this.enemyNodes[i], xlimit); } for (var i = 0; i < numDatabases; ++i) { var stats = { atk: randMult * getRandomInt(20, 30), def: randMult * getRandomInt(25, 40), hp: randMult * getRandomInt(120, 150) } var node = new Node(NodeTypes.Database, stats); node.setControlledByEnemy(); this.setNodeRandomPosition(node, xlimit); this.enemyDatabases.push(node); } this.calculateDefenses(); this.createMap(); } HackingMission.prototype.createPageDom = function() { var container = document.getElementById("mission-container"); var headerText = document.createElement("p"); headerText.innerHTML = "You are about to start a hacking mission! For more information " + "about how hacking missions work, click one of the guide links " + "below (one opens up an in-game guide and the other opens up " + "the guide from the wiki). Click the 'Start' button to begin."; headerText.style.display = "block"; headerText.classList.add("hack-mission-header-element"); headerText.style.width = "80%"; var inGameGuideBtn = document.createElement("a"); inGameGuideBtn.innerText = "How to Play"; inGameGuideBtn.classList.add("a-link-button"); inGameGuideBtn.style.display = "inline-block"; inGameGuideBtn.classList.add("hack-mission-header-element"); inGameGuideBtn.addEventListener("click", function() { dialogBoxCreate(CONSTANTS.HackingMissionHowToPlay); return false; }); var wikiGuideBtn = document.createElement("a"); wikiGuideBtn.innerText = "Wiki Guide"; wikiGuideBtn.classList.add("a-link-button"); wikiGuideBtn.style.display = "inline-block"; wikiGuideBtn.classList.add("hack-mission-header-element"); wikiGuideBtn.target = "_blank"; //TODO Add link to wiki page wikiGuideBtn.href = //Start button will get replaced with forfeit when game is started var startBtn = document.createElement("a"); startBtn.innerHTML = "Start"; startBtn.setAttribute("id", "hack-mission-start-btn"); startBtn.classList.add("a-link-button"); startBtn.classList.add("hack-mission-header-element"); startBtn.style.display = "inline-block"; startBtn.addEventListener("click", ()=>{ this.start(); }); var timer = document.createElement("p"); timer.setAttribute("id", "hacking-mission-timer"); timer.style.display = "inline-block"; //Create Action Buttons (Attack/Scan/Weaken/ etc...) var actionsContainer = document.createElement("span"); actionsContainer.style.display = "block"; actionsContainer.classList.add("hack-mission-action-buttons-container"); for (var i = 0; i < 6; ++i) { this.actionButtons.push(document.createElement("a")); this.actionButtons[i].style.display = "inline-block"; this.actionButtons[i].classList.add("a-link-button-inactive"); //Disabled at start this.actionButtons[i].classList.add("tooltip"); //Disabled at start this.actionButtons[i].classList.add("hack-mission-header-element"); actionsContainer.appendChild(this.actionButtons[i]); } this.actionButtons[0].innerText = "Attack(a)"; var atkTooltip = document.createElement("span"); atkTooltip.classList.add("tooltiptext"); atkTooltip.innerText = "Lowers the targeted node's HP. The effectiveness of this depends on " + "this node's Attack level, your hacking level, and the opponent's defense level."; this.actionButtons[0].appendChild(atkTooltip); this.actionButtons[1].innerText = "Scan(s)"; var scanTooltip = document.createElement("span"); scanTooltip.classList.add("tooltiptext"); scanTooltip.innerText = "Lowers the targeted node's defense. The effectiveness of this depends on " + "this node's Attack level, your hacking level, and the opponent's defense level."; this.actionButtons[1].appendChild(scanTooltip); this.actionButtons[2].innerText = "Weaken(w)"; var WeakenTooltip = document.createElement("span"); WeakenTooltip.classList.add("tooltiptext"); WeakenTooltip.innerText = "Lowers the targeted node's attack. The effectiveness of this depends on " + "this node's Attack level, your hacking level, and the opponent's defense level."; this.actionButtons[2].appendChild(WeakenTooltip); this.actionButtons[3].innerText = "Fortify(f)"; var fortifyTooltip = document.createElement("span"); fortifyTooltip.classList.add("tooltiptext"); fortifyTooltip.innerText = "Raises this node's Defense level. The effectiveness of this depends on " + "your hacking level"; this.actionButtons[3].appendChild(fortifyTooltip); this.actionButtons[4].innerText = "Overflow(r)"; var overflowTooltip = document.createElement("span"); overflowTooltip.classList.add("tooltiptext"); overflowTooltip.innerText = "Raises this node's Attack level but lowers its Defense level. The effectiveness " + "of this depends on your hacking level."; this.actionButtons[4].appendChild(overflowTooltip); this.actionButtons[5].innerText = "Drop Connection(d)"; var dropconnTooltip = document.createElement("span"); dropconnTooltip.classList.add("tooltiptext"); dropconnTooltip.innerText = "Removes this Node's current connection to some target Node, if it has one. This can " + "also be done by simply clicking the white connection line."; this.actionButtons[5].appendChild(dropconnTooltip); //Player/enemy defense displays will be in action container var playerDefense = document.createElement("p"); var enemyDefense = document.createElement("p"); playerDefense.style.display = "inline-block"; enemyDefense.style.display = "inline-block"; playerDefense.style.color = "blue"; enemyDefense.style.color = "red"; playerDefense.style.margin = "4px"; enemyDefense.style.margin = "4px"; playerDefense.setAttribute("id", "hacking-mission-player-def"); enemyDefense.setAttribute("id", "hacking-mission-enemy-def"); actionsContainer.appendChild(playerDefense); actionsContainer.appendChild(enemyDefense); //Set Action Button event listeners this.actionButtons[0].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } this.setActionButtonsActive(); this.setActionButton(NodeActions.Attack, false); //Set attack button inactive this.selectedNode.action = NodeActions.Attack; }); this.actionButtons[1].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } this.setActionButtonsActive(); this.setActionButton(NodeActions.Scan, false); //Set scan button inactive this.selectedNode.action = NodeActions.Scan; }); this.actionButtons[2].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } this.setActionButtonsActive(); this.setActionButton(NodeActions.Weaken, false); //Set Weaken button inactive this.selectedNode.action = NodeActions.Weaken; }); this.actionButtons[3].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } this.setActionButtonsActive(); this.setActionButton(NodeActions.Fortify, false); //Set Fortify button inactive this.selectedNode.action = NodeActions.Fortify; }); this.actionButtons[4].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } this.setActionButtonsActive(); this.setActionButton(NodeActions.Overflow, false); //Set Overflow button inactive this.selectedNode.action = NodeActions.Overflow; }); this.actionButtons[5].addEventListener("click", ()=>{ if (!(this.selectedNode instanceof Node)) { console.log("ERR: Pressing Action button without selected node"); return; } if (this.selectedNode.conn) { var endpoints = this.selectedNode.conn.endpoints; endpoints[0].detachFrom(endpoints[1]); } }) var timeDisplay = document.createElement("p"); container.appendChild(headerText); container.appendChild(inGameGuideBtn); container.appendChild(wikiGuideBtn); container.appendChild(startBtn); container.appendChild(timer); container.appendChild(actionsContainer); container.appendChild(timeDisplay); } HackingMission.prototype.setActionButtonsInactive = function() { for (var i = 0; i < this.actionButtons.length; ++i) { this.actionButtons[i].classList.remove("a-link-button"); this.actionButtons[i].classList.add("a-link-button-inactive"); } } HackingMission.prototype.setActionButtonsActive = function() { for (var i = 0; i < this.actionButtons.length; ++i) { this.actionButtons[i].classList.add("a-link-button"); this.actionButtons[i].classList.remove("a-link-button-inactive"); } } //True for active, false for inactive HackingMission.prototype.setActionButton = function(i, active=true) { if (isString(i)) { switch (i) { case NodeActions.Attack: i = 0; break; case NodeActions.Scan: i = 1; break; case NodeActions.Weaken: i = 2; break; case NodeActions.Fortify: i = 3; break; case NodeActions.Overflow: default: i = 4; break; } } if (active) { this.actionButtons[i].classList.remove("a-link-button-inactive"); this.actionButtons[i].classList.add("a-link-button"); } else { this.actionButtons[i].classList.remove("a-link-button"); this.actionButtons[i].classList.add("a-link-button-inactive"); } } //Should only be used at the start HackingMission.prototype.calculateDefenses = function() { var total = 0; for (var i = 0; i < this.playerCores.length; ++i) { total += this.playerCores[i].def; } for (var i = 0; i < this.playerNodes.length; ++i) { total += this.playerNodes[i].def; } this.playerDef = total; document.getElementById("hacking-mission-player-def").innerText = "Player Defense: " + formatNumber(this.playerDef, 1); total = 0; for (var i = 0; i < this.enemyCores.length; ++i) { total += this.enemyCores[i].def; } for (var i = 0; i < this.enemyDatabases.length; ++i) { total += this.enemyDatabases[i].def; } for (var i = 0; i < this.enemyNodes.length; ++i) { total += this.enemyNodes[i].def; } this.enemyDef = total; document.getElementById("hacking-mission-enemy-def").innerText = "Enemy Defense: " + formatNumber(this.enemyDef, 1); } HackingMission.prototype.removeAvailablePosition = function(x, y) { for (var i = 0; i < this.availablePositions.length; ++i) { if (this.availablePositions[i][0] === x && this.availablePositions[i][1] === y) { this.availablePositions.splice(i, 1); return; } } console.log("WARNING: removeAvailablePosition() did not remove " + x + ", " + y); } HackingMission.prototype.setNodePosition = function(nodeObj, x, y) { if (!(nodeObj instanceof Node)) { console.log("WARNING: Non-Node object passed into setNodePOsition"); return; } if (isNaN(x) || isNaN(y)) { console.log("ERR: Invalid values passed as x and y for setNodePosition"); console.log(x); console.log(y); return; } nodeObj.pos = [x, y]; this.map[x][y] = nodeObj; } HackingMission.prototype.setNodeRandomPosition = function(nodeObj, xlimit=0) { var i = getRandomInt(0, this.availablePositions.length - 1); var pos = this.availablePositions.splice(i, 1); pos = pos[0]; this.setNodePosition(nodeObj, pos[0], pos[1]); } HackingMission.prototype.createMap = function() { //Use a grid var map = document.createElement("div"); map.classList.add("hack-mission-grid"); map.setAttribute("id", "hacking-mission-map"); document.getElementById("mission-container").appendChild(map); //Create random Nodes for every space in the map that //hasn't been filled yet for (var x = 0; x < 8; ++x) { for (var y = 0; y < 8; ++y) { if (!(this.map[x][y] instanceof Node)) { var node, type = getRandomInt(0, 2); var randMult = addOffset(this.difficulty, 20); switch (type) { case 0: //Spam var stats = { atk: 0, def: randMult * getRandomInt(30, 40), hp: randMult * getRandomInt(70, 90) } node = new Node(NodeTypes.Spam, stats); break; case 1: //Transfer var stats = { atk: 0, def: randMult * getRandomInt(50, 70), hp: randMult * getRandomInt(80, 95) } node = new Node(NodeTypes.Transfer, stats); break; case 2: //Shield default: var stats = { atk: 0, def: randMult * getRandomInt(90, 105), hp: randMult * getRandomInt(130, 150) } node = new Node(NodeTypes.Shield, stats); break; } this.setNodePosition(node, x, y); this.removeAvailablePosition(x, y); this.miscNodes.push(node); } } } //Create DOM elements in order for (var r = 0; r < 8; ++r) { for (var c = 0; c < 8; ++c) { this.createNodeDomElement(this.map[r][c]); } } //Configure all Player CPUS for (var i = 0; i < this.playerCores.length; ++i) { console.log("Configuring Player Node: " + this.playerCores[i].el.id); this.configurePlayerNodeElement(this.playerCores[i].el); } } HackingMission.prototype.createNodeDomElement = function(nodeObj) { var nodeDiv = document.createElement("a"); nodeObj.el = nodeDiv; document.getElementById("hacking-mission-map").appendChild(nodeDiv); //Set the node element's id based on its coordinates nodeDiv.setAttribute("id", "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]); //Set node classes for owner nodeDiv.classList.add("hack-mission-node"); if (nodeObj.plyrCtrl) { nodeDiv.classList.add("hack-mission-player-node"); } else if (nodeObj.enmyCtrl) { nodeDiv.classList.add("hack-mission-enemy-node"); } //Set node classes based on type var txt; switch (nodeObj.type) { case NodeTypes.Core: txt = "

CPU Core
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-cpu-node"); break; case NodeTypes.Firewall: txt = "

Firewall
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-firewall-node"); break; case NodeTypes.Database: txt = "

Database
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-database-node"); break; case NodeTypes.Spam: txt = "

Spam
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-spam-node"); break; case NodeTypes.Transfer: txt = "

Transfer
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-transfer-node"); break; case NodeTypes.Shield: default: txt = "

Shield
" + "HP: " + formatNumber(nodeObj.hp, 1); nodeDiv.classList.add("hack-mission-shield-node"); break; } txt += "
Atk: " + formatNumber(nodeObj.atk, 1) + "
Def: " + formatNumber(nodeObj.def, 1) + "

"; nodeDiv.innerHTML = txt; } HackingMission.prototype.updateNodeDomElement = function(nodeObj) { if (nodeObj.el === null) { console.log("ERR: Calling updateNodeDomElement on a Node without an element"); return; } var nodeDiv = document.getElementById("hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]); //Set node classes based on type var txt; switch (nodeObj.type) { case NodeTypes.Core: txt = "

CPU Core
" + "HP: " + formatNumber(nodeObj.hp, 1); break; case NodeTypes.Firewall: txt = "

Firewall
" + "HP: " + formatNumber(nodeObj.hp, 1); break; case NodeTypes.Database: txt = "

Database
" + "HP: " + formatNumber(nodeObj.hp, 1); break; case NodeTypes.Spam: txt = "

Spam
" + "HP: " + formatNumber(nodeObj.hp, 1); break; case NodeTypes.Transfer: txt = "

Transfer
" + "HP: " + formatNumber(nodeObj.hp, 1); break; case NodeTypes.Shield: default: txt = "

Shield
" + "HP: " + formatNumber(nodeObj.hp, 1); break; } txt += "
Atk: " + formatNumber(nodeObj.atk, 1) + "
Def: " + formatNumber(nodeObj.def, 1); if (nodeObj.action) { txt += "
" + nodeObj.action; } txt += "

"; nodeDiv.innerHTML = txt; } //Gets a Node DOM element's corresponding Node object using its //element id HackingMission.prototype.getNodeFromElement = function(el) { var id = el.id; id = id.replace("hacking-mission-node-", ""); var res = id.split('-'); if (res.length != 2) { console.log("ERROR Parsing Hacking Mission Node Id. Could not find coordinates"); return null; } var x = res[0], y = res[1]; if (isNaN(x) || isNaN(y) || x >= 8 || y >= 8 || x < 0 || y < 0) { console.log("ERROR: Unexpected values for x and y: " + x + ", " + y); return null; } return this.map[x][y]; } //Configures a DOM element representing a player-owned node to //be selectable and actionable //Note: Does NOT change its css class. This is handled by Node.setControlledBy... HackingMission.prototype.configurePlayerNodeElement = function(el) { var nodeObj = this.getNodeFromElement(el); if (nodeObj === null) {console.log("Error getting Node object");} //Add event listener el.addEventListener("click", ()=>{ if (this.selectedNode instanceof Node) { this.selectedNode.deselect(this.actionButtons); this.selectedNode = null; } console.log("Selecting node :" + el.id); nodeObj.select(this.actionButtons); this.selectedNode = nodeObj; }); } //Configures a DOM element representing an enemy-node by removing //any event listeners HackingMission.prototype.configureEnemyNodeElement = function(el) { //Deselect node if it was the selected node var nodeObj = this.getNodeFromElement(el); if (this.selectedNode == nodeObj) { nodeObj.deselect(this.actionButtons); } //TODO Need to remove event listeners } //Returns bool indicating whether a node is reachable by player //by checking if any of the adjacent nodes are owned by the player HackingMission.prototype.nodeReachable = function(node) { var x = node.pos[0], y = node.pos[1]; if (x > 0 && this.map[x-1][y].plyrCtrl) {return true;} if (x < 7 && this.map[x+1][y].plyrCtrl) {return true;} if (y > 0 && this.map[x][y-1].plyrCtrl) {return true;} if (y < 7 && this.map[x][y+1].plyrCtrl) {return true;} return false; } HackingMission.prototype.start = function() { this.started = true; this.initJsPlumb(); var startBtn = clearEventListeners("hack-mission-start-btn"); startBtn.innerHTML = "Forfeit Mission"; startBtn.addEventListener("click", ()=>{ this.finishMission(false); return false; }); } HackingMission.prototype.initJsPlumb = function() { var instance = jsPlumb.getInstance({ DragOptions:{cursor:"pointer", zIndex:2000}, PaintStyle: { gradient: { stops: [ [ 0, "#FFFFFF" ], [ 1, "#FFFFFF" ] ] }, stroke: "#FFFFFF", strokeWidth: 8 }, }); this.jsplumbinstance = instance; //All player cores are sources for (var i = 0; i < this.playerCores.length; ++i) { instance.makeSource(this.playerCores[i].el, { deleteEndpointsOnEmpty:true, maxConnections:1, anchor:"Center", connector:"Straight" }); } //Everything else is a target for (var i = 0; i < this.enemyCores.length; ++i) { instance.makeTarget(this.enemyCores[i].el, { maxConnections:-1, anchor:"Center", connector:"Straight" }); } for (var i = 0; i < this.enemyDatabases.length; ++i) { instance.makeTarget(this.enemyDatabases[i].el, { maxConnections:-1, anchor:"Center", connector:["Straight"] }); } for (var i = 0; i < this.enemyNodes.length; ++i) { instance.makeTarget(this.enemyNodes[i].el, { maxConnections:-1, anchor:"Center", connector:"Straight" }); } for (var i = 0; i < this.miscNodes.length; ++i) { instance.makeTarget(this.miscNodes[i].el, { maxConnections:-1, anchor:"Center", connector:"Straight" }); } //Clicking a connection drops it instance.bind("click", function(conn, originalEvent) { var endpoints = conn.endpoints; endpoints[0].detachFrom(endpoints[1]); }); //Connection events instance.bind("connection", (info)=>{ var targetNode = this.getNodeFromElement(info.target); //If the node is not reachable, drop the connection if (!this.nodeReachable(targetNode)) { info.sourceEndpoint.detachFrom(info.targetEndpoint); return; } var sourceNode = this.getNodeFromElement(info.source); sourceNode.conn = info.connection; }); //Detach Connection events instance.bind("connectionDetached", (info, originalEvent)=>{ var sourceNode = this.getNodeFromElement(info.source); sourceNode.conn = null; }); } //Drops all connections where the specified node is the source HackingMission.prototype.dropAllConnectionsFromNode = function(node) { var allConns = this.jsplumbinstance.getAllConnections(); for (var i = allConns.length-1; i >= 0; --i) { if (allConns[i].source == node.el) { allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); } } } //Drops all connections where the specified node is the target HackingMission.prototype.dropAllConnectionsToNode = function(node) { var allConns = this.jsplumbinstance.getAllConnections(); for (var i = allConns.length-1; i >= 0; --i) { if (allConns[i].target == node.el) { allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); } } } HackingMission.prototype.process = function(numCycles=1) { if (!this.started) {return;} var res = false; //Process actions of all player nodes this.playerCores.forEach((node)=>{ res |= this.processNode(node, numCycles); }); this.playerNodes.forEach((node)=>{ if (node.type === NodeTypes.Transfer) { res |= this.processNode(node, numCycles); } }); //Process actions of all enemy nodes this.enemyCores.forEach((node)=>{ res |= this.processNode(node, numCycles); }); this.enemyNodes.forEach((node)=>{ if (node.type === NodeTypes.Transfer) { res |= this.processNode(node, numCycles); } }); if (res) {this.calculateDefenses();} if (this.enemyDatabases.length === 0) { this.finishMission(true); return; } //Update timer and check if player lost this.time -= (numCycles * Engine._idleSpeed); if (this.time <= 0) { this.finishMission(false); return; } this.updateTimer(); } //Returns a bool representing whether defenses need to be re-calculated HackingMission.prototype.processNode = function(nodeObj, numCycles=1) { if (nodeObj.action === null) { return; } var targetNode = null, def; if (nodeObj.conn) { targetNode = this.getNodeFromElement(nodeObj.conn.target); if (targetNode.plyrCtrl) { def = this.playerDef; } else if (targetNode.enmyCtrl) { def = this.enemyDef; } else { //Misc Node def = targetNode.def; } } //Calculations are per second, so divide everything by 5 var calcDefenses = false; switch(nodeObj.action) { case NodeActions.Attack: if (nodeObj.conn === null) {break;} var dmg = this.calculateAttackDamage(nodeObj.atk, def, Player.hacking_skill); targetNode.hp -= (dmg/5 * numCycles); break; case NodeActions.Scan: if (nodeObj.conn === null) {break;} var eff = this.calculateScanEffect(nodeObj.atk, def, Player.hacking_skill); targetNode.def -= (eff/5 * numCycles); calcDefenses = true; break; case NodeActions.Weaken: if (nodeObj.conn === null) {break;} var eff = this.calculateWeakenEffect(nodeObj.atk, def, Player.hacking_skill); targetNode.atk -= (eff/5 * numCycles); break; case NodeActions.Fortify: var eff = this.calculateFortifyEffect(Player.hacking_skill); nodeObj.def += (eff/5 * numCycles); calcDefenses = true; break; case NodeActions.Overflow: var eff = this.calculateOverflowEffect(Player.hacking_skill); if (nodeObj.def < eff) {break;} nodeObj.def -= (eff/5 * numCycles); nodeObj.atk += (eff/5 * numCycles); calcDefenses = true; break; default: console.log("ERR: Invalid Node Action: " + nodeObj.action); break; } //Stats can't go below 0 if (nodeObj.atk < 0) {nodeObj.atk = 0;} if (nodeObj.def < 0) {nodeObj.def = 0;} if (targetNode && targetNode.atk < 0) {targetNode.atk = 0;} if (targetNode && targetNode.def < 0) {targetNode.def = 0;} //Conquering a node if (targetNode && targetNode.hp <= 0) { var conqueredByPlayer = nodeObj.plyrCtrl; targetNode.hp = targetNode.maxhp; targetNode.action = null; targetNode.conn = null; if (this.selectedNode == targetNode) { targetNode.deselect(); } //Flag for whether the target node was a misc node var isMiscNode = !targetNode.plyrCtrl && !targetNode.enmyCtrl; console.log("isMiscNode: " + isMiscNode); //Remove all connections from Node this.dropAllConnectionsToNode(targetNode); this.dropAllConnectionsFromNode(targetNode); //Changes the css class and turn the node into a JsPlumb Source/Target if (conqueredByPlayer) { targetNode.setControlledByPlayer(); this.jsplumbinstance.unmakeTarget(targetNode.el); this.jsplumbinstance.makeSource(targetNode.el, { deleteEndpointsOnEmpty:true, maxConnections:1, anchor:"Center", connector:"Straight" }); } else { targetNode.setControlledByEnemy(); this.jsplumbinstance.unmakeSource(targetNode.el); this.jsplumbinstance.makeTarget(targetNode.el, { maxConnections:-1, anchor:"Center", connector:["Straight"] }); } calcDefenses = true; //Helper function to swap nodes between the respective enemyNodes/playerNodes arrays function swapNodes(orig, dest, targetNode) { console.log("swapNodes called"); for (var i = 0; i < orig.length; ++i) { if (orig[i] == targetNode) { console.log("Swapping nodes"); var node = orig.splice(i, 1); node = node[0]; dest.push(node); break; } } } //Whether conquered node was a misc node switch(targetNode.type) { case NodeTypes.Core: if (conqueredByPlayer) { swapNodes(this.enemyCores, this.playerCores, targetNode); this.configurePlayerNodeElement(targetNode.el); } else { swapNodes(this.playerCores, this.enemyCores, targetNode); this.configureEnemyNodeElement(targetNode.el); } break; case NodeTypes.Firewall: if (conqueredByPlayer) { swapNodes(this.enemyNodes, this.playerNodes, targetNode); } else { swapNodes(this.playerNodes, this.enemyNodes, targetNode); } break; case NodeTypes.Database: if (conqueredByPlayer) { swapNodes(this.enemyDatabases, this.playerNodes, targetNode); } else { swapNodes(this.playerNodes, this.enemyDatabases, targetNode); } break; case NodeTypes.Spam: if (conqueredByPlayer) { swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); } else { swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); } //Conquering spam node increases time limit this.time += CONSTANTS.HackingMissionSpamTimeIncrease; break; case NodeTypes.Transfer: //Conquering a Transfer node increases the attack of all cores by some percentages if (conqueredByPlayer) { swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); this.playerCores.forEach(function(node) { node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; }); this.configurePlayerNodeElement(targetNode.el); } else { swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); this.enemyCores.forEach(function(node) { node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; }); this.configureEnemyNodeElement(targetNode.el); } break; case NodeTypes.Shield: if (conqueredByPlayer) { swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); } else { swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); } break; } } this.updateNodeDomElement(nodeObj); if (targetNode) {this.updateNodeDomElement(targetNode);} return calcDefenses; } var hackEffWeightSelf = 100; //Weight for Node actions on self var hackEffWeightTarget = 15; //Weight for Node Actions against Target var hackEffWeightAttack = 100; //Weight for Attack action //Returns damage per cycle based on stats HackingMission.prototype.calculateAttackDamage = function(atk, def, hacking = 0) { return Math.max(atk + (hacking / hackEffWeightAttack) - def, 0.1); } HackingMission.prototype.calculateScanEffect = function(atk, def, hacking=0) { return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 0.1); } HackingMission.prototype.calculateWeakenEffect = function(atk, def, hacking=0) { return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 0.1); } HackingMission.prototype.calculateFortifyEffect = function(hacking=0) { return hacking / hackEffWeightSelf; } HackingMission.prototype.calculateOverflowEffect = function(hacking=0) { return hacking / hackEffWeightSelf; } //Updates timer display HackingMission.prototype.updateTimer = function() { var timer = document.getElementById("hacking-mission-timer"); //Convert time remaining to a string of the form m:ss var seconds = Math.round(this.time / 1000); var minutes = Math.trunc(seconds / 60); seconds %= 60; var str = ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2); timer.innerText = "Time left: " + str; } //The 'win' argument is a bool for whether or not the player won HackingMission.prototype.finishMission = function(win) { inMission = false; currMission = null; if (win) { dialogBoxCreate("Mission won!"); } else { dialogBoxCreate("Mission lost!"); } //Clear mission container var container = document.getElementById("mission-container"); while(container.firstChild) { container.removeChild(container.firstChild); } //Return to Faction page document.getElementById("mainmenu-container").style.visibility = "visible"; document.getElementById("character-overview-wrapper").style.visibility = "visible"; Engine.loadFactionContent(); displayFactionContent(this.faction.name); } export {HackingMission, inMission, setInMission, currMission};