SLEEVES: Add sleeve commands to purchase sleeves and memory via the API (#2443)

This commit is contained in:
TheAimMan
2026-01-20 15:55:51 -05:00
committed by GitHub
parent 19064a1b12
commit 210c338f86
17 changed files with 673 additions and 105 deletions

View File

@@ -0,0 +1,76 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Sleeve](./bitburner.sleeve.md) &gt; [getMemoryUpgradeCost](./bitburner.sleeve.getmemoryupgradecost.md)
## Sleeve.getMemoryUpgradeCost() method
Get the cost of memory upgrades.
**Signature:**
```typescript
getMemoryUpgradeCost(sleeveNumber: number, amount: number): number;
```
## Parameters
<table><thead><tr><th>
Parameter
</th><th>
Type
</th><th>
Description
</th></tr></thead>
<tbody><tr><td>
sleeveNumber
</td><td>
number
</td><td>
Index of the sleeve.
</td></tr>
<tr><td>
amount
</td><td>
number
</td><td>
Number of upgrades. Must be a positive integer.
</td></tr>
</tbody></table>
**Returns:**
number
Cost of the upgrades. Return Infinity if the current memory plus the amount of upgrades is greater than 100.
## Remarks
RAM cost: 4 GB

View File

@@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Sleeve](./bitburner.sleeve.md) &gt; [getSleeveCost](./bitburner.sleeve.getsleevecost.md)
## Sleeve.getSleeveCost() method
Get the cost of the next sleeve.
**Signature:**
```typescript
getSleeveCost(): number;
```
**Returns:**
number
Cost of the next sleeve. Return Infinity if you reach the maximum number of purchasable sleeves.
## Remarks
RAM cost: 4 GB

View File

@@ -31,6 +31,17 @@ Description
</th></tr></thead>
<tbody><tr><td>
[getMemoryUpgradeCost(sleeveNumber, amount)](./bitburner.sleeve.getmemoryupgradecost.md)
</td><td>
Get the cost of memory upgrades.
</td></tr>
<tr><td>
[getNumSleeves()](./bitburner.sleeve.getnumsleeves.md)
@@ -83,6 +94,17 @@ Get reputation requirement of an augmentation.
Get augmentations installed on a sleeve.
</td></tr>
<tr><td>
[getSleeveCost()](./bitburner.sleeve.getsleevecost.md)
</td><td>
Get the cost of the next sleeve.
</td></tr>
<tr><td>
@@ -105,6 +127,17 @@ List purchasable augs for a sleeve.
Get task of a sleeve.
</td></tr>
<tr><td>
[purchaseSleeve()](./bitburner.sleeve.purchasesleeve.md)
</td><td>
Purchase a sleeve. You must be in BitNode 10 to use this API.
</td></tr>
<tr><td>
@@ -226,6 +259,17 @@ Set a sleeve to take a class at a university.
Make a sleeve travel to another city. The cost for using this function is the same as for a player.
</td></tr>
<tr><td>
[upgradeMemory(sleeveNumber, amount)](./bitburner.sleeve.upgradememory.md)
</td><td>
Upgrade memory of a sleeve. You must be in BitNode 10 to use this API.
</td></tr>
</tbody></table>

View File

@@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Sleeve](./bitburner.sleeve.md) &gt; [purchaseSleeve](./bitburner.sleeve.purchasesleeve.md)
## Sleeve.purchaseSleeve() method
Purchase a sleeve. You must be in BitNode 10 to use this API.
**Signature:**
```typescript
purchaseSleeve(): Result;
```
**Returns:**
[Result](./bitburner.result.md)
Action result
## Remarks
RAM cost: 4 GB

View File

@@ -0,0 +1,76 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Sleeve](./bitburner.sleeve.md) &gt; [upgradeMemory](./bitburner.sleeve.upgradememory.md)
## Sleeve.upgradeMemory() method
Upgrade memory of a sleeve. You must be in BitNode 10 to use this API.
**Signature:**
```typescript
upgradeMemory(sleeveNumber: number, amount: number): Result;
```
## Parameters
<table><thead><tr><th>
Parameter
</th><th>
Type
</th><th>
Description
</th></tr></thead>
<tbody><tr><td>
sleeveNumber
</td><td>
number
</td><td>
Index of the sleeve.
</td></tr>
<tr><td>
amount
</td><td>
number
</td><td>
Number of upgrades. Must be a positive integer.
</td></tr>
</tbody></table>
**Returns:**
[Result](./bitburner.result.md)
Action result
## Remarks
RAM cost: 4 GB

View File

