Initial v0.46.0 changes - Fixed BN9 bugs. Rebalanced BN11 and Corporations. Added memory to Dup sleeves. Various bug fixes

This commit is contained in:
danielyxie 2019-03-27 01:36:14 -07:00
parent 83fc4d81b2
commit c4cb7daac5
38 changed files with 579 additions and 209 deletions

@ -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;

@ -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

@ -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,

@ -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 <netscript_sleeveapi_referencingaduplicatesleeve>`
:param string augName: Name of the aug to buy. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>`
: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.

@ -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.<br><br>" +
"In this BitNode:<br><br>" +
"Your hacking stat and experience gain are halved<br>" +
"The starting and maximum amount of money available on servers is significantly decreased<br>" +
"The growth rate of servers is halved<br>" +
"The growth rate of servers is significantly reduced<br>" +
"Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<br>" +
@ -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:<br><br>" +
"Level 1: 24%<br>" +
"Level 2: 36%<br>" +
"Level 3: 42%");
"Level 1: 32%<br>" +
"Level 2: 48%<br>" +
"Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" +
"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;

@ -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;

@ -116,7 +116,7 @@ export let CONSTANTS: IMap<any> = {
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<any> = {
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
`
}

@ -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

@ -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;

@ -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]);

@ -301,7 +301,7 @@ export class IndustryOffice extends BaseReactComponent {
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}</p>
<p>Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{
vechain &&

@ -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 (
<div className={"cmpy-mgmt-warehouse-panel"}>
<p className={"tooltip"} style={sizeUsageStyle}>
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")}
Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)}
<span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
</p>

@ -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", {

@ -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;
}

@ -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 {

@ -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 = <string>p.hacknetNodes[0];
if (typeof ip !== "string") {
this.capacity = 0;
return;
}
const hserver = <HacknetServer>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 = <HacknetServer>(p.hacknetNodes[i]);
total += hacknetServer.hashCapacity;
const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
total += h.hashCapacity;
}
this.capacity = total;

@ -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 (
<HacknetServer
key={node.hostname}
node={node}
key={hserver.hostname}
node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)}
/>

@ -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) {

@ -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);

@ -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);
}
}

@ -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<string>;
karma: number;

@ -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.
*/

@ -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)}?<br><br>` +
`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 });
}

@ -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("<br>");
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`,
`Memory: ${numeralWrapper.format(sleeve.memory, "0")}`].join("<br>");
let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {

@ -45,5 +45,13 @@ export const SleeveFaq: string =
"are not available for sleeves.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>",
"Sleeves are reset when switching BitNodes, but not when installing Augmentations."
"Sleeves are reset when switching BitNodes, but not when installing Augmentations.<br><br>",
"<strong><u>What is Memory?</u></strong><br>",
"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.<br><br>",
"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(" ");

@ -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<IProps, IState> {
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(
<CovenantSleeveUpgrades {...this.props} sleeve={sleeve} index={i} rerender={this.rerender} key={i} />
)
}
return (
<div>
<PopupCloseButton popup={PopupId} text={"Close"} />
<p>
Would you like to purchase an additional Duplicate Sleeve from The Covenant
for {numeralWrapper.formatMoney(this.purchaseCost())}?
</p>
<br />
<p>
These Duplicate Sleeves are permanent (they persist through BitNodes). You can
purchase a total of {MaxSleevesFromCovenant} from The Covenant.
</p>
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
<br /><br />
<p>
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades
are also permanent, meaning they persist across BitNodes.
</p>
{upgradePanels}
</div>
)
}
}

@ -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<IProps, IState> {
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<HTMLInputElement>): 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 (
<div>
<h2><u>Upgrade Memory</u></h2>
<p>
Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory
is 100 (current: {numeralWrapper.format(this.props.sleeve.memory, "0")})
</p>
<label htmlFor={inputId}>
Amount of memory to purchase (must be an integer):
</label>
<input id={inputId} onChange={this.changePurchaseAmount} type={"number"} value={this.state.amt} />
<br />
<StdButton disabled={purchaseBtnDisabled} onClick={this.purchaseMemory} text={purchaseBtnText} />
</div>
)
}
}

@ -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<IProps, any> {
render() {
return (
<div className={"bladeburner-action"}>
<h1>Duplicate Sleeve {this.props.index}</h1>
<CovenantSleeveMemoryUpgrade {...this.props} />
</div>
)
}
}

@ -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
@ -382,8 +383,12 @@ 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) {
if (this.sleeves[i] instanceof Sleeve) {
this.sleeves[i].prestige(this);
} else {
this.sleeves[i] = new Sleeve(this);
}
}
this.isWorking = false;
this.currentWorkFactionName = "";
@ -2333,11 +2338,20 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var totalHacknetRam = 0;
var totalHacknetCores = 0;
var totalHacknetLevels = 0;
for (var i = 0; i < this.hacknetNodes.length; ++i) {
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 &&
totalHacknetCores >= 4 && totalHacknetLevels >= 100) {

@ -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<Server> = {};
export let AllServers: IMap<Server | HacknetServer> = {};
// Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server): void {

@ -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);
}

@ -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:<br><br>" +
"Level 1: 24%<br>" +
"Level 2: 36%<br>" +
"Level 3: 42%<br>");
"Level 1: 32%<br>" +
"Level 2: 48%<br>" +
"Level 3: 56%<br>");
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;

@ -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;

@ -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;
}
}

24
src/ui/React/Popup.tsx Normal file

@ -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<any, any>
interface IProps {
content: ReactComponent;
id: string;
props: object;
}
export class Popup extends React.Component<IProps, any> {
render() {
return (
<div className={"popup-box-content"} id={`${this.props.id}-content`}>
{React.createElement(this.props.content, this.props.props)}
</div>
)
}
}

@ -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<IPopupCloseButtonProps, an
// TODO Check if this is okay? This is essentially calling to unmount a parent component
if (popup instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(popup);
ReactDOM.unmountComponentAtNode(popup); // Removes everything inside the wrapper container
removeElement(popup); // Removes the wrapper container
}
}
@ -58,7 +60,7 @@ export class PopupCloseButton extends React.Component<IPopupCloseButtonProps, an
return (
<button className={className} onClick={this.closePopup} style={this.props.style}>
{this.props.text};
{this.props.text}
</button>
)
}

@ -17,7 +17,7 @@ export class StdButton extends React.Component<IStdButtonProps, any> {
return (
<button className={className} onClick={this.props.onClick} style={this.props.style}>
{this.props.text};
{this.props.text}
</button>
)
}

@ -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<any, any>;
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(<Popup content={rootComponent} id={id} props={props} />, 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);