mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-08 16:53:54 +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 { CityName } from "@enums";
|
||||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||||
import { getRecordValues } from "../Types/Record";
|
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 {
|
export function NewDivision(corporation: Corporation, industry: IndustryType, name: string): void {
|
||||||
if (corporation.divisions.size >= corporation.maxDivisions)
|
if (corporation.divisions.size >= corporation.maxDivisions)
|
||||||
@ -356,17 +362,10 @@ export function BuyBackShares(corporation: Corporation, numShares: number): bool
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size: number): void {
|
export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, increase: PositiveInteger): void {
|
||||||
const initialPriceMult = Math.round(office.size / corpConstants.officeInitialSize);
|
const cost = calculateOfficeSizeUpgradeCost(office.size, increase);
|
||||||
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;
|
|
||||||
if (corp.funds < cost) return;
|
if (corp.funds < cost) return;
|
||||||
office.size += size;
|
office.size += increase;
|
||||||
corp.loseFunds(cost, "office");
|
corp.loseFunds(cost, "office");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { PositiveInteger, isPositiveInteger } from "../types";
|
|||||||
import { formatShares } from "../ui/formatNumber";
|
import { formatShares } from "../ui/formatNumber";
|
||||||
import { Corporation } from "./Corporation";
|
import { Corporation } from "./Corporation";
|
||||||
import { CorpUpgrade } from "./data/CorporationUpgrades";
|
import { CorpUpgrade } from "./data/CorporationUpgrades";
|
||||||
|
import * as corpConstants from "./data/Constants";
|
||||||
|
|
||||||
export function calculateUpgradeCost(corporation: Corporation, upgrade: CorpUpgrade, amount: PositiveInteger): number {
|
export function calculateUpgradeCost(corporation: Corporation, upgrade: CorpUpgrade, amount: PositiveInteger): number {
|
||||||
const priceMult = upgrade.priceMult;
|
const priceMult = upgrade.priceMult;
|
||||||
@ -12,6 +13,15 @@ export function calculateUpgradeCost(corporation: Corporation, upgrade: CorpUpgr
|
|||||||
return cost;
|
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 {
|
export function calculateMaxAffordableUpgrade(corp: Corporation, upgrade: CorpUpgrade): 0 | PositiveInteger {
|
||||||
const Lvl = corp.upgrades[upgrade.name].level;
|
const Lvl = corp.upgrades[upgrade.name].level;
|
||||||
const Multi = upgrade.priceMult;
|
const Multi = upgrade.priceMult;
|
||||||
|
@ -10,6 +10,8 @@ import Typography from "@mui/material/Typography";
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
import { calculateOfficeSizeUpgradeCost } from "../../helpers";
|
||||||
|
import { PositiveInteger } from "../../../types";
|
||||||
|
|
||||||
interface IUpgradeButton {
|
interface IUpgradeButton {
|
||||||
cost: number;
|
cost: number;
|
||||||
@ -22,19 +24,22 @@ interface IUpgradeButton {
|
|||||||
|
|
||||||
function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
|
function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
|
||||||
const corp = useCorporation();
|
const corp = useCorporation();
|
||||||
function upgradeSize(cost: number, size: number): void {
|
function upgradeSize(cost: number, increase: PositiveInteger): void {
|
||||||
if (corp.funds < cost) {
|
if (corp.funds < cost) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpgradeOfficeSize(corp, props.office, size);
|
UpgradeOfficeSize(corp, props.office, increase);
|
||||||
props.rerender();
|
props.rerender();
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip title={formatMoney(props.cost)}>
|
<Tooltip title={formatMoney(props.cost)}>
|
||||||
<span>
|
<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}
|
+{props.size}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
@ -51,33 +56,23 @@ interface IProps {
|
|||||||
|
|
||||||
export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {
|
export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {
|
||||||
const corp = useCorporation();
|
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
|
// appears as +3 office size upgrade
|
||||||
let mult = 0;
|
const upgradeSizeBase = corpConstants.officeInitialSize as PositiveInteger;
|
||||||
for (let i = 0; i < 5; ++i) {
|
const upgradeCostBase = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSizeBase);
|
||||||
mult += Math.pow(costMultiplier, initialPriceMult + i);
|
|
||||||
}
|
|
||||||
const upgradeCost15 = corpConstants.officeInitialCost * mult;
|
|
||||||
|
|
||||||
//Calculate max upgrade size and cost
|
// appears as +15 office size upgrade
|
||||||
const maxMult = corp.funds / corpConstants.officeInitialCost;
|
const upgradeSize5x = (5 * corpConstants.officeInitialSize) as PositiveInteger;
|
||||||
let maxNum = 1;
|
const upgradeCost5x = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSize5x);
|
||||||
mult = Math.pow(costMultiplier, initialPriceMult);
|
|
||||||
while (maxNum < 50) {
|
// optionally appears as largest affordable office size upgrade (up to +150)
|
||||||
//Hard cap of 50x (extra 150 employees)
|
let upgradeSizeMax = 150;
|
||||||
if (mult >= maxMult) break;
|
let upgradeCostMax = Infinity;
|
||||||
const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
|
while (upgradeSizeMax > props.office.size) {
|
||||||
if (mult + multIncrease > maxMult) {
|
upgradeCostMax = calculateOfficeSizeUpgradeCost(props.office.size, upgradeSizeMax as PositiveInteger);
|
||||||
break;
|
if (corp.funds > upgradeCostMax) break;
|
||||||
} else {
|
upgradeSizeMax -= corpConstants.officeInitialSize;
|
||||||
mult += multIncrease;
|
|
||||||
}
|
}
|
||||||
++maxNum;
|
|
||||||
}
|
|
||||||
const upgradeCostMax = corpConstants.officeInitialCost * mult;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={props.open} onClose={props.onClose}>
|
<Modal open={props.open} onClose={props.onClose}>
|
||||||
@ -89,25 +84,28 @@ export function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {
|
|||||||
rerender={props.rerender}
|
rerender={props.rerender}
|
||||||
office={props.office}
|
office={props.office}
|
||||||
corp={corp}
|
corp={corp}
|
||||||
cost={upgradeCost}
|
cost={upgradeCostBase}
|
||||||
size={corpConstants.officeInitialSize}
|
size={upgradeSizeBase}
|
||||||
/>
|
/>
|
||||||
<UpgradeSizeButton
|
<UpgradeSizeButton
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
rerender={props.rerender}
|
rerender={props.rerender}
|
||||||
office={props.office}
|
office={props.office}
|
||||||
corp={corp}
|
corp={corp}
|
||||||
cost={upgradeCost15}
|
cost={upgradeCost5x}
|
||||||
size={corpConstants.officeInitialSize * 5}
|
size={upgradeSize5x}
|
||||||
/>
|
/>
|
||||||
{maxNum !== 1 && maxNum !== 5 && (
|
{upgradeSizeMax > 0 &&
|
||||||
|
upgradeCostMax < Infinity &&
|
||||||
|
upgradeSizeMax !== upgradeSizeBase &&
|
||||||
|
upgradeSizeMax !== upgradeSize5x && (
|
||||||
<UpgradeSizeButton
|
<UpgradeSizeButton
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
rerender={props.rerender}
|
rerender={props.rerender}
|
||||||
office={props.office}
|
office={props.office}
|
||||||
corp={corp}
|
corp={corp}
|
||||||
cost={upgradeCostMax}
|
cost={upgradeCostMax}
|
||||||
size={maxNum * corpConstants.officeInitialSize}
|
size={upgradeSizeMax}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</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)$$
|
$$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
|
## Energy and morale
|
||||||
|
|
||||||
They are calculated in START state.
|
They are calculated in START state.
|
||||||
|
@ -63,7 +63,7 @@ import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript
|
|||||||
import { helpers } from "../Netscript/NetscriptHelpers";
|
import { helpers } from "../Netscript/NetscriptHelpers";
|
||||||
import { getEnumHelper } from "../utils/EnumHelper";
|
import { getEnumHelper } from "../utils/EnumHelper";
|
||||||
import { MaterialInfo } from "../Corporation/MaterialInfo";
|
import { MaterialInfo } from "../Corporation/MaterialInfo";
|
||||||
import { calculateUpgradeCost } from "../Corporation/helpers";
|
import { calculateOfficeSizeUpgradeCost, calculateUpgradeCost } from "../Corporation/helpers";
|
||||||
import { PositiveInteger } from "../types";
|
import { PositiveInteger } from "../types";
|
||||||
import { getRecordKeys } from "../Types/Record";
|
import { getRecordKeys } from "../Types/Record";
|
||||||
|
|
||||||
@ -504,20 +504,13 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
|||||||
const researchName = getEnumHelper("CorpResearchName").nsGetMember(ctx, _researchName, "researchName");
|
const researchName = getEnumHelper("CorpResearchName").nsGetMember(ctx, _researchName, "researchName");
|
||||||
return hasResearched(getDivision(divisionName), researchName);
|
return hasResearched(getDivision(divisionName), researchName);
|
||||||
},
|
},
|
||||||
getOfficeSizeUpgradeCost: (ctx) => (_divisionName, _cityName, _size) => {
|
getOfficeSizeUpgradeCost: (ctx) => (_divisionName, _cityName, _increase) => {
|
||||||
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
||||||
const divisionName = helpers.string(ctx, "divisionName", _divisionName);
|
const divisionName = helpers.string(ctx, "divisionName", _divisionName);
|
||||||
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
|
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
|
||||||
const size = helpers.number(ctx, "size", _size);
|
const increase = helpers.positiveInteger(ctx, "increase", _increase);
|
||||||
if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0");
|
|
||||||
const office = getOffice(divisionName, cityName);
|
const office = getOffice(divisionName, cityName);
|
||||||
const initialPriceMult = Math.round(office.size / corpConstants.officeInitialSize);
|
return calculateOfficeSizeUpgradeCost(office.size, increase);
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
setAutoJobAssignment: (ctx) => (_divisionName, _cityName, _job, _amount) => {
|
setAutoJobAssignment: (ctx) => (_divisionName, _cityName, _job, _amount) => {
|
||||||
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
||||||
@ -558,8 +551,8 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
|||||||
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
checkAccess(ctx, CorpUnlockName.OfficeAPI);
|
||||||
const divisionName = helpers.string(ctx, "divisionName", _divisionName);
|
const divisionName = helpers.string(ctx, "divisionName", _divisionName);
|
||||||
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
|
const cityName = getEnumHelper("CityName").nsGetMember(ctx, _cityName);
|
||||||
const size = helpers.number(ctx, "size", _size);
|
const size = helpers.positiveInteger(ctx, "size", _size);
|
||||||
if (size < 0) throw new Error("Invalid value for size field! Must be numeric and greater than 0");
|
|
||||||
const office = getOffice(divisionName, cityName);
|
const office = getOffice(divisionName, cityName);
|
||||||
const corporation = getCorporation();
|
const corporation = getCorporation();
|
||||||
UpgradeOfficeSize(corporation, office, size);
|
UpgradeOfficeSize(corporation, office, size);
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { PositiveInteger } from "../../src/types";
|
import { PositiveInteger } from "../../src/types";
|
||||||
import { Corporation } from "../../src/Corporation/Corporation";
|
import { Corporation } from "../../src/Corporation/Corporation";
|
||||||
import { CorpUpgrades } from "../../src/Corporation/data/CorporationUpgrades";
|
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 { Player, setPlayer } from "../../src/Player";
|
||||||
import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
|
import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
|
||||||
import {
|
import {
|
||||||
@ -112,4 +116,25 @@ describe("Corporation", () => {
|
|||||||
expectSharesToAddUp(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