@@ -5,9 +5,11 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { makeStyles } from "tss-react/mui";
import { Player } from "@player";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip";
import { MaxSleevesFromCovenant } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import {
MaxSleevesFromCovenant,
recalculateNumberOfOwnedSleeves,
} from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { validBitNodes } from "../../BitNode/Constants";
import { DeleteServer, GetAllServers } from "../../Server/AllServers";
import { HacknetServer } from "../../Hacknet/HacknetServer";
@@ -48,7 +50,7 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
Player.sourceFiles.delete(sfN);
Player.bitNodeOptions.sourceFileOverrides.delete(sfN);
if (sfN === 10) {
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
}
parentRerender();
return;
@@ -56,7 +58,7 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
Player.sourceFiles.set(sfN, sfLvl);
Player.bitNodeOptions.sourceFileOverrides.set(sfN, sfLvl);
if (sfN === 10) {
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
}
parentRerender();
},
@@ -69,14 +71,14 @@ export function SourceFilesDev({ parentRerender }: { parentRerender: () => void
const addSleeve = useCallback(() => {
if (Player.sleevesFromCovenant >= 10) return;
Player.sleevesFromCovenant += 1;
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
parentRerender();
}, [parentRerender]);
const removeSleeve = useCallback(() => {
if (Player.sleevesFromCovenant <= 0) return;
Player.sleevesFromCovenant -= 1;
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
parentRerender();
}, [parentRerender]);

View File

@@ -1283,14 +1283,17 @@ import nsDoc_bitburner_skills_strength_md from "../../markdown/bitburner.skills.
import nsDoc_bitburner_skillsformulas_calculateexp_md from "../../markdown/bitburner.skillsformulas.calculateexp.md?raw";
import nsDoc_bitburner_skillsformulas_calculateskill_md from "../../markdown/bitburner.skillsformulas.calculateskill.md?raw";
import nsDoc_bitburner_skillsformulas_md from "../../markdown/bitburner.skillsformulas.md?raw";
import nsDoc_bitburner_sleeve_getmemoryupgradecost_md from "../../markdown/bitburner.sleeve.getmemoryupgradecost.md?raw";
import nsDoc_bitburner_sleeve_getnumsleeves_md from "../../markdown/bitburner.sleeve.getnumsleeves.md?raw";
import nsDoc_bitburner_sleeve_getsleeve_md from "../../markdown/bitburner.sleeve.getsleeve.md?raw";
import nsDoc_bitburner_sleeve_getsleeveaugmentationprice_md from "../../markdown/bitburner.sleeve.getsleeveaugmentationprice.md?raw";
import nsDoc_bitburner_sleeve_getsleeveaugmentationrepreq_md from "../../markdown/bitburner.sleeve.getsleeveaugmentationrepreq.md?raw";
import nsDoc_bitburner_sleeve_getsleeveaugmentations_md from "../../markdown/bitburner.sleeve.getsleeveaugmentations.md?raw";
import nsDoc_bitburner_sleeve_getsleevecost_md from "../../markdown/bitburner.sleeve.getsleevecost.md?raw";
import nsDoc_bitburner_sleeve_getsleevepurchasableaugs_md from "../../markdown/bitburner.sleeve.getsleevepurchasableaugs.md?raw";
import nsDoc_bitburner_sleeve_gettask_md from "../../markdown/bitburner.sleeve.gettask.md?raw";
import nsDoc_bitburner_sleeve_md from "../../markdown/bitburner.sleeve.md?raw";
import nsDoc_bitburner_sleeve_purchasesleeve_md from "../../markdown/bitburner.sleeve.purchasesleeve.md?raw";
import nsDoc_bitburner_sleeve_purchasesleeveaug_md from "../../markdown/bitburner.sleeve.purchasesleeveaug.md?raw";
import nsDoc_bitburner_sleeve_settobladeburneraction_md from "../../markdown/bitburner.sleeve.settobladeburneraction.md?raw";
import nsDoc_bitburner_sleeve_settocommitcrime_md from "../../markdown/bitburner.sleeve.settocommitcrime.md?raw";
@@ -1302,6 +1305,7 @@ import nsDoc_bitburner_sleeve_settoshockrecovery_md from "../../markdown/bitburn
import nsDoc_bitburner_sleeve_settosynchronize_md from "../../markdown/bitburner.sleeve.settosynchronize.md?raw";
import nsDoc_bitburner_sleeve_settouniversitycourse_md from "../../markdown/bitburner.sleeve.settouniversitycourse.md?raw";
import nsDoc_bitburner_sleeve_travel_md from "../../markdown/bitburner.sleeve.travel.md?raw";
import nsDoc_bitburner_sleeve_upgradememory_md from "../../markdown/bitburner.sleeve.upgradememory.md?raw";
import nsDoc_bitburner_sleevebladeburnertask_md from "../../markdown/bitburner.sleevebladeburnertask.md?raw";
import nsDoc_bitburner_sleeveclasstask_md from "../../markdown/bitburner.sleeveclasstask.md?raw";
import nsDoc_bitburner_sleevecompanytask_md from "../../markdown/bitburner.sleevecompanytask.md?raw";
@@ -2796,14 +2800,17 @@ AllPages["nsDoc/bitburner.skills.strength.md"] = nsDoc_bitburner_skills_strength
AllPages["nsDoc/bitburner.skillsformulas.calculateexp.md"] = nsDoc_bitburner_skillsformulas_calculateexp_md;
AllPages["nsDoc/bitburner.skillsformulas.calculateskill.md"] = nsDoc_bitburner_skillsformulas_calculateskill_md;
AllPages["nsDoc/bitburner.skillsformulas.md"] = nsDoc_bitburner_skillsformulas_md;
AllPages["nsDoc/bitburner.sleeve.getmemoryupgradecost.md"] = nsDoc_bitburner_sleeve_getmemoryupgradecost_md;
AllPages["nsDoc/bitburner.sleeve.getnumsleeves.md"] = nsDoc_bitburner_sleeve_getnumsleeves_md;
AllPages["nsDoc/bitburner.sleeve.getsleeve.md"] = nsDoc_bitburner_sleeve_getsleeve_md;
AllPages["nsDoc/bitburner.sleeve.getsleeveaugmentationprice.md"] = nsDoc_bitburner_sleeve_getsleeveaugmentationprice_md;
AllPages["nsDoc/bitburner.sleeve.getsleeveaugmentationrepreq.md"] = nsDoc_bitburner_sleeve_getsleeveaugmentationrepreq_md;
AllPages["nsDoc/bitburner.sleeve.getsleeveaugmentations.md"] = nsDoc_bitburner_sleeve_getsleeveaugmentations_md;
AllPages["nsDoc/bitburner.sleeve.getsleevecost.md"] = nsDoc_bitburner_sleeve_getsleevecost_md;
AllPages["nsDoc/bitburner.sleeve.getsleevepurchasableaugs.md"] = nsDoc_bitburner_sleeve_getsleevepurchasableaugs_md;
AllPages["nsDoc/bitburner.sleeve.gettask.md"] = nsDoc_bitburner_sleeve_gettask_md;
AllPages["nsDoc/bitburner.sleeve.md"] = nsDoc_bitburner_sleeve_md;
AllPages["nsDoc/bitburner.sleeve.purchasesleeve.md"] = nsDoc_bitburner_sleeve_purchasesleeve_md;
AllPages["nsDoc/bitburner.sleeve.purchasesleeveaug.md"] = nsDoc_bitburner_sleeve_purchasesleeveaug_md;
AllPages["nsDoc/bitburner.sleeve.settobladeburneraction.md"] = nsDoc_bitburner_sleeve_settobladeburneraction_md;
AllPages["nsDoc/bitburner.sleeve.settocommitcrime.md"] = nsDoc_bitburner_sleeve_settocommitcrime_md;
@@ -2815,6 +2822,7 @@ AllPages["nsDoc/bitburner.sleeve.settoshockrecovery.md"] = nsDoc_bitburner_sleev
AllPages["nsDoc/bitburner.sleeve.settosynchronize.md"] = nsDoc_bitburner_sleeve_settosynchronize_md;
AllPages["nsDoc/bitburner.sleeve.settouniversitycourse.md"] = nsDoc_bitburner_sleeve_settouniversitycourse_md;
AllPages["nsDoc/bitburner.sleeve.travel.md"] = nsDoc_bitburner_sleeve_travel_md;
AllPages["nsDoc/bitburner.sleeve.upgradememory.md"] = nsDoc_bitburner_sleeve_upgradememory_md;
AllPages["nsDoc/bitburner.sleevebladeburnertask.md"] = nsDoc_bitburner_sleevebladeburnertask_md;
AllPages["nsDoc/bitburner.sleeveclasstask.md"] = nsDoc_bitburner_sleeveclasstask_md;
AllPages["nsDoc/bitburner.sleevecompanytask.md"] = nsDoc_bitburner_sleevecompanytask_md;

View File

@@ -384,6 +384,10 @@ const sleeve = {
setToBladeburnerAction: RamCostConstants.SleeveBase,
getSleeveAugmentationPrice: RamCostConstants.SleeveBase,
getSleeveAugmentationRepReq: RamCostConstants.SleeveBase,
purchaseSleeve: RamCostConstants.SleeveBase,
upgradeMemory: RamCostConstants.SleeveBase,
getSleeveCost: RamCostConstants.SleeveBase,
getMemoryUpgradeCost: RamCostConstants.SleeveBase,
} as const;
// Stanek API

View File

@@ -15,6 +15,17 @@ import { Factions } from "../Faction/Factions";
import { SleeveWorkType } from "../PersonObjects/Sleeve/Work/Work";
import { canAccessBitNodeFeature } from "../BitNode/BitNodeUtils";
import { Crimes } from "../Crime/Crimes";
import {
getSleeveCost,
purchaseSleeve,
purchaseSleeveMemoryUpgrade,
} from "../PersonObjects/Sleeve/SleeveCovenantPurchases";
export const checkBitNodeRequirement = function (ctx: NetscriptContext) {
if (Player.bitNodeN !== 10) {
throw helpers.errorMessage(ctx, "You must be in BitNode 10 to use this API.");
}
};
export const checkSleeveAPIAccess = function (ctx: NetscriptContext) {
/**
@@ -290,6 +301,36 @@ export function NetscriptSleeve(): InternalAPI<NetscriptSleeve> {
}
return Player.sleeves[sleeveNumber].bladeburner(action, contract);
},
purchaseSleeve: (ctx) => () => {
checkBitNodeRequirement(ctx);
const result = purchaseSleeve();
if (!result.success) {
helpers.log(ctx, () => result.message);
}
return result;
},
upgradeMemory: (ctx) => (_sleeveNumber, _amount) => {
checkBitNodeRequirement(ctx);
const amount = helpers.positiveInteger(ctx, "amount", _amount);
const sleeveNumber = helpers.integer(ctx, "sleeveNumber", _sleeveNumber);
checkSleeveNumber(ctx, sleeveNumber);
const result = purchaseSleeveMemoryUpgrade(Player.sleeves[sleeveNumber], amount);
if (!result.success) {
helpers.log(ctx, () => result.message);
}
return result;
},
getSleeveCost: (ctx) => () => {
checkSleeveAPIAccess(ctx);
return getSleeveCost(Player.sleevesFromCovenant);
},
getMemoryUpgradeCost: (ctx) => (_sleeveNumber, _amount) => {
checkSleeveAPIAccess(ctx);
const amount = helpers.positiveInteger(ctx, "amount", _amount);
const sleeveNumber = helpers.integer(ctx, "sleeveNumber", _sleeveNumber);
checkSleeveNumber(ctx, sleeveNumber);
return Player.sleeves[sleeveNumber].getMemoryUpgradeCost(amount);
},
};
// Removed functions

View File

@@ -28,7 +28,6 @@ import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { FactionInvitationEvents } from "../../Faction/ui/FactionInvitationManager";
import { resetGangs } from "../../Gang/AllGangs";
import { Sleeve } from "../Sleeve/Sleeve";
import { SleeveWorkType } from "../Sleeve/Work/Work";
import { calculateSkillProgress as calculateSkillProgressF, ISkillProgress } from "../formulas/skill";
import { AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers";
@@ -57,6 +56,7 @@ import { PlayerEventType, PlayerEvents } from "./PlayerEvents";
import type { Result } from "@nsdefs";
import type { AchievementId } from "../../Achievements/Types";
import { Infiltration } from "../../Infiltration/Infiltration";
import { recalculateNumberOfOwnedSleeves } from "../Sleeve/SleeveCovenantPurchases";
export function init(this: PlayerObject): void {
/* Initialize Player's home computer */
@@ -114,7 +114,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.queuedAugmentations = [];
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
this.sleeves.forEach((sleeve) => (sleeve.shock <= 0 ? sleeve.synchronize() : sleeve.shockRecovery()));

View File

@@ -195,13 +195,8 @@ export class Sleeve extends Person implements SleevePerson {
/** 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);
if (!Number.isInteger(n) || n < 0 || this.memory + n > 100) {
return Infinity;
}
const mult = 1.02;
@@ -524,24 +519,6 @@ export class Sleeve extends Person implements SleevePerson {
}
}
static recalculateNumOwned() {
/**
* Don't change sourceFileLvl to activeSourceFileLvl. The number of sleeves is a permanent effect. It's too
* troublesome for the player if they lose Sleeves and have to go BN10 to buy them again when they override the
* level of SF 10.
*/
const numSleeves =
Math.min(3, Player.sourceFileLvl(10) + (Player.bitNodeN === 10 ? 1 : 0)) + Player.sleevesFromCovenant;
while (Player.sleeves.length > numSleeves) {
const destroyedSleeve = Player.sleeves.pop();
// This should not happen, but avoid an infinite loop in case sleevesFromCovenent or sf10 level are somehow negative
if (!destroyedSleeve) return;
// Stop work, to prevent destroyed sleeves from continuing their tasks in the void
destroyedSleeve.stopWork();
}
while (Player.sleeves.length < numSleeves) Player.sleeves.push(new Sleeve());
}
whoAmI(): string {
return "Sleeve";
}

View File

@@ -1,3 +1,10 @@
import { Player } from "@player";
import type { Result } from "@nsdefs";
import { Sleeve } from "./Sleeve";
import { Factions } from "../../Faction/Factions";
import { FactionName } from "@enums";
import { formatMoney } from "../../ui/formatNumber";
/**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
@@ -5,3 +12,112 @@
export const MaxSleevesFromCovenant = 5;
export const BaseCostPerSleeve = 10e12;
export function getSleeveCost(sleevesFromCovenant: number): number {
// The first two checks are a bit over-the-top, but they are useful if we allow passing an arbitrary value to this
// function instead of only Player.sleevesFromCovenant.
if (
!Number.isInteger(sleevesFromCovenant) ||
sleevesFromCovenant < 0 ||
sleevesFromCovenant >= MaxSleevesFromCovenant
) {
return Infinity;
}
return Math.pow(10, sleevesFromCovenant) * BaseCostPerSleeve;
}
export function canPurchaseSleeve(): Result {
if (Player.bitNodeN !== 10) {
return {
success: false,
message: "You must be in BitNode 10 to purchase sleeves.",
};
}
if (!Factions[FactionName.TheCovenant].isMember) {
return {
success: false,
message: `You must be a member of ${FactionName.TheCovenant} to purchase sleeves.`,
};
}
if (Player.sleevesFromCovenant >= MaxSleevesFromCovenant) {
return {
success: false,
message: `You already have the maximum amount of sleeves purchasable from ${FactionName.TheCovenant}.`,
};
}
const cost = getSleeveCost(Player.sleevesFromCovenant);
if (!Player.canAfford(cost)) {
return {
success: false,
message: `You must have at least ${formatMoney(cost)} to buy this sleeve.`,
};
}
return { success: true };
}
export function recalculateNumberOfOwnedSleeves(): void {
// Don't change sourceFileLvl to activeSourceFileLvl. The number of sleeves is a permanent effect. It's too
// troublesome for the player if they lose Sleeves and have to go BN10 to buy them again when they override the
// level of SF 10.
const numSleeves =
Math.min(3, Player.sourceFileLvl(10) + (Player.bitNodeN === 10 ? 1 : 0)) + Player.sleevesFromCovenant;
while (Player.sleeves.length > numSleeves) {
const destroyedSleeve = Player.sleeves.pop();
// This should not happen, but avoid an infinite loop in case Player.sleevesFromCovenant or sf10 level are somehow
// negative
if (!destroyedSleeve) return;
// Stop work, to prevent destroyed sleeves from continuing their tasks in the void
destroyedSleeve.stopWork();
}
while (Player.sleeves.length < numSleeves) Player.sleeves.push(new Sleeve());
}
export function purchaseSleeve(): Result {
const validationResult = canPurchaseSleeve();
if (!validationResult.success) {
return validationResult;
}
Player.loseMoney(getSleeveCost(Player.sleevesFromCovenant), "sleeves");
Player.sleevesFromCovenant += 1;
recalculateNumberOfOwnedSleeves();
return { success: true };
}
export function canPurchaseMemoryUpgrade(sleeve: Sleeve, amount: number): Result {
if (Player.bitNodeN !== 10) {
return {
success: false,
message: "You must be in BitNode 10 to purchase sleeves' memory upgrade.",
};
}
if (!Factions[FactionName.TheCovenant].isMember) {
return {
success: false,
message: `You must be a member of ${FactionName.TheCovenant} to purchase sleeves' memory upgrade.`,
};
}
if (sleeve.memory + amount > 100) {
return {
success: false,
message: `Invalid amount of upgrade: ${amount}. The max memory of a sleeve is 100.`,
};
}
const cost = sleeve.getMemoryUpgradeCost(amount);
if (!Player.canAfford(cost)) {
return {
success: false,
message: `You must have at least ${formatMoney(cost)} to buy this sleeve's memory upgrade.`,
};
}
return { success: true };
}
export function purchaseSleeveMemoryUpgrade(sleeve: Sleeve, amount: number): Result {
const validationResult = canPurchaseMemoryUpgrade(sleeve, amount);
if (!validationResult.success) {
return validationResult;
}
Player.loseMoney(sleeve.getMemoryUpgradeCost(amount), "sleeves");
sleeve.upgradeMemory(amount);
return { success: true };
}

View File

@@ -6,8 +6,7 @@ import React from "react";
import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade";
import { Sleeve } from "../Sleeve";
import { BaseCostPerSleeve, MaxSleevesFromCovenant } from "../SleeveCovenantPurchases";
import { MaxSleevesFromCovenant, canPurchaseSleeve, getSleeveCost, purchaseSleeve } from "../SleeveCovenantPurchases";
import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
@@ -27,31 +26,13 @@ interface IProps {
export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
const rerender = useRerender();
/** Get the cost to purchase a new Duplicate Sleeve */
function purchaseCost(): number {
return Math.pow(10, Player.sleevesFromCovenant) * BaseCostPerSleeve;
}
// Purchasing a new Duplicate Sleeve
let purchaseDisabled = false;
if (!Player.canAfford(purchaseCost())) {
purchaseDisabled = true;
}
if (Player.sleevesFromCovenant >= MaxSleevesFromCovenant) {
purchaseDisabled = true;
}
function purchaseOnClick(): void {
if (Player.sleevesFromCovenant >= MaxSleevesFromCovenant) return;
if (Player.canAfford(purchaseCost())) {
Player.loseMoney(purchaseCost(), "sleeves");
Player.sleevesFromCovenant += 1;
Sleeve.recalculateNumOwned();
rerender();
} else {
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`);
const result = purchaseSleeve();
if (!result.success) {
dialogBoxCreate(result.message);
return;
}
rerender();
}
// Purchasing Upgrades for Sleeves
@@ -70,14 +51,14 @@ export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
Purchase an additional Sleeve. These Duplicate Sleeves are permanent (they persist through BitNodes). You
can purchase a total of {MaxSleevesFromCovenant} from {FactionName.TheCovenant}.
</Typography>
<Button disabled={purchaseDisabled} onClick={purchaseOnClick}>
<Button disabled={!canPurchaseSleeve().success} onClick={purchaseOnClick}>
Purchase -&nbsp;
<Money money={purchaseCost()} forPurchase={true} />
<Money money={getSleeveCost(Player.sleevesFromCovenant)} forPurchase={true} />
</Button>
<br />
<br />
</>
)}
<br />
<br />
<Typography>You can also purchase upgrades for your Sleeves. These upgrades are also permanent.</Typography>
{upgradePanels}
</>

View File

@@ -5,7 +5,6 @@
import React, { useState } from "react";
import { Sleeve } from "../Sleeve";
import { Player } from "@player";
import { formatSleeveMemory } from "../../../ui/formatNumber";
import { Money } from "../../../ui/React/Money";
@@ -15,6 +14,8 @@ import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import { canPurchaseMemoryUpgrade, purchaseSleeveMemoryUpgrade } from "../SleeveCovenantPurchases";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
interface IProps {
index: number;
@@ -26,51 +27,29 @@ export function CovenantSleeveMemoryUpgrade(props: IProps): React.ReactElement {
const [amt, setAmt] = useState(1);
function changePurchaseAmount(e: React.ChangeEvent<HTMLInputElement>): void {
let n: number = parseInt(e.target.value);
let n = parseInt(e.target.value);
if (isNaN(n)) n = 1;
if (n < 1) n = 1;
const maxMemory = 100 - props.sleeve.memory;
if (n > maxMemory) n = maxMemory;
if (!Number.isInteger(n)) {
n = 1;
}
const maxAmount = 100 - props.sleeve.memory;
if (n > maxAmount) {
n = maxAmount;
}
if (n < 1) {
n = 1;
}
setAmt(n);
}
function getPurchaseCost(): number {
if (isNaN(amt)) {
return Infinity;
}
const maxMemory = 100 - props.sleeve.memory;
if (amt > maxMemory) {
return Infinity;
}
return props.sleeve.getMemoryUpgradeCost(amt);
}
function purchaseMemory(): void {
const cost = getPurchaseCost();
if (Player.canAfford(cost)) {
props.sleeve.upgradeMemory(amt);
Player.loseMoney(cost, "sleeves");
props.rerender();
const result = purchaseSleeveMemoryUpgrade(props.sleeve, amt);
if (!result.success) {
dialogBoxCreate(result.message);
return;
}
}
// Purchase button props
const cost = getPurchaseCost();
const purchaseBtnDisabled = !Player.canAfford(cost);
let purchaseBtnContent = <></>;
if (isNaN(amt)) {
purchaseBtnContent = <>Invalid value</>;
} else {
purchaseBtnContent = (
<>
Purchase {amt} memory&nbsp;-&nbsp;
<Money money={cost} forPurchase={true} />
</>
);
props.rerender();
}
return (
@@ -88,8 +67,9 @@ export function CovenantSleeveMemoryUpgrade(props: IProps): React.ReactElement {
<TextField onChange={changePurchaseAmount} type={"number"} value={amt} />
</Box>
<br />
<Button disabled={purchaseBtnDisabled} onClick={purchaseMemory}>
{purchaseBtnContent}
<Button disabled={!canPurchaseMemoryUpgrade(props.sleeve, amt).success} onClick={purchaseMemory}>
Purchase {amt} memory&nbsp;-&nbsp;
<Money money={props.sleeve.getMemoryUpgradeCost(amt)} forPurchase={true} />
</Button>
</Paper>
);

View File

@@ -5436,6 +5436,51 @@ export interface Sleeve {
action: BladeburnerActionTypeForSleeve,
contract?: BladeburnerContractName,
): boolean;
/**
* Purchase a sleeve. You must be in BitNode 10 to use this API.
*
* @remarks
* RAM cost: 4 GB
*
* @returns Action result
*/
purchaseSleeve(): Result;
/**
* Upgrade memory of a sleeve. You must be in BitNode 10 to use this API.
*
* @remarks
* RAM cost: 4 GB
*
* @param sleeveNumber - Index of the sleeve.
* @param amount - Number of upgrades. Must be a positive integer.
* @returns Action result
*/
upgradeMemory(sleeveNumber: number, amount: number): Result;
/**
* Get the cost of the next sleeve.
*
* @remarks
* RAM cost: 4 GB
*
* @returns Cost of the next sleeve. Return Infinity if you reach the maximum number of purchasable sleeves.
*/
getSleeveCost(): number;
/**
* Get the cost of memory upgrades.
*
* @remarks
* RAM cost: 4 GB
*
* @param sleeveNumber - Index of the sleeve.
* @param amount - Number of upgrades. Must be a positive integer.
* @returns Cost of the upgrades. Return Infinity if the current memory plus the amount of upgrades is greater than
* 100.
*/
getMemoryUpgradeCost(sleeveNumber: number, amount: number): number;
}
/**

View File

@@ -3,10 +3,10 @@ import { FormatsNeedToChange } from "../../../src/ui/formatNumber";
import type { ActionIdFor } from "../../../src/Bladeburner/Types";
import type { Bladeburner } from "../../../src/Bladeburner/Bladeburner";
import { BlackOperation, Contract, Operation } from "../../../src/Bladeburner/Actions";
import { Sleeve } from "../../../src/PersonObjects/Sleeve/Sleeve";
import { SleeveSupportWork } from "../../../src/PersonObjects/Sleeve/Work/SleeveSupportWork";
import { BladeburnerBlackOpName, BladeburnerContractName, BladeburnerOperationName } from "@enums";
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
import { recalculateNumberOfOwnedSleeves } from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases";
/**
* You may want to use hook to help with debugging
@@ -40,7 +40,7 @@ describe("Bladeburner Team", () => {
Player.sourceFiles.set(10, 3);
Player.sleevesFromCovenant = 5;
Sleeve.recalculateNumOwned();
recalculateNumberOfOwnedSleeves();
Player.sleeves.forEach((s) => (s.shock = 0));
});

View File

@@ -0,0 +1,172 @@
import { FactionName } from "@enums";
import { Player } from "@player";
import { joinFaction } from "../../../src/Faction/FactionHelpers";
import { Factions } from "../../../src/Faction/Factions";
import {
getSleeveCost,
MaxSleevesFromCovenant,
recalculateNumberOfOwnedSleeves,
} from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases";
import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
import { prestigeSourceFile } from "../../../src/Prestige";
beforeAll(() => {
initGameEnvironment();
});
describe("Cost", () => {
beforeEach(() => {
setupBasicTestingEnvironment();
Player.bitNodeN = 10;
Player.sourceFiles.set(10, 0);
Player.sleevesFromCovenant = 0;
recalculateNumberOfOwnedSleeves();
});
test("getSleeveCost", () => {
// Invalid values
expect(getSleeveCost(-1)).toStrictEqual(Infinity);
expect(getSleeveCost(1.5)).toStrictEqual(Infinity);
expect(getSleeveCost(5)).toStrictEqual(Infinity);
expect(getSleeveCost(Infinity)).toStrictEqual(Infinity);
expect(getSleeveCost(NaN)).toStrictEqual(Infinity);
// Valid values
expect(getSleeveCost(0)).toStrictEqual(10e12);
expect(getSleeveCost(1)).toStrictEqual(100e12);
expect(getSleeveCost(2)).toStrictEqual(1e15);
expect(getSleeveCost(3)).toStrictEqual(10e15);
expect(getSleeveCost(4)).toStrictEqual(100e15);
});
test("getMemoryUpgradeCost", () => {
// Invalid values
Player.sleeves[0].memory = 1;
expect(Player.sleeves[0].getMemoryUpgradeCost(-1)).toStrictEqual(Infinity);
expect(Player.sleeves[0].getMemoryUpgradeCost(1.5)).toStrictEqual(Infinity);
expect(Player.sleeves[0].getMemoryUpgradeCost(100)).toStrictEqual(Infinity);
expect(Player.sleeves[0].getMemoryUpgradeCost(Infinity)).toStrictEqual(Infinity);
expect(Player.sleeves[0].getMemoryUpgradeCost(NaN)).toStrictEqual(Infinity);
// Valid values
Player.sleeves[0].memory = 1;
expect(Player.sleeves[0].getMemoryUpgradeCost(0)).toStrictEqual(0);
expect(Player.sleeves[0].getMemoryUpgradeCost(1)).toStrictEqual(1e12);
expect(Player.sleeves[0].getMemoryUpgradeCost(10)).toStrictEqual(1.0949720999737857e13);
expect(Player.sleeves[0].getMemoryUpgradeCost(99)).toStrictEqual(3.051297116790363e14);
Player.sleeves[0].memory = 50;
expect(Player.sleeves[0].getMemoryUpgradeCost(0)).toStrictEqual(0);
expect(Player.sleeves[0].getMemoryUpgradeCost(1)).toStrictEqual(2.6388117932094194e12);
expect(Player.sleeves[0].getMemoryUpgradeCost(10)).toStrictEqual(2.889425290646109e13);
expect(Player.sleeves[0].getMemoryUpgradeCost(50)).toStrictEqual(2.231891220185655e14);
});
});
describe("Success", () => {
beforeEach(() => {
setupBasicTestingEnvironment();
Player.bitNodeN = 10;
Player.sourceFiles.set(10, 0);
Player.sleevesFromCovenant = 0;
recalculateNumberOfOwnedSleeves();
joinFaction(Factions[FactionName.TheCovenant]);
});
test("purchaseSleeve", () => {
const ns = getNS();
for (let i = 0; i < 5; ++i) {
Player.money = getSleeveCost(Player.sleevesFromCovenant);
expect(Player.sleevesFromCovenant).toStrictEqual(i);
expect(ns.sleeve.getNumSleeves()).toStrictEqual(i + 1);
expect(ns.sleeve.purchaseSleeve().success).toStrictEqual(true);
expect(Player.sleevesFromCovenant).toStrictEqual(i + 1);
expect(ns.sleeve.getNumSleeves()).toStrictEqual(i + 2);
expect(Player.money).toStrictEqual(0);
}
});
test("upgradeMemory", () => {
const ns = getNS();
const sleeve = Player.sleeves[0];
// Upgrade memory from 1 to 100 gradually
sleeve.memory = 1;
for (let i = 0; i < 99; ++i) {
Player.money = sleeve.getMemoryUpgradeCost(1);
expect(sleeve.memory).toStrictEqual(i + 1);
expect(ns.sleeve.upgradeMemory(0, 1).success).toStrictEqual(true);
expect(sleeve.memory).toStrictEqual(i + 2);
expect(Player.money).toStrictEqual(0);
}
// Upgrade memory from 1 to 100 immediately
sleeve.memory = 1;
Player.money = sleeve.getMemoryUpgradeCost(99);
expect(ns.sleeve.upgradeMemory(0, 99).success).toStrictEqual(true);
expect(sleeve.memory).toStrictEqual(100);
expect(Player.money).toStrictEqual(0);
});
});
describe("Failure", () => {
beforeEach(() => {
setupBasicTestingEnvironment();
// Set BN to anything that is not BN10, then bitflume.
Player.bitNodeN = 1;
prestigeSourceFile(true);
});
test("purchaseSleeve", () => {
const ns = getNS();
// Not in BN10
expect(() => ns.sleeve.purchaseSleeve()).toThrow("You must be in BitNode 10");
let result;
// Not a member of The Covenant
Player.bitNodeN = 10;
Player.sourceFiles.set(10, 0);
Player.sleevesFromCovenant = 0;
recalculateNumberOfOwnedSleeves();
result = ns.sleeve.purchaseSleeve();
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("You must be a member of");
// Have max number of purchasable sleeves
joinFaction(Factions[FactionName.TheCovenant]);
Player.sleevesFromCovenant = MaxSleevesFromCovenant;
recalculateNumberOfOwnedSleeves();
result = ns.sleeve.purchaseSleeve();
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("You already have the maximum amount of sleeves");
// Not enough money
Player.sleevesFromCovenant = 0;
recalculateNumberOfOwnedSleeves();
Player.money = 0;
result = ns.sleeve.purchaseSleeve();
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("You must have at least");
});
test("purchaseSleeve", () => {
const ns = getNS();
// Not in BN10
expect(() => ns.sleeve.upgradeMemory(0, 1)).toThrow("You must be in BitNode 10");
let result;
// Not a member of The Covenant
Player.bitNodeN = 10;
Player.sourceFiles.set(10, 0);
Player.sleevesFromCovenant = 0;
recalculateNumberOfOwnedSleeves();
result = ns.sleeve.upgradeMemory(0, 1);
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("You must be a member of");
// Purchase too many upgrades
joinFaction(Factions[FactionName.TheCovenant]);
result = ns.sleeve.upgradeMemory(0, 100);
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("Invalid amount of upgrade");
// Not enough money
Player.money = 0;
result = ns.sleeve.upgradeMemory(0, 1);
expect(result.success).toStrictEqual(false);
expect(result.message).toContain("You must have at least");
});
});