diff --git a/css/popupboxes.scss b/css/popupboxes.scss index 174044db3..d6845b9f0 100644 --- a/css/popupboxes.scss +++ b/css/popupboxes.scss @@ -3,7 +3,7 @@ /* Pop-up boxes */ .popup-box-container { - display: none; /* Hidden by default */ + display: none; /* Initially hidden */ position: fixed; /* Stay in place */ z-index: 10; /* Sit on top */ left: 0; diff --git a/doc/source/advancedgameplay/sleeves.rst b/doc/source/advancedgameplay/sleeves.rst index b0db5b682..35eeaa9ac 100644 --- a/doc/source/advancedgameplay/sleeves.rst +++ b/doc/source/advancedgameplay/sleeves.rst @@ -80,6 +80,16 @@ when you normally install Augmentations. The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected by how many Augmentations you have purchased for yourself, and vice versa. +Memory +~~~~~~ +Sleeve memory dictates what a sleeve's synchronization will be when its reset by +switching BitNodes. For example, if a sleeve has a memory of 10, then when you +switch BitNodes its synchronization will initially be set to 10, rather than 1. + +Memory can only be increased by purchasing upgrades from The Covenant. +It is a persistent stat, meaning it never gets reset back to 1. +The maximum possible value for a sleeve's memory is 100. + Re-sleeving ^^^^^^^^^^^ Re-sleeving is the process of digitizing and transferring your consciousness into a diff --git a/doc/source/netscript/sleeveapi/getSleeveStats.rst b/doc/source/netscript/sleeveapi/getSleeveStats.rst index 698fa9e4c..56f90388c 100644 --- a/doc/source/netscript/sleeveapi/getSleeveStats.rst +++ b/doc/source/netscript/sleeveapi/getSleeveStats.rst @@ -10,8 +10,8 @@ getSleeveStats() Netscript Function .. code-block:: javascript { - shock: current shock of the sleeve [0-1], - sync: current sync of the sleeve [0-1], + shock: current shock of the sleeve [0-100], + sync: current sync of the sleeve [0-100], hacking_skill: current hacking skill of the sleeve, strength: current strength of the sleeve, defense: current defense of the sleeve, diff --git a/doc/source/netscript/sleeveapi/purchaseSleeveAug.rst b/doc/source/netscript/sleeveapi/purchaseSleeveAug.rst index 31fff9f49..70fe4ebc2 100644 --- a/doc/source/netscript/sleeveapi/purchaseSleeveAug.rst +++ b/doc/source/netscript/sleeveapi/purchaseSleeveAug.rst @@ -4,6 +4,6 @@ purchaseSleeveAug() Netscript Function .. js:function:: purchaseSleeveAug(sleeveNumber, augName) :param int sleeveNumber: Index of the sleeve to buy an aug for. See :ref:`here ` - :param string augName: Name of the aug to buy. See :ref:`here ` + :param string augName: Name of the aug to buy. Must be an exact match - Return true if the aug was purchased and installed on the sleeve. \ No newline at end of file + Return true if the aug was purchased and installed on the sleeve. diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index 2d3568d70..778cb5098 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -201,8 +201,9 @@ export function initBitNodes() { "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

" + "In this BitNode:

" + + "Your hacking stat and experience gain are halved
" + "The starting and maximum amount of money available on servers is significantly decreased
" + - "The growth rate of servers is halved
" + + "The growth rate of servers is significantly reduced
" + "Weakening a server is twice as effective
" + "Company wages are decreased by 50%
" + "Corporation valuations are 99% lower and are therefore significantly less profitable
" + @@ -213,9 +214,9 @@ export function initBitNodes() { "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + "This Source-File also increases the player's company salary and reputation gain multipliers by:

" + - "Level 1: 24%
" + - "Level 2: 36%
" + - "Level 3: 42%"); + "Level 1: 32%
" + + "Level 2: 48%
" + + "Level 3: 56%"); BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", "To iterate is human, to recurse divine.

" + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + @@ -349,17 +350,20 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.CodingContractMoney = 0; break; case 9: // Hacktocracy - BitNodeMultipliers.HackingLevelMultiplier = 0.3; + BitNodeMultipliers.HackingLevelMultiplier = 0.4; BitNodeMultipliers.StrengthLevelMultiplier = 0.45; BitNodeMultipliers.DefenseLevelMultiplier = 0.45; BitNodeMultipliers.DexterityLevelMultiplier = 0.45; BitNodeMultipliers.AgilityLevelMultiplier = 0.45; BitNodeMultipliers.CharismaLevelMultiplier = 0.45; BitNodeMultipliers.PurchasedServerLimit = 0; - BitNodeMultipliers.HomeComputerRamCost = 3; + BitNodeMultipliers.HomeComputerRamCost = 5; BitNodeMultipliers.CrimeMoney = 0.5; BitNodeMultipliers.ScriptHackMoney = 0.1; BitNodeMultipliers.HackExpGain = 0.1; + BitNodeMultipliers.ServerStartingMoney = 0.1; + BitNodeMultipliers.ServerMaxMoney = 0.1; + BitNodeMultipliers.ServerStartingSecurity = 2.5; break; case 10: // Digital Carbon BitNodeMultipliers.HackingLevelMultiplier = 0.2; @@ -385,9 +389,11 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.BladeburnerRank = 0.8; break; case 11: //The Big Crash + BitNodeMultipliers.HackingLevelMultiplier = 0.5; + BitNodeMultipliers.HackExpGain = 0.5; BitNodeMultipliers.ServerMaxMoney = 0.1; BitNodeMultipliers.ServerStartingMoney = 0.1; - BitNodeMultipliers.ServerGrowthRate = 0.5; + BitNodeMultipliers.ServerGrowthRate = 0.2; BitNodeMultipliers.ServerWeakenRate = 2; BitNodeMultipliers.CrimeMoney = 3; BitNodeMultipliers.CompanyWorkMoney = 0.5; @@ -395,8 +401,8 @@ export function initBitNodeMultipliers(p: IPlayer) { BitNodeMultipliers.AugmentationMoneyCost = 2; BitNodeMultipliers.InfiltrationMoney = 2.5; BitNodeMultipliers.InfiltrationRep = 2.5; - BitNodeMultipliers.CorporationValuation = 0.01; - BitNodeMultipliers.CodingContractMoney = 0.5; + BitNodeMultipliers.CorporationValuation = 0.1; + BitNodeMultipliers.CodingContractMoney = 0.25; BitNodeMultipliers.FourSigmaMarketDataCost = 4; BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; break; diff --git a/src/Bladeburner.js b/src/Bladeburner.js index 8e0c6d339..495109ba8 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -78,8 +78,8 @@ const RanksPerSkillPoint = 3; //How many ranks needed to get 1 Skill const ContractBaseMoneyGain = 250e3; //Base Money Gained per contract -const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber -const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber +const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber +const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber //DOM related variables var ActiveActionCssClass = "bladeburner-active-action"; @@ -1417,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() { } this.startAction(this.action); // Repeat Action break; - case ActionTypes["Hyperbolic Regeneration Chamber"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: { Player.regenerateHp(HrcHpGain); - this.stamina = Math.min(this.maxStamina, this.stamina + HrcStaminaGain); + + const staminaGain = this.maxStamina * (HrcStaminaGain / 100); + this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); this.startAction(this.action); if (this.logging.general) { - this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${HrcStaminaGain} stamina`); + this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${HrcHpGain} HP and gained ${numeralWrapper.format(staminaGain, "0.0")} stamina`); } break; + } default: console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); break; @@ -2513,7 +2516,7 @@ Bladeburner.prototype.updateContractsUIElement = function(el, action) { display:"inline-block", innerHTML:action.desc + "\n\n" + `Estimated success chance: ${formatNumber(estimatedSuccessChance*100, 1)}% ${action.isStealth?stealthIcon:''}${action.isKill?killIcon:''}\n` + - + "Time Required (s): " + formatNumber(actionTime, 0) + "\n" + "Contracts remaining: " + Math.floor(action.count) + "\n" + "Successes: " + action.successes + "\n" + diff --git a/src/Constants.ts b/src/Constants.ts index cb56cb3d2..ae1d9c96f 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -116,7 +116,7 @@ export let CONSTANTS: IMap = { InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level InfiltrationMoneyValue: 5e3, //Convert "secret" value to money InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation - InfiltrationExpPow: 0.7, + InfiltrationExpPow: 0.8, //Stock market constants WSEAccountCost: 200e6, @@ -273,37 +273,20 @@ export let CONSTANTS: IMap = { LatestUpdate: ` - v0.45.1 - * Added two new Corporation Researches - * General UI improvements (by hydroflame and koriar) - * Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors - * Bug Fix: sleeve.getSleeveStats() should now work properly + v0.46.0 + * Added BitNode-9: Hacktocracy + * Changed BitNode-11's multipliers to make it slightly harder overall + * Source-File 11 is now slightly stronger + * Added several functions to Netscript Sleeve API for buying Sleeve augmentations (by hydroflame) + * Added a new stat for Duplicate Sleeves: Memory + * Increase baseline experience earned from Infiltration, but it now gives diminishing returns (on exp) as you get to higher difficulties/levels + * In Bladeburner, stamina gained from Hyperbolic Regeneration Chamber is now a percentage of your max stamina - v0.45.0 - * Corporation changes: - ** Decreased the time of a full market cycle from 15 seconds to 10 seconds. - ** This means that each Corporation 'state' will now only take 2 seconds, rather than 3 - ** Increased initial salaries for newly-hired employees - ** Increased the cost multiplier for upgrading office size (the cost will increase faster) - ** The stats of your employees now has a slightly larger effect on production & sales - ** Added several new Research upgrades - ** Market-TA research now allows you to automatically set sale price at optimal values - ** Market-TA research now works for Products (not just Materials) - ** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k - ** Energy Material requirement of the Software industry reduced from 1 to 0.5 - ** It is now slightly easier to increase the Software industry's production multiplier - ** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research. - ** You can now see an approximation of how each material affects an industry's production multiplier by clicking the "?" help tip next to it - ** Significantly changed the effects of the different employee positions. See updated descriptions - ** Reduced the amount of money you gain from private investors - ** Training employees is now 3x more effective - ** Bug Fix: An industry's products are now properly separated between different cities - * The QLink Augemntation is now significantly stronger, but also significantly more expensive (by hydroflame) - * Added a Netscript API for Duplicate Sleeves (by hydroflame) - * Modified the multipliers of BitNode-3 and BitNode-8 to make them slightly harder - * After installing Augmentations, Duplicate Sleeves will now default to Synchronize if their Shock is 0 - * Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina - * Bug Fix: growthAnalyze() function now properly accounts for BitNode multipliers - * Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself + * Corporation Changes: + ** 'Demand' value of products decreases more slowly + ** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research + + * Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually + * Bug Fix: Fixed an issue with the job requirement tooltip for security jobs ` } diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx index cb69bd9f5..f751e21c3 100644 --- a/src/Corporation/Corporation.jsx +++ b/src/Corporation/Corporation.jsx @@ -563,13 +563,15 @@ Industry.prototype.processMaterialMarket = function(marketCycles=1) { } } -//Process change in demand and competition for this industry's products +// Process change in demand and competition for this industry's products Industry.prototype.processProductMarket = function(marketCycles=1) { - //Demand gradually decreases, and competition gradually increases - for (var name in this.products) { + // Demand gradually decreases, and competition gradually increases + for (const name in this.products) { if (this.products.hasOwnProperty(name)) { - var product = this.products[name]; - var change = getRandomInt(1, 3) * 0.0004; + const product = this.products[name]; + let change = getRandomInt(0, 3) * 0.0004; + if (change === 0) { continue; } + if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || this.type === Industries.Robotics) { change *= 3; @@ -770,7 +772,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) { * advertisingFactor * this.getSalesMultiplier()); const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); - const optimalPrice = (numerator / denominator) + mat.bCost; + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + if (sqrtNumerator === 0) { + optimalPrice = 0; // No production + } else { + optimalPrice = mat.bCost + markupLimit; + console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); + } + } else { + optimalPrice = (numerator / denominator) + mat.bCost; + } // We'll store this "Optimal Price" in a property so that we don't have // to re-calculate it for the UI @@ -1089,7 +1101,12 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); let optimalPrice; if (sqrtDenominator === 0 || denominator === 0) { - optimalPrice = 0; + if (sqrtNumerator === 0) { + optimalPrice = 0; // No production + } else { + optimalPrice = product.pCost + markupLimit; + console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); + } } else { optimalPrice = (numerator / denominator) + product.pCost; } @@ -1251,7 +1268,7 @@ Industry.prototype.getAdvertisingFactors = function() { //Returns a multiplier based on a materials demand and competition that affects sales Industry.prototype.getMarketFactor = function(mat) { - return mat.dmd * (100 - mat.cmp)/100; + return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100); } // Returns a boolean indicating whether this Industry has the specified Research diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 7ad8f4ae6..abf4bb86b 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -86,7 +86,7 @@ export class Material { this.mku = 6; break; case "Energy": - this.dmd = 90; this.dmdR = [80, 100]; + this.dmd = 90; this.dmdR = [80, 99]; this.cmp = 80; this.cmpR = [65, 95]; this.bCost = 2000; this.mv = 0.2; this.mku = 6; @@ -122,26 +122,26 @@ export class Material { this.mku = 2; break; case "Real Estate": - this.dmd = 50; this.dmdR = [5, 100]; + this.dmd = 50; this.dmdR = [5, 99]; this.cmp = 50; this.cmpR = [25, 75]; this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice this.mku = 1.5; break; case "Drugs": this.dmd = 60; this.dmdR = [45, 75]; - this.cmp = 70; this.cmpR = [40, 100]; + this.cmp = 70; this.cmpR = [40, 99]; this.bCost = 40e3; this.mv = 1.6; this.mku = 1; break; case "Robots": - this.dmd = 90; this.dmdR = [80, 100]; - this.cmp = 90; this.cmpR = [80, 100]; + this.dmd = 90; this.dmdR = [80, 9]; + this.cmp = 90; this.cmpR = [80, 9]; this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice this.mku = 1; break; case "AI Cores": - this.dmd = 90; this.dmdR = [80, 100]; - this.cmp = 90; this.cmpR = [80, 100]; + this.dmd = 90; this.dmdR = [80, 99]; + this.cmp = 90; this.cmpR = [80, 9]; this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice this.mku = 0.5; break; diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index 1f6edfb2a..46dfa16b3 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -780,7 +780,12 @@ export class CorporationEventHandler { useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); - createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); + const ta2OverridesTa1 = createElement("p", { + innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " + + "both are enabled, then Market-TA.II will take effect, not Market-TA.I" + }); + + createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]); } else { // Market-TA.I only createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); @@ -1052,7 +1057,12 @@ export class CorporationEventHandler { useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); - createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]); + const ta2OverridesTa1 = createElement("p", { + innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " + + "both are enabled, then Market-TA.II will take effect, not Market-TA.I" + }); + + createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]); } else { // Market-TA.I only createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx index 7a0a3a743..40f8c0d1d 100644 --- a/src/Corporation/ui/IndustryOffice.jsx +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -301,7 +301,7 @@ export class IndustryOffice extends BaseReactComponent {

Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}

Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}

