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 */ /* Pop-up boxes */
.popup-box-container { .popup-box-container {
display: none; /* Hidden by default */ display: none; /* Initially hidden */
position: fixed; /* Stay in place */ position: fixed; /* Stay in place */
z-index: 10; /* Sit on top */ z-index: 10; /* Sit on top */
left: 0; left: 0;

@ -80,6 +80,16 @@ when you normally install Augmentations.
The cost of purchasing an Augmentation for a Duplicate Sleeve is **not** affected 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. 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
^^^^^^^^^^^ ^^^^^^^^^^^
Re-sleeving is the process of digitizing and transferring your consciousness into a Re-sleeving is the process of digitizing and transferring your consciousness into a

@ -10,8 +10,8 @@ getSleeveStats() Netscript Function
.. code-block:: javascript .. code-block:: javascript
{ {
shock: current shock of the sleeve [0-1], shock: current shock of the sleeve [0-100],
sync: current sync of the sleeve [0-1], sync: current sync of the sleeve [0-100],
hacking_skill: current hacking skill of the sleeve, hacking_skill: current hacking skill of the sleeve,
strength: current strength of the sleeve, strength: current strength of the sleeve,
defense: current defense of the sleeve, defense: current defense of the sleeve,

@ -4,6 +4,6 @@ purchaseSleeveAug() Netscript Function
.. js:function:: purchaseSleeveAug(sleeveNumber, augName) .. js:function:: purchaseSleeveAug(sleeveNumber, augName)
:param int sleeveNumber: Index of the sleeve to buy an aug for. See :ref:`here <netscript_sleeveapi_referencingaduplicatesleeve>` :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. 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 " + "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>" + "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>" + "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 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>" + "Weakening a server is twice as effective<br>" +
"Company wages are decreased by 50%<br>" + "Company wages are decreased by 50%<br>" +
"Corporation valuations are 99% lower and are therefore significantly less profitable<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 " + "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). " + "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>" + "This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 24%<br>" + "Level 1: 32%<br>" +
"Level 2: 36%<br>" + "Level 2: 48%<br>" +
"Level 3: 42%"); "Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" + "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 " + "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; BitNodeMultipliers.CodingContractMoney = 0;
break; break;
case 9: // Hacktocracy case 9: // Hacktocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.3; BitNodeMultipliers.HackingLevelMultiplier = 0.4;
BitNodeMultipliers.StrengthLevelMultiplier = 0.45; BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
BitNodeMultipliers.DefenseLevelMultiplier = 0.45; BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
BitNodeMultipliers.DexterityLevelMultiplier = 0.45; BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
BitNodeMultipliers.AgilityLevelMultiplier = 0.45; BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
BitNodeMultipliers.CharismaLevelMultiplier = 0.45; BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
BitNodeMultipliers.PurchasedServerLimit = 0; BitNodeMultipliers.PurchasedServerLimit = 0;
BitNodeMultipliers.HomeComputerRamCost = 3; BitNodeMultipliers.HomeComputerRamCost = 5;
BitNodeMultipliers.CrimeMoney = 0.5; BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1; BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.1; BitNodeMultipliers.HackExpGain = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
break; break;
case 10: // Digital Carbon case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2; BitNodeMultipliers.HackingLevelMultiplier = 0.2;
@ -385,9 +389,11 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.BladeburnerRank = 0.8; BitNodeMultipliers.BladeburnerRank = 0.8;
break; break;
case 11: //The Big Crash case 11: //The Big Crash
BitNodeMultipliers.HackingLevelMultiplier = 0.5;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.ServerMaxMoney = 0.1; BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1; BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerGrowthRate = 0.5; BitNodeMultipliers.ServerGrowthRate = 0.2;
BitNodeMultipliers.ServerWeakenRate = 2; BitNodeMultipliers.ServerWeakenRate = 2;
BitNodeMultipliers.CrimeMoney = 3; BitNodeMultipliers.CrimeMoney = 3;
BitNodeMultipliers.CompanyWorkMoney = 0.5; BitNodeMultipliers.CompanyWorkMoney = 0.5;
@ -395,8 +401,8 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.AugmentationMoneyCost = 2; BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.InfiltrationMoney = 2.5; BitNodeMultipliers.InfiltrationMoney = 2.5;
BitNodeMultipliers.InfiltrationRep = 2.5; BitNodeMultipliers.InfiltrationRep = 2.5;
BitNodeMultipliers.CorporationValuation = 0.01; BitNodeMultipliers.CorporationValuation = 0.1;
BitNodeMultipliers.CodingContractMoney = 0.5; BitNodeMultipliers.CodingContractMoney = 0.25;
BitNodeMultipliers.FourSigmaMarketDataCost = 4; BitNodeMultipliers.FourSigmaMarketDataCost = 4;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
break; 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 ContractBaseMoneyGain = 250e3; //Base Money Gained per contract
const HrcHpGain = 2; // HP gained from Hyperbolic Regeneration Chamber const HrcHpGain = 2; // HP Gained from Hyperbolic Regeneration chamber
const HrcStaminaGain = 0.1; // Stamina gained from Hyperbolic Regeneration Chamber const HrcStaminaGain = 1; // Percentage Stamina gained from Hyperbolic Regeneration Chamber
//DOM related variables //DOM related variables
var ActiveActionCssClass = "bladeburner-active-action"; var ActiveActionCssClass = "bladeburner-active-action";
@ -1417,14 +1417,17 @@ Bladeburner.prototype.completeAction = function() {
} }
this.startAction(this.action); // Repeat Action this.startAction(this.action); // Repeat Action
break; break;
case ActionTypes["Hyperbolic Regeneration Chamber"]: case ActionTypes["Hyperbolic Regeneration Chamber"]: {
Player.regenerateHp(HrcHpGain); 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); this.startAction(this.action);
if (this.logging.general) { 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; break;
}
default: default:
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
break; break;

@ -116,7 +116,7 @@ export let CONSTANTS: IMap<any> = {
InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level
InfiltrationMoneyValue: 5e3, //Convert "secret" value to money InfiltrationMoneyValue: 5e3, //Convert "secret" value to money
InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation
InfiltrationExpPow: 0.7, InfiltrationExpPow: 0.8,
//Stock market constants //Stock market constants
WSEAccountCost: 200e6, WSEAccountCost: 200e6,
@ -273,37 +273,20 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.45.1 v0.46.0
* Added two new Corporation Researches * Added BitNode-9: Hacktocracy
* General UI improvements (by hydroflame and koriar) * Changed BitNode-11's multipliers to make it slightly harder overall
* Bug Fix: Sleeve Netscript API should no longer cause Dynamic RAM errors * Source-File 11 is now slightly stronger
* Bug Fix: sleeve.getSleeveStats() should now work properly * 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:
* Corporation changes: ** 'Demand' value of products decreases more slowly
** Decreased the time of a full market cycle from 15 seconds to 10 seconds. ** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
** This means that each Corporation 'state' will now only take 2 seconds, rather than 3
** Increased initial salaries for newly-hired employees * Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
** Increased the cost multiplier for upgrading office size (the cost will increase faster) * Bug Fix: Fixed an issue with the job requirement tooltip for security jobs
** 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
` `
} }

@ -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) { Industry.prototype.processProductMarket = function(marketCycles=1) {
//Demand gradually decreases, and competition gradually increases // Demand gradually decreases, and competition gradually increases
for (var name in this.products) { for (const name in this.products) {
if (this.products.hasOwnProperty(name)) { if (this.products.hasOwnProperty(name)) {
var product = this.products[name]; const product = this.products[name];
var change = getRandomInt(1, 3) * 0.0004; let change = getRandomInt(0, 3) * 0.0004;
if (change === 0) { continue; }
if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
this.type === Industries.Robotics) { this.type === Industries.Robotics) {
change *= 3; change *= 3;
@ -770,7 +772,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
* advertisingFactor * advertisingFactor
* this.getSalesMultiplier()); * this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); 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 // We'll store this "Optimal Price" in a property so that we don't have
// to re-calculate it for the UI // 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); const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
let optimalPrice; let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) { 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 { } else {
optimalPrice = (numerator / denominator) + product.pCost; 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 //Returns a multiplier based on a materials demand and competition that affects sales
Industry.prototype.getMarketFactor = function(mat) { 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 // Returns a boolean indicating whether this Industry has the specified Research

@ -86,7 +86,7 @@ export class Material {
this.mku = 6; this.mku = 6;
break; break;
case "Energy": case "Energy":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 80; this.cmpR = [65, 95]; this.cmp = 80; this.cmpR = [65, 95];
this.bCost = 2000; this.mv = 0.2; this.bCost = 2000; this.mv = 0.2;
this.mku = 6; this.mku = 6;
@ -122,26 +122,26 @@ export class Material {
this.mku = 2; this.mku = 2;
break; break;
case "Real Estate": 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.cmp = 50; this.cmpR = [25, 75];
this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice
this.mku = 1.5; this.mku = 1.5;
break; break;
case "Drugs": case "Drugs":
this.dmd = 60; this.dmdR = [45, 75]; 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.bCost = 40e3; this.mv = 1.6;
this.mku = 1; this.mku = 1;
break; break;
case "Robots": case "Robots":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 9];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice
this.mku = 1; this.mku = 1;
break; break;
case "AI Cores": case "AI Cores":
this.dmd = 90; this.dmdR = [80, 100]; this.dmd = 90; this.dmdR = [80, 99];
this.cmp = 90; this.cmpR = [80, 100]; this.cmp = 90; this.cmpR = [80, 9];
this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice
this.mku = 0.5; this.mku = 0.5;
break; break;

@ -780,7 +780,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); 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 { } else {
// Market-TA.I only // Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@ -1052,7 +1057,12 @@ export class CorporationEventHandler {
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); 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 { } else {
// Market-TA.I only // Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); 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 Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "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> <p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{ {
vechain && vechain &&

@ -218,7 +218,7 @@ function MaterialComponent(props) {
mat.buy === 0 && mat.imp === 0; mat.buy === 0 && mat.imp === 0;
// Purchase material button // 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 purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
@ -229,9 +229,9 @@ function MaterialComponent(props) {
let sellButtonText; let sellButtonText;
if (mat.sllman[0]) { if (mat.sllman[0]) {
if (isString(mat.sllman[1])) { 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 { } 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) { if (mat.marketTa2) {
@ -469,7 +469,7 @@ export class IndustryWarehouse extends BaseReactComponent {
return ( return (
<div className={"cmpy-mgmt-warehouse-panel"}> <div className={"cmpy-mgmt-warehouse-panel"}>
<p className={"tooltip"} style={sizeUsageStyle}> <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> <span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
</p> </p>

@ -14,7 +14,7 @@ import { PurchaseAugmentationsOrderSetting } from "../Settings/SettingEnums";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { createPurchaseSleevesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases"; import { createSleevePurchasesFromCovenantPopup } from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
import {Page, routing} from "../ui/navigationTracking"; import {Page, routing} from "../ui/navigationTracking";
import {numeralWrapper} from "../ui/numeralFormat"; import {numeralWrapper} from "../ui/numeralFormat";
@ -348,7 +348,7 @@ function displayFactionContent(factionName) {
class: "std-button", class: "std-button",
innerText: "Purchase Duplicate Sleeves", innerText: "Purchase Duplicate Sleeves",
clickListener: () => { clickListener: () => {
createPurchaseSleevesFromCovenantPopup(Player); createSleevePurchasesFromCovenantPopup(Player);
} }
})); }));
covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", { covenantPurchaseSleevesDivWrapper.appendChild(createElement("p", {

@ -19,7 +19,8 @@ import { generateRandomContractOnHome } from "../CodingContractGenerator
import { iTutorialSteps, iTutorialNextStep, import { iTutorialSteps, iTutorialNextStep,
ITutorial} from "../InteractiveTutorial"; ITutorial} from "../InteractiveTutorial";
import { Player } from "../Player"; import { Player } from "../Player";
import { AddToAllServers } from "../Server/AllServers"; import { AddToAllServers,
AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers"; import { GetServerByHostname } from "../Server/ServerHelpers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { Page, routing } from "../ui/navigationTracking"; import { Page, routing } from "../ui/navigationTracking";
@ -33,9 +34,10 @@ import { HacknetRoot } from "./ui/Root";
let hacknetNodesDiv; let hacknetNodesDiv;
function hacknetNodesInit() { function hacknetNodesInit() {
hacknetNodesDiv = document.getElementById("hacknet-nodes-container"); 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 // Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes) // (the upgraded form of Hacknet Nodes)
@ -73,7 +75,7 @@ export function purchaseHacknet() {
}); });
Player.loseMoney(cost); Player.loseMoney(cost);
Player.hacknetNodes.push(server); Player.hacknetNodes.push(server.ip);
// Configure the HacknetServer to actually act as a Server // Configure the HacknetServer to actually act as a Server
AddToAllServers(server); AddToAllServers(server);
@ -308,7 +310,8 @@ function processAllHacknetServerEarnings(numCycles) {
let hashes = 0; let hashes = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) { 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); Player.hashManager.storeHashes(hashes);
@ -316,16 +319,6 @@ function processAllHacknetServerEarnings(numCycles) {
return hashes; 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) { export function purchaseHashUpgrade(upgName, upgTarget) {
if (!(Player.hashManager instanceof HashManager)) { if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`); console.error(`Player does not have a HashManager`);
@ -423,7 +416,6 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
return false; return false;
} }
console.log("Hash Upgrade successfully purchased");
return true; return true;
} }

@ -20,7 +20,7 @@ import { Generic_fromJSON,
export const HacknetServerHashesPerLevel: number = 0.001; export const HacknetServerHashesPerLevel: number = 0.001;
// Constants for Hacknet Server purchase/upgrade costs // Constants for Hacknet Server purchase/upgrade costs
export const BaseCostForHacknetServer: number = 10e3; export const BaseCostForHacknetServer: number = 50e3;
export const BaseCostFor1GBHacknetServerRam: number = 200e3; export const BaseCostFor1GBHacknetServerRam: number = 200e3;
export const BaseCostForHacknetServerCore: number = 1e6; export const BaseCostForHacknetServerCore: number = 1e6;
export const BaseCostForHacknetServerCache: number = 10e6; 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 HacknetServerMaxLevel: number = 300;
export const HacknetServerMaxRam: number = 8192; export const HacknetServerMaxRam: number = 8192;
export const HacknetServerMaxCores: number = 128; 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 { interface IConstructorParams {
adminRights?: boolean; adminRights?: boolean;
@ -306,6 +306,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
this.maxRam *= 2; this.maxRam *= 2;
} }
this.maxRam = Math.round(this.maxRam); this.maxRam = Math.round(this.maxRam);
this.updateHashRate(p);
return true; return true;
} }
@ -321,7 +322,7 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
} }
updateHashCapacity(): void { updateHashCapacity(): void {
this.hashCapacity = 16 * Math.pow(2, this.cache); this.hashCapacity = 32 * Math.pow(2, this.cache);
} }
updateHashRate(p: IPlayer): void { updateHashRate(p: IPlayer): void {

@ -11,6 +11,7 @@ import { HashUpgrades } from "./HashUpgrades";
import { IMap } from "../types"; import { IMap } from "../types";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers } from "../Server/AllServers";
import { Generic_fromJSON, import { Generic_fromJSON,
Generic_toJSON, Generic_toJSON,
Reviver } from "../../utils/JSONReviver"; Reviver } from "../../utils/JSONReviver";
@ -106,13 +107,29 @@ export class HashManager {
} }
updateCapacity(p: IPlayer): void { updateCapacity(p: IPlayer): void {
if (p.hacknetNodes.length <= 0) { this.capacity = 0; } if (p.hacknetNodes.length <= 0) {
if (!(p.hacknetNodes[0] instanceof HacknetServer)) { this.capacity = 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; let total: number = 0;
for (let i = 0; i < p.hacknetNodes.length; ++i) { for (let i = 0; i < p.hacknetNodes.length; ++i) {
const hacknetServer = <HacknetServer>(p.hacknetNodes[i]); const h = <HacknetServer>AllServers[<string>p.hacknetNodes[i]];
total += hacknetServer.hashCapacity; total += h.hashCapacity;
} }
this.capacity = total; this.capacity = total;

@ -17,8 +17,10 @@ import { getCostOfNextHacknetNode,
purchaseHacknet } from "../HacknetHelpers"; purchaseHacknet } from "../HacknetHelpers";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { AllServers } from "../../Server/AllServers";
import { createPopup } from "../../ui/React/createPopup"; import { createPopup } from "../../ui/React/createPopup";
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
export const PurchaseMultipliers = Object.freeze({ export const PurchaseMultipliers = Object.freeze({
"x1": 1, "x1": 1,
@ -52,7 +54,12 @@ export class HacknetRoot extends React.Component {
let total = 0; let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) { for (let i = 0; i < Player.hacknetNodes.length; ++i) {
if (hasHacknetServers()) { 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 { } else {
total += Player.hacknetNodes[i].moneyGainRatePerSecond; total += Player.hacknetNodes[i].moneyGainRatePerSecond;
} }
@ -97,10 +104,14 @@ export class HacknetRoot extends React.Component {
// HacknetNode components // HacknetNode components
const nodes = Player.hacknetNodes.map((node) => { const nodes = Player.hacknetNodes.map((node) => {
if (hasHacknetServers()) { 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 ( return (
<HacknetServer <HacknetServer
key={node.hostname} key={hserver.hostname}
node={node} node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier} purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)} recalculate={this.recalculateTotalProduction.bind(this)}
/> />

@ -54,7 +54,7 @@ function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff)
InfiltrationInstance.prototype.expMultiplier = function() { InfiltrationInstance.prototype.expMultiplier = function() {
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1; 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) { InfiltrationInstance.prototype.gainHackingExp = function(amt) {
@ -64,7 +64,7 @@ InfiltrationInstance.prototype.gainHackingExp = function(amt) {
InfiltrationInstance.prototype.calcGainedHackingExp = function() { InfiltrationInstance.prototype.calcGainedHackingExp = function() {
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0; 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) { InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
@ -73,8 +73,8 @@ InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
} }
InfiltrationInstance.prototype.calcGainedStrengthExp = function() { InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
if(!this.strExpGained || isNaN(this.strExpGained)) return 0; if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
return Math.pow(this.strExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
} }
InfiltrationInstance.prototype.gainDefenseExp = function(amt) { InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
@ -83,8 +83,8 @@ InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
} }
InfiltrationInstance.prototype.calcGainedDefenseExp = function() { InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
if(!this.defExpGained || isNaN(this.defExpGained)) return 0; if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
return Math.pow(this.defExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
} }
InfiltrationInstance.prototype.gainDexterityExp = function(amt) { InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
@ -93,8 +93,8 @@ InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
} }
InfiltrationInstance.prototype.calcGainedDexterityExp = function() { InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
if(!this.dexExpGained || isNaN(this.dexExpGained)) return 0; if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
return Math.pow(this.dexExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
} }
InfiltrationInstance.prototype.gainAgilityExp = function(amt) { InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
@ -103,8 +103,8 @@ InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
} }
InfiltrationInstance.prototype.calcGainedAgilityExp = function() { InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
if(!this.agiExpGained || isNaN(this.agiExpGained)) return 0; if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
return Math.pow(this.agiExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
} }
InfiltrationInstance.prototype.gainCharismaExp = function(amt) { InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
@ -113,8 +113,8 @@ InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
} }
InfiltrationInstance.prototype.calcGainedCharismaExp = function() { InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
if(!this.chaExpGained || isNaN(this.chaExpGained)) return 0; if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
return Math.pow(this.chaExpGained*this.expMultiplier(), CONSTANTS.InfiltrationExpPow); return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
} }
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) { InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {

@ -327,7 +327,7 @@ function displayLocationContent() {
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob); 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.AgentCompanyPositions[0]], agentJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob); setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob); 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); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads; runningScriptObj.threads = threads;
server.runScript(runningScriptObj, Player); // Push onto runningScripts
addWorkerScript(runningScriptObj, server); 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); return Promise.resolve(true);
} }
} }

@ -24,7 +24,7 @@ export interface IPlayer {
corporation: any; corporation: any;
currentServer: string; currentServer: string;
factions: string[]; factions: string[];
hacknetNodes: (HacknetNode | HacknetServer)[]; hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
hasWseAccount: boolean; hasWseAccount: boolean;
jobs: IMap<string>; jobs: IMap<string>;
karma: number; karma: number;

@ -114,10 +114,9 @@ export class Sleeve extends Person {
logs: string[] = []; logs: string[] = [];
/** /**
* Clone retains memory% of exp upon prestige. If exp would be lower than previously * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
* kept exp, nothing happens
*/ */
memory: number = 0; memory: number = 1;
/** /**
* Sleeve shock. Number between 0 and 100 * Sleeve shock. Number between 0 and 100
@ -339,6 +338,31 @@ export class Sleeve extends Person {
p.gainMoney(gain); 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 * Gets reputation gain for the current task
* Only applicable when working for company or faction * 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 * Process loop
* Returns an object containing the amount of experience that should be * Returns an object containing the amount of experience that should be
@ -818,6 +856,15 @@ export class Sleeve extends Person {
return true; 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. * 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 { IPlayer } from "../IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat"; import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
import { createPopup,
import { dialogBoxCreate } from "../../../utils/DialogBox"; removePopup } from "../../ui/React/createPopup";
import { yesNoBoxCreate,
yesNoBoxClose,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
export const MaxSleevesFromCovenant: number = 5; export const MaxSleevesFromCovenant: number = 5;
export const BaseCostPerSleeve: number = 10e12;
export const PopupId: string = "covenant-sleeve-purchases-popup";
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) { export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; } const removePopupFn = removePopup.bind(null, PopupId);
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
// 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);
} }

@ -383,7 +383,8 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
`HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`, `HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`,
`City: ${sleeve.city}`, `City: ${sleeve.city}`,
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`, `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 = ""; let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) { if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {

@ -45,5 +45,13 @@ export const SleeveFaq: string =
"are not available for sleeves.<br><br>", "are not available for sleeves.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><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(" "); ].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 { Factions } from "./Faction/Factions";
import { displayFactionContent } from "./Faction/FactionHelpers"; import { displayFactionContent } from "./Faction/FactionHelpers";
import {Gang, resetGangs} from "./Gang"; import {Gang, resetGangs} from "./Gang";
import { hasHacknetServers } from "./Hacknet/HacknetHelpers";
import { HashManager } from "./Hacknet/HashManager"; import { HashManager } from "./Hacknet/HashManager";
import {Locations} from "./Locations"; import {Locations} from "./Locations";
import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions";
@ -117,7 +118,7 @@ function PlayerObject() {
this.purchasedServers = []; //IP Addresses of purchased servers this.purchasedServers = []; //IP Addresses of purchased servers
// Hacknet Nodes/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(); this.hashManager = new HashManager();
//Factions //Factions
@ -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) // 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; this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant;
for (let i = 0; i < this.sleeves.length; ++i) { 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; this.isWorking = false;
@ -2333,10 +2338,19 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var totalHacknetRam = 0; var totalHacknetRam = 0;
var totalHacknetCores = 0; var totalHacknetCores = 0;
var totalHacknetLevels = 0; var totalHacknetLevels = 0;
for (var i = 0; i < this.hacknetNodes.length; ++i) { for (let i = 0; i < this.hacknetNodes.length; ++i) {
totalHacknetLevels += this.hacknetNodes[i].level; if (hasHacknetServers()) {
totalHacknetRam += this.hacknetNodes[i].ram; const hserver = AllServers[this.hacknetNodes[i]];
totalHacknetCores += this.hacknetNodes[i].cores; 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 && if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited &&
this.hacking_skill >= 80 && totalHacknetRam >= 8 && this.hacking_skill >= 80 && totalHacknetRam >= 8 &&

@ -2,6 +2,8 @@ import { Server } from "./Server";
import { SpecialServerIps } from "./SpecialServerIps"; import { SpecialServerIps } from "./SpecialServerIps";
import { serverMetadata } from "./data/servers"; import { serverMetadata } from "./data/servers";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IMap } from "../types"; import { IMap } from "../types";
import { createRandomIp, import { createRandomIp,
ipExists } from "../../utils/IPAddress"; ipExists } from "../../utils/IPAddress";
@ -11,7 +13,7 @@ import { Reviver } from "../../utils/JSONReviver";
// Map of all Servers that exist in the game // Map of all Servers that exist in the game
// Key (string) = IP // Key (string) = IP
// Value = Server object // Value = Server object
export let AllServers: IMap<Server> = {}; export let AllServers: IMap<Server | HacknetServer> = {};
// Saftely add a Server to the AllServers map // Saftely add a Server to the AllServers map
export function AddToAllServers(server: Server): void { export function AddToAllServers(server: Server): void {

@ -3,6 +3,7 @@ import { Server } from "./Server";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
@ -89,7 +90,7 @@ export function prestigeHomeComputer(homeComp: Server) {
//Returns server object with corresponding hostname //Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot // 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) { for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) { if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) { 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 //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)) { if (!isValidIPAddress(s)) {
return GetServerByHostname(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 " + 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 " + "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>" + " increases the player's company salary and reputation gain multipliers by:<br><br>" +
"Level 1: 24%<br>" + "Level 1: 32%<br>" +
"Level 2: 36%<br>" + "Level 2: 48%<br>" +
"Level 3: 42%<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. " + 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)"); "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 case 11: //The Big Crash
var mult = 0; var mult = 0;
for (var i = 0; i < srcFile.lvl; ++i) { 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); var incMult = 1 + (mult / 100);
Player.work_money_mult *= incMult; Player.work_money_mult *= incMult;

@ -462,9 +462,10 @@ function sellStock(stock, shares) {
shares = Math.round(shares); shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;} if (shares > stock.playerShares) {shares = stock.playerShares;}
if (shares === 0) {return false;} 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.gainMoney(gains);
Player.recordMoneySource(gains, "stock"); Player.recordMoneySource(netProfit, "stock");
stock.playerShares = Math.round(stock.playerShares - shares); stock.playerShares = Math.round(stock.playerShares - shares);
if (stock.playerShares == 0) { if (stock.playerShares == 0) {
stock.playerAvgPx = 0; stock.playerAvgPx = 0;

@ -1861,17 +1861,20 @@ let Terminal = {
visited[ip] = 0; visited[ip] = 0;
} }
var stack = []; const stack = [];
var depthQueue = [0]; const depthQueue = [0];
var currServ = Player.getCurrentServer(); const currServ = Player.getCurrentServer();
stack.push(currServ); stack.push(currServ);
while(stack.length != 0) { while(stack.length != 0) {
var s = stack.pop(); const s = stack.pop();
var d = depthQueue.pop(); const d = depthQueue.pop();
const isHacknet = s instanceof HacknetServer;
if (!all && s.purchasedByPlayer && s.hostname != "home") { if (!all && s.purchasedByPlayer && s.hostname != "home") {
continue; //Purchased server continue; // Purchased server
} else if (visited[s.ip] || d > depth) { } 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 { } else {
visited[s.ip] = 1; visited[s.ip] = 1;
} }
@ -1891,8 +1894,8 @@ let Terminal = {
//var dashes = Array(d * 2 + 1).join("-"); //var dashes = Array(d * 2 + 1).join("-");
var c = "NO"; var c = "NO";
if (s.hasAdminRights) {c = "YES";} if (s.hasAdminRights) {c = "YES";}
post(dashes + "Root Access: " + c + ", Required hacking skill: " + s.requiredHackingSkill); post(`${dashes}Root Access: ${c} ${!isHacknet ? ", Required hacking skill: " + s.requiredHackingSkill : ""}`);
post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); if (!isHacknet) { post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); }
post(dashes + "RAM: " + s.maxRam); post(dashes + "RAM: " + s.maxRam);
post(" "); post(" ");
} }
@ -2242,9 +2245,12 @@ let Terminal = {
post("May take a few seconds to start up the process..."); post("May take a few seconds to start up the process...");
var runningScriptObj = new RunningScript(script, args); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads; runningScriptObj.threads = numThreads;
server.runScript(runningScriptObj, Player);
addWorkerScript(runningScriptObj, server); 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; 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 * as ReactDOM from "react-dom";
import { KEY } from "../../../utils/helpers/keyCodes"; import { KEY } from "../../../utils/helpers/keyCodes";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
export interface IPopupCloseButtonProps { export interface IPopupCloseButtonProps {
class?: string; 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 // TODO Check if this is okay? This is essentially calling to unmount a parent component
if (popup instanceof HTMLElement) { 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 ( return (
<button className={className} onClick={this.closePopup} style={this.props.style}> <button className={className} onClick={this.closePopup} style={this.props.style}>
{this.props.text}; {this.props.text}
</button> </button>
) )
} }

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

@ -4,41 +4,45 @@
* Calling this function with the same ID and React Root Component will trigger a re-render * 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 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 React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { Popup } from "./Popup";
import { createElement } from "../../../utils/uiHelpers/createElement"; import { createElement } from "../../../utils/uiHelpers/createElement";
import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
type ReactComponent = new(...args: any[]) => React.Component<any, any>; 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 container = document.getElementById(id);
let content = document.getElementById(`${id}-content`); if (container == null) {
if (container == null || content == null) {
container = createElement("div", { container = createElement("div", {
class: "popup-box-container", class: "popup-box-container",
display: "flex", display: "flex",
id: id, id: id,
}); });
content = createElement("div", { gameContainer.appendChild(container);
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}`);
}
} }
ReactDOM.render(React.createElement(rootComponent, props), content); ReactDOM.render(<Popup content={rootComponent} id={id} props={props} />, container);
return 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 * Closes a popup created with the createPopup() function above
*/ */
export function removePopup(id: string): void { export function removePopup(id: string): void {
let content = document.getElementById(`${id}-content`); let content = document.getElementById(`${id}`);
if (content == null) { return; } if (content == null) { return; }
ReactDOM.unmountComponentAtNode(content); ReactDOM.unmountComponentAtNode(content);