diff --git a/src/Constants.ts b/src/Constants.ts index 0dce70c0e..b02e14809 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -47,14 +47,6 @@ export const CONSTANTS: { IntelligenceTerminalHackBaseExpGain: number; IntelligenceSingFnBaseExpGain: number; IntelligenceClassBaseExpGain: number; - IntelligenceHackingMissionBaseExpGain: number; - HackingMissionRepToDiffConversion: number; - HackingMissionRepToRewardConversion: number; - HackingMissionSpamTimeIncrease: number; - HackingMissionTransferAttackIncrease: number; - HackingMissionMiscDefenseIncrease: number; - HackingMissionDifficultyToHacking: number; - HackingMissionHowToPlay: string; MillisecondsPer20Hours: number; GameCyclesPer20Hours: number; MillisecondsPer10Hours: number; @@ -199,63 +191,6 @@ export const CONSTANTS: { IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain IntelligenceSingFnBaseExpGain: 1.5, IntelligenceClassBaseExpGain: 0.01, - IntelligenceHackingMissionBaseExpGain: 3, // Hacking Mission difficulty multiplied by this to get exp gain - - // Hacking Missions - // TODO Move this into Hacking Mission implementation - HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty - HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward - HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms) - HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node - HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered - HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc) - HackingMissionHowToPlay: - "Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" + - "In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " + - "are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " + - "that initially belong to neither you nor the enemy. The goal of the game is " + - "to capture all of the enemy's Database nodes within the time limit. " + - "If you fail to do this, you will lose.<br><br>" + - "Each Node has three stats: Attack, Defense, and HP. There are five different actions that " + - "a Node can take:<br><br> " + - "Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's " + - "hacking level, and the enemy's defense.<br><br>" + - "Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the " + - "enemy's defense.<br><br>" + - "Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's " + - "defense.<br><br>" + - "Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.<br><br>" + - "Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.<br><br>" + - "Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the " + - "Attack/Defense of the individual Node that is performing the action.<br><br>" + - "To capture a Node, you must lower its HP down to 0.<br><br>" + - "There are six different types of Nodes:<br><br>" + - "CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action<br><br>" + - "Firewall - Nodes with high defense. These Nodes can 'Fortify'<br><br>" + - "Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " + - "the time limit. These Nodes cannot perform any actions<br><br>" + - "Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete " + - "the mission. These Nodes cannot perform any actions<br><br>" + - "Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " + - "These Nodes are capable of performing every action except the 'Attack' action<br><br>" + - "Shield - Nodes with high defense. These Nodes can 'Fortify'<br><br>" + - "To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking " + - "a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes " + - "that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, " + - "select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard " + - "shortcut.<br><br>" + - "For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " + - "another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target " + - "any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " + - "can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents " + - "the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, " + - "or press 'd'.<br><br>" + - "Other Notes:<br><br>" + - "-Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that " + - "are not actively being targeted will increase by a fixed percentage.<br><br>" + - "-Whenever a Node is conquered, its stats are significantly reduced<br><br>" + - "-Miscellaneous Nodes slowly raise their defense over time<br><br>" + - "-Nodes slowly regenerate health over time.", // Time-related constants MillisecondsPer20Hours: 72000000, diff --git a/src/Faction/FactionHelpers.tsx b/src/Faction/FactionHelpers.tsx index 7d4440a7a..876d81e9d 100644 --- a/src/Faction/FactionHelpers.tsx +++ b/src/Faction/FactionHelpers.tsx @@ -7,7 +7,6 @@ import { CONSTANTS } from "../Constants"; import { Faction } from "./Faction"; import { Factions } from "./Factions"; -import { HackingMission, setInMission } from "../Missions"; import { Player } from "../Player"; import { Settings } from "../Settings/Settings"; import { @@ -49,12 +48,6 @@ export function joinFaction(faction: Faction): void { } } -export function startHackingMission(faction: Faction): void { - const mission = new HackingMission(faction.playerReputation, faction); - setInMission(true, mission); //Sets inMission flag to true - mission.init(); -} - //Returns a boolean indicating whether the player has the prerequisites for the //specified Augmentation export function hasAugmentationPrereqs(aug: Augmentation): boolean { diff --git a/src/Faction/ui/FactionRoot.tsx b/src/Faction/ui/FactionRoot.tsx index cc6d656f2..1fb5d7287 100644 --- a/src/Faction/ui/FactionRoot.tsx +++ b/src/Faction/ui/FactionRoot.tsx @@ -30,10 +30,6 @@ type IProps = { // Info text for all options on the UI const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " + "faction reputation"; -const hackingMissionInfo = - "Attempt a hacking mission for your faction. " + - "A mission is a mini game that, if won, earns you " + - "significant reputation with this faction. (Recommended hacking level: 200+)"; const hackingContractsInfo = "Complete hacking contracts for your faction. " + "Your effectiveness, which determines how much " + @@ -96,11 +92,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea player.startFactionHackWork(router, faction); } - function startHackingMission(faction: Faction): void { - player.singularityStopWork(); - router.toHackingMission(faction); - } - function startSecurityWork(faction: Faction): void { player.startFactionSecurityWork(router, faction); } @@ -138,13 +129,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea <CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} /> </> )} - {!isPlayersGang && factionInfo.offerHackingMission && ( - <Option - buttonText={"Hacking Mission"} - infoText={hackingMissionInfo} - onClick={() => startHackingMission(faction)} - /> - )} {!isPlayersGang && factionInfo.offerHackingWork && ( <Option buttonText={"Hacking Contracts"} diff --git a/src/HackingMission/ui/HackingMissionRoot.tsx b/src/HackingMission/ui/HackingMissionRoot.tsx deleted file mode 100644 index 38548a351..000000000 --- a/src/HackingMission/ui/HackingMissionRoot.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { useEffect } from "react"; - -import { startHackingMission } from "../../Faction/FactionHelpers"; -import { Faction } from "../../Faction/Faction"; - -interface IProps { - faction: Faction; -} - -export function HackingMissionRoot(props: IProps): React.ReactElement { - useEffect(() => startHackingMission(props.faction)); - return <div id="mission-container"></div>; -} diff --git a/src/Message/MessageHelpers.ts b/src/Message/MessageHelpers.ts index 32da4fb94..6b6fbaf0a 100644 --- a/src/Message/MessageHelpers.ts +++ b/src/Message/MessageHelpers.ts @@ -2,7 +2,6 @@ import { Message } from "./Message"; import { Augmentations } from "../Augmentation/Augmentations"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { Programs } from "../Programs/Programs"; -import { inMission } from "../Missions"; import { Player } from "../Player"; import { redPillFlag } from "../RedPill"; import { GetServerByHostname } from "../Server/ServerHelpers"; @@ -66,11 +65,11 @@ function checkForMessagesToSend(): void { redpillOwned = true; } - if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) { + if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag) { sendMessage(redpill, true); } else if (redpill && redpillOwned) { //If player has already destroyed a BitNode, message is not forced - if (!redPillFlag && !inMission) { + if (!redPillFlag) { sendMessage(redpill); } } else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) { diff --git a/src/Missions.d.ts b/src/Missions.d.ts deleted file mode 100644 index 8a1e7d522..000000000 --- a/src/Missions.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export declare let inMission: boolean; -export declare class HackingMission { - constructor(reputation: number, faction: Faction); - init(): void; - process(numCycles: number): void; -} -export declare function setInMission(inMission: boolean, mission: HackingMission): void; -export declare let currMission: HackingMission; diff --git a/src/Missions.jsx b/src/Missions.jsx deleted file mode 100644 index 73dac72c1..000000000 --- a/src/Missions.jsx +++ /dev/null @@ -1,1600 +0,0 @@ -import { CONSTANTS } from "./Constants"; -import { Player } from "./Player"; - -import { dialogBoxCreate } from "./ui/React/DialogBox"; -import { formatNumber } from "./utils/StringHelperFunctions"; -import { Reputation } from "./ui/React/Reputation"; - -import { addOffset } from "./utils/helpers/addOffset"; -import { getRandomInt } from "./utils/helpers/getRandomInt"; -import { isString } from "./utils/helpers/isString"; - -import { clearEventListeners } from "./ui/uiHelpers/clearEventListeners"; -import { Router } from "./ui/GameRoot"; - -// For some reason `jsplumb` needs to be imported exactly like this, -// lowercase p, and later in the code used as `jsPlumb` uppercase P. wtf. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import jsplumb from "jsplumb"; - -import React from "react"; -import ReactDOM from "react-dom"; - -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.length != 0) { - 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; - this.targetedCount = 0; // Count of how many connections this node is the target of - - /** - * 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; - case NodeTypes.Shield: - case NodeTypes.Firewall: - actionButtons[3].classList.remove("a-link-button-inactive"); - actionButtons[3].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"); - } -}; - -Node.prototype.untarget = function () { - if (this.targetedCount === 0) { - console.warn(`Node ${this.el.id} is being 'untargeted' when it has no target count`); - return; - } - --this.targetedCount; -}; - -/** - * Hacking mission instance - * @param rep {number} How much reputation the player has for the faction - * @param fac {Faction} Faction for which this mission is being conducted - */ -function HackingMission(rep, fac) { - this.faction = fac; - - this.started = false; - this.time = 180000; // 5 minutes to start, milliseconds - - this.playerCores = []; - this.playerNodes = []; // Non-core nodes - this.playerAtk = 0; - this.playerDef = 0; - - this.enemyCores = []; - this.enemyDatabases = []; - this.enemyNodes = []; // Non-core nodes - this.enemyAtk = 0; - this.enemyDef = 0; - - this.miscNodes = []; - - this.selectedNode = []; // Which of the player's nodes are 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; - - this.difficulty = rep / CONSTANTS.HackingMissionRepToDiffConversion + 1; - this.reward = 250 + 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 / 7.5 + 30, - def: Player.hacking_skill / 20, - hp: Player.hacking_skill / 4, - }; - this.playerCores.push(new Node(NodeTypes.Core, stats)); - this.playerCores[i].setControlledByPlayer(); - this.setNodePosition(this.playerCores[i], i, 0); - this.removeAvailablePosition(i, 0); - } - - // Randomly generate enemy nodes (CPU and Firewall) based on difficulty - var numNodes = Math.min(8, Math.max(1, Math.round(this.difficulty / 4))); - var numFirewalls = Math.min(20, getRandomInt(Math.round(this.difficulty / 3), Math.round(this.difficulty / 3) + 1)); - var numDatabases = Math.min(10, getRandomInt(1, Math.round(this.difficulty / 3) + 1)); - var totalNodes = numNodes + numFirewalls + numDatabases; - var xlimit = 7 - Math.floor(totalNodes / 8); - var randMult = addOffset(0.8 + this.difficulty / 5, 10); - for (var i = 0; i < numNodes; ++i) { - var stats = { - atk: randMult * getRandomInt(80, 86), - def: randMult * getRandomInt(5, 10), - hp: randMult * getRandomInt(210, 230), - }; - 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: 0, - def: randMult * getRandomInt(10, 20), - hp: randMult * getRandomInt(275, 300), - }; - 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: 0, - def: randMult * getRandomInt(30, 55), - hp: randMult * getRandomInt(210, 275), - }; - var node = new Node(NodeTypes.Database, stats); - node.setControlledByEnemy(); - this.setNodeRandomPosition(node, xlimit); - this.enemyDatabases.push(node); - } - this.calculateDefenses(); - this.calculateAttacks(); - this.createMap(); -}; - -HackingMission.prototype.createPageDom = function () { - var container = document.getElementById("mission-container"); - - var favorMult = 1 + this.faction.favor / 100; - var gain = this.reward * Player.faction_rep_mult * favorMult; - var headerText = document.createElement("p"); - ReactDOM.render( - <> - You are about to start a hacking mission! You will gain {Reputation(gain)} faction reputation with{" "} - {this.faction.name} if you win. Click the 'Start' button to begin. - </>, - headerText, - ); - 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; - }); - - // 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(); - return false; - }); - - var forfeitMission = document.createElement("a"); - forfeitMission.innerHTML = "Forfeit Mission (Exit)"; - forfeitMission.classList.add("a-link-button"); - forfeitMission.classList.add("hack-mission-header-element"); - forfeitMission.style.display = "inline-block"; - forfeitMission.addEventListener("click", () => { - this.finishMission(false); - return false; - }); - - var timer = document.createElement("p"); - timer.setAttribute("id", "hacking-mission-timer"); - timer.style.display = "inline-block"; - timer.style.margin = "6px"; - - // 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("tooltiptexthigh"); - 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("tooltiptexthigh"); - 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("tooltiptexthigh"); - 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("tooltiptexthigh"); - 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("tooltiptexthigh"); - 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("tooltiptexthigh"); - 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 playerStats = document.createElement("p"); - var enemyStats = document.createElement("p"); - playerStats.style.display = "inline-block"; - enemyStats.style.display = "inline-block"; - playerStats.style.color = "#00ccff"; - enemyStats.style.color = "red"; - playerStats.style.margin = "4px"; - enemyStats.style.margin = "4px"; - playerStats.setAttribute("id", "hacking-mission-player-stats"); - enemyStats.setAttribute("id", "hacking-mission-enemy-stats"); - actionsContainer.appendChild(playerStats); - actionsContainer.appendChild(enemyStats); - - // Set Action Button event listeners - this.actionButtons[0].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - if (this.selectedNode[0].type !== NodeTypes.Core) { - return; - } - this.setActionButtonsActive(this.selectedNode[0].type); - this.setActionButton(NodeActions.Attack, false); // Set attack button inactive - this.selectedNode.forEach(function (node) { - node.action = NodeActions.Attack; - }); - }); - - this.actionButtons[1].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { - return; - } - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Scan, false); // Set scan button inactive - this.selectedNode.forEach(function (node) { - node.action = NodeActions.Scan; - }); - }); - - this.actionButtons[2].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { - return; - } - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Weaken, false); // Set Weaken button inactive - this.selectedNode.forEach(function (node) { - node.action = NodeActions.Weaken; - }); - }); - - this.actionButtons[3].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - this.setActionButtonsActive(this.selectedNode[0].type); - this.setActionButton(NodeActions.Fortify, false); // Set Fortify button inactive - this.selectedNode.forEach(function (node) { - node.action = NodeActions.Fortify; - }); - }); - - this.actionButtons[4].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { - return; - } - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Overflow, false); // Set Overflow button inactive - this.selectedNode.forEach(function (node) { - node.action = NodeActions.Overflow; - }); - }); - - this.actionButtons[5].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - this.selectedNode.forEach(function (node) { - if (node.conn) { - var endpoints = node.conn.endpoints; - endpoints[0].detachFrom(endpoints[1]); - } - node.action = NodeActions.Fortify; - }); - }); - - var timeDisplay = document.createElement("p"); - - container.appendChild(headerText); - container.appendChild(inGameGuideBtn); - container.appendChild(startBtn); - container.appendChild(forfeitMission); - 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 (nodeType = null) { - 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"); - } - - /** - * For Transfer, FireWall and Shield Nodes, certain buttons should always be disabled - * 0 = Attack, 1 = Scan, 2 = Weaken, 3 = Fortify, 4 = overflow, 5 = Drop conn - */ - if (nodeType) { - switch (nodeType) { - case NodeTypes.Firewall: - case NodeTypes.Shield: - this.actionButtons[0].classList.remove("a-link-button"); - this.actionButtons[0].classList.add("a-link-button-inactive"); - this.actionButtons[1].classList.remove("a-link-button"); - this.actionButtons[1].classList.add("a-link-button-inactive"); - this.actionButtons[2].classList.remove("a-link-button"); - this.actionButtons[2].classList.add("a-link-button-inactive"); - this.actionButtons[4].classList.remove("a-link-button"); - this.actionButtons[4].classList.add("a-link-button-inactive"); - this.actionButtons[5].classList.remove("a-link-button"); - this.actionButtons[5].classList.add("a-link-button-inactive"); - break; - case NodeTypes.Transfer: - this.actionButtons[0].classList.remove("a-link-button"); - this.actionButtons[0].classList.add("a-link-button-inactive"); - break; - default: - break; - } - } -}; - -// 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"); - } -}; - -HackingMission.prototype.calculateAttacks = function () { - var total = 0; - for (var i = 0; i < this.playerCores.length; ++i) { - total += this.playerCores[i].atk; - } - for (var i = 0; i < this.playerNodes.length; ++i) { - total += this.playerNodes[i].atk; - } - this.playerAtk = total; - document.getElementById("hacking-mission-player-stats").innerHTML = - "Player Attack: " + formatNumber(this.playerAtk, 1) + "<br>" + "Player Defense: " + formatNumber(this.playerDef, 1); - total = 0; - for (var i = 0; i < this.enemyCores.length; ++i) { - total += this.enemyCores[i].atk; - } - for (var i = 0; i < this.enemyDatabases.length; ++i) { - total += this.enemyDatabases[i].atk; - } - for (var i = 0; i < this.enemyNodes.length; ++i) { - total += this.enemyNodes[i].atk; - } - this.enemyAtk = total; - document.getElementById("hacking-mission-enemy-stats").innerHTML = - "Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "<br>" + "Enemy Defense: " + formatNumber(this.enemyDef, 1); -}; - -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-stats").innerHTML = - "Player Attack: " + formatNumber(this.playerAtk, 1) + "<br>" + "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-stats").innerHTML = - "Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "<br>" + "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.warn(`removeAvailablePosition() did not remove ${x}, ${y}`); -}; - -HackingMission.prototype.setNodePosition = function (nodeObj, x, y) { - if (!(nodeObj instanceof Node)) { - console.warn("Non-Node object passed into setNodePOsition"); - return; - } - if (isNaN(x) || isNaN(y)) { - console.error(`Invalid values (${x}, ${y}) passed as (x, y) for setNodePosition`); - 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); - if (this.availablePositions[i][1] < xlimit) { - // Recurse if not within limit - return this.setNodeRandomPosition(nodeObj, xlimit); - } - 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. The stats of each Node will be based on - // the player/enemy attack - var averageAttack = (this.playerAtk + this.enemyAtk) / 2; - 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(0.85 + this.difficulty / 2, 15); - switch (type) { - case 0: // Spam - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(15, 45), - hp: randMult * getRandomInt(200, 225), - }; - node = new Node(NodeTypes.Spam, stats); - break; - case 1: // Transfer - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(15, 45), - hp: randMult * getRandomInt(250, 275), - }; - node = new Node(NodeTypes.Transfer, stats); - break; - case 2: // Shield - default: - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(30, 70), - hp: randMult * getRandomInt(300, 320), - }; - 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) { - this.configurePlayerNodeElement(this.playerCores[i].el); - } -}; - -HackingMission.prototype.createNodeDomElement = function (nodeObj) { - var nodeDiv = document.createElement("a"), - txtEl = document.createElement("p"); - nodeObj.el = nodeDiv; - - // Set the node element's id based on its coordinates - var id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; - nodeDiv.setAttribute("id", id); - txtEl.setAttribute("id", id + "-txt"); - - // 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<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-cpu-node"); - break; - case NodeTypes.Firewall: - txt = "Firewall<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-firewall-node"); - break; - case NodeTypes.Database: - txt = "Database<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-database-node"); - break; - case NodeTypes.Spam: - txt = "Spam<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-spam-node"); - break; - case NodeTypes.Transfer: - txt = "Transfer<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-transfer-node"); - break; - case NodeTypes.Shield: - default: - txt = "Shield<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-shield-node"); - break; - } - - txt += "<br>Atk: " + formatNumber(nodeObj.atk, 1) + "<br>Def: " + formatNumber(nodeObj.def, 1); - txtEl.innerHTML = txt; - - nodeDiv.appendChild(txtEl); - document.getElementById("hacking-mission-map").appendChild(nodeDiv); -}; - -HackingMission.prototype.updateNodeDomElement = function (nodeObj) { - if (nodeObj.el == null) { - console.error("Calling updateNodeDomElement on a Node without an element"); - return; - } - - let id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; - let txtEl = document.getElementById(id + "-txt"); - - // Set node classes based on type - let txt; - switch (nodeObj.type) { - case NodeTypes.Core: - txt = "CPU Core<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Firewall: - txt = "Firewall<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Database: - txt = "Database<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Spam: - txt = "Spam<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Transfer: - txt = "Transfer<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Shield: - default: - txt = "Shield<br>" + "HP: " + formatNumber(nodeObj.hp, 1); - break; - } - - txt += "<br>Atk: " + formatNumber(nodeObj.atk, 1) + "<br>Def: " + formatNumber(nodeObj.def, 1); - if (nodeObj.action) { - txt += "<br>" + nodeObj.action; - } - txtEl.innerHTML = txt; -}; - -/** - * Gets a Node DOM elements corresponding Node object using its - * element id. Function accepts either the DOM element object or the ID as - * an argument - */ -HackingMission.prototype.getNodeFromElement = function (el) { - var id; - if (isString(el)) { - id = el; - } else { - id = el.id; - } - id = id.replace("hacking-mission-node-", ""); - var res = id.split("-"); - if (res.length != 2) { - console.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.error(`Unexpected values (${x}, ${y}) for (x, y)`); - return null; - } - return this.map[x][y]; -}; - -function selectNode(hackMissionInst, el) { - var nodeObj = hackMissionInst.getNodeFromElement(el); - if (nodeObj == null) { - console.error("Failed getting Node object"); - } - if (!nodeObj.plyrCtrl) { - return; - } - - clearAllSelectedNodes(hackMissionInst); - nodeObj.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(nodeObj); -} - -function multiselectNode(hackMissionInst, el) { - var nodeObj = hackMissionInst.getNodeFromElement(el); - if (nodeObj == null) { - console.error("Failed getting Node Object in multiselectNode()"); - } - if (!nodeObj.plyrCtrl) { - return; - } - - clearAllSelectedNodes(hackMissionInst); - var type = nodeObj.type; - if (type === NodeTypes.Core) { - hackMissionInst.playerCores.forEach(function (node) { - node.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(node); - }); - } else { - hackMissionInst.playerNodes.forEach(function (node) { - if (node.type === type) { - node.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(node); - } - }); - } -} - -function clearAllSelectedNodes(hackMissionInst) { - if (hackMissionInst.selectedNode.length > 0) { - hackMissionInst.selectedNode.forEach(function (node) { - node.deselect(hackMissionInst.actionButtons); - }); - hackMissionInst.selectedNode.length = 0; - } -} - -/** - * 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.error("Failed getting Node object"); - } - - // Add event listener - const selectNodeWrapper = () => { - selectNode(this, el); - }; - el.addEventListener("click", selectNodeWrapper); - - const multiselectNodeWrapper = () => { - multiselectNode(this, el); - }; - el.addEventListener("dblclick", multiselectNodeWrapper); - - if (el.firstChild) { - el.firstChild.addEventListener("click", selectNodeWrapper); - } -}; - -/** - * 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); - for (var i = 0; i < this.selectedNode.length; ++i) { - if (this.selectedNode[i] == nodeObj) { - nodeObj.deselect(this.actionButtons); - this.selectedNode.splice(i, 1); - break; - } - } -}; - -/** - * 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.nodeReachableByEnemy = function (node) { - if (node == null) { - return false; - } - var x = node.pos[0], - y = node.pos[1]; - if (x > 0 && this.map[x - 1][y].enmyCtrl) { - return true; - } - if (x < 7 && this.map[x + 1][y].enmyCtrl) { - return true; - } - if (y > 0 && this.map[x][y - 1].enmyCtrl) { - return true; - } - if (y < 7 && this.map[x][y + 1].enmyCtrl) { - return true; - } - return false; -}; - -HackingMission.prototype.start = function () { - this.started = true; - this.initJsPlumb(); - var startBtn = clearEventListeners("hack-mission-start-btn"); - startBtn.classList.remove("a-link-button"); - startBtn.classList.add("a-link-button-inactive"); -}; - -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: "Continuous", - connector: "Flowchart", - }); - } - - // Everything else is a target - for (var i = 0; i < this.enemyCores.length; ++i) { - instance.makeTarget(this.enemyCores[i].el, { - maxConnections: -1, - anchor: "Continuous", - connector: "Flowchart", - }); - } - for (var i = 0; i < this.enemyDatabases.length; ++i) { - instance.makeTarget(this.enemyDatabases[i].el, { - maxConnections: -1, - anchor: "Continuous", - connector: ["Flowchart"], - }); - } - for (var i = 0; i < this.enemyNodes.length; ++i) { - instance.makeTarget(this.enemyNodes[i].el, { - maxConnections: -1, - anchor: "Continuous", - connector: "Flowchart", - }); - } - for (var i = 0; i < this.miscNodes.length; ++i) { - instance.makeTarget(this.miscNodes[i].el, { - maxConnections: -1, - anchor: "Continuous", - connector: "Flowchart", - }); - } - - // Clicking a connection drops it - instance.bind("click", (conn) => { - // Cannot drop enemy's connections - const sourceNode = this.getNodeFromElement(conn.source); - if (sourceNode.enmyCtrl) { - return; - } - - var endpoints = conn.endpoints; - endpoints[0].detachFrom(endpoints[1]); - }); - - // Connection events - instance.bind("connection", (info) => { - var targetNode = this.getNodeFromElement(info.target); - - // Do not detach for enemy nodes - var thisNode = this.getNodeFromElement(info.source); - if (thisNode.enmyCtrl) { - return; - } - - // 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; - var targetNode = this.getNodeFromElement(info.target); - ++targetNode.targetedCount; - }); - - // Detach Connection events - instance.bind("connectionDetached", (info) => { - var sourceNode = this.getNodeFromElement(info.source); - sourceNode.conn = null; - var targetNode = this.getNodeFromElement(info.target); - targetNode.untarget(); - }); -}; - -// 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]); - } - } - node.beingTargeted = false; -}; - -var storedCycles = 0; -HackingMission.prototype.process = function (numCycles = 1) { - if (!this.started) { - return; - } - storedCycles += numCycles; - if (storedCycles < 2) { - return; - } // Only process every 3 cycles minimum - - var res = false; - // Process actions of all player nodes - this.playerCores.forEach((node) => { - res |= this.processNode(node, storedCycles); - }); - - this.playerNodes.forEach((node) => { - if (node.type === NodeTypes.Transfer || node.type === NodeTypes.Shield || node.type === NodeTypes.Firewall) { - res |= this.processNode(node, storedCycles); - } - }); - - // Process actions of all enemy nodes - this.enemyCores.forEach((node) => { - this.enemyAISelectAction(node); - res |= this.processNode(node, storedCycles); - }); - - this.enemyNodes.forEach((node) => { - if (node.type === NodeTypes.Transfer || node.type === NodeTypes.Shield || node.type === NodeTypes.Firewall) { - this.enemyAISelectAction(node); - res |= this.processNode(node, storedCycles); - } - }); - - // The hp of enemy databases increases slowly - this.enemyDatabases.forEach((node) => { - node.maxhp += 0.1 * storedCycles; - node.hp += 0.1 * storedCycles; - }); - - if (res) { - this.calculateAttacks(); - this.calculateDefenses(); - } - - // Win if all enemy databases are conquered - if (this.enemyDatabases.length === 0) { - this.finishMission(true); - return; - } - - // Lose if all your cores are gone - if (this.playerCores.length === 0) { - this.finishMission(false); - return; - } - - // Defense/hp of misc nodes increases slowly over time - this.miscNodes.forEach((node) => { - node.def += 0.1 * storedCycles; - node.maxhp += 0.05 * storedCycles; - node.hp += 0.1 * storedCycles; - if (node.hp > node.maxhp) { - node.hp = node.maxhp; - } - this.updateNodeDomElement(node); - }); - - // Update timer and check if player lost - this.time -= storedCycles * CONSTANTS._idleSpeed; - if (this.time <= 0) { - this.finishMission(false); - return; - } - this.updateTimer(); - - storedCycles = 0; -}; - -// 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, - atk; - if (nodeObj.conn) { - if (nodeObj.conn.target != null) { - targetNode = this.getNodeFromElement(nodeObj.conn.target); - } else { - targetNode = this.getNodeFromElement(nodeObj.conn.targetId); - } - - if (targetNode == null) { - // Player is in the middle of dragging the connection, - // so the target node is null. Do nothing here - } else if (targetNode.plyrCtrl) { - def = this.playerDef; - atk = this.enemyAtk; - } else if (targetNode.enmyCtrl) { - def = this.enemyDef; - atk = this.playerAtk; - } else { - // Misc Node - def = targetNode.def; - nodeObj.plyrCtrl ? (atk = this.playerAtk) : (atk = this.enemyAtk); - } - } - - // Calculations are per second, so divide everything by 5 - var calcStats = false, - plyr = nodeObj.plyrCtrl; - var enmyHacking = this.difficulty * CONSTANTS.HackingMissionDifficultyToHacking; - switch (nodeObj.action) { - case NodeActions.Attack: - if (targetNode == null) { - break; - } - if (nodeObj.conn == null) { - break; - } - var dmg = this.calculateAttackDamage(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.hp -= (dmg / 5) * numCycles; - break; - case NodeActions.Scan: - if (targetNode == null) { - break; - } - if (nodeObj.conn == null) { - break; - } - var eff = this.calculateScanEffect(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.def -= (eff / 5) * numCycles; - calcStats = true; - break; - case NodeActions.Weaken: - if (targetNode == null) { - break; - } - if (nodeObj.conn == null) { - break; - } - var eff = this.calculateWeakenEffect(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.atk -= (eff / 5) * numCycles; - calcStats = true; - break; - case NodeActions.Fortify: - var eff = this.calculateFortifyEffect(Player.hacking_skill); - nodeObj.def += (eff / 5) * numCycles; - calcStats = 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; - calcStats = true; - break; - default: - console.error(`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(this.actionButtons); - } - - // The conquered node has its stats reduced - targetNode.atk /= 2; - targetNode.def /= 3.5; - - // Flag for whether the target node was a misc node - var isMiscNode = !targetNode.plyrCtrl && !targetNode.enmyCtrl; - - // 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: "Continuous", - connector: "Flowchart", - }); - } else { - targetNode.setControlledByEnemy(); - nodeObj.conn = null; // Clear connection - this.jsplumbinstance.unmakeSource(targetNode.el); - this.jsplumbinstance.makeTarget(targetNode.el, { - maxConnections: -1, - anchor: "Continuous", - connector: ["Flowchart"], - }); - } - - calcStats = true; - - // Helper function to swap nodes between the respective enemyNodes/playerNodes arrays - function swapNodes(orig, dest, targetNode) { - for (var i = 0; i < orig.length; ++i) { - if (orig[i] == targetNode) { - var node = orig.splice(i, 1); - node = node[0]; - dest.push(node); - break; - } - } - } - - 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); - this.configureEnemyNodeElement(targetNode.el); - } - 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); - // Conquering spam node increases time limit - this.time += CONSTANTS.HackingMissionSpamTimeIncrease; - } else { - swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); - } - - 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); - this.configurePlayerNodeElement(targetNode.el); - } else { - swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); - this.configureEnemyNodeElement(targetNode.el); - } - break; - } - - // If a misc node was conquered, the defense for all misc nodes increases by some fixed amount - if (isMiscNode) { - //&& conqueredByPlayer) { - this.miscNodes.forEach((node) => { - if (node.targetedCount === 0) { - node.def *= CONSTANTS.HackingMissionMiscDefenseIncrease; - } - }); - } - } - - // Update node DOMs - this.updateNodeDomElement(nodeObj); - if (targetNode) { - this.updateNodeDomElement(targetNode); - } - return calcStats; -}; - -// Enemy "AI" for CPU Core and Transfer Nodes -HackingMission.prototype.enemyAISelectAction = function (nodeObj) { - if (nodeObj == null) { - return; - } - switch (nodeObj.type) { - case NodeTypes.Core: - /** - * Select a single RANDOM target from miscNodes and player's Nodes - * If it is reachable, it will target it. If not, no target will - * be selected for now, and the next time process() gets called this will repeat - */ - if (nodeObj.conn == null) { - if (this.miscNodes.length === 0) { - // Randomly pick a player node and attack it if its reachable - var rand = getRandomInt(0, this.playerNodes.length - 1); - var node; - if (this.playerNodes.length === 0) { - node = null; - } else { - node = this.playerNodes[rand]; - } - if (this.nodeReachableByEnemy(node)) { - // Create connection - nodeObj.conn = this.jsplumbinstance.connect({ - source: nodeObj.el, - target: node.el, - }); - ++node.targetedCount; - } else { - // Randomly pick a player core and attack it if its reachable - rand = getRandomInt(0, this.playerCores.length - 1); - if (this.playerCores.length === 0) { - return; // No Misc Nodes, no player Nodes, no Player cores. Player lost - } else { - node = this.playerCores[rand]; - } - - if (this.nodeReachableByEnemy(node)) { - // Create connection - nodeObj.conn = this.jsplumbinstance.connect({ - source: nodeObj.el, - target: node.el, - }); - ++node.targetedCount; - } - } - } else { - // Randomly pick a misc node and attack it if its reachable - var rand = getRandomInt(0, this.miscNodes.length - 1); - var node = this.miscNodes[rand]; - if (this.nodeReachableByEnemy(node)) { - nodeObj.conn = this.jsplumbinstance.connect({ - source: nodeObj.el, - target: node.el, - }); - ++node.targetedCount; - } - } - - // If no connection was made, set the Core to Fortify - nodeObj.action = NodeActions.Fortify; - } else { - // If this node has a selected target - var targetNode; - if (nodeObj.conn.target) { - targetNode = this.getNodeFromElement(nodeObj.conn.target); - } else { - targetNode = this.getNodeFromElement(nodeObj.conn.targetId); - } - if (targetNode == null) { - console.error("Error getting Target node Object in enemyAISelectAction()"); - } - - if (targetNode.def > this.enemyAtk + 15) { - if (nodeObj.def < 50) { - nodeObj.action = NodeActions.Fortify; - } else { - nodeObj.action = NodeActions.Overflow; - } - } else if (Math.abs(targetNode.def - this.enemyAtk) <= 15) { - nodeObj.action = NodeActions.Scan; - } else { - nodeObj.action = NodeActions.Attack; - } - } - break; - case NodeTypes.Transfer: - // Switch between fortifying and overflowing as necessary - if (nodeObj.def < 125) { - nodeObj.action = NodeActions.Fortify; - } else { - nodeObj.action = NodeActions.Overflow; - } - break; - case NodeTypes.Firewall: - case NodeTypes.Shield: - nodeObj.action = NodeActions.Fortify; - break; - default: - break; - } -}; - -var hackEffWeightSelf = 130; // Weight for Node actions on self -var hackEffWeightTarget = 25; // Weight for Node Actions against Target -var hackEffWeightAttack = 80; // Weight for Attack action - -// Returns damage per cycle based on stats -HackingMission.prototype.calculateAttackDamage = function (atk, def, hacking = 0) { - return Math.max(0.55 * (atk + hacking / hackEffWeightAttack - def), 1); -}; - -HackingMission.prototype.calculateScanEffect = function (atk, def, hacking = 0) { - return Math.max(0.6 * (atk + hacking / hackEffWeightTarget - def * 0.95), 2); -}; - -HackingMission.prototype.calculateWeakenEffect = function (atk, def, hacking = 0) { - return Math.max(atk + hacking / hackEffWeightTarget - def * 0.95, 2); -}; - -HackingMission.prototype.calculateFortifyEffect = function (hacking = 0) { - return (0.9 * hacking) / hackEffWeightSelf; -}; - -HackingMission.prototype.calculateOverflowEffect = function (hacking = 0) { - return (0.95 * 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 mm: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) { - var favorMult = 1 + this.faction.favor / 100; - var gain = this.reward * Player.faction_rep_mult * favorMult; - dialogBoxCreate( - <> - Mission won! You earned {Reputation(gain)} reputation with {this.faction.name} - </>, - ); - Player.gainIntelligenceExp(Math.pow(this.difficulty * CONSTANTS.IntelligenceHackingMissionBaseExpGain, 0.5)); - this.faction.playerReputation += gain; - } else { - dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation."); - } - Router.toFaction(); -}; - -export { HackingMission, inMission, setInMission, currMission }; diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index f9755b191..675a1d076 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -98,7 +98,6 @@ import { Terminal } from "./Terminal"; import { calculateSkill, calculateExp } from "./PersonObjects/formulas/skill"; import { Message } from "./Message/Message"; -import { inMission } from "./Missions"; import { Player } from "./Player"; import { Programs } from "./Programs/Programs"; import { Script } from "./Script/Script"; @@ -2965,10 +2964,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { universityCourse: function (universityName: any, className: any): any { updateDynamicRam("universityCourse", getRamCost("universityCourse")); checkSingularityAccess("universityCourse", 1); - if (inMission) { - workerScript.log("universityCourse", "You are in the middle of a mission."); - return; - } if (Player.isWorking) { const txt = Player.singularityStopWork(); workerScript.log("universityCourse", txt); @@ -3049,10 +3044,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { gymWorkout: function (gymName: any, stat: any): any { updateDynamicRam("gymWorkout", getRamCost("gymWorkout")); checkSingularityAccess("gymWorkout", 1); - if (inMission) { - workerScript.log("gymWorkout", "You are in the middle of a mission."); - return; - } if (Player.isWorking) { const txt = Player.singularityStopWork(); workerScript.log("gymWorkout", txt); @@ -3486,7 +3477,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { isBusy: function (): any { updateDynamicRam("isBusy", getRamCost("isBusy")); checkSingularityAccess("isBusy", 1); - return Player.isWorking || inMission; + return Player.isWorking; }, stopAction: function (): any { updateDynamicRam("stopAction", getRamCost("stopAction")); @@ -3553,12 +3544,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { return false; } - // Cant work while in a mission - if (inMission) { - workerScript.log("workForCompany", "You are in the middle of a mission."); - return false; - } - // Check to make sure company position data is valid const companyPositionName = Player.jobs[companyName]; const companyPosition = CompanyPositions[companyPositionName]; @@ -3708,11 +3693,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { return; } - if (inMission) { - workerScript.log("workForFaction", "You are in the middle of a mission."); - return; - } - if (!Player.factions.includes(name)) { workerScript.log("workForFaction", `You are not a member of '${name}'`); return false; @@ -3900,10 +3880,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { updateDynamicRam("createProgram", getRamCost("createProgram")); checkSingularityAccess("createProgram", 3); - if (inMission) { - workerScript.log("createProgram", "You are in the middle of a mission."); - return; - } if (Player.isWorking) { const txt = Player.singularityStopWork(); workerScript.log("createProgram", txt); @@ -3946,10 +3922,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { commitCrime: function (crimeRoughName: any): any { updateDynamicRam("commitCrime", getRamCost("commitCrime")); checkSingularityAccess("commitCrime", 3); - if (inMission) { - workerScript.log("commitCrime", "You are in the middle of a mission."); - return; - } + if (Player.isWorking) { const txt = Player.singularityStopWork(); workerScript.log("commitCrime", txt); diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index e314e6393..4aed01d3e 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -50,7 +50,6 @@ import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers"; import { Settings } from "../../Settings/Settings"; import { redPillFlag } from "../../RedPill"; -import { inMission } from "../../Missions"; import { KEY } from "../../utils/helpers/keyCodes"; const openedMixin = (theme: Theme): CSSObject => ({ @@ -266,7 +265,7 @@ export function SidebarRoot(props: IProps): React.ReactElement { // Alt-o - Options function handleShortcuts(this: Document, event: KeyboardEvent): any { if (Settings.DisableHotkeys) return; - if (props.player.isWorking || redPillFlag || inMission) return; + if (props.player.isWorking || redPillFlag) return; if (event.keyCode == KEY.T && event.altKey) { event.preventDefault(); clickTerminal(); diff --git a/src/engine.tsx b/src/engine.tsx index 361c71685..a691952a6 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -23,7 +23,6 @@ import { import { hasHacknetServers, processHacknetEarnings } from "./Hacknet/HacknetHelpers"; import { iTutorialStart } from "./InteractiveTutorial"; import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; -import { inMission, currMission } from "./Missions"; import { loadAllRunningScripts, updateOnlineScriptTimes } from "./NetscriptWorker"; import { Player } from "./Player"; import { saveObject, loadGame } from "./SaveObject"; @@ -131,11 +130,6 @@ const Engine: { Player.gang.process(numCycles, Player); } - // Mission - if (inMission && currMission) { - currMission.process(numCycles); - } - // Corporation if (Player.corporation instanceof Corporation) { // Stores cycles in a "buffer". Processed separately using Engine Counters diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 1816067d6..beeaffef4 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -56,7 +56,6 @@ import { TerminalRoot } from "../Terminal/ui/TerminalRoot"; import { TutorialRoot } from "../Tutorial/ui/TutorialRoot"; import { ActiveScriptsRoot } from "../ui/ActiveScripts/ActiveScriptsRoot"; import { FactionsRoot } from "../Faction/ui/FactionsRoot"; -import { HackingMissionRoot } from "../HackingMission/ui/HackingMissionRoot"; import { FactionRoot } from "../Faction/ui/FactionRoot"; import { CharacterStats } from "./CharacterStats"; import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot"; @@ -178,9 +177,6 @@ export let Router: IRouter = { toLocation: () => { throw new Error("Router called before initialization"); }, - toHackingMission: () => { - throw new Error("Router called before initialization"); - }, }; function determineStartPage(player: IPlayer): Page { @@ -270,10 +266,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme setLocation(location); setPage(Page.Location); }, - toHackingMission: (faction: Faction) => { - setPage(Page.HackingMission); - setFaction(faction); - }, }; useEffect(() => { @@ -296,8 +288,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme <BitverseRoot flume={flume} enter={enterBitNode} quick={quick} /> ) : page === Page.Infiltration ? ( <InfiltrationRoot location={location} /> - ) : page === Page.HackingMission ? ( - <HackingMissionRoot faction={faction} /> ) : page === Page.BladeburnerCinematic ? ( <BladeburnerCinematic /> ) : page === Page.Work ? ( diff --git a/src/ui/React/Overview.tsx b/src/ui/React/Overview.tsx index ca3d08024..3b0dc71bb 100644 --- a/src/ui/React/Overview.tsx +++ b/src/ui/React/Overview.tsx @@ -23,8 +23,7 @@ export function Overview({ children }: IProps): React.ReactElement { const [open, setOpen] = useState(true); const classes = useStyles(); const router = use.Router(); - if (router.page() === Page.BitVerse || router.page() === Page.HackingMission || router.page() === Page.Loading) - return <></>; + if (router.page() === Page.BitVerse || router.page() === Page.Loading) return <></>; let icon; if (open) { icon = <VisibilityOffIcon color="primary" />; diff --git a/src/ui/Router.ts b/src/ui/Router.ts index 206377b42..267d137a7 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -33,7 +33,6 @@ export enum Page { Work, BladeburnerCinematic, Location, - HackingMission, Loading, } @@ -74,5 +73,4 @@ export interface IRouter { toWork(): void; toBladeburnerCinematic(): void; toLocation(location: Location): void; - toHackingMission(faction: Faction): void; }