mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-04 22:59:42 +02:00
SLEEVES: Add sleeve commands to purchase sleeves and memory via the API (#2443)
This commit is contained in:
@@ -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()));
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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 -
|
||||
<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}
|
||||
</>
|
||||
|
||||
@@ -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 -
|
||||
<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 -
|
||||
<Money money={props.sleeve.getMemoryUpgradeCost(amt)} forPurchase={true} />
|
||||
</Button>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user