Compare commits

...

53 Commits

Author SHA1 Message Date
omuretsu
5c738919fc Update changelog.md for 2.5.0 2023-10-02 19:30:37 -04:00
Snarling
223d9b9cdb 2.5.0 as release version (#842) 2023-10-02 19:18:10 -04:00
omuretsu
6a63f8a02c Final dev changelog for 2.5.0 cycle? 2023-09-29 21:32:36 -04:00
Jesse Clark
ad6f919d64 CORPORATION: Dramatically lower API RAM costs (#818) 2023-09-27 17:38:30 -04:00
muesli4brekkies
c5e2f65cb0 API: Added spawnDelay parameter to ns.spawn options, allowing user defined delay (#807) 2023-09-27 01:31:47 -04:00
Michael Ficocelli
7fad6e0778 UI: Rename Territory Warfare gang status to avoid confusion with gang member task of the same name (#790) 2023-09-26 21:04:43 -04:00
Snarling
6d3495d05f Changelog update for 2.5.0 (#828) 2023-09-24 09:11:49 -04:00
David Walker
875bddbbdb BUGFIX: Patch a minor hole in RAM calculation (#820) 2023-09-21 21:44:32 -04:00
Snarling
648c180952 UI: Modals no longer update content and become inert while closing (#817) 2023-09-21 21:33:18 -04:00
Jesse Clark
3ae3f947ac CORPORATION: Rework share price calculation + UI improvements (#782) 2023-09-20 00:36:48 -04:00
Michael Ficocelli
f6e1c171ae MISC: Add configuration to eslintrc to fix the warning thrown when linting (#814) 2023-09-19 12:24:12 -04:00
Yichi Zhang
1b81fe8766 CORPORATION: Rework valuation calculation (#789) 2023-09-19 08:47:16 -04:00
missymae#2783
e1d2e12747 UI: Improve soft reset dialog, and always confirm soft resets (#809) 2023-09-17 16:30:10 -04:00
Jesse Clark
b9d13063ac Format time in ns.sleep and ns.asleep (#806) 2023-09-17 15:40:25 -04:00
missymae#2783
47176b7809 Workout achievement requires work in gym (#805) 2023-09-17 15:38:52 -04:00
Caldwell
d914040ae7 CORPORATION: Remove Market TA string in sell dialog text fields (#784) 2023-09-12 19:49:51 -04:00
omuretsu
cb93f4d108 Changelog update 12sep 2023-09-12 06:09:18 -04:00
omuretsu
fe0f8cad4d Import changes
Just some import changes in a couple files.
2023-09-12 05:38:25 -04:00
Caldwell
b6eafce563 CORPORATION: remove TA modals and integrate into sell modal (#796) 2023-09-12 05:30:45 -04:00
Zelow79
99e5c5e6b0 Added BB Training to sleeve actions (#713) 2023-09-12 05:07:36 -04:00
Caldwell
bf5e638891 CORPORATION: Fix "Use same 'Sell Amount' for all cities" toggle for products (#775) 2023-09-12 04:31:51 -04:00
Caldwell
d2b3659512 DOC: Add CorporationDivisions multiplier to BitNodeMultipliers API interface (#798) 2023-09-12 04:30:19 -04:00
aschmider
a6ff0d3e14 CORPORATION: Fix additive value in party result message (#793) (#794) 2023-09-12 03:56:59 -04:00
aschmider
c4482a70f9 HACKNET: Fix spendHashes ignoring count for Company Favor upgrade (#791) (#792) 2023-09-12 03:55:43 -04:00
missymae#2783
3c42880185 DOCS: Updates to basic documentations (#788) 2023-09-12 02:07:04 -04:00
Noah Harris
7b2e8e5312 UI: clear search suggestions on clearall modification (#787) 2023-09-12 02:00:20 -04:00
Michael Ficocelli
b844593e22 UI: Add filter to faction augmentation purchase page (#783) 2023-09-12 01:36:47 -04:00
Caldwell
624a0a5b02 CORPORATION: improve State description (#778) 2023-09-12 01:25:51 -04:00
Caldwell
bba2ccd83a CORPORATION: add issueNewSharesCooldown and makesMaterial (#774) 2023-09-12 01:23:36 -04:00
Michael Ficocelli
25dae7ec8b UI: Basic Map and Set support in ns.print/tprint (#756) 2023-09-12 01:07:41 -04:00
omuretsu
f95ca64e01 Changelog update 5 sep 2023-09-05 18:34:26 -04:00
Caldwell
0ed88a4317 UI: Stats page uses same number format as overview for skills. (#776) 2023-09-05 18:13:41 -04:00
missymae#2783
8d3f2bd750 GANG: Updates to Docs, UI, API (#773)
API Changes:
Adds ns.gang.getRecruitsAvailable: Gets the number of additional gang members that can currently be recruited
Adds ns.gang.respectForNextRecruit: Gets the respect threshold for recruiting the next gang member
Adds ns.gang.renameMember: Renames a gang member

Plus many doc and ui improvements
2023-09-05 18:07:19 -04:00
missymae#2783
bec737a253 DOCS: Add documentation descriptions for GangMemberInfo properties (#779) 2023-09-03 16:50:24 -04:00
muesli4brekkies
66ac31ee99 DOCS: Improve/correct some API docs related to ports (#772) 2023-09-02 10:57:31 -04:00
Michael Ficocelli
52769706d2 UI: Keep tail windows and dialogs mounted but hidden on pages where they are not shown (#769) 2023-08-31 21:31:59 -04:00
Michael Ficocelli
ae87851889 Improve pagination for Active Scripts page (#763) 2023-08-31 20:31:48 -04:00
Caldwell
7c82221a13 CORP: Change some number formatting to look better (#764)
changed formatCorpStat to formatBigNumber to make big numbers readable
2023-08-31 20:29:04 -04:00
VictorS
b7fdcdf35d DOCS: Improved documentation for functions that take a FilenameOrPID (#759) 2023-08-30 16:36:34 -04:00
missymae#2783
ad9bde40e0 DOCS: Improve documentation for ns.bladeburner.getActionRepGain (#760) 2023-08-30 16:28:21 -04:00
Caldwell
8c86e1e07a fix NaN in Bladeburner success Chance (#761) 2023-08-30 14:51:16 -04:00
Valentin Dewilde
1ea555f572 UI: Move pagination to top of active scripts, improve filtering (#494) 2023-08-30 14:45:27 -04:00
omuretsu
bc7482b0a2 Changelog update 2023-08-28 15:47:59 -04:00
missymae#2783
12de5505b5 DOCS: Bladeburner in-game documentation update (#755) 2023-08-28 15:36:49 -04:00
Michael Ficocelli
62058a7f78 UI: Suppress confusing reputation-earned dialog on augment install (#739) 2023-08-28 15:12:13 -04:00
Michael Ficocelli
9a0d688909 NETSCRIPT: Add more helpful error messages to getPurchasedServerCost (#757) 2023-08-28 15:03:16 -04:00
Eli Doran
b4f33fe655 Remove an unnecessary for loop in HacknetServer upgradeRam (#754) 2023-08-28 14:51:20 -04:00
missymae#2783
4b627cde1e DOCS: ns.getScriptLog update to use "FilenameOrPID" and related ns.tail change (#753) 2023-08-28 14:49:37 -04:00
Caldwell
101fb21c58 UI: Faction augmentation page rerenders automatically (#744) 2023-08-28 14:40:34 -04:00
Michael Ficocelli
79b677973b MESSAGES: Re-implement limit on Icarus messages after a bitnode is completed (#742) 2023-08-28 14:38:15 -04:00
Michael Ficocelli
93235570d0 TERMINAL: Prevent recursive aliases from being resolved. (#741) 2023-08-28 14:26:25 -04:00
omuretsu
51b03003f6 Fix changelog 2023-08-26 18:35:44 -04:00
omuretsu
281e22c90c 2.4.2dev Start 2023-08-26 18:32:14 -04:00
160 changed files with 2386 additions and 1436 deletions

View File

@@ -35,4 +35,9 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "off",
"react/no-unescaped-entities": "off",
},
settings: {
react: {
version: "detect",
},
},
};

View File

@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "2.3.2dev",
"version": "2.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "2.3.2dev",
"version": "2.5.0",
"dependencies": {
"electron-config": "^2.0.0",
"electron-log": "^4.4.8",

View File

@@ -1,6 +1,6 @@
{
"name": "bitburner",
"version": "2.4.1",
"version": "2.5.0",
"description": "A cyberpunk-themed programming incremental game",
"main": "main.js",
"author": "Daniel Xie, Olivier Gagnon, et al.",

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [BitNodeMultipliers](./bitburner.bitnodemultipliers.md) &gt; [CorporationDivisions](./bitburner.bitnodemultipliers.corporationdivisions.md)
## BitNodeMultipliers.CorporationDivisions property
Influences the amount of divisions a corporation can have have at the same time
**Signature:**
```typescript
CorporationDivisions: number;
```

View File

@@ -26,6 +26,7 @@ interface BitNodeMultipliers
| [CodingContractMoney](./bitburner.bitnodemultipliers.codingcontractmoney.md) | | number | Influences the amount of money gained from completing Coding Contracts |
| [CompanyWorkExpGain](./bitburner.bitnodemultipliers.companyworkexpgain.md) | | number | Influences the experience gained for each ability when the player completes working their job. |
| [CompanyWorkMoney](./bitburner.bitnodemultipliers.companyworkmoney.md) | | number | Influences how much money the player earns when completing working their job. |
| [CorporationDivisions](./bitburner.bitnodemultipliers.corporationdivisions.md) | | number | Influences the amount of divisions a corporation can have have at the same time |
| [CorporationSoftcap](./bitburner.bitnodemultipliers.corporationsoftcap.md) | | number | Influences the money gain from dividends of corporations created by the player. |
| [CorporationValuation](./bitburner.bitnodemultipliers.corporationvaluation.md) | | number | Influences the valuation of corporations created by the player. |
| [CrimeExpGain](./bitburner.bitnodemultipliers.crimeexpgain.md) | | number | Influences the base experience gained for each ability when the player commits a crime. |

View File

@@ -18,7 +18,7 @@ getActionRepGain(type: string, name: string, level: number): number;
| --- | --- | --- |
| type | string | Type of action. |
| name | string | Name of action. Must be an exact match. |
| level | number | Optional action level at which to calculate the gain |
| level | number | Optional number. Action level at which to calculate the gain. Will be the action's current level if not given. |
**Returns:**

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [CorpIndustryData](./bitburner.corpindustrydata.md) &gt; [makesMaterials](./bitburner.corpindustrydata.makesmaterials.md)
## CorpIndustryData.makesMaterials property
Whether the industry of this division is capable of producing materials
**Signature:**
```typescript
makesMaterials: boolean;
```

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [CorpIndustryData](./bitburner.corpindustrydata.md) &gt; [makesProducts](./bitburner.corpindustrydata.makesproducts.md)
## CorpIndustryData.makesProducts property
Whether the industry of this division is capable of developing and producing products
**Signature:**
```typescript
makesProducts: boolean;
```

View File

@@ -20,7 +20,9 @@ interface CorpIndustryData
| [aiCoreFactor?](./bitburner.corpindustrydata.aicorefactor.md) | | number | _(Optional)_ AI Cores factor |
| [description](./bitburner.corpindustrydata.description.md) | | string | |
| [hardwareFactor?](./bitburner.corpindustrydata.hardwarefactor.md) | | number | _(Optional)_ Hardware factor |
| [producedMaterials?](./bitburner.corpindustrydata.producedmaterials.md) | | [CorpMaterialName](./bitburner.corpmaterialname.md)<!-- -->\[\] | _(Optional)_ |
| [makesMaterials](./bitburner.corpindustrydata.makesmaterials.md) | | boolean | Whether the industry of this division is capable of producing materials |
| [makesProducts](./bitburner.corpindustrydata.makesproducts.md) | | boolean | Whether the industry of this division is capable of developing and producing products |
| [producedMaterials?](./bitburner.corpindustrydata.producedmaterials.md) | | [CorpMaterialName](./bitburner.corpmaterialname.md)<!-- -->\[\] | _(Optional)_ Array of Materials produced |
| [product?](./bitburner.corpindustrydata.product.md) | | [CorpProductData](./bitburner.corpproductdata.md) | _(Optional)_ |
| [realEstateFactor?](./bitburner.corpindustrydata.realestatefactor.md) | | number | _(Optional)_ Real estate factor |
| [recommendStarting](./bitburner.corpindustrydata.recommendstarting.md) | | boolean | |

View File

@@ -4,6 +4,8 @@
## CorpIndustryData.producedMaterials property
Array of Materials produced
**Signature:**
```typescript

View File

@@ -4,7 +4,7 @@
## Corporation.buyBackShares() method
Buyback Shares
Buyback Shares. Spend money from the player's wallet to transfer shares from public traders to the CEO.
**Signature:**

View File

@@ -19,7 +19,7 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
| --- | --- |
| [acceptInvestmentOffer()](./bitburner.corporation.acceptinvestmentoffer.md) | Accept investment based on you companies current valuation |
| [bribe(factionName, amountCash)](./bitburner.corporation.bribe.md) | Bribe a faction |
| [buyBackShares(amount)](./bitburner.corporation.buybackshares.md) | Buyback Shares |
| [buyBackShares(amount)](./bitburner.corporation.buybackshares.md) | Buyback Shares. Spend money from the player's wallet to transfer shares from public traders to the CEO. |
| [createCorporation(corporationName, selfFund)](./bitburner.corporation.createcorporation.md) | Create a Corporation |
| [expandCity(divisionName, city)](./bitburner.corporation.expandcity.md) | Expand to a new city |
| [expandIndustry(industryType, divisionName)](./bitburner.corporation.expandindustry.md) | Expand to a new industry |
@@ -40,5 +40,5 @@ export interface Corporation extends WarehouseAPI, OfficeAPI
| [issueNewShares(amount)](./bitburner.corporation.issuenewshares.md) | Issue new shares |
| [levelUpgrade(upgradeName)](./bitburner.corporation.levelupgrade.md) | Level an upgrade. |
| [purchaseUnlock(upgradeName)](./bitburner.corporation.purchaseunlock.md) | Unlock an upgrade |
| [sellShares(amount)](./bitburner.corporation.sellshares.md) | Sell Shares |
| [sellShares(amount)](./bitburner.corporation.sellshares.md) | Sell Shares. Transfer shares from the CEO to public traders to receive money in the player's wallet. |

View File

@@ -4,7 +4,7 @@
## Corporation.sellShares() method
Sell Shares
Sell Shares. Transfer shares from the CEO to public traders to receive money in the player's wallet.
**Signature:**

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [CorporationInfo](./bitburner.corporationinfo.md) &gt; [investorShares](./bitburner.corporationinfo.investorshares.md)
## CorporationInfo.investorShares property
Amount of shares owned by private investors. Not available for public sale or CEO buyback.
**Signature:**
```typescript
investorShares: number;
```

View File

@@ -4,7 +4,7 @@
## CorporationInfo.issuedShares property
Amount of acquirable shares.
Amount of shares owned by public traders. Available for CEO buyback.
**Signature:**

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [CorporationInfo](./bitburner.corporationinfo.md) &gt; [issueNewSharesCooldown](./bitburner.corporationinfo.issuenewsharescooldown.md)
## CorporationInfo.issueNewSharesCooldown property
Cooldown until new shares can be issued
**Signature:**
```typescript
issueNewSharesCooldown: number;
```

View File

@@ -22,13 +22,15 @@ interface CorporationInfo
| [divisions](./bitburner.corporationinfo.divisions.md) | | string\[\] | Array of all division names |
| [expenses](./bitburner.corporationinfo.expenses.md) | | number | Expenses per second this cycle |
| [funds](./bitburner.corporationinfo.funds.md) | | number | Funds available |
| [issuedShares](./bitburner.corporationinfo.issuedshares.md) | | number | Amount of acquirable shares. |
| [investorShares](./bitburner.corporationinfo.investorshares.md) | | number | Amount of shares owned by private investors. Not available for public sale or CEO buyback. |
| [issuedShares](./bitburner.corporationinfo.issuedshares.md) | | number | Amount of shares owned by public traders. Available for CEO buyback. |
| [issueNewSharesCooldown](./bitburner.corporationinfo.issuenewsharescooldown.md) | | number | Cooldown until new shares can be issued |
| [name](./bitburner.corporationinfo.name.md) | | string | Name of the corporation |
| [numShares](./bitburner.corporationinfo.numshares.md) | | number | Amount of share owned |
| [numShares](./bitburner.corporationinfo.numshares.md) | | number | Amount of shares owned by the CEO. |
| [public](./bitburner.corporationinfo.public.md) | | boolean | Indicating if the company is public |
| [revenue](./bitburner.corporationinfo.revenue.md) | | number | Revenue per second this cycle |
| [sharePrice](./bitburner.corporationinfo.shareprice.md) | | number | Price of the shares |
| [shareSaleCooldown](./bitburner.corporationinfo.sharesalecooldown.md) | | number | Cooldown until shares can be sold again |
| [state](./bitburner.corporationinfo.state.md) | | string | State of the corporation. Possible states are START, PURCHASE, PRODUCTION, EXPORT, SALE. |
| [totalShares](./bitburner.corporationinfo.totalshares.md) | | number | Total number of shares issues by this corporation |
| [state](./bitburner.corporationinfo.state.md) | | string | <p>The next state to be processed.</p><p>I.e. when the state is PURCHASE, it means purchasing will occur during the next state transition.</p><p>Possible states are START, PURCHASE, PRODUCTION, EXPORT, SALE.</p> |
| [totalShares](./bitburner.corporationinfo.totalshares.md) | | number | Total number of shares issued by this corporation. |

View File

@@ -4,7 +4,7 @@
## CorporationInfo.numShares property
Amount of share owned
Amount of shares owned by the CEO.
**Signature:**

View File

@@ -4,7 +4,11 @@
## CorporationInfo.state property
State of the corporation. Possible states are START, PURCHASE, PRODUCTION, EXPORT, SALE.
The next state to be processed.
I.e. when the state is PURCHASE, it means purchasing will occur during the next state transition.
Possible states are START, PURCHASE, PRODUCTION, EXPORT, SALE.
**Signature:**

View File

@@ -4,7 +4,7 @@
## CorporationInfo.totalShares property
Total number of shares issues by this corporation
Total number of shares issued by this corporation.
**Signature:**

View File

@@ -4,7 +4,7 @@
## Division.makesProducts property
Whether the industry this division is in is capable of making products
Whether the industry of this division is capable of developing and producing products
**Signature:**

View File

@@ -20,7 +20,7 @@ interface Division
| [cities](./bitburner.division.cities.md) | | [CityName](./bitburner.cityname.md)<!-- -->\[\] | Cities in which this division has expanded |
| [lastCycleExpenses](./bitburner.division.lastcycleexpenses.md) | | number | Expenses last cycle |
| [lastCycleRevenue](./bitburner.division.lastcyclerevenue.md) | | number | Revenue last cycle |
| [makesProducts](./bitburner.division.makesproducts.md) | | boolean | Whether the industry this division is in is capable of making products |
| [makesProducts](./bitburner.division.makesproducts.md) | | boolean | Whether the industry of this division is capable of developing and producing products |
| [maxProducts](./bitburner.division.maxproducts.md) | | number | How many products this division can support |
| [name](./bitburner.division.name.md) | | string | Name of the division |
| [numAdVerts](./bitburner.division.numadverts.md) | | number | Number of times AdVert has been bought |

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; [Gang](./bitburner.gang.md) &gt; [getRecruitsAvailable](./bitburner.gang.getrecruitsavailable.md)
## Gang.getRecruitsAvailable() method
Check how many gang members you can currently recruit.
**Signature:**
```typescript
getRecruitsAvailable(): number;
```
**Returns:**
number
Number indicating how many members can be recruited, considering current reputation and gang size.
## Remarks
RAM cost: 1 GB

View File

@@ -34,11 +34,14 @@ If you are not in BitNode-2, then you must have Source-File 2 in order to use th
| [getMemberInformation(name)](./bitburner.gang.getmemberinformation.md) | Get information about a specific gang member. |
| [getMemberNames()](./bitburner.gang.getmembernames.md) | List all gang members. |
| [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about the other gangs. |
| [getRecruitsAvailable()](./bitburner.gang.getrecruitsavailable.md) | Check how many gang members you can currently recruit. |
| [getTaskNames()](./bitburner.gang.gettasknames.md) | List member task names. |
| [getTaskStats(name)](./bitburner.gang.gettaskstats.md) | Get stats of a task. |
| [inGang()](./bitburner.gang.ingang.md) | Check if you're in a gang. |
| [purchaseEquipment(memberName, equipName)](./bitburner.gang.purchaseequipment.md) | Purchase an equipment for a gang member. |
| [recruitMember(name)](./bitburner.gang.recruitmember.md) | Recruit a new gang member. |
| [renameMember(memberName, newName)](./bitburner.gang.renamemember.md) | Rename a Gang member to a new unique name. |
| [respectForNextRecruit()](./bitburner.gang.respectfornextrecruit.md) | Check the amount of Respect needed for your next gang recruit. |
| [setMemberTask(memberName, taskName)](./bitburner.gang.setmembertask.md) | Set gang member to task. |
| [setTerritoryWarfare(engage)](./bitburner.gang.setterritorywarfare.md) | Enable/Disable territory warfare. |

View File

@@ -0,0 +1,33 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Gang](./bitburner.gang.md) &gt; [renameMember](./bitburner.gang.renamemember.md)
## Gang.renameMember() method
Rename a Gang member to a new unique name.
**Signature:**
```typescript
renameMember(memberName: string, newName: string): boolean;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| memberName | string | Name of the member to change. |
| newName | string | New name for that gang member. |
**Returns:**
boolean
True if successful, and false if not.
## Remarks
RAM cost: 0 GB
Rename a Gang Member if none already has the new name.

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; [Gang](./bitburner.gang.md) &gt; [respectForNextRecruit](./bitburner.gang.respectfornextrecruit.md)
## Gang.respectForNextRecruit() method
Check the amount of Respect needed for your next gang recruit.
**Signature:**
```typescript
respectForNextRecruit(): number;
```
**Returns:**
number
The static number value of Respect needed for the next recruit, with consideration to your current gang size. Returns `Infinity` if you have reached the gang size limit.
## Remarks
RAM cost: 1 GB

View File

@@ -21,6 +21,7 @@ interface GangGenInfo
| [moneyGainRate](./bitburner.ganggeninfo.moneygainrate.md) | | number | Money earned per game cycle |
| [power](./bitburner.ganggeninfo.power.md) | | number | Gang's power for territory warfare |
| [respect](./bitburner.ganggeninfo.respect.md) | | number | Gang's respect |
| [respectForNextRecruit](./bitburner.ganggeninfo.respectfornextrecruit.md) | | number | Amount of Respect needed for next gang recruit, if possible |
| [respectGainRate](./bitburner.ganggeninfo.respectgainrate.md) | | number | Respect earned per game cycle |
| [territory](./bitburner.ganggeninfo.territory.md) | | number | Amount of territory held |
| [territoryClashChance](./bitburner.ganggeninfo.territoryclashchance.md) | | number | Clash chance |

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [GangGenInfo](./bitburner.ganggeninfo.md) &gt; [respectForNextRecruit](./bitburner.ganggeninfo.respectfornextrecruit.md)
## GangGenInfo.respectForNextRecruit property
Amount of Respect needed for next gang recruit, if possible
**Signature:**
```typescript
respectForNextRecruit: number;
```

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.agi\_asc\_points property
Total earned agility experience
Total Agility Ascension points accumulated
**Signature:**

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.augmentations property
List of all Augmentations currently installed on gang member
**Signature:**
```typescript

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.cha\_asc\_points property
Total earned charisma experience
Total Charisma Ascension points accumulated
**Signature:**

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.def\_asc\_points property
Total earned defense experience
Total Defense Ascension points accumulated
**Signature:**

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.dex\_asc\_points property
Total earned dexterity experience
Total Dexterity Ascension points accumulated
**Signature:**

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.earnedRespect property
Amount of Respect earned by member since they last Ascended
**Signature:**
```typescript

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.hack\_asc\_points property
Total earned hack experience
Total Hack Ascension points accumulated
**Signature:**

View File

@@ -16,41 +16,41 @@ interface GangMemberInfo
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [agi\_asc\_mult](./bitburner.gangmemberinfo.agi_asc_mult.md) | | number | Agility multiplier from ascensions |
| [agi\_asc\_points](./bitburner.gangmemberinfo.agi_asc_points.md) | | number | Total earned agility experience |
| [agi\_asc\_points](./bitburner.gangmemberinfo.agi_asc_points.md) | | number | Total Agility Ascension points accumulated |
| [agi\_exp](./bitburner.gangmemberinfo.agi_exp.md) | | number | Current agility experience |
| [agi\_mult](./bitburner.gangmemberinfo.agi_mult.md) | | number | Agility multiplier from equipment |
| [agi](./bitburner.gangmemberinfo.agi.md) | | number | Agility skill level |
| [augmentations](./bitburner.gangmemberinfo.augmentations.md) | | string\[\] | |
| [augmentations](./bitburner.gangmemberinfo.augmentations.md) | | string\[\] | List of all Augmentations currently installed on gang member |
| [cha\_asc\_mult](./bitburner.gangmemberinfo.cha_asc_mult.md) | | number | Charisma multiplier from ascensions |
| [cha\_asc\_points](./bitburner.gangmemberinfo.cha_asc_points.md) | | number | Total earned charisma experience |
| [cha\_asc\_points](./bitburner.gangmemberinfo.cha_asc_points.md) | | number | Total Charisma Ascension points accumulated |
| [cha\_exp](./bitburner.gangmemberinfo.cha_exp.md) | | number | Current charisma experience |
| [cha\_mult](./bitburner.gangmemberinfo.cha_mult.md) | | number | Charisma multiplier from equipment |
| [cha](./bitburner.gangmemberinfo.cha.md) | | number | Charisma skill level |
| [def\_asc\_mult](./bitburner.gangmemberinfo.def_asc_mult.md) | | number | Defense multiplier from ascensions |
| [def\_asc\_points](./bitburner.gangmemberinfo.def_asc_points.md) | | number | Total earned defense experience |
| [def\_asc\_points](./bitburner.gangmemberinfo.def_asc_points.md) | | number | Total Defense Ascension points accumulated |
| [def\_exp](./bitburner.gangmemberinfo.def_exp.md) | | number | Current defense experience |
| [def\_mult](./bitburner.gangmemberinfo.def_mult.md) | | number | Defense multiplier from equipment |
| [def](./bitburner.gangmemberinfo.def.md) | | number | Defense skill level |
| [dex\_asc\_mult](./bitburner.gangmemberinfo.dex_asc_mult.md) | | number | Dexterity multiplier from ascensions |
| [dex\_asc\_points](./bitburner.gangmemberinfo.dex_asc_points.md) | | number | Total earned dexterity experience |
| [dex\_asc\_points](./bitburner.gangmemberinfo.dex_asc_points.md) | | number | Total Dexterity Ascension points accumulated |
| [dex\_exp](./bitburner.gangmemberinfo.dex_exp.md) | | number | Current dexterity experience |
| [dex\_mult](./bitburner.gangmemberinfo.dex_mult.md) | | number | Dexterity multiplier from equipment |
| [dex](./bitburner.gangmemberinfo.dex.md) | | number | Dexterity skill level |
| [earnedRespect](./bitburner.gangmemberinfo.earnedrespect.md) | | number | |
| [earnedRespect](./bitburner.gangmemberinfo.earnedrespect.md) | | number | Amount of Respect earned by member since they last Ascended |
| [hack\_asc\_mult](./bitburner.gangmemberinfo.hack_asc_mult.md) | | number | Hack multiplier from ascensions |
| [hack\_asc\_points](./bitburner.gangmemberinfo.hack_asc_points.md) | | number | Total earned hack experience |
| [hack\_asc\_points](./bitburner.gangmemberinfo.hack_asc_points.md) | | number | Total Hack Ascension points accumulated |
| [hack\_exp](./bitburner.gangmemberinfo.hack_exp.md) | | number | Current hack experience |
| [hack\_mult](./bitburner.gangmemberinfo.hack_mult.md) | | number | Hack multiplier from equipment |
| [hack](./bitburner.gangmemberinfo.hack.md) | | number | Hack skill level |
| [moneyGain](./bitburner.gangmemberinfo.moneygain.md) | | number | |
| [moneyGain](./bitburner.gangmemberinfo.moneygain.md) | | number | Per Cycle Income for this gang member |
| [name](./bitburner.gangmemberinfo.name.md) | | string | Name of the gang member |
| [respectGain](./bitburner.gangmemberinfo.respectgain.md) | | number | |
| [respectGain](./bitburner.gangmemberinfo.respectgain.md) | | number | Per Cycle Rate this member is currently gaining Respect |
| [str\_asc\_mult](./bitburner.gangmemberinfo.str_asc_mult.md) | | number | Strength multiplier from ascensions |
| [str\_asc\_points](./bitburner.gangmemberinfo.str_asc_points.md) | | number | Total earned strength experience |
| [str\_asc\_points](./bitburner.gangmemberinfo.str_asc_points.md) | | number | Total Strength Ascension points accumulated |
| [str\_exp](./bitburner.gangmemberinfo.str_exp.md) | | number | Current strength experience |
| [str\_mult](./bitburner.gangmemberinfo.str_mult.md) | | number | Strength multiplier from equipment |
| [str](./bitburner.gangmemberinfo.str.md) | | number | Strength skill level |
| [task](./bitburner.gangmemberinfo.task.md) | | string | Currently assigned task |
| [upgrades](./bitburner.gangmemberinfo.upgrades.md) | | string\[\] | |
| [wantedLevelGain](./bitburner.gangmemberinfo.wantedlevelgain.md) | | number | |
| [upgrades](./bitburner.gangmemberinfo.upgrades.md) | | string\[\] | List of all non-Augmentation Equipment owned by gang member |
| [wantedLevelGain](./bitburner.gangmemberinfo.wantedlevelgain.md) | | number | Per Cycle Rate by which this member is affecting your gang's Wanted Level |

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.moneyGain property
Per Cycle Income for this gang member
**Signature:**
```typescript

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.respectGain property
Per Cycle Rate this member is currently gaining Respect
**Signature:**
```typescript

View File

@@ -4,7 +4,7 @@
## GangMemberInfo.str\_asc\_points property
Total earned strength experience
Total Strength Ascension points accumulated
**Signature:**

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.upgrades property
List of all non-Augmentation Equipment owned by gang member
**Signature:**
```typescript

View File

@@ -4,6 +4,8 @@
## GangMemberInfo.wantedLevelGain property
Per Cycle Rate by which this member is affecting your gang's Wanted Level
**Signature:**
```typescript

View File

@@ -98,6 +98,7 @@
| [Sleeve](./bitburner.sleeve.md) | Sleeve API |
| [SleevePerson](./bitburner.sleeveperson.md) | |
| [SourceFileLvl](./bitburner.sourcefilelvl.md) | |
| [SpawnOptions](./bitburner.spawnoptions.md) | |
| [Stanek](./bitburner.stanek.md) | Stanek's Gift API. |
| [StockMarketConstants](./bitburner.stockmarketconstants.md) | Constants used for the stockmarket game mechanic. |
| [StockOrder](./bitburner.stockorder.md) | <p>Return value of [getOrders](./bitburner.tix.getorders.md)</p><p>Keys are stock symbols, properties are arrays of [StockOrderObject](./bitburner.stockorderobject.md)</p> |

View File

@@ -9,14 +9,14 @@ Clear data from a port.
**Signature:**
```typescript
clearPort(handle: number): void;
clearPort(portNumber: number): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| handle | number | Port to clear. |
| portNumber | number | Port to clear data from. Must be a positive integer. |
**Returns:**

View File

@@ -31,8 +31,6 @@ RAM cost: 0.1 GB
Returns a boolean indicating whether the specified file exists on the target server. The filename for programs is case insensitive, other file types are case sensitive. For example, fileExists(“brutessh.exe”) will work fine, even though the actual program is named 'BruteSSH.exe'.
\*
## Example

View File

@@ -16,7 +16,7 @@ getPortHandle(portNumber: number): NetscriptPort;
| Parameter | Type | Description |
| --- | --- | --- |
| portNumber | number | Port number. Must be an integer between 1 and 20. |
| portNumber | number | Port number. Must be a positive integer. |
**Returns:**

View File

@@ -21,8 +21,8 @@ getRunningScript(
| Parameter | Type | Description |
| --- | --- | --- |
| filename | [FilenameOrPID](./bitburner.filenameorpid.md) | _(Optional)_ Optional. Filename or PID of the script. |
| hostname | string | _(Optional)_ Optional. Name of host server the script is running on. |
| args | (string \| number \| boolean)\[\] | Arguments to identify the script |
| hostname | string | _(Optional)_ Hostname of target server. Optional, defaults to the server the calling script is running on. |
| args | (string \| number \| boolean)\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
**Returns:**
@@ -34,5 +34,5 @@ The info about the running script if found, and null otherwise.
RAM cost: 0.3 GB
Running with no args returns current script. If you use a PID as the first parameter, the hostname and args parameters are unnecessary.
Running with no args returns current script. If you use a PID as the first parameter, the hostname and args parameters are unnecessary. If hostname is omitted while filename is used as the first parameter, hostname defaults to the server the calling script is running on. Remember that a script is semi-uniquely identified by both its name and its arguments. (You can run multiple copies of scripts with the same arguments, but for the purposes of functions like this that check based on filename, the filename plus arguments forms the key.)

View File

@@ -9,14 +9,14 @@ Get all the logs of a script.
**Signature:**
```typescript
getScriptLogs(fn?: string, host?: string, ...args: (string | number | boolean)[]): string[];
getScriptLogs(fn?: FilenameOrPID, host?: string, ...args: (string | number | boolean)[]): string[];
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| fn | string | _(Optional)_ Optional. Filename of script to get logs from. |
| fn | [FilenameOrPID](./bitburner.filenameorpid.md) | _(Optional)_ Optional. Filename or PID of script to get logs from. |
| host | string | _(Optional)_ Optional. Hostname of the server that the script is on. |
| args | (string \| number \| boolean)\[\] | Arguments to identify which scripts to get logs for. |
@@ -32,7 +32,7 @@ RAM cost: 0 GB
Returns a scripts logs. The logs are returned as an array, where each line is an element in the array. The most recently logged line is at the end of the array. Note that there is a maximum number of lines that a script stores in its logs. This is configurable in the games options. If the function is called with no arguments, it will return the current scripts logs.
Otherwise, the fn, hostname/ip, and args… arguments can be used to get the logs from another script. Remember that scripts are uniquely identified by both their names and arguments.
Otherwise, the PID or filename, hostname/ip, and args… arguments can be used to get logs from another script. Remember that scripts are uniquely identified by both their names and arguments.
## Example

View File

@@ -17,8 +17,8 @@ isRunning(script: FilenameOrPID, host?: string, ...args: (string | number | bool
| Parameter | Type | Description |
| --- | --- | --- |
| script | [FilenameOrPID](./bitburner.filenameorpid.md) | Filename or PID of script to check. This is case-sensitive. |
| host | string | _(Optional)_ Hostname of target server. |
| args | (string \| number \| boolean)\[\] | Arguments to specify/identify which scripts to search for. |
| host | string | _(Optional)_ Hostname of target server. Optional, defaults to the server the calling script is running on. |
| args | (string \| number \| boolean)\[\] | Arguments to specify/identify the script. Optional, when looking for scripts run without arguments. |
**Returns:**
@@ -30,7 +30,7 @@ True if the specified script is running on the target server, and false otherwis
RAM cost: 0.1 GB
Returns a boolean indicating whether the specified script is running on the target server. If you use a PID instead of a filename, the hostname and args parameters are unnecessary. Remember that a script is semi-uniquely identified by both its name and its arguments. (You can run multiple copies of scripts with the same arguments, but for the purposes of functions like this that check based on filename, the filename plus arguments forms the key.)
Returns a boolean indicating whether the specified script is running on the target server. If you use a PID instead of a filename, the hostname and args parameters are unnecessary. If hostname is omitted while filename is used as the first parameter, hostname defaults to the server the calling script is running on. Remember that a script is semi-uniquely identified by both its name and its arguments. (You can run multiple copies of scripts with the same arguments, but for the purposes of functions like this that check based on filename, the filename plus arguments forms the key.)
## Example

View File

@@ -58,7 +58,7 @@ export async function main(ns) {
| [brutessh(host)](./bitburner.ns.brutessh.md) | Runs BruteSSH.exe on a server. |
| [clear(handle)](./bitburner.ns.clear.md) | Clear data from a file. |
| [clearLog()](./bitburner.ns.clearlog.md) | Clears the scripts logs. |
| [clearPort(handle)](./bitburner.ns.clearport.md) | Clear data from a port. |
| [clearPort(portNumber)](./bitburner.ns.clearport.md) | Clear data from a port. |
| [closeTail(pid)](./bitburner.ns.closetail.md) | Close the tail window of a script. |
| [deleteServer(host)](./bitburner.ns.deleteserver.md) | Delete a purchased server. |
| [disableLog(fn)](./bitburner.ns.disablelog.md) | Disables logging for the given function. |
@@ -155,7 +155,7 @@ export async function main(ns) {
| [setTitle(title, pid)](./bitburner.ns.settitle.md) | Set the title of the tail window of a script. |
| [share()](./bitburner.ns.share.md) | Share the server's ram with your factions. |
| [sleep(millis)](./bitburner.ns.sleep.md) | Suspends the script for n milliseconds. |
| [spawn(script, threadOrOptions, args)](./bitburner.ns.spawn.md) | Terminate current script and start another in 10 seconds. |
| [spawn(script, threadOrOptions, args)](./bitburner.ns.spawn.md) | Terminate current script and start another in a defined number of milliseconds. |
| [sprintf(format, args)](./bitburner.ns.sprintf.md) | Format a string. |
| [sqlinject(host)](./bitburner.ns.sqlinject.md) | Runs SQLInject.exe on a server. |
| [tail(fn, host, args)](./bitburner.ns.tail.md) | Open the tail window of a script. |

View File

@@ -16,7 +16,7 @@ peek(portNumber: number): PortData;
| Parameter | Type | Description |
| --- | --- | --- |
| portNumber | number | Port to peek. Must be an integer between 1 and 20. |
| portNumber | number | Port to peek. Must be a positive integer. |
**Returns:**

View File

@@ -16,7 +16,7 @@ readPort(portNumber: number): PortData;
| Parameter | Type | Description |
| --- | --- | --- |
| portNumber | number | |
| portNumber | number | Port to read from. Must be a positive integer. |
**Returns:**

View File

@@ -4,12 +4,12 @@
## NS.spawn() method
Terminate current script and start another in 10 seconds.
Terminate current script and start another in a defined number of milliseconds.
**Signature:**
```typescript
spawn(script: string, threadOrOptions?: number | RunOptions, ...args: (string | number | boolean)[]): void;
spawn(script: string, threadOrOptions?: number | SpawnOptions, ...args: (string | number | boolean)[]): void;
```
## Parameters
@@ -17,7 +17,7 @@ spawn(script: string, threadOrOptions?: number | RunOptions, ...args: (string |
| Parameter | Type | Description |
| --- | --- | --- |
| script | string | Filename of script to execute. |
| threadOrOptions | number \| [RunOptions](./bitburner.runoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [RunOptions](./bitburner.runoptions.md) object. Threads defaults to 1. |
| threadOrOptions | number \| [SpawnOptions](./bitburner.spawnoptions.md) | _(Optional)_ Either an integer number of threads for new script, or a [SpawnOptions](./bitburner.spawnoptions.md) object. Threads defaults to 1. |
| args | (string \| number \| boolean)\[\] | Additional arguments to pass into the new script that is being run. |
**Returns:**
@@ -28,7 +28,7 @@ void
RAM cost: 2 GB
Terminates the current script, and then after a delay of about 10 seconds it will execute the newly-specified script. The purpose of this function is to execute a new script without being constrained by the RAM usage of the current one. This function can only be used to run scripts on the local server.
Terminates the current script, and then after a defined delay it will execute the newly-specified script. The purpose of this function is to execute a new script without being constrained by the RAM usage of the current one. This function can only be used to run scripts on the local server.
Because this function immediately terminates the script, it does not have a return value.
@@ -38,7 +38,7 @@ Running this function with 0 or fewer threads will cause a runtime error.
```js
//The following example will execute the script foo.js with 10 threads and the arguments foodnstuff and 90:
ns.spawn('foo.js', 10, 'foodnstuff', 90);
//The following example will execute the script foo.js with 10 threads, in 500 milliseconds and the arguments foodnstuff and 90:
ns.spawn('foo.js', 10, 500, 'foodnstuff', 90);
```

View File

@@ -32,7 +32,7 @@ Opens a scripts logs. This is functionally the same as the tail Terminal comm
If the function is called with no arguments, it will open the current scripts logs.
Otherwise, the fn, hostname/ip, and args… arguments can be used to get the logs from another script. Remember that scripts are uniquely identified by both their names and arguments.
Otherwise, the PID or filename, hostname/ip, and args… arguments can be used to get the logs from another script. Remember that scripts are uniquely identified by both their names and arguments.
## Example

View File

@@ -16,7 +16,7 @@ tryWritePort(portNumber: number, data: string | number): boolean;
| Parameter | Type | Description |
| --- | --- | --- |
| portNumber | number | Port or text file that will be written to. |
| portNumber | number | Port to attempt to write to to. Must be a positive integer. |
| data | string \| number | Data to write. |
**Returns:**

View File

@@ -16,7 +16,7 @@ writePort(portNumber: number, data: string | number): PortData | null;
| Parameter | Type | Description |
| --- | --- | --- |
| portNumber | number | |
| portNumber | number | Port to write to. Must be a positive integer. |
| data | string \| number | |
**Returns:**

View File

@@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [SpawnOptions](./bitburner.spawnoptions.md)
## SpawnOptions interface
**Signature:**
```typescript
interface SpawnOptions extends RunOptions
```
**Extends:** [RunOptions](./bitburner.runoptions.md)
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [spawnDelay?](./bitburner.spawnoptions.spawndelay.md) | | number | _(Optional)_ Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a positive integer. |

View File

@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [SpawnOptions](./bitburner.spawnoptions.md) &gt; [spawnDelay](./bitburner.spawnoptions.spawndelay.md)
## SpawnOptions.spawnDelay property
Number of milliseconds to delay before spawning script, defaults to 10000 (10s). Must be a positive integer.
**Signature:**
```typescript
spawnDelay?: number;
```

View File

@@ -28,7 +28,7 @@ sellProduct(
| productName | string | Name of the product |
| amt | string | Amount to sell, can be "MAX" |
| price | string | Price to sell, can be "MP" |
| all | boolean | Sell in all city |
| all | boolean | Set sell amount and price in all cities |
**Returns:**

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "2.4.1",
"version": "2.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "2.4.1",
"version": "2.5.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
@@ -6233,9 +6233,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001473",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==",
"version": "1.0.30001539",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
"integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
"funding": [
{
"type": "opencollective",
@@ -22616,9 +22616,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001473",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz",
"integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg=="
"version": "1.0.30001539",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
"integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA=="
},
"ccount": {
"version": "2.0.1",

View File

@@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "2.4.1",
"version": "2.5.0",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie, Olivier Gagnon, et al."
@@ -111,7 +111,7 @@
"doc": "bash ./tools/doc.sh",
"format": "prettier -c --write .",
"format:report": "prettier -c .",
"start": "cd .app && http-server -p 8000",
"start": "electron .app/index.html",
"start:dev": "webpack serve --progress --env devServer --mode development",
"build": "bash ./tools/build.sh production",
"build:dev": "bash ./tools/build.sh development",

View File

@@ -322,7 +322,7 @@ export const achievements: Record<string, Achievement> = {
WORKOUT: {
...achievementData.WORKOUT,
Icon: "WORKOUT",
Condition: () => isClassWork(Player.currentWork),
Condition: () => isClassWork(Player.currentWork) && Player.currentWork.isGym(),
},
TOR: {
...achievementData.TOR,

View File

@@ -65,39 +65,51 @@ export function removeAlias(name: string): boolean {
/**
* Returns the original string with any aliases substituted in.
* Aliases are only applied to "whole words", one level deep
* @param origCommand the original command string
*/
export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" ");
if (commandArray.length > 0) {
// For the alias and unalias commands, don't substitute
if (commandArray[0] === "unalias" || commandArray[0] === "alias") {
return commandArray.join(" ");
}
let somethingSubstituted = true;
let depth = 0;
let lastAlias;
while (somethingSubstituted && depth < 10) {
depth++;
somethingSubstituted = false;
const alias = Aliases.get(commandArray[0])?.split(" ");
if (alias !== undefined) {
somethingSubstituted = true;
commandArray.splice(0, 1, ...alias);
//commandArray[0] = alias;
}
for (let i = 0; i < commandArray.length; ++i) {
const alias = GlobalAliases.get(commandArray[i])?.split(" ");
if (alias !== undefined && (commandArray[i] != lastAlias || somethingSubstituted)) {
somethingSubstituted = true;
lastAlias = commandArray[i];
commandArray.splice(i, 1, ...alias);
i += alias.length - 1;
//commandArray[i] = alias;
}
}
}
}
return commandArray.join(" ");
return applyAliases(origCommand);
}
/**
* Recursively evaluates aliases and applies them to the command string,
* unless there are any reference loops or the reference chain is too deep
* @param origCommand the original command string
* @param depth the current recursion depth
* @param currentlyProcessingAliases any aliases that have been applied in the recursive evaluation leading to this point
* @return { string } the provided command with all of its referenced aliases evaluated
*/
function applyAliases(origCommand: string, depth = 0, currentlyProcessingAliases: string[] = []) {
if (!origCommand) {
return origCommand;
}
const commandArray = origCommand.split(" ");
// Do not apply aliases when defining a new alias
if (commandArray[0] === "unalias" || commandArray[0] === "alias") {
return commandArray.join(" ");
}
// First get non-global aliases, and recursively apply them
// (unless there are any reference loops or the reference chain is too deep)
const localAlias = Aliases.get(commandArray[0]);
if (localAlias && !currentlyProcessingAliases.includes(localAlias)) {
const appliedAlias = applyAliases(localAlias, depth + 1, [commandArray[0], ...currentlyProcessingAliases]);
commandArray.splice(0, 1, ...appliedAlias.split(" "));
}
// Once local aliasing is complete (or if none are present) handle any global aliases
const processedCommands = commandArray.reduce((resolvedCommandArray: string[], command) => {
const globalAlias = GlobalAliases.get(command);
if (globalAlias && !currentlyProcessingAliases.includes(globalAlias)) {
const appliedAlias = applyAliases(globalAlias, depth + 1, [command, ...currentlyProcessingAliases]);
resolvedCommandArray.push(appliedAlias);
} else {
// If there is no alias, or if the alias has a circular reference, leave the command as-is
resolvedCommandArray.push(command);
}
return resolvedCommandArray;
}, []);
return processedCommands.join(" ");
}

View File

@@ -154,6 +154,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<br />- skill / experience
<br />- every server except home
<br />- factions and reputation
<br />- current work activity
<br />
<br />
You will keep:
@@ -162,8 +163,7 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
<br />- home ram and cores
<br />
<br />
It is recommended to install several Augmentations at once. Preferably everything from any faction of
your choosing.
It is recommended to install several Augmentations at once.
</>
}
/>

View File

@@ -73,16 +73,16 @@ export function initBitNodes() {
<br />
Certain Factions ({FactionName.SlumSnakes}, {FactionName.Tetrads}, {FactionName.TheSyndicate},{" "}
{FactionName.TheDarkArmy}, {FactionName.SpeakersForTheDead}, {FactionName.NiteSec}, {FactionName.TheBlackHand}
) give the player the ability to form and manage their own gangs. These gangs will earn the player money and
reputation with the corresponding Faction
) give the player the ability to form and manage their own gang, which can earn the player money and reputation
with the corresponding Faction. Gangs offer more Augmentations than Factions, and in BitNode-2 offer a way to
destroy the BitNode.
<br />
Every Augmentation in the game will be available through the Factions listed above
<br />
<br />
Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma
decreases to a certain value. It also increases the player's crime success rate, crime money, and charisma
multipliers by:
decreases to a certain value. It also increases your crime success rate, crime money, and charisma multipliers
by:
<br />
<br />
Level 1: 24%

View File

@@ -157,7 +157,7 @@ export class BitNodeMultipliers {
/** Influences profits from corporation dividends and selling shares. */
CorporationSoftcap = 1;
/** Influences number of divisions a corporation can have. */
/** Influences the amount of divisions a corporation can have have at the same time*/
CorporationDivisions = 1;
constructor(a: PartialRecord<keyof BitNodeMultipliers, number> = {}) {

View File

@@ -223,7 +223,8 @@ export class Action {
let low = real - diff;
let high = real + diff;
const city = inst.getCurrentCity();
const r = city.pop / city.popEst;
let r = city.pop / city.popEst;
if (Number.isNaN(r)) r = 0;
if (r < 1) low *= r;
else high *= r;
return [clamp(low), clamp(high)];

View File

@@ -83,9 +83,9 @@ export const CONSTANTS: {
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string;
} = {
VersionString: "2.4.1",
VersionString: "2.5.0",
isDevBranch: false,
VersionNumber: 34,
VersionNumber: 35,
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@@ -219,44 +219,57 @@ export const CONSTANTS: {
// Also update doc/source/changelog.rst
LatestUpdate: `
## v2.4.1 Update (8/26/23)
## v2.5.0 (10/2/2023)
### FEATURE ADDITIONS:
### NOTES
(Corporation) Bugfixes in Corporation may cause a large balance shift for this mechanic.
- Added "Enable terminal history search with arrow keys" option in Misc options category, inspired by similar functionality in shells like zsh. (@ficocelliguy)
### API CHANGES:
- ns.print and ns.tprint now handle printing Set and Map objects directly (@ficocelliguy)
- ns.spawn can now use a configurable delay instead of always 10 seconds (@muesli4brekkies)
- (Corporation) Added CorporationDivisions property to BitNodeMultipliers interface (@Caldwell-74)
- (Corporation) Added makesMaterials and makesProducts properties to CorpIndustryData interface (@Caldwell-74)
- (Corporation) Added issueNewSharesCooldown property to the CorporationInfo interface (@Caldwell-74)
- (Corporation) Significantly lowered ram cost of all corporation functions (@jjclark1982)
- (Gang) Added ns.gang.getRecruitsAvailable: Gets the number of additional gang members that can currently be recruited (@myCatsName)
- (Gang) Added ns.gang.respectForNextRecruit: Gets the respect threshold for recruiting the next gang member (@myCatsName)
- (Gang) Added ns.gang.renameMember: Renames a gang member (@myCatsName)
### BUGFIX:
### BUGFIX
- Taking a university class no longer gives the player an achievement for working out in a gym (@myCatsName)
- Bash keybind ctrl-C clears an ongoing terminal history search (@ncharris93)
- (Corporation): Fix bug in valuation calculation (@yichizhng)
- (Corporation): Fix bug in share price calculation (@jjclark1982)
- (Corporation) "Same sell amount in all cities" works with Products again (@Caldwell-74)
- (Hashnet) Buying multiple company favor upgrades at the same time will actually apply them all instead of just one (@aschmider)
- Fixed a bug where buying NeuroFlux Governor would buy one less level than expected (@zerbosh)
- Fixed an issue that could cause the Coding Contract UI to become unreachable (@myCatsName)
- Infiltration: Knowledge of Apollo aug no longer highlights the incorrect wires (@Snarling)
### CODEBASE / DOCS / MISC:
- Added a new theme "zerenity" (@Zelow79)
- Reorganize game constants (@zerbosh)
- Reorganize ingame documentation folder structure, simplify documentation bundling (@Snarling)
- IP Address coding contract accepts single-quoted entries (@myCatsName)
- Updated an outdated message on ns.killall logs (@myCatsName)
- Updated documentation for ns.share and ns.getSharePower (@myCatsName)
- Removed functions (like ns.getServerRam) are no longer shown when enumerating ns entries. (@Snarling)
- Removed more references to ReadTheDocs that remained after 2.4.0 (@hydroflame)
- Fixed some typos/spacing (@myCatsName)
- Fixed an issue with incorrect React keys in active scripts page (@zornlemma)
### API CHANGES (NON-SPOILER)
- Added ns.stock.getConstants (@Snarling)
- Added ownedAugs and ownedSF properties to return data of ns.getResetInfo (@Snarling)
### API CHANGES (SPOILERS):
- Added ns.singularity.getAugmentationFactions to provide a list of factions that have a given augmentation (@myCatsName)
- ns.corporation.getConstants now has a ram cost of 0 (@Snarling)
### OTHER CHANGES (SPOILERS):
- Successes for next level is now accurate in the UI for Bladeburner operations (@myCatsName)
- ns.sleeve.setToFactionWork no longer allows working for factions the player has not joined (@Snarling)
### OTHER CHANGES
- MISC: Improved handling of aliases in the Terminal (@ficocelliguy)
- MISC: Improved error messages for ns.getPurchasedServer (@ficocelliguy)
- MISC: ns.sleep and ns.asleep now show a formatted time in the script log. (@ficocelliguy)
- MISC: Fix an exploit that allowed over 100% utilization of a server's ram (@d0sboots)
- MISC: (Bladeburner / Sleeve) Bladeburner training action is available for sleeves (@Zelow79)
- MISC: (Gang) Renamed the Territory Warfare mechanic (now referred to as Territory Clashes) to deconflict with the Territory Warfare gang member task (@ficocelliguy)
- UI: Infiltration now hides tail windows instead of temporarily removing them from the page. This means position/size will remain as they were before the infiltration, and any React content will remain active instead of being unmounted/remounted (@ficocelliguy)
- UI: Faction augmentation page updates more reliably (@zerbosh)
- UI: Added a text filter on the Faction Augmentations page (@ficocelliguy)
- UI: Improved pagination of Active Scripts page (@Ookamiko, @ficocelliguy)
- UI: Icarus message no longer shows repeatedly for players that are in the endgame (@ficocelliguy)
- UI: Remove work completion dialogs when performing an augmentation install (@ficocelliguy)
- UI: Improve soft reset dialog, and always show dialog when soft resetting (@myCatsName)
- UI: While closing, modals no longer update displayed info and become inert (@Snarling)
- UI: (Bladeburner) Fix a possible NaN display value in Bladeburner (@zerbosh)
- UI: (Corporation) Multiple UI improvements for Corporation (@jjclark1982)
- UI: (Corporation) Tweaked some number formatting to look better in Corp and Stats page (@zerbosh)
- UI: (Corporation) Market TA no longer has its own dialog box, it's set in the normal sell dialog (@Caldwell-74)
- UI: (Corporation) Fix an incorrect value in the party dialog box (@aschmider)
- UI: (Corporation) Improved the descriptions for Corporation states (@Caldwell-74)
- UI: (Gang) Various UI improvements for Gang (@myCatsName)
- DOCS: Improve documentation for ports (@muesli4brekkies)
- DOCS: Updated documentation for ns.tail and ns.getScriptLogs to make it clear a PID can be used (@myCatsName)
- DOCS: Improve documentation for FilenameOrPID functions (@VictorS)
- DOCS: Improved various existing ingame documentation pages (@myCatsName)
- DOCS: (Bladeburner / Gang) Added initial ingame documentation for Bladeburner and Gang (@myCatsName)
- DOCS: (Bladeburner / Gang) Improve API documentation for Bladeburner and Gang functions (@myCatsName)
`,
};

View File

@@ -1,5 +1,3 @@
import { isInteger } from "lodash";
import { Player } from "@player";
import { CorpResearchName, CorpSmartSupplyOption } from "@nsdefs";
@@ -18,6 +16,7 @@ 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";
export function NewDivision(corporation: Corporation, industry: IndustryType, name: string): void {
if (corporation.divisions.size >= corporation.maxDivisions)
@@ -70,7 +69,7 @@ export function purchaseOffice(corporation: Corporation, division: Division, cit
if (division.offices[city]) {
throw new Error(`You have already expanded into ${city} for ${division.name}`);
}
corporation.funds = corporation.funds - corpConstants.officeInitialCost;
corporation.addNonIncomeFunds(-corpConstants.officeInitialCost);
division.offices[city] = new OfficeSpace({
city: city,
size: corpConstants.officeInitialSize,
@@ -85,33 +84,72 @@ export function IssueDividends(corporation: Corporation, rate: number): void {
corporation.dividendRate = rate;
}
export function IssueNewShares(corporation: Corporation, amount: number): [number, number, number] {
const max = corporation.calculateMaxNewShares();
export function GoPublic(corporation: Corporation, numShares: number): void {
const ceoOwnership = (corporation.numShares - numShares) / corporation.totalShares;
const initialSharePrice = corporation.getTargetSharePrice(ceoOwnership);
// Round to nearest ten-millionth
amount = Math.round(amount / 10e6) * 10e6;
if (isNaN(amount) || amount < 10e6 || amount > max) {
throw new Error(`Invalid value. Must be an number between 10m and ${max} (20% of total shares)`);
if (isNaN(numShares) || numShares < 0) {
throw new Error("Invalid value for number of issued shares");
}
if (numShares > corporation.numShares) {
throw new Error("You don't have that many shares to issue!");
}
corporation.public = true;
corporation.sharePrice = initialSharePrice;
corporation.issuedShares += numShares;
corporation.numShares -= numShares;
corporation.addNonIncomeFunds(numShares * initialSharePrice);
}
const newSharePrice = Math.round(corporation.sharePrice * 0.8);
export function IssueNewShares(
corporation: Corporation,
amount: number,
): [profit: number, amount: number, privateShares: number] {
const failureReason = issueNewSharesFailureReason(corporation, amount);
if (failureReason) throw new Error(failureReason);
const profit = amount * newSharePrice;
corporation.issueNewSharesCooldown = corpConstants.issueNewSharesCooldown;
const ceoOwnership = corporation.numShares / (corporation.totalShares + amount);
const newSharePrice = corporation.getTargetSharePrice(ceoOwnership);
const privateOwnedRatio = 1 - (corporation.numShares + corporation.issuedShares) / corporation.totalShares;
const profit = (amount * (corporation.sharePrice + newSharePrice)) / 2;
const cooldownMultiplier = corporation.totalShares / corpConstants.initialShares;
corporation.issueNewSharesCooldown = corpConstants.issueNewSharesCooldown * cooldownMultiplier;
const privateOwnedRatio = corporation.investorShares / corporation.totalShares;
const maxPrivateShares = Math.round((amount / 2) * privateOwnedRatio);
const privateShares = Math.round(getRandomInt(0, maxPrivateShares) / 10e6) * 10e6;
corporation.issuedShares += amount - privateShares;
corporation.investorShares += privateShares;
corporation.totalShares += amount;
corporation.funds = corporation.funds + profit;
corporation.immediatelyUpdateSharePrice();
corporation.addNonIncomeFunds(profit);
// Set sharePrice directly because all formulas will be based on stale cycleValuation data
corporation.sharePrice = newSharePrice;
return [profit, amount, privateShares];
}
export function AcceptInvestmentOffer(corporation: Corporation): void {
if (
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
corporation.public
) {
throw new Error("No more investment offers are available.");
}
const val = corporation.valuation;
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
const funding = val * percShares * roundMultiplier;
const investShares = Math.floor(corpConstants.initialShares * percShares);
corporation.fundingRound++;
corporation.addNonIncomeFunds(funding);
corporation.numShares -= investShares;
corporation.investorShares += investShares;
}
export function SellMaterial(material: Material, amount: string, price: string): void {
if (price === "") price = "0";
if (amount === "") amount = "0";
@@ -166,6 +204,8 @@ export function SellMaterial(material: Material, amount: string, price: string):
export function SellProduct(product: Product, city: CityName, amt: string, price: string, all: boolean): void {
//Parse price
// initliaze newPrice with oldPrice as default
let newPrice = product.cityData[city].desiredSellPrice;
if (price.includes("MP")) {
//Dynamically evaluated quantity. First test to make sure its valid
//Sanitize input, then replace dynamic variables with arbitrary numbers
@@ -181,17 +221,19 @@ export function SellProduct(product: Product, city: CityName, amt: string, price
if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell price field.");
}
product.cityData[city].desiredSellPrice = price; //Use sanitized price
newPrice = price; //Use sanitized price
} else {
const cost = parseFloat(price);
if (isNaN(cost)) {
throw new Error("Invalid value for sell price field");
}
product.cityData[city].desiredSellPrice = cost;
newPrice = cost;
}
// Parse quantity
amt = amt.toUpperCase();
//initialize newAmount with old as default
let newAmount = product.cityData[city].desiredSellAmount;
if (amt.includes("MAX") || amt.includes("PROD") || amt.includes("INV")) {
//Dynamically evaluated quantity. First test to make sure its valid
let qty = amt.replace(/\s+/g, "");
@@ -208,14 +250,7 @@ export function SellProduct(product: Product, city: CityName, amt: string, price
if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell quantity field");
}
if (all) {
for (const cityName of Object.values(CityName)) {
product.cityData[cityName].desiredSellAmount = qty; //Use sanitized input
}
} else {
product.cityData[city].desiredSellAmount = qty; //Use sanitized input
}
newAmount = qty; //Use sanitized input
} else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) {
throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'");
} else {
@@ -223,21 +258,17 @@ export function SellProduct(product: Product, city: CityName, amt: string, price
if (isNaN(qty)) {
qty = 0;
}
if (qty === 0) {
if (all) {
for (const cityName of Object.values(CityName)) {
product.cityData[cityName].desiredSellAmount = 0;
}
} else {
product.cityData[city].desiredSellAmount = 0;
}
} else if (all) {
for (const cityName of Object.values(CityName)) {
product.cityData[cityName].desiredSellAmount = qty;
}
} else {
product.cityData[city].desiredSellAmount = qty;
newAmount = qty;
}
//apply new price and amount to all or just current
if (all) {
for (const cityName of Object.values(CityName)) {
product.cityData[cityName].desiredSellAmount = newAmount;
product.cityData[cityName].desiredSellPrice = newPrice;
}
} else {
product.cityData[city].desiredSellAmount = newAmount;
product.cityData[city].desiredSellPrice = newPrice;
}
}
@@ -287,17 +318,10 @@ export function BulkPurchase(
}
export function SellShares(corporation: Corporation, numShares: number): number {
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
if (numShares <= 0) throw new Error("Invalid value for number of shares");
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!");
if (numShares === corporation.numShares) throw new Error("You cant't sell all your shares!");
if (numShares > 1e14) throw new Error("Invalid value for number of shares");
if (!corporation.public) throw new Error("You haven't gone public!");
if (corporation.shareSaleCooldown) throw new Error("Share sale on cooldown!");
const stockSaleResults = corporation.calculateShareSale(numShares);
const profit = stockSaleResults[0];
const newSharePrice = stockSaleResults[1];
const newSharesUntilUpdate = stockSaleResults[2];
const failureReason = sellSharesFailureReason(corporation, numShares);
if (failureReason) throw new Error(failureReason);
const [profit, newSharePrice, newSharesUntilUpdate] = corporation.calculateShareSale(numShares);
corporation.numShares -= numShares;
corporation.issuedShares += numShares;
@@ -309,15 +333,16 @@ export function SellShares(corporation: Corporation, numShares: number): number
}
export function BuyBackShares(corporation: Corporation, numShares: number): boolean {
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
if (numShares <= 0) throw new Error("Invalid value for number of shares");
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
if (!corporation.public) throw new Error("You haven't gone public!");
const buybackPrice = corporation.sharePrice * 1.1;
if (Player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
const failureReason = buybackSharesFailureReason(corporation, numShares);
if (failureReason) throw new Error(failureReason);
const [cost, newSharePrice, newSharesUntilUpdate] = corporation.calculateShareBuyback(numShares);
corporation.numShares += numShares;
corporation.issuedShares -= numShares;
Player.loseMoney(numShares * buybackPrice, "corporation");
corporation.sharePrice = newSharePrice;
corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
Player.loseMoney(cost, "corporation");
return true;
}
@@ -332,7 +357,7 @@ export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size:
const cost = corpConstants.officeInitialCost * mult;
if (corp.funds < cost) return;
office.size += size;
corp.funds = corp.funds - cost;
corp.addNonIncomeFunds(-cost);
}
export function BuyTea(corp: Corporation, office: OfficeSpace): boolean {
@@ -360,7 +385,7 @@ export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmploy
export function purchaseWarehouse(corp: Corporation, division: Division, city: CityName): void {
if (corp.funds < corpConstants.warehouseInitialCost) return;
if (division.warehouses[city]) return;
corp.funds = corp.funds - corpConstants.warehouseInitialCost;
corp.addNonIncomeFunds(-corpConstants.warehouseInitialCost);
division.warehouses[city] = new Warehouse({
division: division,
loc: city,
@@ -380,7 +405,7 @@ export function UpgradeWarehouse(corp: Corporation, division: Division, warehous
if (corp.funds < sizeUpgradeCost) return;
warehouse.level += amt;
warehouse.updateSize(corp, division);
corp.funds = corp.funds - sizeUpgradeCost;
corp.addNonIncomeFunds(-sizeUpgradeCost);
}
export function HireAdVert(corp: Corporation, division: Division): void {

View File

@@ -4,6 +4,7 @@ import { CorporationState } from "./CorporationState";
import { CorpUnlocks } from "./data/CorporationUnlocks";
import { CorpUpgrades } from "./data/CorporationUpgrades";
import * as corpConstants from "./data/Constants";
import { IndustriesData } from "./data/IndustryData";
import { Division } from "./Division";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
@@ -11,7 +12,7 @@ import { showLiterature } from "../Literature/LiteratureHelpers";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { CorpStateName } from "@nsdefs";
import { CorpStateName, InvestmentOffer } from "@nsdefs";
import { calculateUpgradeCost } from "./helpers";
import { JSONMap, JSONSet } from "../Types/Jsonable";
import { formatMoney } from "../ui/formatNumber";
@@ -45,6 +46,7 @@ export class Corporation {
issueNewSharesCooldown = 0; // Game cycles until player can issue shares again
dividendRate = 0;
dividendTax = 1 - currentNodeMults.CorporationSoftcap + 0.15;
investorShares = 0;
issuedShares = 0;
sharePrice = 0;
storedCycles = 0;
@@ -56,6 +58,8 @@ export class Corporation {
value: name === CorpUpgradeName.DreamSense ? 0 : 1,
}));
previousTotalAssets = 150e9;
totalAssets = 150e9;
cycleValuation = 0;
valuationsList = [0];
valuation = 0;
@@ -77,6 +81,17 @@ export class Corporation {
this.funds += amt;
}
// Add or subtract funds which should not be counted for valuation; e.g. investments,
// upgrades, stock issuance
addNonIncomeFunds(amt: number): void {
if (!isFinite(amt)) {
console.error("Trying to add invalid amount of funds. Report to a developer.");
return;
}
this.totalAssets += amt;
this.funds += amt;
}
getState(): CorpStateName {
return this.state.getState();
}
@@ -126,8 +141,6 @@ export class Corporation {
this.expenses = this.expenses + ind.lastCycleExpenses;
});
const profit = this.revenue - this.expenses;
this.cycleValuation = this.determineCycleValuation();
this.determineValuation();
const cycleProfit = profit * (marketCycles * corpConstants.secondsPerMarketCycle);
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate(
@@ -137,7 +150,6 @@ export class Corporation {
);
this.funds = 150e9;
}
if (this.dividendRate > 0 && cycleProfit > 0) {
// Validate input again, just to be safe
if (isNaN(this.dividendRate) || this.dividendRate < 0 || this.dividendRate > corpConstants.dividendMaxRate) {
@@ -151,7 +163,9 @@ export class Corporation {
} else {
this.addFunds(cycleProfit);
}
this.updateTotalAssets();
this.cycleValuation = this.determineCycleValuation();
this.determineValuation();
this.updateSharePrice();
}
@@ -170,24 +184,27 @@ export class Corporation {
determineCycleValuation(): number {
let val,
profit = this.revenue - this.expenses;
assetDelta = (this.totalAssets - this.previousTotalAssets) / corpConstants.secondsPerMarketCycle;
// Handle pre-totalAssets saves
assetDelta ??= this.revenue - this.expenses;
if (this.public) {
// Account for dividends
if (this.dividendRate > 0) {
profit *= 1 - this.dividendRate;
assetDelta *= 1 - this.dividendRate;
}
val = this.funds + profit * 85e3;
val = this.funds + assetDelta * 85e3;
val *= Math.pow(1.1, this.divisions.size);
val = Math.max(val, 0);
} else {
val = 10e9 + Math.max(this.funds, 0) / 3; //Base valuation
if (profit > 0) {
val += profit * 315e3;
val = 10e9 + this.funds / 3;
if (assetDelta > 0) {
val += assetDelta * 315e3;
}
val *= Math.pow(1.1, this.divisions.size);
val -= val % 1e6; //Round down to nearest millionth
}
if (val < 10e9) val = 10e9; // Base valuation
return val * currentNodeMults.CorporationValuation;
}
@@ -195,14 +212,38 @@ export class Corporation {
this.valuationsList.push(this.cycleValuation); //Add current valuation to the list
if (this.valuationsList.length > corpConstants.valuationLength) this.valuationsList.shift();
let val = this.valuationsList.reduce((a, b) => a + b); //Calculate valuations sum
val /= corpConstants.valuationLength; //Calculate the average
val /= this.valuationsList.length; //Calculate the average
this.valuation = val;
}
getTargetSharePrice(): number {
// Note: totalShares - numShares is not the same as issuedShares because
// issuedShares does not account for private investors
return this.valuation / (2 * (this.totalShares - this.numShares) + 1);
updateTotalAssets(): void {
let assets = this.funds;
this.divisions.forEach((ind) => {
assets += IndustriesData[ind.type].startingCost;
for (const warehouse of getRecordValues(ind.warehouses)) {
for (const mat of getRecordValues(warehouse.materials)) {
assets += mat.stored * mat.averagePrice;
}
for (const prod of ind.products.values()) {
assets += prod.cityData[warehouse.city].stored * prod.productionCost;
}
}
});
this.previousTotalAssets = this.totalAssets;
this.totalAssets = assets;
}
getTargetSharePrice(ceoOwnership: number | null = null): number {
// Share price is proportional to total corporation valuation.
// When the CEO owns 0% of the company, market cap is 0.5x valuation.
// When the CEO owns 25% of the company, market cap is 1.0x valuation.
// When the CEO owns 100% of shares, market cap is 1.5x valuation.
if (ceoOwnership === null) {
ceoOwnership = this.numShares / this.totalShares;
}
const ceoConfidence = 0.5 + Math.sqrt(Math.max(0, ceoOwnership));
const marketCap = this.valuation * ceoConfidence;
return marketCap / this.totalShares;
}
updateSharePrice(): void {
@@ -217,10 +258,6 @@ export class Corporation {
}
}
immediatelyUpdateSharePrice(): void {
this.sharePrice = this.getTargetSharePrice();
}
calculateMaxNewShares(): number {
const maxNewSharesUnrounded = Math.round(this.totalShares * 0.2);
const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 10e6);
@@ -228,17 +265,18 @@ export class Corporation {
}
// Calculates how much money will be made and what the resulting stock price
// will be when the player sells his/her shares
// will be when the player sells their shares
// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]
calculateShareSale(numShares: number): [number, number, number] {
let sharesTracker = numShares;
calculateShareSale(numShares: number): [profit: number, sharePrice: number, sharesUntilUpdate: number] {
let sharesRemaining = numShares;
let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;
let sharePrice = this.sharePrice;
let sharesSold = 0;
let profit = 0;
let targetPrice = this.getTargetSharePrice();
const maxIterations = Math.ceil(numShares / corpConstants.sharesPerPriceUpdate);
const sharesPerStep = Math.sign(numShares || 1) * corpConstants.sharesPerPriceUpdate;
const maxIterations = Math.ceil(numShares / sharesPerStep);
if (isNaN(maxIterations) || maxIterations > 10e6) {
console.error(
`Something went wrong or unexpected when calculating share sale. Max iterations calculated to be ${maxIterations}`,
@@ -247,28 +285,59 @@ export class Corporation {
}
for (let i = 0; i < maxIterations; ++i) {
if (sharesTracker < sharesUntilUpdate) {
profit += sharePrice * sharesTracker;
sharesUntilUpdate -= sharesTracker;
if (Math.abs(sharesRemaining) < Math.abs(sharesUntilUpdate)) {
profit += sharePrice * sharesRemaining;
sharesUntilUpdate -= sharesRemaining;
break;
} else {
profit += sharePrice * sharesUntilUpdate;
sharesUntilUpdate = corpConstants.sharesPerPriceUpdate;
sharesTracker -= sharesUntilUpdate;
sharesSold += sharesUntilUpdate;
targetPrice = this.valuation / (2 * (this.totalShares + sharesSold - this.numShares));
// Calculate what new share price would be
profit += sharePrice * sharesPerStep;
sharesRemaining -= sharesPerStep;
sharesSold += sharesPerStep;
// Update the share price
const ceoOwnership = (this.numShares - sharesSold) / this.totalShares;
const targetPrice = this.getTargetSharePrice(ceoOwnership);
if (sharePrice <= targetPrice) {
sharePrice *= 1 + 0.5 * 0.01;
} else {
sharePrice *= 1 - 0.5 * 0.01;
}
sharesUntilUpdate = corpConstants.sharesPerPriceUpdate;
}
}
return [profit, sharePrice, sharesUntilUpdate];
}
calculateShareBuyback(numShares: number): [cost: number, sharePrice: number, sharesUntilUpdate: number] {
const [profit, sharePrice, sharesUntilUpdate] = this.calculateShareSale(-numShares);
const cost = -1.1 * profit;
return [cost, sharePrice, sharesUntilUpdate];
}
getInvestmentOffer(): InvestmentOffer {
if (
this.fundingRound >= corpConstants.fundingRoundShares.length ||
this.fundingRound >= corpConstants.fundingRoundMultiplier.length ||
this.public
)
return {
funds: 0,
shares: 0,
round: this.fundingRound + 1, // Make more readable
}; // Don't throw an error here, no reason to have a second function to check if you can get investment.
const val = this.valuation;
const percShares = corpConstants.fundingRoundShares[this.fundingRound];
const roundMultiplier = corpConstants.fundingRoundMultiplier[this.fundingRound];
const funding = val * percShares * roundMultiplier;
const investShares = Math.floor(corpConstants.initialShares * percShares);
return {
funds: funding,
shares: investShares,
round: this.fundingRound + 1, // Make more readable
};
}
convertCooldownToString(cd: number): string {
// The cooldown value is based on game cycles. Convert to a simple string
const seconds = cd / 5;
@@ -291,7 +360,7 @@ export class Corporation {
if (this.unlocks.has(unlockName)) return `The corporation has already unlocked ${unlockName}`;
const price = CorpUnlocks[unlockName].price;
if (this.funds < price) return `Insufficient funds to purchase ${unlockName}, requires ${formatMoney(price)}`;
this.funds -= price;
this.addNonIncomeFunds(-price);
this.unlocks.add(unlockName);
// Apply effects for one-time unlocks
@@ -308,7 +377,7 @@ export class Corporation {
const upgrade = CorpUpgrades[upgradeName];
const totalCost = calculateUpgradeCost(this, upgrade, amount);
if (this.funds < totalCost) return `Not enough funds to purchase ${amount} of upgrade ${upgradeName}.`;
this.funds -= totalCost;
this.addNonIncomeFunds(-totalCost);
this.upgrades[upgradeName].level += amount;
this.upgrades[upgradeName].value += upgrade.benefit * amount;

View File

@@ -33,7 +33,7 @@ export class Division {
// Not included in save file. Just used for tracking whether research tree has been updated since game load.
treeInitialized = false;
//An array of the name of materials being produced
/** An array of the name of materials being produced */
producedMaterials: CorpMaterialName[] = [];
products = new JSONMap<string, Product>();
@@ -101,7 +101,7 @@ export class Division {
// Loading data based on this division's industry type
const data = IndustriesData[this.type];
this.startingCost = data.startingCost;
this.makesProducts = data.product ? true : false;
this.makesProducts = data.makesProducts;
this.realEstateFactor = data.realEstateFactor ?? 0;
this.researchFactor = data.scienceFactor ?? 0;
this.hardwareFactor = data.hardwareFactor ?? 0;
@@ -311,6 +311,7 @@ export class Division {
buyAmt = Math.min(buyAmt, maxAmt);
if (buyAmt > 0) {
mat.quality = Math.max(0.1, (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt));
mat.averagePrice = (mat.stored * mat.averagePrice + buyAmt * mat.marketPrice) / (mat.stored + buyAmt);
mat.stored += buyAmt;
expenses += buyAmt * mat.marketPrice;
}
@@ -364,8 +365,13 @@ export class Division {
// buy them
for (const [matName, [buyAmt]] of getRecordEntries(smartBuy)) {
const mat = warehouse.materials[matName];
if (mat.stored + buyAmt != 0) mat.quality = (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt);
else mat.quality = 1;
if (mat.stored + buyAmt != 0) {
mat.quality = (mat.quality * mat.stored + 1 * buyAmt) / (mat.stored + buyAmt);
mat.averagePrice = (mat.averagePrice * mat.stored + mat.marketPrice * buyAmt) / (mat.stored + buyAmt);
} else {
mat.quality = 1;
mat.averagePrice = mat.marketPrice;
}
mat.stored += buyAmt;
mat.buyAmount = buyAmt / (corpConstants.secondsPerMarketCycle * marketCycles);
expenses += buyAmt * mat.marketPrice;
@@ -460,6 +466,11 @@ export class Division {
tempQlt * prod * producableFrac) /
(warehouse.materials[this.producedMaterials[j]].stored + prod * producableFrac),
);
warehouse.materials[this.producedMaterials[j]].averagePrice =
(warehouse.materials[this.producedMaterials[j]].averagePrice *
warehouse.materials[this.producedMaterials[j]].stored +
warehouse.materials[this.producedMaterials[j]].marketPrice * prod * producableFrac) /
(warehouse.materials[this.producedMaterials[j]].stored + prod * producableFrac);
warehouse.materials[this.producedMaterials[j]].stored += prod * producableFrac;
}
} else {
@@ -553,7 +564,8 @@ export class Division {
sCost = optimalPrice;
} else if (mat.marketTa1) {
sCost = mat.marketPrice + markupLimit;
} else if (isString(mat.desiredSellPrice)) {
// check truthyness to avoid unnecessary eval
} else if (isString(mat.desiredSellPrice) && mat.desiredSellPrice) {
sCost = mat.desiredSellPrice.replace(/MP/g, mat.marketPrice.toString());
sCost = eval(sCost);
} else {
@@ -677,6 +689,10 @@ export class Division {
(expWarehouse.materials[matName].stored + amt),
);
expWarehouse.materials[matName].averagePrice =
(expWarehouse.materials[matName].averagePrice * expWarehouse.materials[matName].stored +
expWarehouse.materials[matName].marketPrice * amt) /
(expWarehouse.materials[matName].stored + amt);
expWarehouse.materials[matName].stored += amt;
mat.stored -= amt;
mat.exportedLastCycle += amt;

View File

@@ -52,12 +52,15 @@ export class Material {
// Cost / sec to buy this material. AKA Market Price
marketPrice = 0;
// Average price paid for the material (accounted as marketPrice for produced/imported materials)
averagePrice = 0;
/** null if there is no limit set on production. 0 actually limits production to 0. */
productionLimit: number | null = null;
// Player inputs for sell price and amount.
desiredSellAmount: string | number = 0;
desiredSellPrice: string | number = 0;
desiredSellPrice: string | number = "";
// Flags that signal whether automatic sale pricing through Market TA is enabled
marketTa1 = false;

View File

@@ -96,7 +96,7 @@ export class OfficeSpace {
if (this.autoParty) {
this.avgMorale = this.maxMorale;
} else {
// Each 5% multiplier gives an extra flat +1 to morale to make recovering from low morale easier.
// Each 10% multiplier gives an extra flat +1 to morale to make recovering from low morale easier.
const increase = this.partyMult > 1 ? (this.partyMult - 1) * 10 : 0;
this.avgMorale = ((this.avgMorale - reduction * Math.random()) * perfMult + increase) * this.partyMult;
}

View File

@@ -81,7 +81,7 @@ export class Product {
/** Player input sell amount e.g. "MAX" */
desiredSellAmount: 0 as number | string,
/** Player input sell price e.g. "MP * 5" */
desiredSellPrice: 0 as number | string,
desiredSellPrice: "" as string | number,
}));
/** How much warehouse space is occupied per unit of this product */

View File

@@ -33,7 +33,7 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", "
/** Names of all one-time corporation-wide unlocks */
unlockNames: APIUnlockName[] = Object.values(CorpUnlockName),
upgradeNames: APIUpgradeName[] = Object.values(CorpUpgradeName),
/** Names of all reasearches common to all industries */
/** Names of all researches common to all industries */
researchNamesBase: CorpResearchName[] = Object.values(CorpBaseResearchName),
/** Names of all researches only available to product industries */
researchNamesProductOnly: CorpResearchName[] = Object.values(CorpProductResearchName),
@@ -42,8 +42,8 @@ export const stateNames: CorpStateName[] = ["START", "PURCHASE", "PRODUCTION", "
initialShares = 1e9,
/** When selling large number of shares, price is dynamically updated for every batch of this amount */
sharesPerPriceUpdate = 1e6,
/** Cooldown for issue new shares cooldown in game cycles. 12 hours. */
issueNewSharesCooldown = 216e3,
/** Cooldown for issue new shares cooldown in game cycles. Initially 4 hours. */
issueNewSharesCooldown = 72e3,
/** Cooldown for selling shares in game cycles. 1 hour. */
sellSharesCooldown = 18e3,
teaCostPerEmployee = 500e3,

View File

@@ -17,6 +17,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.04,
requiredMaterials: { Water: 0.5, Chemicals: 0.2 },
producedMaterials: ["Plants", "Food"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Spring]: {
startingCost: 10e9,
@@ -30,6 +32,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.03,
requiredMaterials: {},
producedMaterials: ["Water"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Refinery]: {
startingCost: 50e9,
@@ -43,6 +47,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.04,
requiredMaterials: { Ore: 1 },
producedMaterials: ["Metal"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Chemical]: {
startingCost: 70e9,
@@ -56,6 +62,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.07,
requiredMaterials: { Plants: 1, Water: 0.5 },
producedMaterials: ["Chemicals"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Computers]: {
startingCost: 500e9,
@@ -81,6 +89,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.17,
requiredMaterials: { Metal: 2 },
producedMaterials: ["Hardware"],
makesMaterials: true,
makesProducts: true,
},
[IndustryType.Fishing]: {
startingCost: 80e9,
@@ -94,6 +104,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.08,
requiredMaterials: { Plants: 0.5 },
producedMaterials: ["Food"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Restaurant]: {
startingCost: 10e9,
@@ -116,6 +128,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.25,
realEstateFactor: 0.05,
requiredMaterials: { Food: 0.5, Water: 0.5 },
makesMaterials: false,
makesProducts: true,
},
[IndustryType.Healthcare]: {
startingCost: 750e9,
@@ -140,6 +154,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
robotFactor: 0.1,
aiCoreFactor: 0.1,
requiredMaterials: { Robots: 10, "AI Cores": 5, Drugs: 5, Food: 5 },
makesMaterials: false,
makesProducts: true,
},
[IndustryType.Mining]: {
startingCost: 300e9,
@@ -153,6 +169,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.06,
requiredMaterials: { Hardware: 0.1 },
producedMaterials: ["Ore", "Minerals"],
makesMaterials: true,
makesProducts: false,
},
[IndustryType.Pharmaceutical]: {
startingCost: 200e9,
@@ -178,6 +196,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.16,
requiredMaterials: { Chemicals: 2, Water: 0.5 },
producedMaterials: ["Drugs"],
makesMaterials: true,
makesProducts: true,
},
[IndustryType.RealEstate]: {
startingCost: 600e9,
@@ -202,6 +222,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
hardwareFactor: 0.05,
requiredMaterials: { Metal: 5, Plants: 1, Water: 2, Hardware: 4 },
producedMaterials: ["Real Estate"],
makesMaterials: true,
makesProducts: true,
},
[IndustryType.Robotics]: {
startingCost: 1e12,
@@ -227,6 +249,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
hardwareFactor: 0.19,
requiredMaterials: { Hardware: 5, "AI Cores": 3 },
producedMaterials: ["Robots"],
makesMaterials: true,
makesProducts: true,
},
[IndustryType.Software]: {
startingCost: 25e9,
@@ -252,6 +276,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
robotFactor: 0.05,
requiredMaterials: { Hardware: 0.5 },
producedMaterials: ["AI Cores"],
makesMaterials: true,
makesProducts: true,
},
[IndustryType.Tobacco]: {
startingCost: 20e9,
@@ -274,6 +300,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
aiCoreFactor: 0.15,
advertisingFactor: 0.2,
requiredMaterials: { Plants: 1 },
makesMaterials: false,
makesProducts: true,
},
[IndustryType.Water]: {
startingCost: 150e9,
@@ -286,6 +314,8 @@ export const IndustriesData: Record<IndustryType, CorpIndustryData> = {
advertisingFactor: 0.08,
requiredMaterials: { Hardware: 0.1 },
producedMaterials: ["Water"],
makesMaterials: true,
makesProducts: false,
},
};

View File

@@ -1,4 +1,6 @@
import { PositiveInteger } from "../types";
import { Player } from "@player";
import { PositiveInteger, isPositiveInteger } from "../types";
import { formatShares } from "../ui/formatNumber";
import { Corporation } from "./Corporation";
import { CorpUpgrade } from "./data/CorporationUpgrades";
@@ -29,3 +31,43 @@ export function calculateMaxAffordableUpgrade(corp: Corporation, upgrade: CorpUp
const sanitizedValue = maxAffordableUpgrades >= 0 ? maxAffordableUpgrades : 0;
return sanitizedValue as PositiveInteger | 0;
}
/** Returns a string representing the reason a share sale should fail, or empty string if there is no issue. */
export function sellSharesFailureReason(corp: Corporation, numShares: number): string {
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
else if (numShares > corp.numShares) return "You do not have that many shares to sell.";
else if (numShares === corp.numShares) return "You cannot sell all your shares.";
else if (numShares > 1e14) return `Cannot sell more than ${formatShares(1e14)} shares at a time.`;
else if (!corp.public) return "Cannot sell shares before going public.";
else if (corp.shareSaleCooldown)
return `Cannot sell shares for another ${corp.convertCooldownToString(corp.shareSaleCooldown)}.`;
return "";
}
/** Returns a string representing the reason a share buyback should fail, or empty string if there is no issue. */
export function buybackSharesFailureReason(corp: Corporation, numShares: number): string {
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
if (numShares > corp.issuedShares) return "Not enough shares are available for buyback.";
if (numShares > 1e14) return `Cannot buy more than ${formatShares(1e14)} shares at a time.`;
if (!corp.public) return "Cannot buy back shares before going public.";
const [cost] = corp.calculateShareBuyback(numShares);
if (Player.money < cost) return "You cannot afford that many shares.";
return "";
}
/** Returns a string representing the reason issuing new shares should fail, or empty string if there is no issue. */
export function issueNewSharesFailureReason(corp: Corporation, numShares: number): string {
if (!isPositiveInteger(numShares)) return "Number of shares must be a positive integer.";
if (numShares % 10e6 !== 0) return "Number of shares must be a multiple of 10 million.";
if (!corp.public) return "Cannot issue new shares before going public.";
const maxNewShares = corp.calculateMaxNewShares();
if (numShares > maxNewShares) return `Number of shares cannot exceed ${maxNewShares} (20% of total shares).`;
const cooldown = corp.issueNewSharesCooldown;
if (cooldown > 0) return `Cannot issue new shares for another ${corp.convertCooldownToString(cooldown)}.`;
return "";
}

View File

@@ -58,23 +58,23 @@ function WarehouseRoot(props: WarehouseProps): React.ReactElement {
props.rerender();
}
// Current State:
// Next state which will be processed:
let stateText;
switch (division.state) {
case "START":
stateText = "Current state: Preparing...";
stateText = "Next state: Preparing";
break;
case "PURCHASE":
stateText = "Current state: Purchasing materials...";
stateText = "Next state: Purchasing materials";
break;
case "PRODUCTION":
stateText = "Current state: Producing materials and/or products...";
stateText = "Next state: Producing materials and/or products";
break;
case "SALE":
stateText = "Current state: Selling materials and/or products...";
stateText = "Next state: Selling materials and/or products";
break;
case "EXPORT":
stateText = "Current state: Exporting materials and/or products...";
stateText = "Next state: Exporting materials and/or products";
break;
default:
console.error(`Invalid state: ${division.state}`);

View File

@@ -6,10 +6,9 @@ import { CityName, CorpUnlockName } from "@enums";
import { Material } from "../Material";
import { Warehouse } from "../Warehouse";
import { ExportModal } from "./modals/ExportModal";
import { MaterialMarketTaModal } from "./modals/MaterialMarketTaModal";
import { SellMaterialModal } from "./modals/SellMaterialModal";
import { PurchaseMaterialModal } from "./modals/PurchaseMaterialModal";
import { formatBigNumber, formatCorpStat, formatMoney, formatQuality } from "../../ui/formatNumber";
import { formatBigNumber, formatCorpStat, formatQuality } from "../../ui/formatNumber";
import { isString } from "../../utils/helpers/string";
import { Money } from "../../ui/React/Money";
import { useCorporation, useDivision } from "./Context";
@@ -29,7 +28,6 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
const [purchaseMaterialOpen, setPurchaseMaterialOpen] = useState(false);
const [exportOpen, setExportOpen] = useState(false);
const [sellMaterialOpen, setSellMaterialOpen] = useState(false);
const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false);
const [limitProductionOpen, setLimitProductionOpen] = useState(false);
const warehouse = props.warehouse;
@@ -120,7 +118,9 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
</Typography>
}
>
<Typography>MP: {formatMoney(mat.marketPrice)}</Typography>
<Typography>
MP: <Money money={mat.marketPrice} />
</Typography>
</Tooltip>
<Tooltip
title={<Typography>The quality of your material. Higher quality will lead to more sales</Typography>}
@@ -159,18 +159,12 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
>
{sellButtonText}
</Button>
<SellMaterialModal mat={mat} open={sellMaterialOpen} onClose={() => setSellMaterialOpen(false)} />
{division.hasResearch("Market-TA.I") && (
<>
<Button onClick={() => setMaterialMarketTaOpen(true)}>Market-TA</Button>
<MaterialMarketTaModal
mat={mat}
open={materialMarketTaOpen}
onClose={() => setMaterialMarketTaOpen(false)}
/>
</>
)}
<SellMaterialModal
mat={mat}
div={division}
open={sellMaterialOpen}
onClose={() => setSellMaterialOpen(false)}
/>
<Button color={tutorial ? "error" : "primary"} onClick={() => setLimitProductionOpen(true)}>
{limitMaterialButtonText}
</Button>

View File

@@ -62,6 +62,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
<StatsTable
rows={[
["Total Funds:", <Money key="funds" money={corp.funds} />],
["Total Assets:", <Money key="assets" money={corp.totalAssets} />],
["Total Revenue:", <MoneyRate key="revenue" money={corp.revenue} />],
["Total Expenses:", <MoneyRate key="expenses" money={corp.expenses} />],
["Total Profit:", <MoneyRate key="profit" money={corp.revenue - corp.expenses} />],
@@ -76,8 +77,21 @@ export function Overview({ rerender }: IProps): React.ReactElement {
title={
<StatsTable
rows={[
["Outstanding Shares:", formatShares(corp.issuedShares)],
["Private Shares:", formatShares(corp.totalShares - corp.issuedShares - corp.numShares)],
[
"Owned Stock Shares:",
<>&nbsp;{formatShares(corp.numShares)}&nbsp;</>,
<>({formatPercent(corp.numShares / corp.totalShares)})</>,
],
[
"Outstanding Shares:",
<>&nbsp;{formatShares(corp.issuedShares)}&nbsp;</>,
<>({formatPercent(corp.issuedShares / corp.totalShares)})</>,
],
[
"Private Shares:",
<>&nbsp;{formatShares(corp.investorShares)}&nbsp;</>,
<>({formatPercent(corp.investorShares / corp.totalShares)})</>,
],
]}
/>
}
@@ -95,8 +109,8 @@ export function Overview({ rerender }: IProps): React.ReactElement {
<ButtonWithTooltip
normalTooltip={
<>
Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' This is a .lit file
that guides you through the beginning of setting up a Corporation and provides some tips/pointers for
Get a copy of and read <i>The Complete Handbook for Creating a Successful Corporation</i>. This is a .lit
file that guides you through the beginning of setting up a Corporation and provides some tips/pointers for
helping you get started with managing it.
</>
}
@@ -124,7 +138,7 @@ function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement
const [findInvestorsopen, setFindInvestorsopen] = useState(false);
const [goPublicopen, setGoPublicopen] = useState(false);
const fundingAvailable = corp.fundingRound < 4;
const fundingAvailable = corp.fundingRound < corpConstants.fundingRoundShares.length;
const findInvestorsTooltip = fundingAvailable
? "Search for private investors who will give you startup funding in exchange for equity (stock shares) in your company"
: "";
@@ -212,29 +226,27 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
const [issueDividendsOpen, setIssueDividendsOpen] = useState(false);
const sellSharesOnCd = corp.shareSaleCooldown > 0;
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture.";
const sellSharesTooltip =
"Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture.";
const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
return (
<>
<ButtonWithTooltip
normalTooltip={sellSharesTooltip}
disabledTooltip={sellSharesOnCd ? "On cooldown" : ""}
disabledTooltip={
sellSharesOnCd ? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown) : ""
}
onClick={() => setSellSharesOpen(true)}
>
Sell Shares
</ButtonWithTooltip>
<SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
<ButtonWithTooltip
normalTooltip={"Buy back shares you that previously issued or sold at market price."}
normalTooltip={"Buy back shares you that previously issued or sold on the market"}
disabledTooltip={corp.issuedShares < 1 ? "No shares available to buy back" : ""}
onClick={() => setBuybackSharesOpen(true)}
>
@@ -242,13 +254,15 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
</ButtonWithTooltip>
<BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} />
<ButtonWithTooltip
normalTooltip={issueNewSharesTooltip}
disabledTooltip={issueNewSharesOnCd ? "On cooldown" : ""}
normalTooltip={"Issue new equity shares to raise capital"}
disabledTooltip={
issueNewSharesOnCd ? `On cooldown for ${corp.convertCooldownToString(corp.issueNewSharesCooldown)}` : ""
}
onClick={() => setIssueNewSharesOpen(true)}
>
Issue New Shares
</ButtonWithTooltip>
<IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} />
<IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} rerender={rerender} />
<ButtonWithTooltip
normalTooltip={"Manage the dividends that are paid out to shareholders (including yourself)"}
onClick={() => setIssueDividendsOpen(true)}
@@ -305,13 +319,22 @@ function SellDivisionButton(): React.ReactElement {
function RestartButton(): React.ReactElement {
const [open, setOpen] = useState(false);
const corp = useCorporation();
const sellSharesOnCd = corp.shareSaleCooldown > 0;
function restart(): void {
setOpen(true);
}
return (
<>
<ButtonWithTooltip normalTooltip={"Sell corporation and start over"} onClick={restart}>
<ButtonWithTooltip
normalTooltip={"Sell corporation and start over"}
disabledTooltip={
sellSharesOnCd ? "Sell corporation and start over. Cannot do this while Sell Shares is on cooldown." : ""
}
onClick={restart}
>
Sell CEO position
</ButtonWithTooltip>
<SellCorporationModal open={open} onClose={() => setOpen(false)} />

View File

@@ -6,10 +6,9 @@ import { Product } from "../Product";
import { DiscontinueProductModal } from "./modals/DiscontinueProductModal";
import { LimitProductProductionModal } from "./modals/LimitProductProductionModal";
import { SellProductModal } from "./modals/SellProductModal";
import { ProductMarketTaModal } from "./modals/ProductMarketTaModal";
import { CancelProductModal } from "./modals/CancelProductModal";
import { formatBigNumber, formatCorpStat, formatMoney, formatPercent } from "../../ui/formatNumber";
import { formatBigNumber, formatPercent } from "../../ui/formatNumber";
import { isString } from "../../utils/helpers/string";
import { Money } from "../../ui/React/Money";
@@ -29,7 +28,6 @@ export function ProductElem(props: IProductProps): React.ReactElement {
const [limitOpen, setLimitOpen] = useState(false);
const [discontinueOpen, setDiscontinueOpen] = useState(false);
const [cancelOpen, setCancelOpen] = useState(false);
const [marketTaOpen, setMarketTaOpen] = useState(false);
const city = props.city;
const product = props.product;
@@ -67,7 +65,7 @@ export function ProductElem(props: IProductProps): React.ReactElement {
// Limit Production button
const productionLimit = product.cityData[city].productionLimit;
const limitProductionButtonText =
"Limit Production" + (productionLimit !== null ? " (" + formatCorpStat(productionLimit) + ")" : "");
"Limit Production" + (productionLimit !== null ? " (" + formatBigNumber(productionLimit) + ")" : "");
return (
<Paper>
@@ -109,35 +107,35 @@ export function ProductElem(props: IProductProps): React.ReactElement {
title={
<Typography>
Effective rating is calculated from product rating and the quality of materials used <br />
Rating: {formatCorpStat(product.rating)} <br /> <br />
Quality: {formatCorpStat(product.stats.quality)} <br />
Performance: {formatCorpStat(product.stats.performance)} <br />
Durability: {formatCorpStat(product.stats.durability)} <br />
Reliability: {formatCorpStat(product.stats.reliability)} <br />
Aesthetics: {formatCorpStat(product.stats.aesthetics)} <br />
Features: {formatCorpStat(product.stats.features)}
Rating: {formatBigNumber(product.rating)} <br /> <br />
Quality: {formatBigNumber(product.stats.quality)} <br />
Performance: {formatBigNumber(product.stats.performance)} <br />
Durability: {formatBigNumber(product.stats.durability)} <br />
Reliability: {formatBigNumber(product.stats.reliability)} <br />
Aesthetics: {formatBigNumber(product.stats.aesthetics)} <br />
Features: {formatBigNumber(product.stats.features)}
{corp.unlocks.has(CorpUnlockName.MarketResearchDemand) && (
<>
<br />
{"Demand: " + formatCorpStat(product.demand)}
{"Demand: " + formatBigNumber(product.demand)}
</>
)}
{corp.unlocks.has(CorpUnlockName.MarketDataCompetition) && (
<>
<br />
{"Competition: " + formatCorpStat(product.competition)}
{"Competition: " + formatBigNumber(product.competition)}
</>
)}
</Typography>
}
>
<Typography>Effective rating: {formatCorpStat(product.cityData[city].effectiveRating)}</Typography>
<Typography>Effective rating: {formatBigNumber(product.cityData[city].effectiveRating)}</Typography>
</Tooltip>
</Box>
<Box display="flex">
<Tooltip title={<Typography>An estimate of the material cost it takes to create this Product.</Typography>}>
<Typography>
Est. Production Cost: {formatMoney(product.productionCost / corpConstants.baseProductProfitMult)}
Est. Production Cost: <Money money={product.productionCost / corpConstants.baseProductProfitMult} />
</Typography>
</Tooltip>
</Box>
@@ -150,7 +148,9 @@ export function ProductElem(props: IProductProps): React.ReactElement {
</Typography>
}
>
<Typography>Est. Market Price: {formatMoney(product.productionCost)}</Typography>
<Typography>
Est. Market Price: <Money money={product.productionCost} />
</Typography>
</Tooltip>
</Box>
<Button onClick={() => setDiscontinueOpen(true)}>Discontinue</Button>
@@ -166,7 +166,13 @@ export function ProductElem(props: IProductProps): React.ReactElement {
{(hasUpgradeDashboard || product.finished) && (
<>
<Button onClick={() => setSellOpen(true)}>{sellButtonText}</Button>
<SellProductModal product={product} city={city} open={sellOpen} onClose={() => setSellOpen(false)} />
<SellProductModal
product={product}
div={division}
city={city}
open={sellOpen}
onClose={() => setSellOpen(false)}
/>
<br />
<Button onClick={() => setLimitOpen(true)}>{limitProductionButtonText}</Button>
<LimitProductProductionModal
@@ -175,12 +181,6 @@ export function ProductElem(props: IProductProps): React.ReactElement {
open={limitOpen}
onClose={() => setLimitOpen(false)}
/>
{division.hasResearch("Market-TA.I") && (
<>
<Button onClick={() => setMarketTaOpen(true)}>Market-TA</Button>
<ProductMarketTaModal product={product} open={marketTaOpen} onClose={() => setMarketTaOpen(false)} />
</>
)}
</>
)}
</Paper>

View File

@@ -1,15 +1,15 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { formatBigNumber, formatMoney } from "../../../ui/formatNumber";
import { Player } from "@player";
import { Money } from "../../../ui/React/Money";
import { formatShares } from "../../../ui/formatNumber";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
import { NumberInput } from "../../../ui/React/NumberInput";
import { BuyBackShares } from "../../Actions";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { KEY } from "../../../utils/helpers/keyCodes";
import { isPositiveInteger } from "../../../types";
import { buybackSharesFailureReason } from "../../helpers";
interface IProps {
open: boolean;
@@ -23,44 +23,28 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN);
const currentStockPrice = corp.sharePrice;
const buybackPrice = currentStockPrice * 1.1;
const disabledText = !isPositiveInteger(shares)
? "Number of shares must be a positive integer"
: shares > corp.issuedShares
? "There are not enough shares available to buyback this many"
: shares * buybackPrice > Player.money
? "Insufficient player funds"
: "";
const [cost, sharePrice] = corp.calculateShareBuyback((props.open && shares) || 0);
const disabledText = buybackSharesFailureReason(corp, shares);
function buy(): void {
if (disabledText) return;
try {
BuyBackShares(corp, shares);
dialogBoxCreate(
<>
<Typography>
You bought {formatShares(shares)} shares for <Money money={cost} />.
</Typography>
<Typography>
<b>{corp.name}</b>'s stock price rose to <Money money={sharePrice} /> per share.
</Typography>
</>,
);
props.onClose();
props.rerender();
setShares(NaN);
} catch (err) {
dialogBoxCreate(err + "");
}
props.onClose();
props.rerender();
}
function CostIndicator(): React.ReactElement {
if (shares === null) return <></>;
if (isNaN(shares) || shares <= 0) {
return <>ERROR: Invalid value entered for number of shares to buyback</>;
} else if (shares > corp.issuedShares) {
return (
<>
There are not this many shares available to buy back. There are only {formatBigNumber(corp.issuedShares)}{" "}
outstanding shares.
</>
);
} else {
return (
<>
Purchase {shares} shares for a total of {formatMoney(shares * buybackPrice)}
</>
);
dialogBoxCreate(`${err}`);
}
}
@@ -70,23 +54,45 @@ export function BuybackSharesModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Enter the number of outstanding shares you would like to buy back. These shares must be bought at a 10% premium.
However, repurchasing shares from the market tends to lead to an increase in stock price.
<br />
<br />
To purchase these shares, you must use your own money (NOT your Corporation's funds).
<br />
<br />
The current buyback price of your company's stock is {formatMoney(buybackPrice)}. Your company currently has{" "}
{formatBigNumber(corp.issuedShares)} outstanding stock shares.
<Typography component="div">
Enter the number of outstanding shares you would like to buy back.
<ul>
<li>Buying back shares will cause the stock price to rise due to market forces.</li>
<li>These shares must be bought at a 10% premium over the market price.</li>
<li>You purchase these shares with your own money (NOT your Corporation's funds).</li>
</ul>
<b>{corp.name}</b> currently has {formatShares(corp.issuedShares)} outstanding stock shares, valued at{" "}
<Money money={corp.sharePrice} /> per share.
</Typography>
<CostIndicator />
<br />
<NumberInput autoFocus={true} placeholder="Shares to buyback" onChange={setShares} onKeyDown={onKeyDown} />
<NumberInput
defaultValue={shares || ""}
autoFocus={true}
placeholder="Shares to buyback"
onChange={setShares}
onKeyDown={onKeyDown}
/>
<ButtonWithTooltip disabledTooltip={disabledText} onClick={buy}>
Buy shares
{cost > 0 ? (
<>
&nbsp;-&nbsp;
<Money money={cost} forPurchase={true} />{" "}
</>
) : (
<></>
)}
</ButtonWithTooltip>
<br />
<Typography sx={{ minHeight: "1.5em" }}>
{!shares ? null : disabledText ? (
disabledText
) : (
<>
<b>{corp.name}</b>'s stock price will rise to <Money money={sharePrice} /> per share.
</>
)}
</Typography>
</Modal>
);
}

View File

@@ -4,6 +4,7 @@ import { Money } from "../../../ui/React/Money";
import { Modal } from "../../../ui/React/Modal";
import { Router } from "../../../ui/GameRoot";
import { Page } from "../../../ui/Router";
import { formatShares } from "../../../ui/formatNumber";
import { Player } from "@player";
import Typography from "@mui/material/Typography";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
@@ -54,15 +55,19 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Would you like to start a corporation? This will require $150b for registration and initial funding.{" "}
{Player.bitNodeN === 3 &&
`This $150b
can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million
shares`}
Would you like to start a corporation? This will require <Money money={150e9} forPurchase={true} /> for
registration and initial funding.{" "}
{Player.bitNodeN === 3 && (
<>
This <Money money={150e9} /> can either be self-funded, or you can obtain the seed money from the government
in exchange for {formatShares(500e6)} shares (a <b>33.3%</b> stake in the company).
</>
)}
<br />
<br />
If you would like to start one, please enter a name for your corporation below:
</Typography>
<br />
<TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} />
{Player.bitNodeN === 3 && (
<ButtonWithTooltip onClick={seed} disabledTooltip={disabledTextForNoName}>
@@ -71,7 +76,7 @@ export function CreateCorporationModal(props: IProps): React.ReactElement {
)}
<ButtonWithTooltip
onClick={selfFund}
disabledTooltip={disabledTextForNoName || canSelfFund ? "" : "Insufficient player funds"}
disabledTooltip={disabledTextForNoName || (canSelfFund ? "" : "Insufficient player funds")}
>
Self-Fund (<Money money={150e9} forPurchase={true} />)
</ButtonWithTooltip>

View File

@@ -1,8 +1,10 @@
import React from "react";
import { formatMoney, formatPercent, formatShares } from "../../../ui/formatNumber";
import * as corpConstants from "../../data/Constants";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { formatPercent, formatShares } from "../../../ui/formatNumber";
import { Modal } from "../../../ui/React/Modal";
import { Money } from "../../../ui/React/Money";
import { useCorporation } from "../Context";
import { AcceptInvestmentOffer } from "../../Actions";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@@ -13,40 +15,52 @@ interface IProps {
rerender: () => void;
}
// Create a popup that lets the player manage exports
// Create a popup that lets the player manage investment offers
export function FindInvestorsModal(props: IProps): React.ReactElement {
const corporation = useCorporation();
const val = corporation.valuation;
if (
corporation.fundingRound >= corpConstants.fundingRoundShares.length ||
corporation.fundingRound >= corpConstants.fundingRoundMultiplier.length
)
return <></>;
const percShares = corpConstants.fundingRoundShares[corporation.fundingRound];
const roundMultiplier = corpConstants.fundingRoundMultiplier[corporation.fundingRound];
const funding = val * percShares * roundMultiplier;
const investShares = Math.floor(corpConstants.initialShares * percShares);
const corp = useCorporation();
const { funds, shares } = corp.getInvestmentOffer();
function findInvestors(): void {
corporation.fundingRound++;
corporation.addFunds(funding);
corporation.numShares -= investShares;
props.rerender();
props.onClose();
if (shares === 0) return;
try {
AcceptInvestmentOffer(corp);
dialogBoxCreate(
<>
<Typography>You accepted the investment offer.</Typography>
<Typography>
<b>{corp.name}</b> received <Money money={funds} />.
</Typography>
<Typography>
Your remaining equity is <b>{formatPercent(corp.numShares / corp.totalShares, 1)}</b>.
</Typography>
</>,
);
props.onClose();
props.rerender();
} catch (err) {
dialogBoxCreate(`${err}`);
}
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
An investment firm has offered you {formatMoney(funding)} in funding in exchange for a{" "}
{formatPercent(percShares, 3)} stake in the company ({formatShares(investShares)} shares).
An investment firm has offered to buy {formatShares(shares)} shares of stock (a{" "}
<b>{formatPercent(shares / corp.totalShares, 1)}</b> stake in the company).
<br />
<br />
Do you accept or reject this offer?
<b>{corp.name}</b> will receive <Money money={funds} />.
<br />
Your equity will fall to <b>{formatPercent((corp.numShares - shares) / corp.totalShares, 1)}</b>.
<br />
<br />
Hint: Investment firms will offer more money if your corporation is turning a profit
<b>Hint</b>: Investment firms will offer more money if your Corporation is turning a profit.
<br />
<br />
Do you accept this offer?
</Typography>
<Button onClick={findInvestors}>Accept</Button>
<br />
<Button onClick={findInvestors}>Accept</Button> <Button onClick={props.onClose}>Ignore</Button>
</Modal>
);
}

View File

@@ -1,7 +1,8 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { formatMoney, formatShares } from "../../../ui/formatNumber";
import { Money } from "../../../ui/React/Money";
import { formatShares } from "../../../ui/formatNumber";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
@@ -9,6 +10,7 @@ import { NumberInput } from "../../../ui/React/NumberInput";
import Box from "@mui/material/Box";
import { KEY } from "../../../utils/helpers/keyCodes";
import { isPositiveInteger } from "../../../types";
import { GoPublic } from "../../Actions";
interface IProps {
open: boolean;
@@ -20,26 +22,9 @@ interface IProps {
export function GoPublicModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN);
const initialSharePrice = corp.valuation / corp.totalShares;
function goPublic(): void {
const initialSharePrice = corp.valuation / corp.totalShares;
if (shares >= corp.numShares || (shares !== 0 && !isPositiveInteger(shares))) return;
corp.public = true;
corp.sharePrice = initialSharePrice;
corp.issuedShares = shares;
corp.numShares -= shares;
corp.addFunds(shares * initialSharePrice);
props.rerender();
dialogBoxCreate(
`You took your ${corp.name} public and earned ` + `${formatMoney(shares * initialSharePrice)} in your IPO`,
);
props.onClose();
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.key === KEY.ENTER) goPublic();
}
const ceoOwnership = (corp.numShares - (shares || 0)) / corp.totalShares;
const initialSharePrice = corp.getTargetSharePrice(ceoOwnership);
const disabledText =
shares >= corp.numShares
@@ -48,22 +33,62 @@ export function GoPublicModal(props: IProps): React.ReactElement {
? "Must issue an non-negative integer number of shares"
: "";
function goPublic(): void {
if (disabledText) return;
try {
GoPublic(corp, shares);
dialogBoxCreate(
<Typography>
<b>{corp.name}</b> went public and earned <Money money={shares * initialSharePrice} /> in its IPO.
</Typography>,
);
props.onClose();
props.rerender();
setShares(NaN);
} catch (err) {
dialogBoxCreate(`${err}`);
}
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.key === KEY.ENTER) goPublic();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will
no longer own them. Your Corporation will receive {formatMoney(initialSharePrice)} per share (the IPO money will
be deposited directly into your Corporation's funds).
<br />
<br />
<Typography component="div">
Enter the number of shares you would like to issue for your IPO.
<ul>
<li>These shares will be publicly sold and you will no longer own them.</li>
<li>The IPO money will be deposited directly into your Corporation's funds.</li>
</ul>
You can issue some, but not all, of your {formatShares(corp.numShares)} shares.
</Typography>
<br />
<Box display="flex" alignItems="center">
<NumberInput onChange={setShares} autoFocus placeholder="Shares to issue" onKeyDown={onKeyDown} />
<NumberInput
defaultValue={shares || ""}
onChange={setShares}
autoFocus
placeholder="Shares to issue"
onKeyDown={onKeyDown}
/>
<ButtonWithTooltip disabledTooltip={disabledText} onClick={goPublic}>
Go Public
</ButtonWithTooltip>
</Box>
<br />
<Typography sx={{ minHeight: "3em" }}>
{isNaN(shares) ? null : disabledText ? (
disabledText
) : (
<>
Go public at <Money money={initialSharePrice} /> per share?
<br />
<b>{corp.name}</b> will receive <Money money={initialSharePrice * (shares || 0)} />.
</>
)}
</Typography>
</Modal>
);
}

View File

@@ -1,6 +1,8 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { Money } from "../../../ui/React/Money";
import { MoneyRate } from "../../../ui/React/MoneyRate";
import * as corpConstants from "../../data/Constants";
import { IssueDividends } from "../../Actions";
import { useCorporation } from "../Context";
@@ -53,20 +55,19 @@ export function IssueDividendsModal(props: IProps): React.ReactElement {
yourself, as well.
<br />
<br />
In order to issue dividends, simply allocate some percentage of your corporation's profits to dividends. This
percentage must be an integer between 0 and 100. (A percentage of 0 means no dividends will be issued)
Note that issuing dividends will negatively affect <b>{corp.name}</b>'s stock price.
<br />
<br />
Two important things to note:
<br />
* Issuing dividends will negatively affect your corporation's stock price
In order to issue dividends, simply allocate some percentage of your Corporation's profits to dividends. This
percentage must be an integer between 0 and 100. (A percentage of 0 means no dividends will be issued.)
<br />
<br />
Example: Assume your corporation makes $100m / sec in profit and you allocate 40% of that towards dividends.
That means your corporation will gain $60m / sec in funds and the remaining $40m / sec will be paid as
dividends. Since your corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share
per second before taxes.
<b>Example:</b> Assume your corporation makes <MoneyRate money={100e6} /> in profit and you allocate 40% of that
towards dividends. That means your corporation will gain <MoneyRate money={60e6} /> in funds and the remaining{" "}
<MoneyRate money={40e6} /> will be paid as dividends. Since your corporation starts with 1 billion shares, every
shareholder will be paid <Money money={0.04} /> per share per second before taxes.
</Typography>
<br />
<TextField
autoFocus
value={percent}

View File

@@ -1,50 +1,21 @@
import React, { useState } from "react";
import { formatMoney, formatShares } from "../../../ui/formatNumber";
import { formatShares, formatPercent } from "../../../ui/formatNumber";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Modal } from "../../../ui/React/Modal";
import { Money } from "../../../ui/React/Money";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
import { NumberInput } from "../../../ui/React/NumberInput";
import Button from "@mui/material/Button";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
import { KEY } from "../../../utils/helpers/keyCodes";
import { IssueNewShares } from "../../Actions";
interface IEffectTextProps {
shares: number | null;
}
function EffectText(props: IEffectTextProps): React.ReactElement {
const corp = useCorporation();
if (props.shares === null) return <></>;
const newSharePrice = Math.round(corp.sharePrice * 0.9);
const maxNewShares = corp.calculateMaxNewShares();
let newShares = props.shares;
if (isNaN(newShares)) {
return <Typography>Invalid input</Typography>;
}
// Round to nearest ten-millionth
newShares /= 10e6;
newShares = Math.round(newShares) * 10e6;
if (newShares < 10e6) {
return <Typography>Must issue at least 10 million new shares</Typography>;
}
if (newShares > maxNewShares) {
return <Typography>You cannot issue that many shares</Typography>;
}
return (
<Typography>
Issue {formatShares(newShares)} new shares for {formatMoney(newShares * newSharePrice)}?
</Typography>
);
}
import * as corpConstants from "../../data/Constants";
import { issueNewSharesFailureReason } from "../../helpers";
interface IProps {
open: boolean;
onClose: () => void;
rerender: () => void;
}
// Create a popup that lets the player issue new shares
@@ -52,56 +23,103 @@ interface IProps {
export function IssueNewSharesModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN);
const maxNewShares = corp.calculateMaxNewShares();
const maxNewShares = corp.calculateMaxNewShares();
const newShares = Math.round((shares || 0) / 10e6) * 10e6;
const disabled = isNaN(shares) || isNaN(newShares) || newShares < 10e6 || newShares > maxNewShares;
const ceoOwnership = corp.numShares / (corp.totalShares + (newShares || 0));
const newSharePrice = corp.getTargetSharePrice(ceoOwnership);
const profit = ((shares || 0) * (corp.sharePrice + newSharePrice)) / 2;
const privateOwnedRatio = corp.investorShares / corp.totalShares;
const maxPrivateShares = Math.round(((newShares / 2) * privateOwnedRatio) / 10e6) * 10e6;
const disabledText = issueNewSharesFailureReason(corp, shares);
function issueNewShares(): void {
if (isNaN(shares)) return;
if (disabled) return;
const [profit, newShares, privateShares] = IssueNewShares(corp, shares);
props.onClose();
let dialogContents =
`Issued ${formatShares(newShares)} new shares` + ` and raised ${formatMoney(profit)}.` + (privateShares > 0)
? "\n" + formatShares(privateShares) + " of these shares were bought by private investors."
: "";
dialogContents += `\n\nStock price decreased to ${formatMoney(corp.sharePrice)}`;
dialogBoxCreate(dialogContents);
if (disabledText) return;
try {
const [profit, newShares, privateShares] = IssueNewShares(corp, shares);
dialogBoxCreate(
<>
<Typography>
Issued {formatShares(newShares)} new shares and raised <Money money={profit} />.
</Typography>
{privateShares > 0 ? (
<Typography>{formatShares(privateShares)} of these shares were bought by private investors.</Typography>
) : null}
<Typography>
<b>{corp.name}</b>'s stock price fell to <Money money={corp.sharePrice} />.
</Typography>
</>,
);
props.onClose();
props.rerender();
} catch (err) {
dialogBoxCreate(`${err}`);
}
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.key === KEY.ENTER) issueNewShares();
}
const nextCooldown = corpConstants.issueNewSharesCooldown * (corp.totalShares / corpConstants.initialShares);
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
You can issue new equity shares (i.e. stocks) in order to raise capital for your corporation.
<Typography component="div">
You can issue new equity shares (i.e. stocks) in order to raise capital.
<ul>
<li>Issuing new shares will cause dilution, lowering stock price and reducing dividends per share.</li>
<li>New shares are sold between the current price and the updated price.</li>
<li>The money from issuing new shares will be deposited directly into your Corporation's funds.</li>
<li>
Private shareholders have first priority for buying new shares, up to half of their existing stake in the
company <b>({formatPercent(privateOwnedRatio / 2, 1)})</b>.
<br />
If they choose to exercise this option, these newly issued shares become private, restricted shares, which
means you cannot buy them back.
</li>
<li>
You will not be able to issue new shares again for <b>{corp.convertCooldownToString(nextCooldown)}</b>.
</li>
</ul>
You can issue at most {formatShares(maxNewShares)} new shares.
<br />
<br />
&nbsp;* You can issue at most {formatShares(maxNewShares)} new shares
<br />
&nbsp;* New shares are sold at a 10% discount
<br />
&nbsp;* You can only issue new shares once every 12 hours
<br />
&nbsp;* Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
<br />
&nbsp;* Number of new shares issued must be a multiple of 10 million
<br />
<br />
When you choose to issue new equity, private shareholders have first priority for up to 0.5n% of the new shares,
where n is the percentage of the company currently owned by private shareholders. If they choose to exercise
this option, these newly issued shares become private, restricted shares, which means you cannot buy them back.
The number of new shares issued must be a multiple of 10 million.
</Typography>
<EffectText shares={shares} />
<NumberInput autoFocus placeholder="# New Shares" onChange={setShares} onKeyDown={onKeyDown} />
<Button disabled={disabled} onClick={issueNewShares} sx={{ mx: 1 }}>
<br />
<NumberInput
defaultValue={shares || ""}
autoFocus
placeholder="# New Shares"
onChange={setShares}
onKeyDown={onKeyDown}
/>
<ButtonWithTooltip disabledTooltip={disabledText} onClick={issueNewShares}>
Issue New Shares
</Button>
</ButtonWithTooltip>
<br />
<Typography sx={{ minHeight: "6em" }}>
{disabledText ? (
disabledText
) : (
<>
Issue {formatShares(newShares)} new shares?
<br />
{maxPrivateShares > 0
? `Private investors may buy up to ${formatShares(
maxPrivateShares,
)} of these shares and keep them off the market.`
: null}
<br />
<b>{corp.name}</b> will receive <Money money={profit} />.
<br />
<b>{corp.name}</b>'s stock price will fall to <Money money={newSharePrice} /> per share.
</>
)}
</Typography>
</Modal>
);
}

View File

@@ -1,84 +0,0 @@
import React, { useState } from "react";
import { formatMoney } from "../../../ui/formatNumber";
import { Material } from "../../Material";
import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "../Context";
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { useRerender } from "../../../ui/React/hooks";
interface IMarketTA2Props {
mat: Material;
}
function MarketTA2(props: IMarketTA2Props): React.ReactElement {
const rerender = useRerender();
const division = useDivision();
if (!division.hasResearch("Market-TA.II")) return <></>;
function onMarketTA2(event: React.ChangeEvent<HTMLInputElement>): void {
props.mat.marketTa2 = event.target.checked;
rerender();
}
return (
<>
<Typography variant="h4">Market-TA.II</Typography>
<Typography>
If this is enabled, then this Material will automatically be sold at the optimal price such that the amount sold
matches the amount produced. (i.e. the highest possible price, while still ensuring that all produced materials
will be sold)
</Typography>
<br />
<FormControlLabel
control={<Switch checked={props.mat.marketTa2} onChange={onMarketTA2} />}
label={<Typography>Use Market-TA.II for Auto-Sale Price</Typography>}
/>
</>
);
}
interface IProps {
open: boolean;
onClose: () => void;
mat: Material;
}
// Create a popup that lets the player use the Market TA research for Materials
export function MaterialMarketTaModal(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const markupLimit = props.mat.getMarkupLimit();
function onMarketTA1(event: React.ChangeEvent<HTMLInputElement>): void {
props.mat.marketTa1 = event.target.checked;
rerender();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is {formatMoney(props.mat.marketPrice + markupLimit)}. This
means that if you set the sale price higher than this, you will begin to experience a loss in number of sales
<br></br>
<br></br>
If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I (i.e.
the price shown above)
</Typography>
<FormControlLabel
control={<Switch checked={props.mat.marketTa1} onChange={onMarketTA1} />}
label={<Typography>Use Market-TA.I for Auto-Sale Price</Typography>}
/>
</>
<MarketTA2 mat={props.mat} />
</Modal>
);
}

View File

@@ -1,86 +0,0 @@
import React, { useState } from "react";
import { formatMoney } from "../../../ui/formatNumber";
import { Product } from "../../Product";
import { Modal } from "../../../ui/React/Modal";
import { useDivision } from "../Context";
import Typography from "@mui/material/Typography";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { useRerender } from "../../../ui/React/hooks";
interface ITa2Props {
product: Product;
}
function MarketTA2(props: ITa2Props): React.ReactElement {
const rerender = useRerender();
const division = useDivision();
if (!division.hasResearch("Market-TA.II")) return <></>;
function onCheckedChange(event: React.ChangeEvent<HTMLInputElement>): void {
props.product.marketTa2 = event.target.checked;
rerender();
}
return (
<>
<Typography variant="h4">Market-TA.II</Typography>
<br />
<Typography>
If this is enabled, then this product will automatically be sold at the optimal price such that the amount sold
matches the amount produced. (i.e. the highest possible price, while still ensuring that all produced materials
will be sold)
</Typography>
<br />
<FormControlLabel
control={<Switch checked={props.product.marketTa2} onChange={onCheckedChange} />}
label={<Typography>Use Market-TA.II for Auto-Sale Price</Typography>}
/>
</>
);
}
interface IProps {
open: boolean;
onClose: () => void;
product: Product;
}
// Create a popup that lets the player use the Market TA research for Products
export function ProductMarketTaModal(props: IProps): React.ReactElement {
const markupLimit = props.product.rating / props.product.markup;
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
props.product.marketTa1 = event.target.checked;
rerender();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography variant="h4">Market-TA.I</Typography>
<Typography>
The maximum sale price you can mark this up to is {formatMoney(props.product.productionCost + markupLimit)}.
This means that if you set the sale price higher than this, you will begin to experience a loss in number of
sales
<br></br>
<br></br>
If this is enabled, then this product will automatically be sold at the price identified by Market-TA.I (i.e.
the price shown above)
</Typography>
<FormControlLabel
control={<Switch checked={props.product.marketTa1} onChange={onChange} />}
label={<Typography>Use Market-TA.I for Auto-Sale Price</Typography>}
/>
</>
<MarketTA2 product={props.product} />
</Modal>
);
}

View File

@@ -3,9 +3,10 @@ import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { MaterialInfo } from "../../MaterialInfo";
import { Warehouse } from "../../Warehouse";
import { Material } from "../../Material";
import { formatMatPurchaseAmount, formatMoney } from "../../../ui/formatNumber";
import { formatMatPurchaseAmount } from "../../../ui/formatNumber";
import { BulkPurchase, BuyMaterial } from "../../Actions";
import { Modal } from "../../../ui/React/Modal";
import { Money } from "../../../ui/React/Money";
import { useCorporation, useDivision } from "../Context";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
@@ -56,7 +57,7 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement {
return (
<>
<Typography>
Purchasing {formatMatPurchaseAmount(parsedAmt)} of {props.mat.name} will cost {formatMoney(cost)}
Purchasing {formatMatPurchaseAmount(parsedAmt)} of {props.mat.name} will cost <Money money={cost} />
</Typography>
</>
);

View File

@@ -57,6 +57,7 @@ export function SellCorporationModal(props: IProps): React.ReactElement {
<br />
If you would like to start new one, please enter a name for your corporation below:
</Typography>
<br />
<TextField autoFocus={true} placeholder="Corporation Name" onChange={onChange} value={name} />
{Player.bitNodeN === 3 && (
<Button onClick={seed} disabled={name == ""}>

View File

@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { Modal } from "../../../ui/React/Modal";
import { Money } from "../../../ui/React/Money";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
@@ -8,7 +9,6 @@ import Select, { SelectChangeEvent } from "@mui/material/Select";
import { useCorporation } from "../../ui/Context";
import { CityName } from "@enums";
import * as corpConstants from "../../data/Constants";
import { formatMoney } from "../../../ui/formatNumber";
import { removeDivision as removeDivision } from "../../Actions";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { getRecordKeys } from "../../../Types/Record";
@@ -47,9 +47,10 @@ export function SellDivisionModal(props: IProps): React.ReactElement {
corp.funds += price;
props.onClose();
dialogBoxCreate(
`Sold ${divisionToSell.name} for ${formatMoney(price)}, you now have space for ${
corp.maxDivisions - corp.divisions.size
} more divisions.`,
<Typography>
Sold <b>{divisionToSell.name}</b> for <Money money={price} />, you now have space for
{corp.maxDivisions - corp.divisions.size} more divisions.
</Typography>,
);
}
@@ -70,13 +71,15 @@ export function SellDivisionModal(props: IProps): React.ReactElement {
</Select>
<Typography>Division {divisionToSell.name} has:</Typography>
<Typography>
Profit: {formatMoney((divisionToSell.lastCycleRevenue - divisionToSell.lastCycleExpenses) / 10)} / sec{" "}
Profit: <Money money={(divisionToSell.lastCycleRevenue - divisionToSell.lastCycleExpenses) / 10} /> / sec{" "}
</Typography>
<Typography>Cities:{getRecordKeys(divisionToSell.offices).length}</Typography>
<Typography>Warehouses:{getRecordKeys(divisionToSell.warehouses).length}</Typography>
{divisionToSell.makesProducts ?? <Typography>Products: {divisionToSell.products.size}</Typography>}
<br />
<Typography>Sell price: {formatMoney(price)}</Typography>
<Typography>
Sell price: <Money money={price} />
</Typography>
<Button onClick={sellDivision}>Sell division</Button>
</>
</Modal>

View File

@@ -1,33 +1,25 @@
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Material } from "../../Material";
import { SellMaterial } from "../../Actions";
import { Modal } from "../../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { KEY } from "../../../utils/helpers/keyCodes";
import type { Division } from "../../Division";
import type { Material } from "../../Material";
function initialPrice(mat: Material): string {
let val = mat.desiredSellPrice ? mat.desiredSellPrice + "" : "";
if (mat.marketTa2) {
val += " (Market-TA.II)";
} else if (mat.marketTa1) {
val += " (Market-TA.I)";
}
return val;
}
import React, { useState } from "react";
import { Button, FormControlLabel, Switch, TextField, Tooltip, Typography } from "@mui/material";
import { Modal } from "../../../ui/React/Modal";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { SellMaterial } from "../../Actions";
import { KEY } from "../../../utils/helpers/keyCodes";
interface IProps {
open: boolean;
onClose: () => void;
mat: Material;
div: Division;
}
// Create a popup that let the player manage sales of a material
export function SellMaterialModal(props: IProps): React.ReactElement {
const [amt, setAmt] = useState<string>(props.mat.desiredSellAmount + "");
const [price, setPrice] = useState<string>(initialPrice(props.mat));
const [amt, setAmt] = useState<string>(String(props.mat.desiredSellAmount));
const [price, setPrice] = useState<string>(String(props.mat.desiredSellPrice));
function sellMaterial(): void {
try {
@@ -83,7 +75,51 @@ export function SellMaterialModal(props: IProps): React.ReactElement {
onKeyDown={onKeyDown}
/>
<TextField value={price} type="text" placeholder="Sell price" onChange={onPriceChange} onKeyDown={onKeyDown} />
<Button onClick={sellMaterial}>Confirm</Button>
<Button onClick={sellMaterial} style={{ marginLeft: ".5rem", marginRight: ".5rem" }}>
Confirm
</Button>
{props.div.hasResearch("Market-TA.I") && (
<FormControlLabel
style={{ marginRight: "1rem" }}
control={
<Switch checked={props.mat.marketTa1} onChange={(event) => (props.mat.marketTa1 = event.target.checked)} />
}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at market price + markup.
<br />
This overrides player set pricing and gets overriden by an active TA2.
</Typography>
}
>
<Typography>Market-TA.I</Typography>
</Tooltip>
}
/>
)}
{props.div.hasResearch("Market-TA.II") && (
<FormControlLabel
control={
<Switch checked={props.mat.marketTa2} onChange={(event) => (props.mat.marketTa2 = event.target.checked)} />
}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the optimal price such that the
amount sold matches the amount specified.
<br />
This overrides player set pricing and TA1.
</Typography>
}
>
<Typography>Market-TA.II</Typography>
</Tooltip>
}
/>
)}
</Modal>
);
}

View File

@@ -1,39 +1,28 @@
import type { CityName } from "@enums";
import type { Division } from "../../Division";
import type { Product } from "../../Product";
import React, { useState } from "react";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { Product } from "../../Product";
import { SellProduct } from "../../Actions";
import { Button, FormControlLabel, Switch, TextField, Tooltip, Typography } from "@mui/material";
import { Modal } from "../../../ui/React/Modal";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { SellProduct } from "../../Actions";
import { KEY } from "../../../utils/helpers/keyCodes";
import { CityName } from "@enums";
function initialPrice(product: Product, city: CityName): string {
let val = String(product.cityData[city].desiredSellPrice || "");
if (product.marketTa2) {
val += " (Market-TA.II)";
} else if (product.marketTa1) {
val += " (Market-TA.I)";
}
return val;
}
interface IProps {
open: boolean;
onClose: () => void;
product: Product;
city: CityName;
div: Division;
}
// Create a popup that let the player manage sales of a material
export function SellProductModal(props: IProps): React.ReactElement {
const [checked, setChecked] = useState(true);
const [iQty, setQty] = useState<string>((props.product.cityData[props.city].desiredSellAmount ?? "").toString());
const [px, setPx] = useState<string>(initialPrice(props.product, props.city));
const [iQty, setQty] = useState<string>(String(props.product.cityData[props.city].desiredSellAmount));
const [px, setPx] = useState<string>(String(props.product.cityData[props.city].desiredSellPrice));
function onCheckedChange(event: React.ChangeEvent<HTMLInputElement>): void {
setChecked(event.target.checked);
@@ -94,11 +83,62 @@ export function SellProductModal(props: IProps): React.ReactElement {
onKeyDown={onKeyDown}
/>
<TextField value={px} type="text" placeholder="Sell price" onChange={onPriceChange} onKeyDown={onKeyDown} />
<Button onClick={sellProduct}>Confirm</Button>
<Button onClick={sellProduct} style={{ marginLeft: ".5rem", marginRight: ".5rem" }}>
Confirm
</Button>
<FormControlLabel
style={{ marginRight: ".5rem" }}
control={<Switch checked={checked} onChange={onCheckedChange} />}
label={<Typography>Use same 'Sell Amount' for all cities</Typography>}
label={<Typography>Set for all cities</Typography>}
/>
{props.div.hasResearch("Market-TA.I") && (
<FormControlLabel
style={{ marginRight: "1rem" }}
control={
<Switch
checked={props.product.marketTa1}
onChange={(event) => (props.product.marketTa1 = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at market price + markup.
<br />
This overrides player set pricing and gets overriden by an active TA2.
</Typography>
}
>
<Typography>Market-TA.I</Typography>
</Tooltip>
}
/>
)}
{props.div.hasResearch("Market-TA.II") && (
<FormControlLabel
control={
<Switch
checked={props.product.marketTa2}
onChange={(event) => (props.product.marketTa2 = event.target.checked)}
/>
}
label={
<Tooltip
title={
<Typography>
If this is enabled, then this Material will automatically be sold at the optimal price such that the
amount sold matches the amount specified.
<br />
This overrides player set pricing and TA1.
</Typography>
}
>
<Typography>Market-TA.II</Typography>
</Tooltip>
}
/>
)}
</Modal>
);
}

View File

@@ -1,16 +1,17 @@
import React, { useState } from "react";
import { formatMoney } from "../../../ui/formatNumber";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { formatShares } from "../../../ui/formatNumber";
import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "../Context";
import { Corporation } from "../../Corporation";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { Money } from "../../../ui/React/Money";
import { useCorporation } from "../Context";
import * as corpConstants from "../../data/Constants";
import Typography from "@mui/material/Typography";
import { ButtonWithTooltip } from "../../../ui/Components/ButtonWithTooltip";
import { SellShares } from "../../Actions";
import { KEY } from "../../../utils/helpers/keyCodes";
import { NumberInput } from "../../../ui/React/NumberInput";
import { isInteger } from "lodash";
import { sellSharesFailureReason } from "../../helpers";
interface IProps {
open: boolean;
onClose: () => void;
@@ -23,48 +24,28 @@ export function SellSharesModal(props: IProps): React.ReactElement {
const corp = useCorporation();
const [shares, setShares] = useState<number>(NaN);
const disabled = isNaN(shares) || shares <= 0 || shares >= corp.numShares;
function ProfitIndicator(props: { shares: number | null; corp: Corporation }): React.ReactElement {
if (props.shares === null) return <></>;
let text = "";
if (isNaN(props.shares) || props.shares <= 0 || !isInteger(props.shares)) {
text = `ERROR: Invalid value entered for number of shares to sell`;
} else if (props.shares > corp.numShares) {
text = `You don't have this many shares to sell!`;
} else if (props.shares === corp.numShares) {
text = `You can not sell all your shares!`;
} else if (props.shares > 1e14) {
text = `You can't sell more than 100t shares at once!`;
} else {
const stockSaleResults = corp.calculateShareSale(props.shares);
const profit = stockSaleResults[0];
text = `Sell ${props.shares} shares for a total of ${formatMoney(profit)}`;
}
return (
<Typography>
<small>{text}</small>
</Typography>
);
}
const [profit, sharePrice] = corp.calculateShareSale((props.open && shares) || 0);
const disabledText = sellSharesFailureReason(corp, shares);
function sell(): void {
if (disabled) return;
if (disabledText) return;
try {
const profit = SellShares(corp, shares);
props.onClose();
SellShares(corp, shares);
dialogBoxCreate(
<>
Sold {formatMoney(shares)} shares for
<Money money={profit} />. The corporation's stock price fell to&nbsp; <Money money={corp.sharePrice} />
as a result of dilution.
<Typography>
You sold {formatShares(shares)} shares for <Money money={profit} />.
</Typography>
<Typography>
<b>{corp.name}</b>'s stock price fell to <Money money={sharePrice} /> per share.
</Typography>
</>,
);
props.onClose();
props.rerender();
setShares(NaN);
} catch (err) {
dialogBoxCreate(err + "");
dialogBoxCreate(`${err as Error}`);
}
}
@@ -74,32 +55,43 @@ export function SellSharesModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography>
Enter the number of shares you would like to sell. The money from selling your shares will go directly to you
(NOT your Corporation).
<br />
<br />
The amount sold must be an integer between 1 and 100t.
<br />
<br />
Selling your shares will cause your corporation's stock price to fall due to dilution. Furthermore, selling a
large number of shares all at once will have an immediate effect in reducing your stock price.
<br />
<br />
The current price of your company's stock is {formatMoney(corp.sharePrice)}
<Typography component="div">
Enter the number of shares you would like to sell.
<ul>
<li>Selling shares will cause stock price to fall due to market forces.</li>
<li>The money from selling your shares will go directly to you (NOT your Corporation).</li>
<li>
You will not be able to sell shares again (or dissolve the corporation) for{" "}
<b>{corp.convertCooldownToString(corpConstants.sellSharesCooldown)}</b>.
</li>
</ul>
You currently have {formatShares(corp.numShares)} shares of <b>{corp.name}</b> stock, valued at{" "}
<Money money={corp.sharePrice} /> per share.
</Typography>
<br />
<NumberInput
defaultValue={shares || ""}
variant="standard"
autoFocus
placeholder="Shares to sell"
onChange={setShares}
onKeyDown={onKeyDown}
/>
<Button disabled={disabled} onClick={sell} sx={{ mx: 1 }}>
<ButtonWithTooltip disabledTooltip={disabledText} onClick={sell}>
Sell shares
</Button>
<ProfitIndicator shares={shares} corp={corp} />
</ButtonWithTooltip>
<br />
<Typography sx={{ minHeight: "3em" }}>
{!shares ? null : disabledText ? (
disabledText
) : (
<>
You will receive <Money money={profit} />.
<br />
<b>{corp.name}</b>'s stock price will fall to <Money money={sharePrice} /> per share.
</>
)}
</Typography>
</Modal>
);
}

View File

@@ -95,7 +95,7 @@ export function SmartSupplyModal(props: IProps): React.ReactElement {
label={<Typography>Enable Smart Supply</Typography>}
/>
<br />
<Typography>
<Typography component="div">
Options:
<ul>
<li>

View File

@@ -3,7 +3,7 @@ import { formatMultiplier, formatPercent } from "../../../ui/formatNumber";
import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import { OfficeSpace } from "../../OfficeSpace";
import { ThrowParty } from "../../Actions";
import { Money } from "../../../ui/React/Money";
import { MoneyCost } from "../MoneyCost";
import { Modal } from "../../../ui/React/Modal";
import { useCorporation } from "../Context";
import Typography from "@mui/material/Typography";
@@ -38,8 +38,8 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
dialogBoxCreate("You don't have enough company funds to throw a party!");
} else {
const mult = ThrowParty(corp, props.office, cost);
// Each 5% multiplier gives an extra flat +1 to morale to make recovering from low morale easier.
const increase = mult > 1 ? (mult - 1) * 0.2 : 0;
// Each 10% multiplier gives an extra flat +1 to morale to make recovering from low morale easier.
const increase = mult > 1 ? (mult - 1) * 0.1 : 0;
if (mult > 0) {
dialogBoxCreate(
@@ -59,7 +59,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
if (isNaN(cost) || cost < 0) return <Typography>Invalid value entered!</Typography>;
return (
<Typography>
Throwing this party will cost a total of <Money money={totalCost} />
Throwing this party will cost a total of <MoneyCost money={totalCost} corp={corp} />
</Typography>
);
}

View File

@@ -69,6 +69,12 @@ export function CorporationDev(): React.ReactElement {
});
}
function resetCorporationCooldowns(): void {
if (!Player.corporation) return;
Player.corporation.shareSaleCooldown = 0;
Player.corporation.issueNewSharesCooldown = 0;
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -117,6 +123,11 @@ export function CorporationDev(): React.ReactElement {
<Button onClick={addCorporationResearch}>Tons of research</Button>
</td>
</tr>
<tr>
<td>
<Button onClick={resetCorporationCooldowns}>Reset stock cooldowns</Button>
</td>
</tr>
</tbody>
</table>
</AccordionDetails>

View File

@@ -1,3 +1,11 @@
# Bladeburners
PLACEHOLDER
Bladeburner divisions were created internationally in the mid-21st century. Tasked with monitoring and "managing" rogue Synthoids, which may have blended into society and are sometimes considered a threat, working for Bladeburners gives access to powerful enhancements and a goal of destroying [BitNodes](bitnodes.md) by operating against increasingly strong Synthoid opposition.
## Bladeburner Skills:
Designed to improve a human agent's capabilities beyond their natural limits, Bladeburner skills add a persistent bonus while in the [BitNode](bitnodes.md) where they were purchased. Bladeburner skills are purchased with Bladeburner skill points, not money.
## Faction and Rank:
Bladeburners also offer unique [Augmentations](../basic/augmentations.md) and a [Faction](factions.md) invite to agents who "put in the work" to gain a small amount of rank. While Bladeburner rank and skill points persist after any augmentation installs, faction reputation will be reset. Bladeburner faction reputation can only be gained through Bladeburner actions.

View File

@@ -1,5 +1,35 @@
# Gang
# Gangs
Managing a gang can be very rewarding. By rising above all other gangs you get access to almost all augmentations in the game.
In the wake of crisis and war, Gang activity surged. Stronger than ever in a lawless world - Enhanced with fantastic technology, no longer held back by ethics and morals, 'free from the shadows'....
PLACEHOLDER
Seen by most of the population as nihilistic, murderous and vile, occassional rumors suggest Gangs sometimes involve themselves with vigilanteism, hacktivism, perhaps even plotting against The Enders, seeking to destroy a world they cannot save.
## Starting and Recruiting
Outside of [BitNode-2](bitnodes.md) gangs require much more crime and heartbreak to create, but can still be a great help. Creating a Gang in other [BitNodes](bitnodes.md) will offer more [Augmentations](../basic/augmentations.md) than other [Factions](../basic/factions.md), but they will not be a way to destroy the [BitNode](bitnodes.md) alone.
After creating a gang, you will be able to start recruiting, adding members to your gang as you gain Respect. While in a BitNode, your gang and gang member stats will not reset if you install augmentations.
## Respect
Earned as your gang members complete tasks, Respect affects your gang's productivity, including your Faction Reputation (needed to buy augmentations from your Gang Faction), and the number of recruits you can have. An individual gang member's Respect is lost or reset if they Ascend, or are killed in a Territory Warfare clash.
## Ascending
When experienced enough, gang members are offered Ascension, a permanent boost to their stat multipliers at the cost of resetting their base stats and equipment to 0, and reducing your Gang Reputation by the same amount as that member had earned since they last Ascended.
## Equipping and Managing
Buying Equipment for a gang member will give them a stat boost until they Ascend or are killed, at which point most equipment will reset.
Augmentations you install on gang members (in the Gang Equipment subpage) do not reset when they Ascend.
Active gang members earn stats, respect and money based on their current stats, their equipment, and the effects of Ascending.
## Wanted, Territory and Clashes
Your gang's "Wanted Level" can make tasks much less productive, and is affected by the tasks assigned to gang members. "Ethical Hacking" or "Vigilante Justice" tasks can lower Wanted Level.
"Territory Warfare" is a special task that builds Power for your gang. If "Territory Clashes" are enabled [see the Territory subpage of your Gang page], members have a chance to win or lose territory by clashing with other gangs. The % of Territory you control affects most aspects of your gang productivity.
Note that gang members can die during clashes, even if your gang wins.

View File

@@ -0,0 +1,27 @@
# How Scripts Work Offline
The scripts you write and execute in the BitVerse are real, working JavaScript.
For this reason, it is not possible for Bitburner scripts to run when
- the game is closed
- the browser tab is inactive (if playing Bitburner in a web browser)
- or your system is sleeping
all of which we call being "offline" for game purposes.
It is important to know that logic such as `if`/`else` statements and most functions such as `ns.purchaseHacknetNode()`, `ns.hack()`, and `ns.nuke()` will not work while the game is offline.
However, scripts WILL continue to generate money and hacking exp for you while offline.
This offline production is based on the average online production of all your hacking scripts since your last augmentation, as shown on your Active Scripts page.
`ns.grow()` and `ns.weaken()` methods are also applied when the game is offline, although at a slower rate.
This is done by having each script track the rate at which the `ns.grow()` and `ns.weaken()` commands are called while online,
then determining how many calls would have been made while offline, and their effect is applied.
Also, note that because of the way the JavaScript engine works, whenever you reload or re-open the game all of your Active Scripts will start again from the BEGINNING of their code. The game does not keep track of where exactly the execution of a script is when it saves/loads.
# Bonus Time
Because of the above details, some activities in Bitburner accumulate "Bonus Time" while the game is closed or in an inactive browser tab . For mechanics that have a Bonus Time effect, the rate of the associated activity or task is significantly increased.
For example if a certain [Bladeburner](bladeburners.md) contract requires 15 seconds to complete under normal conditions, the same task will be finished instead in 3 seconds if the Bonus Time effect is 5x. The specific details and effects of Bonus Time vary by mechanic.

Some files were not shown because too many files have changed in this diff Show More