From 08097aaf098626058ec1979c0b39a3699fd692f6 Mon Sep 17 00:00:00 2001 From: adeilt <56762639+adeilt@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:37:08 -0700 Subject: [PATCH] CORPORATION: more granular office size upgrades (#1179) Allows corporation.upgradeOfficeSize to increase the size of a Corporation office by a non-multiple of 3 and also be charged a corresponding amount of corporate funds. See #1166 for details of current behavior. --- src/Corporation/Actions.ts | 21 +++-- src/Corporation/helpers.ts | 10 +++ .../ui/modals/UpgradeOfficeSizeModal.tsx | 80 +++++++++---------- .../doc/advanced/corporation/office.md | 2 - src/NetscriptFunctions/Corporation.ts | 19 ++--- test/jest/Corporation.test.ts | 27 ++++++- 6 files changed, 91 insertions(+), 68 deletions(-) diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index b68f38649..aaaf5973c 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -16,7 +16,13 @@ import { isRelevantMaterial } from "./ui/Helpers"; import { CityName } from "@enums"; import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRecordValues } from "../Types/Record"; -import { sellSharesFailureReason, buybackSharesFailureReason, issueNewSharesFailureReason } from "./helpers"; +import { + calculateOfficeSizeUpgradeCost, + sellSharesFailureReason, + buybackSharesFailureReason, + issueNewSharesFailureReason, +} from "./helpers"; +import { PositiveInteger } from "../types"; export function NewDivision(corporation: Corporation, industry: IndustryType, name: string): void { if (corporation.divisions.size >= corporation.maxDivisions) @@ -356,17 +362,10 @@ export function BuyBackShares(corporation: Corporation, numShares: number): bool return true; } -export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size: number): void { - const initialPriceMult = Math.round(office.size / corpConstants.officeInitialSize); - const costMultiplier = 1.09; - // Calculate cost to upgrade size by 15 employees - let mult = 0; - for (let i = 0; i < size / corpConstants.officeInitialSize; ++i) { - mult += Math.pow(costMultiplier, initialPriceMult + i); - } - const cost = corpConstants.officeInitialCost * mult; +export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, increase: PositiveInteger): void { + const cost = calculateOfficeSizeUpgradeCost(office.size, increase); if (corp.funds < cost) return; - office.size += size; + office.size += increase; corp.loseFunds(cost, "office"); } diff --git a/src/Corporation/helpers.ts b/src/Corporation/helpers.ts index 2f0511f2e..d82ff7a0e 100644 --- a/src/Corporation/helpers.ts +++ b/src/Corporation/helpers.ts @@ -3,6 +3,7 @@ import { PositiveInteger, isPositiveInteger } from "../types"; import { formatShares } from "../ui/formatNumber"; import { Corporation } from "./Corporation"; import { CorpUpgrade } from "./data/CorporationUpgrades"; +import * as corpConstants from "./data/Constants"; export function calculateUpgradeCost(corporation: Corporation, upgrade: CorpUpgrade, amount: PositiveInteger): number { const priceMult = upgrade.priceMult; @@ -12,6 +13,15 @@ export function calculateUpgradeCost(corporation: Corporation, upgrade: CorpUpgr return cost; } +export function calculateOfficeSizeUpgradeCost(currentSize: number, sizeIncrease: PositiveInteger): number { + if (sizeIncrease <= 0) throw new Error("Invalid value for sizeIncrease argument! Must be at least 0!"); + const baseCostDivisor = 0.09; + const baseCostMultiplier = 1 + baseCostDivisor; + const currentSizeFactor = baseCostMultiplier ** (currentSize / 3); + const sizeIncreaseFactor = baseCostMultiplier ** (sizeIncrease / 3) - 1; + return (corpConstants.officeInitialCost / baseCostDivisor) * currentSizeFactor * sizeIncreaseFactor; +} + export function calculateMaxAffordableUpgrade(corp: Corporation, upgrade: CorpUpgrade): 0 | PositiveInteger { const Lvl = corp.upgrades[upgrade.name].level; const Multi = upgrade.priceMult; diff --git a/src/Corporation/ui/modals/UpgradeOfficeSizeModal.tsx b/src/Corporation/ui/modals/UpgradeOfficeSizeModal.tsx index 7e08e710c..5f04a5345 100644 --- a/src/Corporation/ui/modals/UpgradeOfficeSizeModal.tsx +++ b/src/Corporation/ui/modals/UpgradeOfficeSizeModal.tsx @@ -10,6 +10,8 @@ import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; import Box from "@mui/material/Box"; +import { calculateOfficeSizeUpgradeCost } from "../../helpers"; +import { PositiveInteger } from "../../../types"; interface IUpgradeButton { cost: number; @@ -22,19 +24,22 @@ interface IUpgradeButton { function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement { const corp = useCorporation(); - function upgradeSize(cost: number, size: number): void { + function upgradeSize(cost: number, increase: PositiveInteger): void { if (corp.funds < cost) { return; } - UpgradeOfficeSize(corp, props.office, size); + UpgradeOfficeSize(corp, props.office, increase); props.rerender(); props.onClose(); } return ( - @@ -51,33 +56,23 @@ interface IProps { export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement { const corp = useCorporation(); - const initialPriceMult = Math.round(props.office.size / corpConstants.officeInitialSize); - const costMultiplier = 1.09; - const upgradeCost = corpConstants.officeInitialCost * Math.pow(costMultiplier, initialPriceMult); - // Calculate cost to upgrade size by 15 employees - let mult = 0; - for (let i = 0; i < 5; ++i) { - mult += Math.pow(costMultiplier, initialPriceMult + i); - } - const upgradeCost15 = corpConstants.officeInitialCost * mult; + // appears as +3 office size upgrade + const upgradeSizeBase = corpConstants.officeInitialSize as PositiveInteger; + const upgradeCostBase = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSizeBase); - //Calculate max upgrade size and cost - const maxMult = corp.funds / corpConstants.officeInitialCost; - let maxNum = 1; - mult = Math.pow(costMultiplier, initialPriceMult); - while (maxNum < 50) { - //Hard cap of 50x (extra 150 employees) - if (mult >= maxMult) break; - const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum); - if (mult + multIncrease > maxMult) { - break; - } else { - mult += multIncrease; - } - ++maxNum; + // appears as +15 office size upgrade + const upgradeSize5x = (5 * corpConstants.officeInitialSize) as PositiveInteger; + const upgradeCost5x = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSize5x); + + // optionally appears as largest affordable office size upgrade (up to +150) + let upgradeSizeMax = 150; + let upgradeCostMax = Infinity; + while (upgradeSizeMax > props.office.size) { + upgradeCostMax = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSizeMax as PositiveInteger); + if (corp.funds > upgradeCostMax) break; + upgradeSizeMax -= corpConstants.officeInitialSize; } - const upgradeCostMax = corpConstants.officeInitialCost * mult; return ( @@ -89,27 +84,30 @@ export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement { rerender={props.rerender} office={props.office} corp={corp} - cost={upgradeCost} - size={corpConstants.officeInitialSize} + cost={upgradeCostBase} + size={upgradeSizeBase} /> - {maxNum !== 1 && maxNum !== 5 && ( - - )} + {upgradeSizeMax > 0 && + upgradeCostMax < Infinity && + upgradeSizeMax !== upgradeSizeBase && + upgradeSizeMax !== upgradeSize5x && ( + + )} ); diff --git a/src/Documentation/doc/advanced/corporation/office.md b/src/Documentation/doc/advanced/corporation/office.md index f5332d1cb..55e8d7348 100644 --- a/src/Documentation/doc/advanced/corporation/office.md +++ b/src/Documentation/doc/advanced/corporation/office.md @@ -55,8 +55,6 @@ Maximum size with a given `MaxCost`: $$MaxUpgradeLevel = 3\ast\log_{1.09}\left( MaxCost\ast\frac{0.09}{BasePrice} + {1.09}^{\frac{CurrentSize}{3}} \right)$$ -When we divide office's size by 3, we need to use `Math.ceil()` to round up the value. This means upgrading from size 3 to size 4 costs the same as upgrading from size 3 to size 6. The most cost-effective size is always a multiple of 3. - ## Energy and morale They are calculated in START state. diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index 7d613ad42..44a574437 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -63,7 +63,7 @@ import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript import { helpers } from "../Netscript/NetscriptHelpers"; import { getEnumHelper } from "../utils/EnumHelper"; import { MaterialInfo } from "../Corporation/MaterialInfo"; -import { calculateUpgradeCost } from "../Corporation/helpers"; +import { calculateOfficeSizeUpgradeCost, calculateUpgradeCost } from "../Corporation/helpers"; import { PositiveInteger } from "../types"; import { getRecordKeys } from "../Types/Record"; @@ -504,20 +504,13 @@ export function NetscriptCorporation(): InternalAPI { const researchName = getEnumHelper("CorpResearchName").nsGetMember(ctx, _researchName, "researchName"); return hasResearched(getDivision(divisionName), researchName); }, - getOfficeSizeUpgradeCost: (ctx) => (_divisionName, _cityName, _size) => { + getOfficeSizeUpgradeCost: (ctx) => (_divisionName, _cityName, _increase) => { checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName); - const size = helpers.number(ctx, "size", _size); - if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0"); + const increase = helpers.positiveInteger(ctx, "increase", _increase); const office = getOffice(divisionName, cityName); - const initialPriceMult = Math.round(office.size / corpConstants.officeInitialSize); - const costMultiplier = 1.09; - let mult = 0; - for (let i = 0; i < size / corpConstants.officeInitialSize; ++i) { - mult += Math.pow(costMultiplier, initialPriceMult + i); - } - return corpConstants.officeInitialCost * mult; + return calculateOfficeSizeUpgradeCost(office.size, increase); }, setAutoJobAssignment: (ctx) => (_divisionName, _cityName, _job, _amount) => { checkAccess(ctx, CorpUnlockName.OfficeAPI); @@ -558,8 +551,8 @@ export function NetscriptCorporation(): InternalAPI { checkAccess(ctx, CorpUnlockName.OfficeAPI); const divisionName = helpers.string(ctx, "divisionName", _divisionName); const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName); - const size = helpers.number(ctx, "size", _size); - if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0"); + const size = helpers.positiveInteger(ctx, "size", _size); + const office = getOffice(divisionName, cityName); const corporation = getCorporation(); UpgradeOfficeSize(corporation, office, size); diff --git a/test/jest/Corporation.test.ts b/test/jest/Corporation.test.ts index 66cc9369a..92aac82c9 100644 --- a/test/jest/Corporation.test.ts +++ b/test/jest/Corporation.test.ts @@ -1,7 +1,11 @@ import { PositiveInteger } from "../../src/types"; import { Corporation } from "../../src/Corporation/Corporation"; import { CorpUpgrades } from "../../src/Corporation/data/CorporationUpgrades"; -import { calculateMaxAffordableUpgrade, calculateUpgradeCost } from "../../src/Corporation/helpers"; +import { + calculateMaxAffordableUpgrade, + calculateUpgradeCost, + calculateOfficeSizeUpgradeCost, +} from "../../src/Corporation/helpers"; import { Player, setPlayer } from "../../src/Player"; import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject"; import { @@ -112,4 +116,25 @@ describe("Corporation", () => { expectSharesToAddUp(corporation); }); }); + + describe("helpers.calculateOfficeSizeUpgradeCost matches documented formula", () => { + // for discussion and computation of these test values, see: + // https://github.com/bitburner-official/bitburner-src/pull/1179#discussion_r1534948725 + it.each([ + { fromSize: 3, increaseBy: 3, expectedCost: 4360000000.0 }, + { fromSize: 3, increaseBy: 15, expectedCost: 26093338259.6 }, + { fromSize: 3, increaseBy: 150, expectedCost: 3553764305895.24902 }, + { fromSize: 6, increaseBy: 3, expectedCost: 4752400000.0 }, + { fromSize: 6, increaseBy: 15, expectedCost: 28441738702.964 }, + { fromSize: 6, increaseBy: 150, expectedCost: 3873603093425.821 }, + { fromSize: 9, increaseBy: 3, expectedCost: 5180116000.0 }, + { fromSize: 9, increaseBy: 15, expectedCost: 31001495186.23076 }, + { fromSize: 9, increaseBy: 150, expectedCost: 4222227371834.145 }, + ])( + "should cost $expectedCost to upgrade office by $increaseBy from size $fromSize", + ({ fromSize, increaseBy, expectedCost }) => { + expect(calculateOfficeSizeUpgradeCost(fromSize, increaseBy as PositiveInteger)).toBeCloseTo(expectedCost, 1); + }, + ); + }); });