-

Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}

+

Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}

Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

{ vechain && diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx index acb02ebd8..f36f451e4 100644 --- a/src/Corporation/ui/IndustryWarehouse.jsx +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -218,7 +218,7 @@ function MaterialComponent(props) { mat.buy === 0 && mat.imp === 0; // Purchase material button - const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; + const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`; const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); @@ -229,9 +229,9 @@ function MaterialComponent(props) { let sellButtonText; if (mat.sllman[0]) { if (isString(mat.sllman[1])) { - sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${mat.sllman[1]})` + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})` } else { - sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`; + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`; } if (mat.marketTa2) { @@ -469,7 +469,7 @@ export class IndustryWarehouse extends BaseReactComponent { return (

- Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} + Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}

diff --git a/src/Faction/FactionHelpers.js b/src/Faction/FactionHelpers.js index c061c359c..f73698b10 100644 --- a/src/Faction/FactionHelpers.js +++ b/src/Faction/FactionHelpers.js @@ -14,7 +14,7 @@ import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums"; import { Settings } from "../Settings/Settings"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; -import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases"; +import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases"; import {Page, routing} from "../ui/navigationTracking"; import {numeralWrapper} from "../ui/numeralFormat"; @@ -348,7 +348,7 @@ function displayFactionContent(factionName) { class: "std-button", innerText: "Purchase Duplicate Sleeves", clickListener: () => { - createPurchaseSleevesFromCovenantPopup(Player); + createSleevePurchasesFromCovenantPopup(Player); } })); covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", { diff --git a/src/Hacknet/HacknetHelpers.jsx b/src/Hacknet/HacknetHelpers.jsx index 38246f75a..50d1e5ccd 100644 --- a/src/Hacknet/HacknetHelpers.jsx +++ b/src/Hacknet/HacknetHelpers.jsx @@ -19,7 +19,8 @@ import { generateRandomContractOnHome } from "../CodingContractGenerator import { iTutorialSteps, iTutorialNextStep, ITutorial} from "../InteractiveTutorial"; import { Player } from "../Player"; -import { AddToAllServers } from "../Server/AllServers"; +import { AddToAllServers, + AllServers } from "../Server/AllServers"; import { GetServerByHostname } from "../Server/ServerHelpers"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { Page, routing } from "../ui/navigationTracking"; @@ -33,9 +34,10 @@ import { HacknetRoot } from "./ui/Root"; let hacknetNodesDiv; function hacknetNodesInit() { hacknetNodesDiv = document.getElementById("hacknet-nodes-container"); + document.removeEventListener("DOMContentLoaded", hacknetNodesInit); } -document.addEventListener("DOMContentLoaded", hacknetNodesInit, false); +document.addEventListener("DOMContentLoaded", hacknetNodesInit); // Returns a boolean indicating whether the player has Hacknet Servers // (the upgraded form of Hacknet Nodes) @@ -73,7 +75,7 @@ export function purchaseHacknet() { }); Player.loseMoney(cost); - Player.hacknetNodes.push(server); + Player.hacknetNodes.push(server.ip); // Configure the HacknetServer to actually act as a Server AddToAllServers(server); @@ -308,7 +310,8 @@ function processAllHacknetServerEarnings(numCycles) { let hashes = 0; for (let i = 0; i < Player.hacknetNodes.length; ++i) { - hashes += Player.hacknetNodes[i].process(numCycles); + const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses + hashes += hserver.process(numCycles); } Player.hashManager.storeHashes(hashes); @@ -316,16 +319,6 @@ function processAllHacknetServerEarnings(numCycles) { return hashes; } -export function getHacknetNode(name) { - for (var i = 0; i < Player.hacknetNodes.length; ++i) { - if (Player.hacknetNodes[i].name == name) { - return Player.hacknetNodes[i]; - } - } - - return null; -} - export function purchaseHashUpgrade(upgName, upgTarget) { if (!(Player.hashManager instanceof HashManager)) { console.error(`Player does not have a HashManager`); @@ -423,7 +416,6 @@ export function purchaseHashUpgrade(upgName, upgTarget) { return false; } - console.log("Hash Upgrade successfully purchased"); return true; } diff --git a/src/Hacknet/HacknetServer.ts b/src/Hacknet/HacknetServer.ts index 15022cd1b..af86618d9 100644 --- a/src/Hacknet/HacknetServer.ts +++ b/src/Hacknet/HacknetServer.ts @@ -20,7 +20,7 @@ import { Generic_fromJSON, export const HacknetServerHashesPerLevel: number = 0.001; // Constants for Hacknet Server purchase/upgrade costs -export const BaseCostForHacknetServer: number = 10e3; +export const BaseCostForHacknetServer: number = 50e3; export const BaseCostFor1GBHacknetServerRam: number = 200e3; export const BaseCostForHacknetServerCore: number = 1e6; export const BaseCostForHacknetServerCache: number = 10e6; @@ -37,7 +37,7 @@ export const MaxNumberHacknetServers: number = 25; // Max number of Hac export const HacknetServerMaxLevel: number = 300; export const HacknetServerMaxRam: number = 8192; export const HacknetServerMaxCores: number = 128; -export const HacknetServerMaxCache: number = 15; // Max cache level. So max capacity is 2 ^ 12 +export const HacknetServerMaxCache: number = 15; interface IConstructorParams { adminRights?: boolean; @@ -306,6 +306,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode { this.maxRam *= 2; } this.maxRam = Math.round(this.maxRam); + this.updateHashRate(p); return true; } @@ -321,7 +322,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode { } updateHashCapacity(): void { - this.hashCapacity = 16 * Math.pow(2, this.cache); + this.hashCapacity = 32 * Math.pow(2, this.cache); } updateHashRate(p: IPlayer): void { diff --git a/src/Hacknet/HashManager.ts b/src/Hacknet/HashManager.ts index 4aa9fa5f8..1f01e7c24 100644 --- a/src/Hacknet/HashManager.ts +++ b/src/Hacknet/HashManager.ts @@ -11,6 +11,7 @@ import { HashUpgrades } from "./HashUpgrades"; import { IMap } from "../types"; import { IPlayer } from "../PersonObjects/IPlayer"; +import { AllServers } from "../Server/AllServers"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; @@ -106,13 +107,29 @@ export class HashManager { } updateCapacity(p: IPlayer): void { - if (p.hacknetNodes.length <= 0) { this.capacity = 0; } - if (!(p.hacknetNodes[0] instanceof HacknetServer)) { this.capacity = 0; } + if (p.hacknetNodes.length <= 0) { + this.capacity = 0; + return; + } + + // Make sure the Player's `hacknetNodes` property actually holds Hacknet Servers + const ip: string = p.hacknetNodes[0]; + if (typeof ip !== "string") { + this.capacity = 0; + return; + } + + const hserver = AllServers[ip]; + if (!(hserver instanceof HacknetServer)) { + this.capacity = 0; + return; + } + let total: number = 0; for (let i = 0; i < p.hacknetNodes.length; ++i) { - const hacknetServer = (p.hacknetNodes[i]); - total += hacknetServer.hashCapacity; + const h = AllServers[p.hacknetNodes[i]]; + total += h.hashCapacity; } this.capacity = total; diff --git a/src/Hacknet/ui/Root.jsx b/src/Hacknet/ui/Root.jsx index 3e1a95633..32a9a0694 100644 --- a/src/Hacknet/ui/Root.jsx +++ b/src/Hacknet/ui/Root.jsx @@ -17,8 +17,10 @@ import { getCostOfNextHacknetNode, purchaseHacknet } from "../HacknetHelpers"; import { Player } from "../../Player"; +import { AllServers } from "../../Server/AllServers"; import { createPopup } from "../../ui/React/createPopup"; +import { PopupCloseButton } from "../../ui/React/PopupCloseButton"; export const PurchaseMultipliers = Object.freeze({ "x1": 1, @@ -52,7 +54,12 @@ export class HacknetRoot extends React.Component { let total = 0; for (let i = 0; i < Player.hacknetNodes.length; ++i) { if (hasHacknetServers()) { - total += Player.hacknetNodes[i].hashRate; + const hserver = AllServers[Player.hacknetNodes[i]]; + if (hserver) { + total += hserver.hashRate; + } else { + console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`) + } } else { total += Player.hacknetNodes[i].moneyGainRatePerSecond; } @@ -97,10 +104,14 @@ export class HacknetRoot extends React.Component { // HacknetNode components const nodes = Player.hacknetNodes.map((node) => { if (hasHacknetServers()) { + const hserver = AllServers[node]; + if (hserver == null) { + throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`); + } return ( diff --git a/src/Infiltration.js b/src/Infiltration.js index d856d8252..8997c0f18 100644 --- a/src/Infiltration.js +++ b/src/Infiltration.js @@ -54,7 +54,7 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff) InfiltrationInstance.prototype.expMultiplier = function() { if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1; - return 2 * this.clearanceLevel / this.maxClearanceLevel; + return 2.5 * this.clearanceLevel / this.maxClearanceLevel; } InfiltrationInstance.prototype.gainHackingExp = function(amt) { @@ -64,7 +64,7 @@ InfiltrationInstance.prototype.gainHackingExp = function(amt) { InfiltrationInstance.prototype.calcGainedHackingExp = function() { if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0; - return Math.pow(this.hackingExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainStrengthExp = function(amt) { @@ -73,8 +73,8 @@ InfiltrationInstance.prototype.gainStrengthExp = function(amt) { } InfiltrationInstance.prototype.calcGainedStrengthExp = function() { - if(!this.strExpGained || isNaN(this.strExpGained)) return 0; - return Math.pow(this.strExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + if (!this.strExpGained || isNaN(this.strExpGained)) return 0; + return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainDefenseExp = function(amt) { @@ -83,8 +83,8 @@ InfiltrationInstance.prototype.gainDefenseExp = function(amt) { } InfiltrationInstance.prototype.calcGainedDefenseExp = function() { - if(!this.defExpGained || isNaN(this.defExpGained)) return 0; - return Math.pow(this.defExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + if (!this.defExpGained || isNaN(this.defExpGained)) return 0; + return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainDexterityExp = function(amt) { @@ -93,8 +93,8 @@ InfiltrationInstance.prototype.gainDexterityExp = function(amt) { } InfiltrationInstance.prototype.calcGainedDexterityExp = function() { - if(!this.dexExpGained || isNaN(this.dexExpGained)) return 0; - return Math.pow(this.dexExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0; + return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainAgilityExp = function(amt) { @@ -103,8 +103,8 @@ InfiltrationInstance.prototype.gainAgilityExp = function(amt) { } InfiltrationInstance.prototype.calcGainedAgilityExp = function() { - if(!this.agiExpGained || isNaN(this.agiExpGained)) return 0; - return Math.pow(this.agiExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0; + return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainCharismaExp = function(amt) { @@ -113,8 +113,8 @@ InfiltrationInstance.prototype.gainCharismaExp = function(amt) { } InfiltrationInstance.prototype.calcGainedCharismaExp = function() { - if(!this.chaExpGained || isNaN(this.chaExpGained)) return 0; - return Math.pow(this.chaExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); + if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0; + return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow); } InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) { diff --git a/src/Location.js b/src/Location.js index 33281738f..0e791d01f 100644 --- a/src/Location.js +++ b/src/Location.js @@ -327,7 +327,7 @@ function displayLocationContent() { setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob); - setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[0]], securityJob); + setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[2]], securityJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob); diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index 97e030ab4..6196d77bd 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -196,8 +196,11 @@ export function runScriptFromScript(server, scriptname, args, workerScript, thre } var runningScriptObj = new RunningScript(script, args); runningScriptObj.threads = threads; - server.runScript(runningScriptObj, Player); // Push onto runningScripts addWorkerScript(runningScriptObj, server); + + // Push onto runningScripts. + // This has to come after addWorkerScript() because that fn updates RAM usage + server.runScript(runningScriptObj, Player); return Promise.resolve(true); } } diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index c3406d3c7..1b5908b7f 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -24,7 +24,7 @@ export interface IPlayer { corporation: any; currentServer: string; factions: string[]; - hacknetNodes: (HacknetNode | HacknetServer)[]; + hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server hasWseAccount: boolean; jobs: IMap; karma: number; diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 60ea89487..331fc4c6c 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -114,10 +114,9 @@ export class Sleeve extends Person { logs: string[] = []; /** - * Clone retains memory% of exp upon prestige. If exp would be lower than previously - * kept exp, nothing happens + * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs */ - memory: number = 0; + memory: number = 1; /** * Sleeve shock. Number between 0 and 100 @@ -339,6 +338,31 @@ export class Sleeve extends Person { p.gainMoney(gain); } + /** + * Returns the cost of upgrading this sleeve's memory by a certain amount + */ + getMemoryUpgradeCost(n: number): number { + const amt = Math.round(n); + if (amt < 0) { + return 0; + } + + if (this.memory + amt > 100) { + return this.getMemoryUpgradeCost(100 - this.memory); + } + + const mult = 1.02; + const baseCost = 1e12; + let currCost = 0; + let currMemory = this.memory-1; + for (let i = 0; i < n; ++i) { + currCost += (Math.pow(mult, currMemory)); + ++currMemory; + } + + return currCost * baseCost; + } + /** * Gets reputation gain for the current task * Only applicable when working for company or faction @@ -408,6 +432,20 @@ export class Sleeve extends Person { } } + /** + * Called on every sleeve for a Source File prestige + */ + prestige(p: IPlayer) { + this.resetTaskStatus(); + this.earningsForSleeves = createTaskTracker(); + this.earningsForPlayer = createTaskTracker(); + this.logs = []; + this.shock = 1; + this.storedCycles = 0; + this.sync = Math.max(this.memory, 1); + this.shockRecovery(p); + } + /** * Process loop * Returns an object containing the amount of experience that should be @@ -818,6 +856,15 @@ export class Sleeve extends Person { return true; } + upgradeMemory(n: number): void { + if (n < 0) { + console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`); + return; + } + + this.memory = Math.min(100, Math.round(this.memory + n)); + } + /** * Serialize the current object to a JSON save state. */ @@ -830,7 +877,7 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat // You can only purchase Augmentations that are actually available from // your factions. I.e. you must be in a faction that has the Augmentation // and you must also have enough rep in that faction in order to purchase it. - + const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name}); const availableAugs: Augmentation[] = []; diff --git a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts index 496641415..1ead77eeb 100644 --- a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts +++ b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts @@ -1,48 +1,18 @@ /** - * Implements the purchasing of extra Duplicate Sleeves from The Covenant + * Implements the purchasing of extra Duplicate Sleeves from The Covenant, + * as well as the purchasing of upgrades (memory) */ -import { Sleeve } from "./Sleeve"; import { IPlayer } from "../IPlayer"; -import { numeralWrapper } from "../../ui/numeralFormat"; - -import { dialogBoxCreate } from "../../../utils/DialogBox"; -import { yesNoBoxCreate, - yesNoBoxClose, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton } from "../../../utils/YesNoBox"; +import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot"; +import { createPopup, + removePopup } from "../../ui/React/createPopup"; export const MaxSleevesFromCovenant: number = 5; +export const BaseCostPerSleeve: number = 10e12; +export const PopupId: string = "covenant-sleeve-purchases-popup"; -export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) { - if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; } - - // First sleeve purchased costs the base amount. Then, the price of - // each successive one increases by the same amount - const baseCostPerExtraSleeve: number = 10e12; - const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve; - - const yesBtn = yesNoBoxGetYesButton(); - const noBtn = yesNoBoxGetNoButton(); - - yesBtn!.addEventListener("click", () => { - if (p.canAfford(cost)) { - p.loseMoney(cost); - p.sleevesFromCovenant += 1; - p.sleeves.push(new Sleeve(p)); - yesNoBoxClose(); - } else { - dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false); - } - }); - - noBtn!.addEventListener("click", () => { - yesNoBoxClose(); - }); - - const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` + - `${numeralWrapper.formatMoney(cost)}?

