mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-21 23:23:51 +01:00
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.
This commit is contained in:
parent
db226ce0b8
commit
08097aaf09
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
<Tooltip title={formatMoney(props.cost)}>
|
||||
<span>
|
||||
<Button disabled={corp.funds < props.cost} onClick={() => upgradeSize(props.cost, props.size)}>
|
||||
<Button
|
||||
disabled={corp.funds < props.cost}
|
||||
onClick={() => upgradeSize(props.cost, props.size as PositiveInteger)}
|
||||
>
|
||||
+{props.size}
|
||||
</Button>
|
||||
</span>
|
||||
@ -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;
|
||||
// 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;
|
||||
}
|
||||
++maxNum;
|
||||
}
|
||||
const upgradeCostMax = corpConstants.officeInitialCost * mult;
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
@ -89,25 +84,28 @@ export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {
|
||||
rerender={props.rerender}
|
||||
office={props.office}
|
||||
corp={corp}
|
||||
cost={upgradeCost}
|
||||
size={corpConstants.officeInitialSize}
|
||||
cost={upgradeCostBase}
|
||||
size={upgradeSizeBase}
|
||||
/>
|
||||
<UpgradeSizeButton
|
||||
onClose={props.onClose}
|
||||
rerender={props.rerender}
|
||||
office={props.office}
|
||||
corp={corp}
|
||||
cost={upgradeCost15}
|
||||
size={corpConstants.officeInitialSize * 5}
|
||||
cost={upgradeCost5x}
|
||||
size={upgradeSize5x}
|
||||
/>
|
||||
{maxNum !== 1 && maxNum !== 5 && (
|
||||
{upgradeSizeMax > 0 &&
|
||||
upgradeCostMax < Infinity &&
|
||||
upgradeSizeMax !== upgradeSizeBase &&
|
||||
upgradeSizeMax !== upgradeSize5x && (
|
||||
<UpgradeSizeButton
|
||||
onClose={props.onClose}
|
||||
rerender={props.rerender}
|
||||
office={props.office}
|
||||
corp={corp}
|
||||
cost={upgradeCostMax}
|
||||
size={maxNum * corpConstants.officeInitialSize}
|
||||
size={upgradeSizeMax}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -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.
|
||||
|
@ -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<NSCorporation> {
|
||||
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<NSCorporation> {
|
||||
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);
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user