` + - `These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` + - `Sleeves from The Covenant`; - yesNoBoxCreate(txt); +export function createSleevePurchasesFromCovenantPopup(p: IPlayer) { + const removePopupFn = removePopup.bind(null, PopupId); + createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn }); } diff --git a/src/PersonObjects/Sleeve/SleeveUI.ts b/src/PersonObjects/Sleeve/SleeveUI.ts index e7daf6d55..86fd9af0a 100644 --- a/src/PersonObjects/Sleeve/SleeveUI.ts +++ b/src/PersonObjects/Sleeve/SleeveUI.ts @@ -383,7 +383,8 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) { `HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`, `City: ${sleeve.city}`, `Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`, - `Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`].join("
"); + `Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`, + `Memory: ${numeralWrapper.format(sleeve.memory, "0")}`].join("
"); let repGainText: string = ""; if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) { diff --git a/src/PersonObjects/Sleeve/data/SleeveFaq.ts b/src/PersonObjects/Sleeve/data/SleeveFaq.ts index 72d3a9f99..f05f4c506 100644 --- a/src/PersonObjects/Sleeve/data/SleeveFaq.ts +++ b/src/PersonObjects/Sleeve/data/SleeveFaq.ts @@ -45,5 +45,13 @@ export const SleeveFaq: string = "are not available for sleeves.

", "Do sleeves get reset when installing Augmentations or switching BitNodes?
", -"Sleeves are reset when switching BitNodes, but not when installing Augmentations." +"Sleeves are reset when switching BitNodes, but not when installing Augmentations.

", + +"What is Memory?
", +"Sleeve memory dictates what a sleeve's synchronization will be", +"when its reset by switching BitNodes. For example, if a sleeve has a memory of 25,", +"then when you switch BitNodes its synchronization will initially be set to 25, rather than 1.

", +"Memory can only be increased by purchasing upgrades from The Covenant. It is a", +"persistent stat, meaning it never gets resets back to 1. The maximum possible", +"value for a sleeve's memory is 100." ].join(" "); diff --git a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx new file mode 100644 index 000000000..bed39a39a --- /dev/null +++ b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx @@ -0,0 +1,112 @@ +/** + * Root React component for the popup that lets player purchase Duplicate + * Sleeves and Sleeve-related upgrades from The Covenant + */ +import * as React from "react"; + +import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades"; + +import { Sleeve } from "../Sleeve"; +import { BaseCostPerSleeve, + MaxSleevesFromCovenant, + PopupId } from "../SleeveCovenantPurchases"; +import { IPlayer } from "../../IPlayer"; + +import { numeralWrapper } from "../../../ui/numeralFormat"; + +import { PopupCloseButton } from "../../../ui/React/PopupCloseButton"; +import { StdButton } from "../../../ui/React/StdButton"; + +import { dialogBoxCreate } from "../../../../utils/DialogBox"; + +interface IProps { + closeFn: () => void; + p: IPlayer; + rerender: () => void; +} + +interface IState { + update: number; +} + +export class CovenantPurchasesRoot extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + update: 0, + } + + this.rerender = this.rerender.bind(this); + } + + /** + * Get the cost to purchase a new Duplicate Sleeve + */ + purchaseCost(): number { + return (this.props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve; + } + + /** + * Force a rerender by just changing an arbitrary state value + */ + rerender() { + this.setState((state: IState) => ({ + update: state.update + 1, + })); + } + + render() { + // Purchasing a new Duplicate Sleeve + let purchaseDisabled = false; + if (!this.props.p.canAfford(this.purchaseCost())) { + purchaseDisabled = true; + } + if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { + purchaseDisabled = true; + } + const purchaseOnClick = () => { + if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; } + + if (this.props.p.canAfford(this.purchaseCost())) { + this.props.p.loseMoney(this.purchaseCost()); + this.props.p.sleevesFromCovenant += 1; + this.props.p.sleeves.push(new Sleeve(this.props.p)); + this.rerender(); + } else { + dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false); + } + } + + // Purchasing Upgrades for Sleeves + const upgradePanels = []; + for (let i = 0; i < this.props.p.sleeves.length; ++i) { + const sleeve = this.props.p.sleeves[i]; + upgradePanels.push( + + ) + } + + return ( +
+ +

+ Would you like to purchase an additional Duplicate Sleeve from The Covenant + for {numeralWrapper.formatMoney(this.purchaseCost())}? +

+
+

+ These Duplicate Sleeves are permanent (they persist through BitNodes). You can + purchase a total of {MaxSleevesFromCovenant} from The Covenant. +

+ +

+

+ Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades + are also permanent, meaning they persist across BitNodes. +

+ {upgradePanels} +
+ ) + } +} diff --git a/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx b/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx new file mode 100644 index 000000000..f2b5ccd57 --- /dev/null +++ b/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx @@ -0,0 +1,97 @@ +/** + * React component for a panel that lets you purchase upgrades for a Duplicate + * Sleeve's Memory (through The Covenant) + */ +import * as React from "react"; + +import { Sleeve } from "../Sleeve"; +import { IPlayer } from "../../IPlayer"; + +import { numeralWrapper } from "../../../ui/numeralFormat"; +import { StdButton } from "../../../ui/React/StdButton"; + +interface IProps { + index: number; + p: IPlayer; + rerender: () => void; + sleeve: Sleeve; +} + +interface IState { + amt: number; +} + +export class CovenantSleeveMemoryUpgrade extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + amt: 1, + } + + this.changePurchaseAmount = this.changePurchaseAmount.bind(this); + this.purchaseMemory = this.purchaseMemory.bind(this); + } + + changePurchaseAmount(e: React.ChangeEvent): void { + const n: number = parseInt(e.target.value); + + this.setState({ + amt: n, + }); + } + + getPurchaseCost(): number { + if (isNaN(this.state.amt)) { return Infinity; } + + const maxMemory = 100 - this.props.sleeve.memory; + if (this.state.amt > maxMemory) { return Infinity; } + + return this.props.sleeve.getMemoryUpgradeCost(this.state.amt); + } + + purchaseMemory(): void { + const cost = this.getPurchaseCost(); + if (this.props.p.canAfford(cost)) { + this.props.sleeve.upgradeMemory(this.state.amt); + this.props.p.loseMoney(cost); + this.props.rerender(); + } + } + + render() { + const inputId = `sleeve-${this.props.index}-memory-upgrade-input`; + + // Memory cannot go above 100 + const maxMemory = 100 - this.props.sleeve.memory; + + // Purchase button props + const cost = this.getPurchaseCost(); + const purchaseBtnDisabled = !this.props.p.canAfford(cost); + let purchaseBtnText; + if (this.state.amt > maxMemory) { + purchaseBtnText = `Memory cannot exceed 100`; + } else if (isNaN(this.state.amt)) { + purchaseBtnText = "Invalid value"; + } else { + purchaseBtnText = `Purchase ${this.state.amt} memory - ${numeralWrapper.formatMoney(cost)}`; + } + + return ( +
+

Upgrade Memory

+

+ Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory + is 100 (current: {numeralWrapper.format(this.props.sleeve.memory, "0")}) +

+ + + +
+ +
+ ) + } +} diff --git a/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx b/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx new file mode 100644 index 000000000..317d9a1a8 --- /dev/null +++ b/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx @@ -0,0 +1,28 @@ +/** + * React Component for a panel that lets you purchase upgrades for a single + * Duplicate Sleeve through The Covenant + */ +import * as React from "react"; + +import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade"; + +import { Sleeve } from "../Sleeve"; +import { IPlayer } from "../../IPlayer"; + +interface IProps { + index: number; + p: IPlayer; + rerender: () => void; + sleeve: Sleeve; +} + +export class CovenantSleeveUpgrades extends React.Component { + render() { + return ( +
+

Duplicate Sleeve {this.props.index}

+ +
+ ) + } +} diff --git a/src/Player.js b/src/Player.js index c7ee6502e..a42e021fc 100644 --- a/src/Player.js +++ b/src/Player.js @@ -21,6 +21,7 @@ import { Faction } from "./Faction/Faction"; import { Factions } from "./Faction/Factions"; import { displayFactionContent } from "./Faction/FactionHelpers"; import {Gang, resetGangs} from "./Gang"; +import { hasHacknetServers } from "./Hacknet/HacknetHelpers"; import { HashManager } from "./Hacknet/HashManager"; import {Locations} from "./Locations"; import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; @@ -117,7 +118,7 @@ function PlayerObject() { this.purchasedServers = []; //IP Addresses of purchased servers // Hacknet Nodes/Servers - this.hacknetNodes = []; + this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers this.hashManager = new HashManager(); //Factions @@ -288,7 +289,7 @@ PlayerObject.prototype.prestigeAugmentation = function() { for (let i = 0; i < this.sleeves.length; ++i) { if (this.sleeves[i] instanceof Sleeve) { if (this.sleeves[i].shock >= 100) { - this.sleeves[i].synchronize(this); + this.sleeves[i].synchronize(this); } else { this.sleeves[i].shockRecovery(this); } @@ -382,7 +383,11 @@ PlayerObject.prototype.prestigeSourceFile = function() { // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant; for (let i = 0; i < this.sleeves.length; ++i) { - this.sleeves[i] = new Sleeve(this); + if (this.sleeves[i] instanceof Sleeve) { + this.sleeves[i].prestige(this); + } else { + this.sleeves[i] = new Sleeve(this); + } } this.isWorking = false; @@ -2333,10 +2338,19 @@ PlayerObject.prototype.checkForFactionInvitations = function() { var totalHacknetRam = 0; var totalHacknetCores = 0; var totalHacknetLevels = 0; - for (var i = 0; i < this.hacknetNodes.length; ++i) { - totalHacknetLevels += this.hacknetNodes[i].level; - totalHacknetRam += this.hacknetNodes[i].ram; - totalHacknetCores += this.hacknetNodes[i].cores; + for (let i = 0; i < this.hacknetNodes.length; ++i) { + if (hasHacknetServers()) { + const hserver = AllServers[this.hacknetNodes[i]]; + if (hserver) { + totalHacknetLevels += hserver.level; + totalHacknetRam += hserver.maxRam; + totalHacknetCores += hserver.cores; + } + } else { + totalHacknetLevels += this.hacknetNodes[i].level; + totalHacknetRam += this.hacknetNodes[i].ram; + totalHacknetCores += this.hacknetNodes[i].cores; + } } if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited && this.hacking_skill >= 80 && totalHacknetRam >= 8 && diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index 6d8d91c0b..392215953 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -2,6 +2,8 @@ import { Server } from "./Server"; import { SpecialServerIps } from "./SpecialServerIps"; import { serverMetadata } from "./data/servers"; +import { HacknetServer } from "../Hacknet/HacknetServer"; + import { IMap } from "../types"; import { createRandomIp, ipExists } from "../../utils/IPAddress"; @@ -11,7 +13,7 @@ import { Reviver } from "../../utils/JSONReviver"; // Map of all Servers that exist in the game // Key (string) = IP // Value = Server object -export let AllServers: IMap = {}; +export let AllServers: IMap = {}; // Saftely add a Server to the AllServers map export function AddToAllServers(server: Server): void { diff --git a/src/Server/ServerHelpers.ts b/src/Server/ServerHelpers.ts index cd173fd03..e6ad5df67 100644 --- a/src/Server/ServerHelpers.ts +++ b/src/Server/ServerHelpers.ts @@ -3,6 +3,7 @@ import { Server } from "./Server"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { CONSTANTS } from "../Constants"; +import { HacknetServer } from "../Hacknet/HacknetServer"; import { IPlayer } from "../PersonObjects/IPlayer"; import { Programs } from "../Programs/Programs"; @@ -89,7 +90,7 @@ export function prestigeHomeComputer(homeComp: Server) { //Returns server object with corresponding hostname // Relatively slow, would rather not use this a lot -export function GetServerByHostname(hostname: string): Server | null { +export function GetServerByHostname(hostname: string): Server | HacknetServer | null { for (var ip in AllServers) { if (AllServers.hasOwnProperty(ip)) { if (AllServers[ip].hostname == hostname) { @@ -102,7 +103,7 @@ export function GetServerByHostname(hostname: string): Server | null { } //Get server by IP or hostname. Returns null if invalid -export function getServer(s: string): Server | null { +export function getServer(s: string): Server | HacknetServer | null { if (!isValidIPAddress(s)) { return GetServerByHostname(s); } diff --git a/src/SourceFile.js b/src/SourceFile.js index 05f66ed84..63a9f0a02 100644 --- a/src/SourceFile.js +++ b/src/SourceFile.js @@ -68,9 +68,9 @@ function initSourceFiles() { SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + " increases the player's company salary and reputation gain multipliers by:

" + - "Level 1: 24%
" + - "Level 2: 36%
" + - "Level 3: 42%
"); + "Level 1: 32%
" + + "Level 2: 48%
" + + "Level 3: 56%
"); SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); } @@ -194,7 +194,7 @@ function applySourceFile(srcFile) { case 11: //The Big Crash var mult = 0; for (var i = 0; i < srcFile.lvl; ++i) { - mult += (24 / (Math.pow(2, i))); + mult += (32 / (Math.pow(2, i))); } var incMult = 1 + (mult / 100); Player.work_money_mult *= incMult; diff --git a/src/StockMarket/StockMarket.js b/src/StockMarket/StockMarket.js index 7e7d62091..ee3a5edfe 100644 --- a/src/StockMarket/StockMarket.js +++ b/src/StockMarket/StockMarket.js @@ -462,9 +462,10 @@ function sellStock(stock, shares) { shares = Math.round(shares); if (shares > stock.playerShares) {shares = stock.playerShares;} if (shares === 0) {return false;} - var gains = stock.price * shares - CONSTANTS.StockMarketCommission; + const gains = stock.price * shares - CONSTANTS.StockMarketCommission; + const netProfit = ((stock.price - stock.playerAvgPx) * shares) - CONSTANTS.StockMarketCommission; Player.gainMoney(gains); - Player.recordMoneySource(gains, "stock"); + Player.recordMoneySource(netProfit, "stock"); stock.playerShares = Math.round(stock.playerShares - shares); if (stock.playerShares == 0) { stock.playerAvgPx = 0; diff --git a/src/Terminal.js b/src/Terminal.js index 08595bc5a..e41bb5125 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -1861,17 +1861,20 @@ let Terminal = { visited[ip] = 0; } - var stack = []; - var depthQueue = [0]; - var currServ = Player.getCurrentServer(); + const stack = []; + const depthQueue = [0]; + const currServ = Player.getCurrentServer(); stack.push(currServ); while(stack.length != 0) { - var s = stack.pop(); - var d = depthQueue.pop(); + const s = stack.pop(); + const d = depthQueue.pop(); + const isHacknet = s instanceof HacknetServer; if (!all && s.purchasedByPlayer && s.hostname != "home") { - continue; //Purchased server + continue; // Purchased server } else if (visited[s.ip] || d > depth) { - continue; //Already visited or out-of-depth + continue; // Already visited or out-of-depth + } else if (!all && isHacknet) { + continue; // Hacknet Server } else { visited[s.ip] = 1; } @@ -1891,8 +1894,8 @@ let Terminal = { //var dashes = Array(d * 2 + 1).join("-"); var c = "NO"; if (s.hasAdminRights) {c = "YES";} - post(dashes + "Root Access: " + c + ", Required hacking skill: " + s.requiredHackingSkill); - post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); + post(`${dashes}Root Access: ${c} ${!isHacknet ? ", Required hacking skill: " + s.requiredHackingSkill : ""}`); + if (!isHacknet) { post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); } post(dashes + "RAM: " + s.maxRam); post(" "); } @@ -2242,9 +2245,12 @@ let Terminal = { post("May take a few seconds to start up the process..."); var runningScriptObj = new RunningScript(script, args); runningScriptObj.threads = numThreads; - server.runScript(runningScriptObj, Player); addWorkerScript(runningScriptObj, server); + + // This has to come after addWorkerScript() because that fn + // updates the RAM usage. This kinda sucks, address if possible + server.runScript(runningScriptObj, Player); return; } } diff --git a/src/ui/React/Popup.tsx b/src/ui/React/Popup.tsx new file mode 100644 index 000000000..2d84ad684 --- /dev/null +++ b/src/ui/React/Popup.tsx @@ -0,0 +1,24 @@ +/** + * React component for a popup content container + * + * Takes in a prop for rendering the content inside the popup + */ +import * as React from "react"; + +type ReactComponent = new(...args: any[]) => React.Component + +interface IProps { + content: ReactComponent; + id: string; + props: object; +} + +export class Popup extends React.Component { + render() { + return ( +
+ {React.createElement(this.props.content, this.props.props)} +
+ ) + } +} diff --git a/src/ui/React/PopupCloseButton.tsx b/src/ui/React/PopupCloseButton.tsx index 7749db4f2..dfd581926 100644 --- a/src/ui/React/PopupCloseButton.tsx +++ b/src/ui/React/PopupCloseButton.tsx @@ -9,6 +9,7 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import { KEY } from "../../../utils/helpers/keyCodes"; +import { removeElement } from "../../../utils/uiHelpers/removeElement"; export interface IPopupCloseButtonProps { class?: string; @@ -43,7 +44,8 @@ export class PopupCloseButton extends React.Component - {this.props.text}; + {this.props.text} ) } diff --git a/src/ui/React/StdButton.tsx b/src/ui/React/StdButton.tsx index 21f8ab180..592395c3e 100644 --- a/src/ui/React/StdButton.tsx +++ b/src/ui/React/StdButton.tsx @@ -17,7 +17,7 @@ export class StdButton extends React.Component { return ( ) } diff --git a/src/ui/React/createPopup.ts b/src/ui/React/createPopup.tsx similarity index 56% rename from src/ui/React/createPopup.ts rename to src/ui/React/createPopup.tsx index 2974549fb..3f9bb5e25 100644 --- a/src/ui/React/createPopup.ts +++ b/src/ui/React/createPopup.tsx @@ -4,41 +4,45 @@ * Calling this function with the same ID and React Root Component will trigger a re-render * * @param id The (hopefully) unique identifier for the popup container - * @param rootComponent Root React Component + * @param rootComponent Root React Component for the content (NOT the popup containers themselves) */ import * as React from "react"; import * as ReactDOM from "react-dom"; +import { Popup } from "./Popup"; + import { createElement } from "../../../utils/uiHelpers/createElement"; import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; type ReactComponent = new(...args: any[]) => React.Component; -export function createPopup(id: string, rootComponent: ReactComponent, props: object): HTMLElement { +let gameContainer: HTMLElement; + +function getGameContainer() { + let container = document.getElementById("entire-game-container"); + if (container == null) { + throw new Error(`Failed to find game container DOM element`) + } + + gameContainer = container; + document.removeEventListener("DOMContentLoaded", getGameContainer); +} + +document.addEventListener("DOMContentLoaded", getGameContainer); + +export function createPopup(id: string, rootComponent: ReactComponent, props: object): HTMLElement | null { let container = document.getElementById(id); - let content = document.getElementById(`${id}-content`); - if (container == null || content == null) { + if (container == null) { container = createElement("div", { class: "popup-box-container", display: "flex", id: id, }); - content = createElement("div", { - class: "popup-box-content", - id: `${id}-content`, - }); - - container.appendChild(content); - - try { - document.getElementById("entire-game-container")!.appendChild(container); - } catch(e) { - console.error(`Exception caught when creating popup: ${e}`); - } + gameContainer.appendChild(container); } - ReactDOM.render(React.createElement(rootComponent, props), content); + ReactDOM.render(, container); return container; } @@ -47,7 +51,7 @@ export function createPopup(id: string, rootComponent: ReactComponent, props: ob * Closes a popup created with the createPopup() function above */ export function removePopup(id: string): void { - let content = document.getElementById(`${id}-content`); + let content = document.getElementById(`${id}`); if (content == null) { return; } ReactDOM.unmountComponentAtNode(content);