merge latest dev

This commit is contained in:
phyzical
2022-02-15 20:24:24 +08:00
298 changed files with 7864 additions and 2831 deletions
+6
View File
@@ -1,2 +1,8 @@
// Defined by webpack on startup or compilation
declare let __COMMIT_HASH__: string;
// When using file-loader, we'll get a path to the resource
declare module "*.png" {
const value: string;
export default value;
}
+4 -2
View File
@@ -380,7 +380,7 @@ export const achievements: IMap<Achievement> = {
},
TRAVEL: {
...achievementData["TRAVEL"],
Icon: "travel",
Icon: "TRAVEL",
Condition: () => Player.city !== CityName.Sector12,
},
WORKOUT: {
@@ -553,7 +553,9 @@ export const achievements: IMap<Achievement> = {
...achievementData["MAX_CACHE"],
Icon: "HASHNETCAP",
Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) && Player.hashManager.hashes === Player.hashManager.capacity,
Condition: () => hasHacknetServers(Player) &&
Player.hashManager.hashes === Player.hashManager.capacity &&
Player.hashManager.capacity > 0,
},
SLEEVE_8: {
...achievementData["SLEEVE_8"],
+2 -2
View File
@@ -22,12 +22,12 @@ export function loadGlobalAliases(saveString: string): void {
// Prints all aliases to terminal
export function printAliases(): void {
for (const name in Aliases) {
for (const name of Object.keys(Aliases)) {
if (Aliases.hasOwnProperty(name)) {
Terminal.print("alias " + name + "=" + Aliases[name]);
}
}
for (const name in GlobalAliases) {
for (const name of Object.keys(GlobalAliases)) {
if (GlobalAliases.hasOwnProperty(name)) {
Terminal.print("global alias " + name + "=" + GlobalAliases[name]);
}
+1 -1
View File
@@ -526,7 +526,7 @@ export class Augmentation {
// Adds this Augmentation to all Factions
addToAllFactions(): void {
for (const fac in Factions) {
for (const fac of Object.keys(Factions)) {
if (Factions.hasOwnProperty(fac)) {
const facObj: Faction | null = Factions[fac];
if (facObj == null) {
+4 -4
View File
@@ -112,7 +112,7 @@ function getRandomBonus(): any {
}
function initAugmentations(): void {
for (const name in Factions) {
for (const name of Object.keys(Factions)) {
if (Factions.hasOwnProperty(name)) {
Factions[name].augmentations = [];
}
@@ -2050,7 +2050,7 @@ function initAugmentations(): void {
info:
"A brain implant carefully assembled around the synapses, which " +
"micromanages the activity and levels of various neuroreceptor " +
"chemicals and modulates electrical acvitiy to optimize concentration, " +
"chemicals and modulates electrical activity to optimize concentration, " +
"allowing the user to multitask much more effectively.",
stats: (
<>
@@ -2498,7 +2498,7 @@ function initAugmentations(): void {
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]],
Player.queuedAugmentations.length,
);
for (const name in Augmentations) {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= mult;
}
@@ -2525,7 +2525,7 @@ function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void
const augObj = Augmentations[aug.name];
// Apply multipliers
for (const mult in augObj.mults) {
for (const mult of Object.keys(augObj.mults)) {
const v = Player.getMult(mult) * augObj.mults[mult];
Player.setMult(mult, v);
}
@@ -26,7 +26,7 @@ export function InstalledAugmentations(): React.ReactElement {
if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {
sourceAugs.sort((aug1, aug2) => {
return aug1.name <= aug2.name ? -1 : 1;
return aug1.name.localeCompare(aug2.name);
});
}
+1 -1
View File
@@ -17,7 +17,7 @@ function calculateAugmentedStats(): any {
const augP: any = {};
for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name];
for (const mult in augObj.mults) {
for (const mult of Object.keys(augObj.mults)) {
const v = augP[mult] ? augP[mult] : 1;
augP[mult] = v * augObj.mults[mult];
}
+1 -1
View File
@@ -578,7 +578,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
if (p.bitNodeN == null) {
p.bitNodeN = 1;
}
for (const mult in BitNodeMultipliers) {
for (const mult of Object.keys(BitNodeMultipliers)) {
if (BitNodeMultipliers.hasOwnProperty(mult)) {
BitNodeMultipliers[mult] = 1;
}
+1 -1
View File
@@ -85,7 +85,7 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
</Typography>
}
>
<span onClick={() => setPortalOpen(true)} className={cssClass}>
<span onClick={() => setPortalOpen(true)} className={cssClass} aria-label={`enter-bitnode-${bitNode.number.toString()}`}>
<b>O</b>
</span>
</Tooltip>
+3 -3
View File
@@ -117,7 +117,7 @@ export class Action implements IAction {
// Check to make sure weights are summed properly
let sum = 0;
for (const weight in this.weights) {
for (const weight of Object.keys(this.weights)) {
if (this.weights.hasOwnProperty(weight)) {
sum += this.weights[weight];
}
@@ -131,7 +131,7 @@ export class Action implements IAction {
);
}
for (const decay in this.decays) {
for (const decay of Object.keys(this.decays)) {
if (this.decays.hasOwnProperty(decay)) {
if (this.decays[decay] > 1) {
throw new Error(
@@ -240,7 +240,7 @@ export class Action implements IAction {
}
let difficulty = this.getDifficulty();
let competence = 0;
for (const stat in this.weights) {
for (const stat of Object.keys(this.weights)) {
if (this.weights.hasOwnProperty(stat)) {
const playerStatLvl = Player.queryStatFromString(stat);
const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1);
+2 -2
View File
@@ -135,7 +135,7 @@ export class Bladeburner implements IBladeburner {
// Can't start a BlackOp if you haven't done the one before it
const blackops = [];
for (const nm in BlackOperations) {
for (const nm of Object.keys(BlackOperations)) {
if (BlackOperations.hasOwnProperty(nm)) {
blackops.push(nm);
}
@@ -1074,7 +1074,7 @@ export class Bladeburner implements IBladeburner {
updateSkillMultipliers(): void {
this.resetSkillMultipliers();
for (const skillName in this.skills) {
for (const skillName of Object.keys(this.skills)) {
if (this.skills.hasOwnProperty(skillName)) {
const skill = Skills[skillName];
if (skill == null) {
+1 -1
View File
@@ -12,7 +12,7 @@ interface IProps {
export function BlackOpList(props: IProps): React.ReactElement {
let blackops: BlackOperation[] = [];
for (const blackopName in BlackOperations) {
for (const blackopName of Object.keys(BlackOperations)) {
if (BlackOperations.hasOwnProperty(blackopName)) {
blackops.push(BlackOperations[blackopName]);
}
+1 -1
View File
@@ -12,7 +12,7 @@ interface IProps {
export function GeneralActionList(props: IProps): React.ReactElement {
const actions: Action[] = [];
for (const name in GeneralActions) {
for (const name of Object.keys(GeneralActions)) {
if (GeneralActions.hasOwnProperty(name)) {
actions.push(GeneralActions[name]);
}
+2 -1
View File
@@ -14,6 +14,7 @@ import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
const MAX_BET = 100e6;
export const DECK_COUNT = 5; // 5-deck multideck
enum Result {
Pending = "",
@@ -45,7 +46,7 @@ export class Blackjack extends Game<Props, State> {
constructor(props: Props) {
super(props);
this.deck = new Deck(5); // 5-deck multideck
this.deck = new Deck(DECK_COUNT);
const initialBet = 1e6;
+2 -9
View File
@@ -40,13 +40,13 @@ const strategies: {
} = {
Red: {
match: (n: number): boolean => {
if (n === 0) return false;
return redNumbers.includes(n);
},
payout: 1,
},
Black: {
match: (n: number): boolean => {
if (n === 0) return false;
return !redNumbers.includes(n);
},
payout: 1,
@@ -118,12 +118,6 @@ export function Roulette(props: IProps): React.ReactElement {
const [status, setStatus] = useState<string | JSX.Element>("waiting");
const [n, setN] = useState(0);
const [lock, setLock] = useState(true);
const [strategy, setStrategy] = useState<Strategy>({
payout: 0,
match: (): boolean => {
return false;
},
});
useEffect(() => {
const i = window.setInterval(step, 50);
@@ -156,13 +150,12 @@ export function Roulette(props: IProps): React.ReactElement {
return `${n}${color}`;
}
function play(s: Strategy): void {
function play(strategy: Strategy): void {
if (reachedLimit(props.p)) return;
setCanPlay(false);
setLock(false);
setStatus("playing");
setStrategy(s);
setTimeout(() => {
let n = Math.floor(rng.random() * 37);
+7 -8
View File
@@ -157,20 +157,20 @@ export function SlotMachine(props: IProps): React.ReactElement {
function step(): void {
let stoppedOne = false;
const copy = index.slice();
for (const i in copy) {
for (let i = 0; i < copy.length; i++) {
if (copy[i] === locks[i] && !stoppedOne) continue;
copy[i] = (copy[i] + 1) % symbols.length;
copy[i] = (copy[i] - 1 >= 0) ? copy[i] - 1 : symbols.length - 1;
stoppedOne = true;
}
setIndex(copy);
if (stoppedOne && copy.every((e, i) => e === locks[i])) {
checkWinnings();
checkWinnings(getTable(copy, symbols));
}
}
function getTable(): string[][] {
function getTable(index:number[], symbols:string[]): string[][] {
return [
[
symbols[(index[0] + symbols.length - 1) % symbols.length],
@@ -209,8 +209,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
]);
}
function checkWinnings(): void {
const t = getTable();
function checkWinnings(t:string[][]): void {
const getPaylineData = function (payline: number[][]): string[] {
const data = [];
for (const point of payline) {
@@ -267,7 +266,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
setInvestment(investment);
}
const t = getTable();
const t = getTable(index, symbols);
// prettier-ignore
return (
<>
@@ -288,7 +287,7 @@ export function SlotMachine(props: IProps): React.ReactElement {
disabled={!canPlay}
>Spin!</Button>)}}
/>
<Typography variant="h4">{status}</Typography>
<Typography>Pay lines</Typography>
+1 -1
View File
@@ -26,7 +26,7 @@ export function initCompanies(): void {
});
// Reset data
for (const companyName in Companies) {
for (const companyName of Object.keys(Companies)) {
const company = Companies[companyName];
const oldCompany = oldCompanies[companyName];
if (!(oldCompany instanceof Company)) {
+11 -78
View File
@@ -111,8 +111,8 @@ export const CONSTANTS: {
TotalNumBitNodes: number;
LatestUpdate: string;
} = {
VersionString: "1.3.0",
VersionNumber: 9,
VersionString: "1.4.0",
VersionNumber: 10,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@@ -273,89 +273,22 @@ export const CONSTANTS: {
TotalNumBitNodes: 24,
LatestUpdate: `
v1.3.0 - 2022-01-04 Cleaning up
-------------------------------
v1.4.0 - 2022-01-18 Sharing is caring
-------------------------------------
** External IDE integration **
** Computer sharing **
* The Steam version has a webserver that allows integration with external IDEs.
A VSCode extension is available on the market place. (The documentation for the ext. isn't
written yet)
* A new mechanic has been added, it's is invoked by calling the new function 'share'.
This mechanic helps you farm reputation faster.
** Source-Files **
* SF4 has been reworked.
* New SF -1.
** gang **
** UI **
* Installing augs means losing a little bit of ascension multipliers.
* Fix some edge case with skill bat tooltips (@MartinFournier)
* Made some background match theme color (@Kejikus)
* Fix problem with script editor height not adjusting correctly (@billyvg)
* Fix some formatting issues with Bladeburner (@MartinFournier, @nickofolas)
* Fix some functions like 'alert' format messages better (@MageKing17)
* Many community themes added.
* New script editor theme (@Hedrauta, @Dexalt142)
* Improvements to tail windows (@theit8514)
* Training is more consise (@mikomyazaki)
* Fix Investopedia not displaying properly (@JotaroS)
* Remove alpha from theme editor (@MartinFournier)
* Fix corporation tooltip not displaying properly (@MartinFournier)
* Add tooltip on backdoored location names (@MartinFournier)
* Allow toasts to be dismissed by clicking them (@nickofolas)
* Darkweb item listing now shows what you own. (@hexnaught)
** Bug fix **
* Fix unit tests (@MartinFournier)
* Fixed issue with 'cat' and 'read' not finding foldered files (@Nick-Colclasure)
* Buying on the dark web will remove incomplete exe (@hexnaught)
* Fix bug that would cause the game to crash trying to go to a job without a job (@hexnaught)
* purchaseServer validation (@nickofolas)
* Script Editor focuses code when changing tab (@MartinFournier)
* Fix script editor for .txt files (@65-7a)
* Fix 'buy' command not displaying correctly. (@hexnaught)
* Fix hackAnalyzeThread returning NaN (@mikomyazaki)
* Electron handles exceptions better (@MageKing17)
* Electron will handle 'unresponsive' event and present the opportunity to reload the game with no scripts (@MartinFournier)
* Fix 'cp' between folders (@theit8514)
* Fix throwing null/undefined errors (@nickofolas)
* Allow shortcuts to work when unfocused (@MageKing17)
* Fix some dependency issue (@locriacyber)
* Fix corporation state returning an object instead of a string (@antonvmironov)
* Fix 'mv' overwriting files (@theit8514)
* Fix joesguns not being influenced by hack/grow (@dou867, @MartinFournier)
* Added warning when opening external links. (@MartinFournier)
* Prevent applying for positions that aren't offered (@TheMas3212)
* Import has validation (@MartinFournier)
** There's more but I'm going to write it later. **
** Misc. **
* Added vim mode to script editor (@billyvg)
* Clean up script editor code (@Rez855)
* 'cat' works on scripts (@65-7a)
* Add wordWrap for Monaco (@MartinFournier)
* Include map bundles in electron for easier debugging (@MartinFournier)
* Fix importing very large files (@MartinFournier)
* Cache program blob, reducing ram usage of the game (@theit8514)
* Dev menu can set server to $0 (@mikomyazaki)
* 'backdoor' allows direct connect (@mikomyazaki)
* Github workflow work (@MartinFournier)
* workForFaction / workForCompany have a new parameter (@theit8514)
* Alias accept single quotes (@sporkwitch, @FaintSpeaker)
* Add grep options to 'ps' (@maxtimum)
* Added buy all option to 'buy' (@anthonydroberts)
* Added more shortcuts to terminal input (@Frank-py)
* Refactor some port code (@ErzengelLichtes)
* Settings to control GiB vs GB (@ErzengelLichtes)
* Add electron option to export save game (@MartinFournier)
* Electron improvements (@MartinFournier)
* Expose some notifications functions to electron (@MartinFournier)
* Documentation (@MartinFournier, @cyn, @millennIumAMbiguity, @2PacIsAlive,
@TheCoderJT, @hexnaught, @sschmidTU, @FOLLGAD, @Hedrauta, @Xynrati,
@mikomyazaki, @Icehawk78, @aaronransley, @TheMas3212, @Hedrauta, @alkemann,
@ReeseJones, @amclark42, @thadguidry, @jasonhaxstuff, @pan-kuleczka, @jhollowe,
@ApatheticsAnonymous, @erplsf, @daanflore, @nickofolas, @Kebap, @smolgumball,
@woody-lam-cwl)
* Nerf noodle bar.
`,
};
+1 -1
View File
@@ -307,7 +307,7 @@ export class Corporation {
if (upgN === 1) {
for (let i = 0; i < this.divisions.length; ++i) {
const industry = this.divisions[i];
for (const city in industry.warehouses) {
for (const city of Object.keys(industry.warehouses)) {
const warehouse = industry.warehouses[city];
if (warehouse === 0) continue;
if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) {
+28 -28
View File
@@ -378,7 +378,7 @@ export class Industry implements IIndustry {
updateWarehouseSizeUsed(warehouse: Warehouse): void {
warehouse.updateMaterialSizeUsed();
for (const prodName in this.products) {
for (const prodName of Object.keys(this.products)) {
if (this.products.hasOwnProperty(prodName)) {
const prod = this.products[prodName];
if (prod === undefined) continue;
@@ -414,7 +414,7 @@ export class Industry implements IIndustry {
// Process offices (and the employees in them)
let employeeSalary = 0;
for (const officeLoc in this.offices) {
for (const officeLoc of Object.keys(this.offices)) {
const office = this.offices[officeLoc];
if (office === 0) continue;
if (office instanceof OfficeSpace) {
@@ -473,7 +473,7 @@ export class Industry implements IIndustry {
if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) {
const wh = this.warehouses[CorporationConstants.Cities[i]];
if (wh === 0) continue;
for (const name in reqMats) {
for (const name of Object.keys(reqMats)) {
if (reqMats.hasOwnProperty(name)) {
wh.materials[name].processMarket();
}
@@ -496,7 +496,7 @@ export class Industry implements IIndustry {
// Process change in demand and competition for this industry's products
processProductMarket(marketCycles = 1): void {
// Demand gradually decreases, and competition gradually increases
for (const name in this.products) {
for (const name of Object.keys(this.products)) {
if (this.products.hasOwnProperty(name)) {
const product = this.products[name];
if (product === undefined) continue;
@@ -534,7 +534,7 @@ export class Industry implements IIndustry {
}
const warehouse = this.warehouses[city];
if (warehouse === 0) continue;
for (const matName in warehouse.materials) {
for (const matName of Object.keys(warehouse.materials)) {
if (warehouse.materials.hasOwnProperty(matName)) {
const mat = warehouse.materials[matName];
mat.imp = 0;
@@ -555,7 +555,7 @@ export class Industry implements IIndustry {
switch (this.state) {
case "PURCHASE": {
/* Process purchase of materials */
for (const matName in warehouse.materials) {
for (const matName of Object.keys(warehouse.materials)) {
if (!warehouse.materials.hasOwnProperty(matName)) continue;
const mat = warehouse.materials[matName];
let buyAmt = 0;
@@ -577,7 +577,7 @@ export class Industry implements IIndustry {
// smart supply
const smartBuy: { [key: string]: number | undefined } = {};
for (const matName in warehouse.materials) {
for (const matName of Object.keys(warehouse.materials)) {
if (!warehouse.materials.hasOwnProperty(matName)) continue;
if (!warehouse.smartSupplyEnabled || !Object.keys(this.reqMats).includes(matName)) continue;
const mat = warehouse.materials[matName];
@@ -594,7 +594,7 @@ export class Industry implements IIndustry {
// Find which material were trying to create the least amount of product with.
let worseAmt = 1e99;
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
const buyAmt = smartBuy[matName];
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
const reqMat = this.reqMats[matName];
@@ -604,7 +604,7 @@ export class Industry implements IIndustry {
}
// Align all the materials to the smallest amount.
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
const reqMat = this.reqMats[matName];
if (reqMat === undefined) throw new Error(`reqMat "${matName}" is undefined`);
smartBuy[matName] = worseAmt * reqMat;
@@ -612,7 +612,7 @@ export class Industry implements IIndustry {
// Calculate the total size of all things were trying to buy
let totalSize = 0;
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
const buyAmt = smartBuy[matName];
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
totalSize += buyAmt * MaterialSizes[matName];
@@ -621,7 +621,7 @@ export class Industry implements IIndustry {
// Shrink to the size of available space.
const freeSpace = warehouse.size - warehouse.sizeUsed;
if (totalSize > freeSpace) {
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
const buyAmt = smartBuy[matName];
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
smartBuy[matName] = Math.floor((buyAmt * freeSpace) / totalSize);
@@ -629,7 +629,7 @@ export class Industry implements IIndustry {
}
// Use the materials already in the warehouse if the option is on.
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
if (!warehouse.smartSupplyUseLeftovers[matName]) continue;
const mat = warehouse.materials[matName];
const buyAmt = smartBuy[matName];
@@ -638,7 +638,7 @@ export class Industry implements IIndustry {
}
// buy them
for (const matName in smartBuy) {
for (const matName of Object.keys(smartBuy)) {
const mat = warehouse.materials[matName];
const buyAmt = smartBuy[matName];
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
@@ -675,7 +675,7 @@ export class Industry implements IIndustry {
for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
totalMatSize += MaterialSizes[this.prodMats[tmp]];
}
for (const reqMatName in this.reqMats) {
for (const reqMatName of Object.keys(this.reqMats)) {
const normQty = this.reqMats[reqMatName];
if (normQty === undefined) continue;
totalMatSize -= MaterialSizes[reqMatName] * normQty;
@@ -695,7 +695,7 @@ export class Industry implements IIndustry {
// Make sure we have enough resource to make our materials
let producableFrac = 1;
for (const reqMatName in this.reqMats) {
for (const reqMatName of Object.keys(this.reqMats)) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
const reqMat = this.reqMats[reqMatName];
if (reqMat === undefined) continue;
@@ -712,7 +712,7 @@ export class Industry implements IIndustry {
// Make our materials if they are producable
if (producableFrac > 0 && prod > 0) {
for (const reqMatName in this.reqMats) {
for (const reqMatName of Object.keys(this.reqMats)) {
const reqMat = this.reqMats[reqMatName];
if (reqMat === undefined) continue;
const reqMatQtyNeeded = reqMat * prod * producableFrac;
@@ -729,7 +729,7 @@ export class Industry implements IIndustry {
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3;
}
} else {
for (const reqMatName in this.reqMats) {
for (const reqMatName of Object.keys(this.reqMats)) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
warehouse.materials[reqMatName].prd = 0;
}
@@ -745,7 +745,7 @@ export class Industry implements IIndustry {
//If this doesn't produce any materials, then it only creates
//Products. Creating products will consume materials. The
//Production of all consumed materials must be set to 0
for (const reqMatName in this.reqMats) {
for (const reqMatName of Object.keys(this.reqMats)) {
warehouse.materials[reqMatName].prd = 0;
}
}
@@ -753,7 +753,7 @@ export class Industry implements IIndustry {
case "SALE":
/* Process sale of materials */
for (const matName in warehouse.materials) {
for (const matName of Object.keys(warehouse.materials)) {
if (warehouse.materials.hasOwnProperty(matName)) {
const mat = warehouse.materials[matName];
if (mat.sCost < 0 || mat.sllman[0] === false) {
@@ -884,7 +884,7 @@ export class Industry implements IIndustry {
break;
case "EXPORT":
for (const matName in warehouse.materials) {
for (const matName of Object.keys(warehouse.materials)) {
if (warehouse.materials.hasOwnProperty(matName)) {
const mat = warehouse.materials[matName];
mat.totalExp = 0; //Reset export
@@ -996,7 +996,7 @@ export class Industry implements IIndustry {
//Create products
if (this.state === "PRODUCTION") {
for (const prodName in this.products) {
for (const prodName of Object.keys(this.products)) {
const prod = this.products[prodName];
if (prod === undefined) continue;
if (!prod.fin) {
@@ -1028,7 +1028,7 @@ export class Industry implements IIndustry {
}
//Produce Products
for (const prodName in this.products) {
for (const prodName of Object.keys(this.products)) {
if (this.products.hasOwnProperty(prodName)) {
const prod = this.products[prodName];
if (prod instanceof Product && prod.fin) {
@@ -1070,7 +1070,7 @@ export class Industry implements IIndustry {
//Calculate net change in warehouse storage making the Products will cost
let netStorageSize = product.siz;
for (const reqMatName in product.reqMats) {
for (const reqMatName of Object.keys(product.reqMats)) {
if (product.reqMats.hasOwnProperty(reqMatName)) {
const normQty = product.reqMats[reqMatName];
netStorageSize -= MaterialSizes[reqMatName] * normQty;
@@ -1087,7 +1087,7 @@ export class Industry implements IIndustry {
//Make sure we have enough resources to make our Products
let producableFrac = 1;
for (const reqMatName in product.reqMats) {
for (const reqMatName of Object.keys(product.reqMats)) {
if (product.reqMats.hasOwnProperty(reqMatName)) {
const req = product.reqMats[reqMatName] * prod;
if (warehouse.materials[reqMatName].qty < req) {
@@ -1098,7 +1098,7 @@ export class Industry implements IIndustry {
//Make our Products if they are producable
if (producableFrac > 0 && prod > 0) {
for (const reqMatName in product.reqMats) {
for (const reqMatName of Object.keys(product.reqMats)) {
if (product.reqMats.hasOwnProperty(reqMatName)) {
const reqMatQtyNeeded = product.reqMats[reqMatName] * prod * producableFrac;
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
@@ -1117,7 +1117,7 @@ export class Industry implements IIndustry {
case "SALE": {
//Process sale of Products
product.pCost = 0; //Estimated production cost
for (const reqMatName in product.reqMats) {
for (const reqMatName of Object.keys(product.reqMats)) {
if (product.reqMats.hasOwnProperty(reqMatName)) {
product.pCost += product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost;
}
@@ -1253,7 +1253,7 @@ export class Industry implements IIndustry {
}
discontinueProduct(product: Product): void {
for (const productName in this.products) {
for (const productName of Object.keys(this.products)) {
if (this.products.hasOwnProperty(productName)) {
if (product === this.products[productName]) {
delete this.products[productName];
@@ -1358,7 +1358,7 @@ export class Industry implements IIndustry {
// Since ResearchTree data isnt saved, we'll update the Research Tree data
// based on the stored 'researched' property in the Industry object
if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) {
for (const research in this.researched) {
for (const research of Object.keys(this.researched)) {
researchTree.research(research);
}
}
+1 -1
View File
@@ -103,7 +103,7 @@ export class OfficeSpace {
calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void {
//Reset
for (const name in this.employeeProd) {
for (const name of Object.keys(this.employeeProd)) {
this.employeeProd[name] = 0;
}
+2 -2
View File
@@ -198,7 +198,7 @@ export class Product {
//Calculate the product's required materials
//For now, just set it to be the same as the requirements to make materials
for (const matName in industry.reqMats) {
for (const matName of Object.keys(industry.reqMats)) {
if (industry.reqMats.hasOwnProperty(matName)) {
const reqMat = industry.reqMats[matName];
if (reqMat === undefined) continue;
@@ -209,7 +209,7 @@ export class Product {
//Calculate the product's size
//For now, just set it to be the same size as the requirements to make materials
this.siz = 0;
for (const matName in industry.reqMats) {
for (const matName of Object.keys(industry.reqMats)) {
const reqMat = industry.reqMats[matName];
if (reqMat === undefined) continue;
this.siz += MaterialSizes[matName] * reqMat;
+1 -1
View File
@@ -85,7 +85,7 @@ export class Warehouse {
// Re-calculate how much space is being used by this Warehouse
updateMaterialSizeUsed(): void {
this.sizeUsed = 0;
for (const matName in this.materials) {
for (const matName of Object.keys(this.materials)) {
const mat = this.materials[matName];
if (MaterialSizes.hasOwnProperty(matName)) {
this.sizeUsed += mat.qty * MaterialSizes[matName];
+35 -39
View File
@@ -26,6 +26,7 @@ import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import { TableCell } from "../../ui/React/Table";
import { Box } from "@mui/material";
interface IProps {
office: OfficeSpace;
@@ -430,51 +431,46 @@ export function IndustryOffice(props: IProps): React.ReactElement {
<Typography>
Size: {props.office.employees.length} / {props.office.size} employees
</Typography>
<Tooltip title={<Typography>Automatically hires an employee and gives him/her a random name</Typography>}>
<span>
<Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}>
Hire Employee
</Button>
</span>
</Tooltip>
<br />
<Tooltip title={<Typography>Upgrade the office's size so that it can hold more employees!</Typography>}>
<span>
<Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}>
Upgrade size
</Button>
</span>
</Tooltip>
<UpgradeOfficeSizeModal
rerender={props.rerender}
office={props.office}
open={upgradeOfficeSizeOpen}
onClose={() => setUpgradeOfficeSizeOpen(false)}
/>
{!division.hasResearch("AutoPartyManager") && (
<>
<Tooltip
title={<Typography>Throw an office party to increase your employee's morale and happiness</Typography>}
>
<span>
<Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}>
Throw Party
</Button>
</span>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr', width: 'fit-content' }}>
<Box sx={{ gridTemplateColumns: 'repeat(3, 1fr)' }}>
<Tooltip title={<Typography>Automatically hires an employee and gives him/her a random name</Typography>}>
<Button disabled={props.office.atCapacity()} onClick={autohireEmployeeButtonOnClick}>
Hire Employee
</Button>
</Tooltip>
<ThrowPartyModal
<Tooltip title={<Typography>Upgrade the office's size so that it can hold more employees!</Typography>}>
<Button disabled={corp.funds < 0} onClick={() => setUpgradeOfficeSizeOpen(true)}>
Upgrade size
</Button>
</Tooltip>
<UpgradeOfficeSizeModal
rerender={props.rerender}
office={props.office}
open={throwPartyOpen}
onClose={() => setThrowPartyOpen(false)}
open={upgradeOfficeSizeOpen}
onClose={() => setUpgradeOfficeSizeOpen(false)}
/>
</>
)}
<br />
{!division.hasResearch("AutoPartyManager") && (
<>
<Tooltip
title={<Typography>Throw an office party to increase your employee's morale and happiness</Typography>}
>
<Button disabled={corp.funds < 0} onClick={() => setThrowPartyOpen(true)}>
Throw Party
</Button>
</Tooltip>
<ThrowPartyModal
rerender={props.rerender}
office={props.office}
open={throwPartyOpen}
onClose={() => setThrowPartyOpen(false)}
/>
</>
)}
<SwitchButton manualMode={employeeManualAssignMode} switchMode={setEmployeeManualAssignMode} />
</Box>
<SwitchButton manualMode={employeeManualAssignMode} switchMode={setEmployeeManualAssignMode} />
</Box>
{employeeManualAssignMode ? (
<ManualManagement rerender={props.rerender} office={props.office} />
) : (
+1 -1
View File
@@ -215,7 +215,7 @@ function Upgrades(props: { office: OfficeSpace; rerender: () => void }): React.R
const corp = useCorporation();
const division = useDivision();
const upgrades = [];
for (const index in IndustryUpgrades) {
for (const index of Object.keys(IndustryUpgrades)) {
const upgrade = IndustryUpgrades[index];
// AutoBrew research disables the Coffee upgrade
+9 -9
View File
@@ -81,7 +81,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
// Create React components for materials
const mats = [];
for (const matName in props.warehouse.materials) {
for (const matName of Object.keys(props.warehouse.materials)) {
if (!(props.warehouse.materials[matName] instanceof Material)) continue;
// Only create UI for materials that are relevant for the industry
if (!isRelevantMaterial(matName, division)) continue;
@@ -99,7 +99,7 @@ function WarehouseRoot(props: IProps): React.ReactElement {
// Create React components for products
const products = [];
if (division.makesProducts && Object.keys(division.products).length > 0) {
for (const productName in division.products) {
for (const productName of Object.keys(division.products)) {
const product = division.products[productName];
if (!(product instanceof Product)) continue;
products.push(
@@ -109,14 +109,14 @@ function WarehouseRoot(props: IProps): React.ReactElement {
}
const breakdownItems: JSX.Element[] = [];
for (const matName in props.warehouse.materials) {
for (const matName of Object.keys(props.warehouse.materials)) {
const mat = props.warehouse.materials[matName];
if (!MaterialSizes.hasOwnProperty(matName)) continue;
if (mat.qty === 0) continue;
breakdownItems.push(<>{matName}: {numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0")}</>);
}
for (const prodName in division.products) {
for (const prodName of Object.keys(division.products)) {
const prod = division.products[prodName];
if (prod === undefined) continue;
breakdownItems.push(<>{prodName}: {numeralWrapper.format(prod.data[props.warehouse.loc][0] * prod.siz, "0,0.0")}</>);
@@ -139,13 +139,13 @@ function WarehouseRoot(props: IProps): React.ReactElement {
{numeralWrapper.formatBigNumber(props.warehouse.size)}
</Typography>
</Tooltip>
<Button disabled={!canAffordUpgrade} onClick={upgradeWarehouseOnClick}>
Upgrade Warehouse Size -&nbsp;
<MoneyCost money={sizeUpgradeCost} corp={corp} />
</Button>
</Box>
<Button disabled={!canAffordUpgrade} onClick={upgradeWarehouseOnClick}>
Upgrade Warehouse Size -&nbsp;
<MoneyCost money={sizeUpgradeCost} corp={corp} />
</Button>
<Typography>This industry uses the following equation for its production: </Typography>
<br />
<Typography>
+2 -5
View File
@@ -112,7 +112,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
return (
<Paper>
<Box display="flex">
<Box sx={{ display: 'grid', gridTemplateColumns: '2fr 1fr', m: '5px' }}>
<Box>
<Tooltip
title={
@@ -149,11 +149,10 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
</Tooltip>
</Box>
<Box>
<Box sx={{ "& button": { width: '100%' } }}>
<Tooltip
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
>
<span>
<Button
color={tutorial ? "error" : "primary"}
onClick={() => setPurchaseMaterialOpen(true)}
@@ -161,7 +160,6 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
>
{purchaseButtonText}
</Button>
</span>
</Tooltip>
<PurchaseMaterialModal
mat={mat}
@@ -177,7 +175,6 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
<ExportModal mat={mat} open={exportOpen} onClose={() => setExportOpen(false)} />
</>
)}
<br />
<Button
color={division.prodMats.includes(props.mat.name) && !mat.sllman[0] ? "error" : "primary"}
+32 -43
View File
@@ -89,19 +89,21 @@ export function Overview({ rerender }: IProps): React.ReactElement {
<StatsTable rows={multRows} />
<br />
<BonusTime />
<Tooltip
title={
<Typography>
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
helping you get started with managing it.
</Typography>
}
>
<Button onClick={() => corp.getStarterGuide(player)}>Getting Started Guide</Button>
</Tooltip>
{corp.public ? <PublicButtons rerender={rerender} /> : <PrivateButtons rerender={rerender} />}
<BribeButton />
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', width: 'fit-content' }}>
<Tooltip
title={
<Typography>
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
helping you get started with managing it.
</Typography>
}
>
<Button onClick={() => corp.getStarterGuide(player)}>Getting Started Guide</Button>
</Tooltip>
{corp.public ? <PublicButtons rerender={rerender} /> : <PrivateButtons rerender={rerender} />}
<BribeButton />
</Box>
<br />
<Upgrades rerender={rerender} />
</>
@@ -125,11 +127,9 @@ function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement
return (
<>
<Tooltip title={<Typography>{findInvestorsTooltip}</Typography>}>
<span>
<Button disabled={!fundingAvailable} onClick={() => setFindInvestorsopen(true)}>
Find Investors
</Button>
</span>
<Button disabled={!fundingAvailable} onClick={() => setFindInvestorsopen(true)}>
Find Investors
</Button>
</Tooltip>
<Tooltip
title={
@@ -143,7 +143,6 @@ function PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement
</Tooltip>
<FindInvestorsModal open={findInvestorsopen} onClose={() => setFindInvestorsopen(false)} rerender={rerender} />
<GoPublicModal open={goPublicopen} onClose={() => setGoPublicopen(false)} rerender={rerender} />
<br />
</>
);
}
@@ -201,8 +200,8 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
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.";
"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
@@ -212,28 +211,21 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
return (
<>
<Tooltip title={<Typography>{sellSharesTooltip}</Typography>}>
<span>
<Button disabled={sellSharesOnCd} onClick={() => setSellSharesOpen(true)}>
Sell Shares
</Button>
</span>
<Button disabled={sellSharesOnCd} onClick={() => setSellSharesOpen(true)}>
Sell Shares
</Button>
</Tooltip>
<SellSharesModal open={sellSharesOpen} onClose={() => setSellSharesOpen(false)} rerender={rerender} />
<Tooltip title={<Typography>Buy back shares you that previously issued or sold at market price.</Typography>}>
<span>
<Button disabled={corp.issuedShares < 1} onClick={() => setBuybackSharesOpen(true)}>
Buyback shares
</Button>
</span>
<Button disabled={corp.issuedShares < 1} onClick={() => setBuybackSharesOpen(true)}>
Buyback shares
</Button>
</Tooltip>
<BuybackSharesModal open={buybackSharesOpen} onClose={() => setBuybackSharesOpen(false)} rerender={rerender} />
<br />
<Tooltip title={<Typography>{issueNewSharesTooltip}</Typography>}>
<span>
<Button disabled={issueNewSharesOnCd} onClick={() => setIssueNewSharesOpen(true)}>
Issue New Shares
</Button>
</span>
<Button disabled={issueNewSharesOnCd} onClick={() => setIssueNewSharesOpen(true)}>
Issue New Shares
</Button>
</Tooltip>
<IssueNewSharesModal open={issueNewSharesOpen} onClose={() => setIssueNewSharesOpen(false)} />
<Tooltip
@@ -242,7 +234,6 @@ function PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {
<Button onClick={() => setIssueDividendsOpen(true)}>Issue Dividends</Button>
</Tooltip>
<IssueDividendsModal open={issueDividendsOpen} onClose={() => setIssueDividendsOpen(false)} />
<br />
</>
);
}
@@ -269,11 +260,9 @@ function BribeButton(): React.ReactElement {
: "Your Corporation is not powerful enough to bribe Faction leaders"
}
>
<span>
<Button disabled={!canBribe} onClick={openBribe}>
Bribe Factions
</Button>
</span>
<Button disabled={!canBribe} onClick={openBribe}>
Bribe Factions
</Button>
</Tooltip>
<BribeFactionModal open={open} onClose={() => setOpen(false)} />
</>
+1 -1
View File
@@ -81,7 +81,7 @@ export function ProductElem(props: IProductProps): React.ReactElement {
);
} else if (product.sCost) {
if (isString(product.sCost)) {
const sCost = (product.sCost as string).replace(/MP/g, product.pCost + "");
const sCost = (product.sCost as string).replace(/MP/g, product.pCost + product.rat / product.mku + "");
sellButtonText = (
<>
{sellButtonText} @ <Money money={eval(sCost)} />
+37 -16
View File
@@ -6,17 +6,18 @@ import { IIndustry } from "../IIndustry";
import { Research } from "../Actions";
import { Node } from "../ResearchTree";
import { ResearchMap } from "../ResearchMap";
import { Settings } from "../../Settings/Settings";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
import CheckIcon from '@mui/icons-material/Check';
interface INodeProps {
n: Node | null;
division: IIndustry;
@@ -42,8 +43,8 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
dialogBoxCreate(
`Researched ${n.text}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
);
}
@@ -52,8 +53,8 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
color = "info";
}
const but = (
<Box>
const wrapInTooltip = (ele: React.ReactElement): React.ReactElement => {
return (
<Tooltip
title={
<Typography>
@@ -63,12 +64,22 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
</Typography>
}
>
{ele}
</Tooltip>
)
}
const but = (
<Box>
{wrapInTooltip(
<span>
<Button color={color} disabled={disabled && !n.researched} onClick={research}>
{n.text}
<Button color={color} disabled={disabled && !n.researched} onClick={research}
style={{ width: '100%', textAlign: 'left', justifyContent: 'unset' }}
>
{n.researched && (<CheckIcon sx={{ mr: 1 }} />)}{n.text}
</Button>
</span>
</Tooltip>
)}
</Box>
);
@@ -76,15 +87,25 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement {
return (
<Box>
<Box display="flex">
{but}
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText />
<Box display="flex" sx={{ border: '1px solid ' + Settings.theme.well }}>
{wrapInTooltip(
<span style={{ width: '100%' }}>
<Button color={color} disabled={disabled && !n.researched} onClick={research} sx={{
width: '100%',
textAlign: 'left',
justifyContent: 'unset',
borderColor: Settings.theme.button
}}>
{n.researched && (<CheckIcon sx={{ mr: 1 }} />)}{n.text}
</Button>
</span>
)}
<Button onClick={() => setOpen((old) => !old)} sx={{ borderColor: Settings.theme.button, minWidth: 'fit-content' }}>
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
</Button>
</Box>
<Collapse in={open} unmountOnExit>
<Box m={4}>
<Box m={1}>
{n.children.map((m) => (
<Upgrade key={m.text} division={division} n={m} />
))}
@@ -108,7 +129,7 @@ export function ResearchModal(props: IProps): React.ReactElement {
return (
<Modal open={props.open} onClose={props.onClose}>
<Upgrade division={props.industry} n={researchTree.root} />
<Typography>
<Typography sx={{ mt: 1 }}>
Research points: {props.industry.sciResearch.qty.toFixed(3)}
<br />
Multipliers from research:
+1 -1
View File
@@ -61,7 +61,7 @@ export function SmartSupplyModal(props: IProps): React.ReactElement {
// Create React components for materials
const mats = [];
for (const matName in props.warehouse.materials) {
for (const matName of Object.keys(props.warehouse.materials)) {
if (!(props.warehouse.materials[matName] instanceof Material)) continue;
if (!Object.keys(division.reqMats).includes(matName)) continue;
mats.push(<Leftover key={matName} warehouse={props.warehouse} matName={matName} />);
+3 -3
View File
@@ -7,7 +7,7 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
export function determineCrimeSuccess(p: IPlayer, type: string): boolean {
let chance = 0;
let found = false;
for (const i in Crimes) {
for (const i of Object.keys(Crimes)) {
const crime = Crimes[i];
if (crime.type == type) {
chance = crime.successRate(p);
@@ -44,7 +44,7 @@ export function findCrime(roughName: string): Crime | null {
return Crimes.DealDrugs;
} else if (roughName.includes("bond") && roughName.includes("forge")) {
return Crimes.BondForgery;
} else if (roughName.includes("traffick") && roughName.includes("arms")) {
} else if ((roughName.includes("traffic") || roughName.includes("illegal")) && roughName.includes("arms")) {
return Crimes.TraffickArms;
} else if (roughName.includes("homicide")) {
return Crimes.Homicide;
@@ -52,7 +52,7 @@ export function findCrime(roughName: string): Crime | null {
return Crimes.GrandTheftAuto;
} else if (roughName.includes("kidnap")) {
return Crimes.Kidnap;
} else if (roughName.includes("assassinate") || roughName.includes("assassination")) {
} else if (roughName.includes("assassin")) {
return Crimes.Assassination;
} else if (roughName.includes("heist")) {
return Crimes.Heist;
+3 -3
View File
@@ -21,7 +21,7 @@ export function checkIfConnectedToDarkweb(): void {
}
export function listAllDarkwebItems(): void {
for (const key in DarkWebItems) {
for (const key of Object.keys(DarkWebItems)) {
const item = DarkWebItems[key];
const cost = Player.getHomeComputer().programs.includes(item.program) ? (
@@ -44,7 +44,7 @@ export function buyDarkwebItem(itemName: string): void {
// find the program that matches, if any
let item: DarkWebItem | null = null;
for (const key in DarkWebItems) {
for (const key of Object.keys(DarkWebItems)) {
const i = DarkWebItems[key];
if (i.program.toLowerCase() == itemName) {
item = i;
@@ -93,7 +93,7 @@ export function buyAllDarkwebItems(): void {
const itemsToBuy: DarkWebItem[] = [];
let cost = 0;
for (const key in DarkWebItems) {
for (const key of Object.keys(DarkWebItems)) {
const item = DarkWebItems[key];
if (!Player.hasProgram(item.program)) {
itemsToBuy.push(item);
+4 -4
View File
@@ -46,25 +46,25 @@ export function Companies(): React.ReactElement {
}
function tonsOfRepCompanies(): void {
for (const c in AllCompanies) {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].playerReputation = bigNumber;
}
}
function resetAllRepCompanies(): void {
for (const c in AllCompanies) {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].playerReputation = 0;
}
}
function tonsOfFavorCompanies(): void {
for (const c in AllCompanies) {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].favor = bigNumber;
}
}
function resetAllFavorCompanies(): void {
for (const c in AllCompanies) {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].favor = 0;
}
}
+5 -5
View File
@@ -36,7 +36,7 @@ export function Factions(props: IProps): React.ReactElement {
}
function receiveAllInvites(): void {
for (const i in AllFaction) {
for (const i of Object.keys(AllFaction)) {
props.player.receiveInvite(AllFaction[i].name);
}
}
@@ -74,25 +74,25 @@ export function Factions(props: IProps): React.ReactElement {
}
function tonsOfRep(): void {
for (const i in AllFaction) {
for (const i of Object.keys(AllFaction)) {
AllFaction[i].playerReputation = bigNumber;
}
}
function resetAllRep(): void {
for (const i in AllFaction) {
for (const i of Object.keys(AllFaction)) {
AllFaction[i].playerReputation = 0;
}
}
function tonsOfFactionFavor(): void {
for (const i in AllFaction) {
for (const i of Object.keys(AllFaction)) {
AllFaction[i].favor = bigNumber;
}
}
function resetAllFactionFavor(): void {
for (const i in AllFaction) {
for (const i of Object.keys(AllFaction)) {
AllFaction[i].favor = 0;
}
}
+8 -1
View File
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState } from "react";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
@@ -17,6 +17,8 @@ interface IProps {
}
export function General(props: IProps): React.ReactElement {
const [error, setError] = useState(false);
function addMoney(n: number) {
return function () {
props.player.gainMoney(n, "other");
@@ -43,6 +45,10 @@ export function General(props: IProps): React.ReactElement {
props.router.toBitVerse(false, false);
}
useEffect(() => {
if (error) throw new ReferenceError('Manually thrown error');
}, [error]);
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -81,6 +87,7 @@ export function General(props: IProps): React.ReactElement {
<Button onClick={b1tflum3}>Run b1t_flum3.exe</Button>
<Button onClick={quickHackW0r1dD43m0n}>Quick w0rld_d34m0n</Button>
<Button onClick={hackW0r1dD43m0n}>Hack w0rld_d34m0n</Button>
<Button onClick={() => setError(true)}>Throw Error</Button>
</AccordionDetails>
</Accordion>
);
+1 -1
View File
@@ -28,7 +28,7 @@ export function Programs(props: IProps): React.ReactElement {
}
function addAllPrograms(): void {
for (const i in AllPrograms) {
for (const i of Object.keys(AllPrograms)) {
if (!props.player.hasProgram(AllPrograms[i].name)) {
props.player.getHomeComputer().programs.push(AllPrograms[i].name);
}
+19
View File
@@ -8,6 +8,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Adjuster } from "./Adjuster";
interface IProps {
player: IPlayer;
@@ -38,6 +39,12 @@ export function Sleeves(props: IProps): React.ReactElement {
}
}
function sleeveSetStoredCycles(cycles: number): void {
for (let i = 0; i < props.player.sleeves.length; ++i) {
props.player.sleeves[i].storedCycles = cycles;
}
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@@ -68,6 +75,18 @@ export function Sleeves(props: IProps): React.ReactElement {
<Button onClick={sleeveSyncClearAll}>Clear all</Button>
</td>
</tr>
<tr>
<td colSpan={3}>
<Adjuster
label="Stored Cycles"
placeholder="cycles"
tons={() => sleeveSetStoredCycles(10000000)}
add={sleeveSetStoredCycles}
subtract={sleeveSetStoredCycles}
reset={() => sleeveSetStoredCycles(0)}
/>
</td>
</tr>
</tbody>
</table>
</AccordionDetails>
+1 -1
View File
@@ -38,7 +38,7 @@ export function StockMarket(): React.ReactElement {
};
}
for (const name in SM) {
for (const name of Object.keys(SM)) {
if (SM.hasOwnProperty(name)) {
const stock = SM[name];
if (stock instanceof Stock && match(stock.symbol)) {
+190 -21
View File
@@ -1,11 +1,18 @@
import { Player } from "./Player";
import { Router } from "./ui/GameRoot";
import { isScriptFilename } from "./Script/isScriptFilename";
import { Script } from "./Script/Script";
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { IMap } from "./types";
import { IMap, IReturnStatus } from "./types";
import { GetServer } from "./Server/AllServers";
import { resolve } from "cypress/types/bluebird";
import { ImportPlayerData, SaveData, saveObject } from "./SaveObject";
import { Settings } from "./Settings/Settings";
import { exportScripts } from "./Terminal/commands/download";
import { CONSTANTS } from "./Constants";
import { hash } from "./hash/hash";
export function initElectron(): void {
const userAgent = navigator.userAgent.toLowerCase();
@@ -14,36 +21,81 @@ export function initElectron(): void {
(document as any).achievements = [];
initWebserver();
initAppNotifier();
initSaveFunctions();
initElectronBridge();
}
}
function initWebserver(): void {
(document as any).saveFile = function (filename: string, code: string): string {
interface IReturnWebStatus extends IReturnStatus {
data?: {
[propName: string]: any;
};
}
function normalizeFileName(filename: string): string {
filename = filename.replace(/\/\/+/g, "/");
filename = removeLeadingSlash(filename);
if (filename.includes("/")) {
filename = "/" + removeLeadingSlash(filename);
}
return filename;
}
(document as any).getFiles = function (): IReturnWebStatus {
const home = GetServer("home");
if (home === null) {
return {
res: false,
msg: "Home server does not exist.",
};
}
return {
res: true,
data: {
files: home.scripts.map((script) => ({
filename: script.filename,
code: script.code,
ramUsage: script.ramUsage,
})),
},
};
};
(document as any).deleteFile = function (filename: string): IReturnWebStatus {
filename = normalizeFileName(filename);
const home = GetServer("home");
if (home === null) {
return {
res: false,
msg: "Home server does not exist.",
};
}
return home.removeFile(filename);
};
(document as any).saveFile = function (filename: string, code: string): IReturnWebStatus {
filename = normalizeFileName(filename);
code = Buffer.from(code, "base64").toString();
const home = GetServer("home");
if (home === null) return "'home' server not found.";
if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < home.scripts.length; i++) {
if (filename == home.scripts[i].filename) {
home.scripts[i].saveScript(Player, filename, code, "home", home.scripts);
return "written";
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(Player, filename, code, "home", home.scripts);
home.scripts.push(script);
return "written";
if (home === null) {
return {
res: false,
msg: "Home server does not exist.",
};
}
return "not a script file";
const { success, overwritten } = home.writeToScriptFile(Player, filename, code);
let script;
if (success) {
script = home.getScript(filename);
}
return {
res: success,
data: {
overwritten,
ramUsage: script?.ramUsage,
},
};
};
}
@@ -67,6 +119,123 @@ function initAppNotifier(): void {
};
// Will be consumud by the electron wrapper.
// @ts-ignore
window.appNotifier = funcs;
(window as any).appNotifier = funcs;
}
function initSaveFunctions(): void {
const funcs = {
triggerSave: (): Promise<void> => saveObject.saveGame(true),
triggerGameExport: (): void => {
try {
saveObject.exportGame();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", "error", 2000);
}
},
triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()),
getSaveData: (): { save: string; fileName: string } => {
return {
save: saveObject.getSaveString(Settings.ExcludeRunningScriptsFromSave),
fileName: saveObject.getSaveFileName(),
};
},
getSaveInfo: async (base64save: string): Promise<ImportPlayerData | undefined> => {
try {
const data = await saveObject.getImportDataFromString(base64save);
return data.playerData;
} catch (error) {
console.error(error);
return;
}
},
pushSaveData: (base64save: string, automatic = false): void => Router.toImportSave(base64save, automatic),
};
// Will be consumud by the electron wrapper.
(window as any).appSaveFns = funcs;
}
function initElectronBridge(): void {
const bridge = (window as any).electronBridge as any;
if (!bridge) return;
bridge.receive("get-save-data-request", () => {
const data = (window as any).appSaveFns.getSaveData();
bridge.send("get-save-data-response", data);
});
bridge.receive("get-save-info-request", async (save: string) => {
const data = await (window as any).appSaveFns.getSaveInfo(save);
bridge.send("get-save-info-response", data);
});
bridge.receive("push-save-request", ({ save, automatic = false }: { save: string; automatic: boolean }) => {
(window as any).appSaveFns.pushSaveData(save, automatic);
});
bridge.receive("trigger-save", () => {
return (window as any).appSaveFns
.triggerSave()
.then(() => {
bridge.send("save-completed");
})
.catch((error: any) => {
console.log(error);
SnackbarEvents.emit("Could not save game.", "error", 2000);
});
});
bridge.receive("trigger-game-export", () => {
try {
(window as any).appSaveFns.triggerGameExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", "error", 2000);
}
});
bridge.receive("trigger-scripts-export", () => {
try {
(window as any).appSaveFns.triggerScriptsExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export scripts.", "error", 2000);
}
});
}
export function pushGameSaved(data: SaveData): void {
const bridge = (window as any).electronBridge as any;
if (!bridge) return;
bridge.send("push-game-saved", data);
}
export function pushGameReady(): void {
const bridge = (window as any).electronBridge as any;
if (!bridge) return;
// Send basic information to the electron wrapper
bridge.send("push-game-ready", {
player: {
identifier: Player.identifier,
playtime: Player.totalPlaytime,
lastSave: Player.lastSave,
},
game: {
version: CONSTANTS.VersionString,
hash: hash(),
},
});
}
export function pushImportResult(wasImported: boolean): void {
const bridge = (window as any).electronBridge as any;
if (!bridge) return;
bridge.send("push-import-result", { wasImported });
pushDisableRestore();
}
export function pushDisableRestore(): void {
const bridge = (window as any).electronBridge as any;
if (!bridge) return;
bridge.send("push-disable-restore", { duration: 1000 * 60 });
}
+3 -4
View File
@@ -34,8 +34,7 @@ export function joinFaction(faction: Faction): void {
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
for (const i in factionInfo.enemies) {
const enemy = factionInfo.enemies[i];
for (const enemy of factionInfo.enemies) {
if (Factions[enemy] instanceof Faction) {
Factions[enemy].isBanned = true;
}
@@ -121,7 +120,7 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
}
}
for (const name in Augmentations) {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]];
}
@@ -170,7 +169,7 @@ export function getNextNeurofluxLevel(): number {
}
export function processPassiveFactionRepGain(numCycles: number): void {
for (const name in Factions) {
for (const name of Object.keys(Factions)) {
if (name === Player.currentWorkFactionName) continue;
if (!Factions.hasOwnProperty(name)) continue;
const faction = Factions[name];
+1 -1
View File
@@ -34,7 +34,7 @@ export function factionExists(name: string): boolean {
}
export function initFactions(): void {
for (const name in FactionInfos) {
for (const name of Object.keys(FactionInfos)) {
resetFaction(new Faction(name));
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugs(): string[] {
if (isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const aug = Augmentations[augName];
+1 -1
View File
@@ -77,7 +77,7 @@ export function DonateOption(props: IProps): React.ReactElement {
}
return (
<Paper sx={{ my: 1, p: 1, width: "100%" }}>
<Paper sx={{ my: 1, p: 1 }}>
<Status />
{props.disabled ? (
<Typography>
+61 -36
View File
@@ -1,17 +1,17 @@
import React, { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import Paper from "@mui/material/Paper";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import React, { useEffect, useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Table, TableCell } from "../../ui/React/Table";
import { IRouter } from "../../ui/Router";
import { Factions } from "../Factions";
import { Faction } from "../Faction";
import { joinFaction } from "../FactionHelpers";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import Button from "@mui/material/Button";
import TableBody from "@mui/material/TableBody";
import { Table, TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { Factions } from "../Factions";
export const InvitationsSeen: string[] = [];
@@ -48,42 +48,67 @@ export function FactionsRoot(props: IProps): React.ReactElement {
}
return (
<>
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Factions</Typography>
<Typography>Lists all factions you have joined</Typography>
<br />
<Box display="flex" flexDirection="column">
{props.player.factions.map((faction: string) => (
<Link key={faction} variant="h6" onClick={() => openFaction(Factions[faction])}>
{faction}
</Link>
))}
</Box>
<br />
{props.player.factionInvitations.length > 0 && (
<>
<Typography variant="h5" color="primary">
Outstanding Faction Invitations
</Typography>
<Typography>
Lists factions you have been invited to. You can accept these faction invitations at any time.
</Typography>
<Table size="small" padding="none">
<Typography mb={4}>
Throughout the game you may receive invitations from factions. There are many different factions, and each
faction has different criteria for determining its potential members. Joining a faction and furthering its cause
is crucial to progressing in the game and unlocking endgame content.
</Typography>
<Typography variant="h5" color="primary" mt={2} mb={1}>
Factions you have joined:
</Typography>
{(props.player.factions.length > 0 && (
<Paper sx={{ my: 1, p: 1, pb: 0, display: "inline-block" }}>
<Table padding="none">
<TableBody>
{props.player.factionInvitations.map((faction: string) => (
{props.player.factions.map((faction: string) => (
<TableRow key={faction}>
<TableCell>
<Typography noWrap>{faction}</Typography>
<Typography noWrap mb={1}>
{faction}
</Typography>
</TableCell>
<TableCell align="right">
<Button onClick={(e) => acceptInvitation(e, faction)}>Join!</Button>
<Box ml={1} mb={1}>
<Button onClick={() => openFaction(Factions[faction])}>Details</Button>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
)}
</>
</Paper>
)) || <Typography>You haven't joined any factions.</Typography>}
<Typography variant="h5" color="primary" mt={4} mb={1}>
Outstanding Faction Invitations
</Typography>
<Typography mb={1}>
Factions you have been invited to. You can accept these faction invitations at any time:
</Typography>
{(props.player.factionInvitations.length > 0 && (
<Paper sx={{ my: 1, mb: 4, p: 1, pb: 0, display: "inline-block" }}>
<Table padding="none">
<TableBody>
{props.player.factionInvitations.map((faction: string) => (
<TableRow key={faction}>
<TableCell>
<Typography noWrap mb={1}>
{faction}
</Typography>
</TableCell>
<TableCell align="right">
<Box ml={1} mb={1}>
<Button onClick={(e) => acceptInvitation(e, faction)}>Join!</Button>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
)) || <Typography>You have no outstanding faction invites.</Typography>}
</Container>
);
}
+1 -1
View File
@@ -19,7 +19,7 @@ type IProps = {
export function Option(props: IProps): React.ReactElement {
return (
<Box>
<Paper sx={{ my: 1, p: 1, width: "100%" }}>
<Paper sx={{ my: 1, p: 1 }}>
<Button onClick={props.onClick}>{props.buttonText}</Button>
<Typography>{props.infoText}</Typography>
</Paper>
+1 -1
View File
@@ -158,7 +158,7 @@ export class Gang implements IGang {
// Process power first
const gangName = this.facName;
for (const name in AllGangs) {
for (const name of Object.keys(AllGangs)) {
if (AllGangs.hasOwnProperty(name)) {
if (name == gangName) {
AllGangs[name].power += this.calculatePower();
+2 -2
View File
@@ -31,7 +31,7 @@ export function AscensionModal(props: IProps): React.ReactElement {
props.onAscend();
const res = gang.ascendMember(props.member);
dialogBoxCreate(
<Typography>
<>
You ascended {props.member.name}!<br />
<br />
Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.
@@ -51,7 +51,7 @@ export function AscensionModal(props: IProps): React.ReactElement {
<br />
Charisma: x{numeralWrapper.format(res.cha, "0.000")}
<br />
</Typography>,
</>
);
props.onClose();
}
+152 -88
View File
@@ -2,20 +2,27 @@
* React Component for the popup that manages gang members upgrades
*/
import React, { useState } from "react";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMemberUpgrades } from "../GangMemberUpgrades";
import { GangMemberUpgrade } from "../GangMemberUpgrade";
import { Money } from "../../ui/React/Money";
import { useGang } from "./Context";
import { GangMember } from "../GangMember";
import { UpgradeType } from "../data/upgrades";
import { use } from "../../ui/Context";
import { generateTableRow } from "./GangMemberStats";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { MenuItem, Table, TableBody, TextField } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMemberUpgrades } from "../GangMemberUpgrades";
import { GangMemberUpgrade } from "../GangMemberUpgrade";
import { Money } from "../../ui/React/Money";
import { GangMember } from "../GangMember";
import { UpgradeType } from "../data/upgrades";
import { use } from "../../ui/Context";
import { Settings } from "../../Settings/Settings";
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
interface INextRevealProps {
upgrades: string[];
@@ -46,12 +53,10 @@ function NextReveal(props: INextRevealProps): React.ReactElement {
function PurchasedUpgrade({ upgName }: { upgName: string }): React.ReactElement {
const upg = GangMemberUpgrades[upgName];
return (
<Paper sx={{ mx: 1, p: 1 }}>
<Box display="flex">
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: upg.desc }} />}>
<Typography>{upg.name}</Typography>
</Tooltip>
</Box>
<Paper sx={{ p: 1 }}>
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: upg.desc }} />}>
<Typography>{upg.name}</Typography>
</Tooltip>
</Paper>
);
}
@@ -72,8 +77,8 @@ function UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {
return (
<Tooltip title={<Typography dangerouslySetInnerHTML={{ __html: props.upg.desc }} />}>
<span>
<Typography>{props.upg.name}</Typography>
<Button onClick={onClick}>
<Button onClick={onClick} sx={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
<Typography sx={{ display: 'block' }}>{props.upg.name}</Typography>
<Money money={gang.getUpgradeCost(props.upg)} />
</Button>
</span>
@@ -86,12 +91,16 @@ interface IPanelProps {
}
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const classes = useStyles();
const gang = useGang();
const player = use.Player();
const setRerender = useState(false)[1];
const [currentCategory, setCurrentCategory] = useState("Weapons");
function rerender(): void {
setRerender((old) => !old);
}
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
return Object.keys(GangMemberUpgrades)
.filter((upgName: string) => {
@@ -103,12 +112,26 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
})
.map((upgName: string) => GangMemberUpgrades[upgName]);
}
const onChange = (event: SelectChangeEvent<string>): void => {
setCurrentCategory(event.target.value);
rerender()
}
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
const categories: { [key: string]: (GangMemberUpgrade[] | UpgradeType)[] } = {
'Weapons': [weaponUpgrades, UpgradeType.Weapon],
'Armor': [armorUpgrades, UpgradeType.Armor],
'Vehicles': [vehicleUpgrades, UpgradeType.Vehicle],
'Rootkits': [rootkitUpgrades, UpgradeType.Rootkit],
'Augmentations': [augUpgrades, UpgradeType.Augmentation]
};
const asc = {
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
str: props.member.calculateAscensionMult(props.member.str_asc_points),
@@ -119,26 +142,89 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
};
return (
<Paper>
<Typography variant="h5" color="primary">
{props.member.name} ({props.member.task})
</Typography>
<Typography>
Hack: {props.member.hack} (x
{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
Str: {props.member.str} (x
{formatNumber(props.member.str_mult * asc.str, 2)})<br />
Def: {props.member.def} (x
{formatNumber(props.member.def_mult * asc.def, 2)})<br />
Dex: {props.member.dex} (x
{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
Agi: {props.member.agi} (x
{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
Cha: {props.member.cha} (x
{formatNumber(props.member.cha_mult * asc.cha, 2)})
</Typography>
<Box display="flex" flexWrap="wrap">
<Typography>Purchased Upgrades: </Typography>
<br />
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', m: 1, gap: 1 }}>
<span>
<Typography variant="h5" color="primary">
{props.member.name} ({props.member.task})
</Typography>
<Tooltip
title={
<Typography>
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x
{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)}{" "}
Asc)
<br />
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}
(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)}{" "}
Asc)
<br />
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}
(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)}{" "}
Asc)
<br />
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}
(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)}{" "}
Asc)
<br />
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}
(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)}{" "}
Asc)
<br />
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}
(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)}{" "}
Asc)
</Typography>
}
>
<Table>
<TableBody>
{generateTableRow("Hacking", props.member.hack, props.member.hack_exp, Settings.theme.hack, classes)}
{generateTableRow("Strength", props.member.str, props.member.str_exp, Settings.theme.combat, classes)}
{generateTableRow("Defense", props.member.def, props.member.def_exp, Settings.theme.combat, classes)}
{generateTableRow("Dexterity", props.member.dex, props.member.dex_exp, Settings.theme.combat, classes)}
{generateTableRow("Agility", props.member.agi, props.member.agi_exp, Settings.theme.combat, classes)}
{generateTableRow("Charisma", props.member.cha, props.member.cha_exp, Settings.theme.cha, classes)}
</TableBody>
</Table>
</Tooltip>
</span>
<span>
<Select onChange={onChange} value={currentCategory} sx={{ width: '100%', mb: 1 }}>
{Object.keys(categories).map((k, i) => (
<MenuItem key={i + 1} value={k}>
<Typography variant="h6">{k}</Typography>
</MenuItem>
))}
</Select>
<Box sx={{ width: '100%' }}>
{(categories[currentCategory][0] as GangMemberUpgrade[]).length === 0 && (
<Typography>
All upgrades owned!
</Typography>
)}
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr' }}>
{(categories[currentCategory][0] as GangMemberUpgrade[]).map((upg) => (
<UpgradeButton
key={upg.name}
rerender={rerender}
member={props.member}
upg={upg}
/>
))}
</Box>
<NextReveal
type={categories[currentCategory][1] as UpgradeType}
upgrades={props.member.upgrades}
/>
</Box>
</span>
</Box>
<Typography sx={{ mx: 1 }}>Purchased Upgrades: </Typography>
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(4, 1fr)', m: 1 }}>
{props.member.upgrades.map((upg: string) => (
<PurchasedUpgrade key={upg} upgName={upg} />
))}
@@ -146,59 +232,22 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
<PurchasedUpgrade key={upg} upgName={upg} />
))}
</Box>
<Box display="flex" justifyContent="space-around">
<Box>
<Typography variant="h6" color="primary">
Weapons
</Typography>
{weaponUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Weapon} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Armor
</Typography>
{armorUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Armor} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Vehicles
</Typography>
{vehicleUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Vehicle} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Rootkits
</Typography>
{rootkitUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Rootkit} upgrades={props.member.upgrades} />
</Box>
<Box>
<Typography variant="h6" color="primary">
Augmentations
</Typography>
{augUpgrades.map((upg) => (
<UpgradeButton key={upg.name} rerender={rerender} member={props.member} upg={upg} />
))}
<NextReveal type={UpgradeType.Augmentation} upgrades={props.member.upgrades} />
</Box>
</Box>
</Paper>
</Paper >
);
}
export function EquipmentsSubpage(): React.ReactElement {
const gang = useGang();
const [filter, setFilter] = useState("");
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
setFilter(event.target.value.toLowerCase());
}
const members = gang.members
.filter((member) => member && member.name.toLowerCase().includes(filter));
return (
<>
<Tooltip
@@ -209,11 +258,26 @@ export function EquipmentsSubpage(): React.ReactElement {
</Typography>
}
>
<Typography>Discount: -{numeralWrapper.formatPercentage(1 - 1 / gang.getDiscount())}</Typography>
<Typography sx={{ m: 1 }}>Discount: -{numeralWrapper.formatPercentage(1 - 1 / gang.getDiscount())}</Typography>
</Tooltip>
{gang.members.map((member: GangMember) => (
<GangMemberUpgradePanel key={member.name} member={member} />
))}
<TextField
value={filter}
onChange={handleFilterChange}
autoFocus
InputProps={{
startAdornment: <SearchIcon />,
spellCheck: false
}}
placeholder="Filter by member name"
sx={{ m: 1, width: '15%' }}
/>
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: 'fit-content' }}>
{members.map((member: GangMember) => (
<GangMemberUpgradePanel key={member.name} member={member} />
))}
</Box>
</>
);
}
-36
View File
@@ -1,36 +0,0 @@
/**
* React Component for a gang member on the management subpage.
*/
import React, { useState } from "react";
import { GangMember } from "../GangMember";
import { GangMemberAccordionContent } from "./GangMemberAccordionContent";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Paper from "@mui/material/Paper";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
interface IProps {
member: GangMember;
}
export function GangMemberAccordion(props: IProps): React.ReactElement {
const [open, setOpen] = useState(true);
return (
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>{props.member.name}</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open} unmountOnExit>
<Box sx={{ mx: 4 }}>
<GangMemberAccordionContent member={props.member} />
</Box>
</Collapse>
</Box>
);
}
@@ -1,31 +0,0 @@
/**
* React Component for the content of the accordion of gang members on the
* management subpage.
*/
import React, { useState } from "react";
import { GangMemberStats } from "./GangMemberStats";
import { TaskSelector } from "./TaskSelector";
import { TaskDescription } from "./TaskDescription";
import { GangMember } from "../GangMember";
import Grid from "@mui/material/Grid";
interface IProps {
member: GangMember;
}
export function GangMemberAccordionContent(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
return (
<Grid container>
<Grid item xs={4}>
<GangMemberStats onAscend={() => setRerender((old) => !old)} member={props.member} />
</Grid>
<Grid item xs={4}>
<TaskSelector onTaskChange={() => setRerender((old) => !old)} member={props.member} />
</Grid>
<Grid item xs={4}>
<TaskDescription member={props.member} />
</Grid>
</Grid>
);
}
+26
View File
@@ -0,0 +1,26 @@
/**
* React Component for a gang member on the management subpage.
*/
import React from "react";
import { GangMember } from "../GangMember";
import { GangMemberCardContent } from "./GangMemberCardContent";
import Box from "@mui/material/Box";
import ListItemText from "@mui/material/ListItemText";
import Paper from "@mui/material/Paper";
interface IProps {
member: GangMember;
}
export function GangMemberCard(props: IProps): React.ReactElement {
return (
<Box component={Paper} sx={{ width: 'auto' }}>
<Box sx={{ m: 1 }}>
<ListItemText primary={<b>{props.member.name}</b>} />
<GangMemberCardContent member={props.member} />
</Box>
</Box>
);
}
+62
View File
@@ -0,0 +1,62 @@
/**
* React Component for the content of the accordion of gang members on the
* management subpage.
*/
import React, { useState } from "react";
import { GangMemberStats } from "./GangMemberStats";
import { TaskSelector } from "./TaskSelector";
import { AscensionModal } from "./AscensionModal";
import { Box } from "@mui/system";
import { Button, Typography } from "@mui/material";
import HelpIcon from "@mui/icons-material/Help";
import { GangMember } from "../GangMember";
import { StaticModal } from "../../ui/React/StaticModal";
interface IProps {
member: GangMember;
}
export function GangMemberCardContent(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
const [helpOpen, setHelpOpen] = useState(false);
const [ascendOpen, setAscendOpen] = useState(false);
return (
<>
{props.member.canAscend() && (
<Box sx={{ display: 'flex', justifyContent: 'space-between', my: 1 }}>
<Button onClick={() => setAscendOpen(true)} style={{ flexGrow: 1, borderRightWidth: 0 }}>Ascend</Button>
<AscensionModal
open={ascendOpen}
onClose={() => setAscendOpen(false)}
member={props.member}
onAscend={() => setRerender((old) => !old)}
/>
<Button onClick={() => setHelpOpen(true)} style={{ width: 'fit-content', borderLeftWidth: 0 }}>
<HelpIcon />
</Button>
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
<Typography>
Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their
stat multipliers.
<br />
<br />
The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp
they have.
<br />
<br />
Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect
equal to the total respect earned by the member.
</Typography>
</StaticModal>
</Box>
)}
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%', gap: 1 }}>
<GangMemberStats member={props.member} />
<TaskSelector onTaskChange={() => setRerender((old) => !old)} member={props.member} />
</Box>
</>
);
}
+46 -6
View File
@@ -2,23 +2,63 @@
* React Component for the list of gang members on the management subpage.
*/
import React, { useState } from "react";
import { GangMemberAccordion } from "./GangMemberAccordion";
import { GangMember } from "../GangMember";
import { GangMemberCard } from "./GangMemberCard";
import { RecruitButton } from "./RecruitButton";
import { useGang } from "./Context";
import { Box, TextField } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import { GangMember } from "../GangMember";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
export function GangMemberList(): React.ReactElement {
const gang = useGang();
const setRerender = useState(false)[1];
const [filter, setFilter] = useState("");
const [ascendOnly, setAscendOnly] = useState(false);
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
setFilter(event.target.value.toLowerCase());
}
const members = gang.members
.filter((member) => member && member.name.toLowerCase().includes(filter))
.filter((member) => {
if (ascendOnly) return member.canAscend();
return true;
});
return (
<>
<RecruitButton onRecruit={() => setRerender((old) => !old)} />
<ul>
{gang.members.map((member: GangMember) => (
<GangMemberAccordion key={member.name} member={member} />
<TextField
value={filter}
onChange={handleFilterChange}
autoFocus
InputProps={{
startAdornment: <SearchIcon />,
spellCheck: false
}}
placeholder="Filter by member name"
sx={{ m: 1, width: '15%' }}
/>
<OptionSwitch
checked={ascendOnly}
onChange={(newValue) => (setAscendOnly(newValue))}
text="Show only ascendable"
tooltip={
<>
Filter the members list by whether or not the member
can be ascended.
</>
}
/>
<Box display="grid" sx={{ gridTemplateColumns: 'repeat(2, 1fr)' }}>
{members.map((member: GangMember) => (
<GangMemberCard key={member.name} member={member} />
))}
</ul>
</Box>
</>
);
}
+74 -55
View File
@@ -2,26 +2,53 @@
* React Component for the first part of a gang member details.
* Contains skills and exp.
*/
import React, { useState } from "react";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMember } from "../GangMember";
import { AscensionModal } from "./AscensionModal";
import React from "react";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";
import { StaticModal } from "../../ui/React/StaticModal";
import IconButton from "@mui/material/IconButton";
import HelpIcon from "@mui/icons-material/Help";
import {
Table,
TableBody,
TableCell,
TableRow,
} from "@mui/material";
import { numeralWrapper } from "../../ui/numeralFormat";
import { GangMember } from "../GangMember";
import { Settings } from "../../Settings/Settings";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { characterOverviewStyles as useStyles } from "../../ui/React/CharacterOverview";
interface IProps {
member: GangMember;
onAscend: () => void;
}
export const generateTableRow = (
name: string,
level: number,
exp: number,
color: string,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
classes: any
): React.ReactElement => {
return (
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>{name}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography style={{ color: color }}>
{formatNumber(level, 0)} ({numeralWrapper.formatExp(exp)} exp)
</Typography>
</TableCell>
</TableRow>
)
}
export function GangMemberStats(props: IProps): React.ReactElement {
const [helpOpen, setHelpOpen] = useState(false);
const [ascendOpen, setAscendOpen] = useState(false);
const classes = useStyles();
const asc = {
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
@@ -32,6 +59,16 @@ export function GangMemberStats(props: IProps): React.ReactElement {
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
};
const gang = useGang();
const data = [
[`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(gang)} />],
[`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(gang))} / sec`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(gang))} / sec`],
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
];
return (
<>
<Tooltip
@@ -63,50 +100,32 @@ export function GangMemberStats(props: IProps): React.ReactElement {
</Typography>
}
>
<Typography>
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
<br />
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
<br />
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
<br />
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
<br />
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
<br />
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
<br />
</Typography>
<Table sx={{ display: 'table', mb: 1, width: '100%' }}>
<TableBody>
{generateTableRow("Hacking", props.member.hack, props.member.hack_exp, Settings.theme.hack, classes)}
{generateTableRow("Strength", props.member.str, props.member.str_exp, Settings.theme.combat, classes)}
{generateTableRow("Defense", props.member.def, props.member.def_exp, Settings.theme.combat, classes)}
{generateTableRow("Dexterity", props.member.dex, props.member.dex_exp, Settings.theme.combat, classes)}
{generateTableRow("Agility", props.member.agi, props.member.agi_exp, Settings.theme.combat, classes)}
{generateTableRow("Charisma", props.member.cha, props.member.cha_exp, Settings.theme.cha, classes)}
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<br />
</TableCell>
</TableRow>
{data.map(([a, b]) => (
<TableRow key={a.toString() + b.toString()}>
<TableCell classes={{ root: classes.cellNone }}>
<Typography>{a}</Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography>{b}</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Tooltip>
<br />
{props.member.canAscend() && (
<>
<Button onClick={() => setAscendOpen(true)}>Ascend</Button>
<AscensionModal
open={ascendOpen}
onClose={() => setAscendOpen(false)}
member={props.member}
onAscend={props.onAscend}
/>
<IconButton onClick={() => setHelpOpen(true)}>
<HelpIcon />
</IconButton>
<StaticModal open={helpOpen} onClose={() => setHelpOpen(false)}>
<Typography>
Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their
stat multipliers.
<br />
<br />
The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp
they have.
<br />
<br />
Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect
equal to the total respect earned by the member.
</Typography>
</StaticModal>
</>
)}
</>
);
}
+7 -5
View File
@@ -4,7 +4,7 @@
import React, { useState } from "react";
import { RecruitModal } from "./RecruitModal";
import { GangConstants } from "../data/Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { useGang } from "./Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
@@ -24,18 +24,20 @@ export function RecruitButton(props: IProps): React.ReactElement {
if (!gang.canRecruitMember()) {
const respect = gang.getRespectNeededToRecruitMember();
return (
<Box display="flex" alignItems="center">
<Button sx={{ mx: 1 }} disabled>
<Box display="flex" alignItems="center" sx={{ mx: 1 }}>
<Button disabled>
Recruit Gang Member
</Button>
<Typography>{formatNumber(respect, 2)} respect needed to recruit next member</Typography>
<Typography sx={{ ml: 1 }}>{numeralWrapper.formatRespect(respect)} respect needed to recruit next member</Typography>
</Box>
);
}
return (
<>
<Button onClick={() => setOpen(true)}>Recruit Gang Member</Button>
<Box sx={{ mx: 1 }}>
<Button onClick={() => setOpen(true)}>Recruit Gang Member</Button>
</Box>
<RecruitModal open={open} onClose={() => setOpen(false)} onRecruit={props.onRecruit} />
</>
);
+9 -16
View File
@@ -3,14 +3,15 @@
* the task selector as well as some stats.
*/
import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StatsTable } from "../../ui/React/StatsTable";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { useGang } from "./Context";
import { GangMember } from "../GangMember";
import { TaskDescription } from "./TaskDescription";
import { Box } from "@mui/material";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { GangMember } from "../GangMember";
interface IProps {
member: GangMember;
onTaskChange: () => void;
@@ -29,16 +30,9 @@ export function TaskSelector(props: IProps): React.ReactElement {
const tasks = gang.getAllTaskNames();
const data = [
[`Money:`, <MoneyRate money={5 * props.member.calculateMoneyGain(gang)} />],
[`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(gang))} / sec`],
[`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(gang))} / sec`],
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
];
return (
<>
<Select onChange={onChange} value={currentTask}>
<Box>
<Select onChange={onChange} value={currentTask} sx={{ width: '100%' }}>
<MenuItem key={0} value={"Unassigned"}>
Unassigned
</MenuItem>
@@ -48,8 +42,7 @@ export function TaskSelector(props: IProps): React.ReactElement {
</MenuItem>
))}
</Select>
<StatsTable rows={data} />
</>
<TaskDescription member={props.member} />
</Box>
);
}
+2 -2
View File
@@ -34,9 +34,9 @@ export function calculateHackingExpGain(server: Server, player: IPlayer): number
server.baseDifficulty = server.hackDifficulty;
}
let expGain = baseExpGain;
expGain += server.baseDifficulty * player.hacking_exp_mult * diffFactor;
expGain += server.baseDifficulty * diffFactor;
return expGain * BitNodeMultipliers.HackExpGain;
return expGain * player.hacking_exp_mult * BitNodeMultipliers.HackExpGain;
}
/**
+2 -2
View File
@@ -24,7 +24,7 @@ export class HashManager {
upgrades: IMap<number> = {};
constructor() {
for (const name in HashUpgrades) {
for (const name of Object.keys(HashUpgrades)) {
this.upgrades[name] = 0;
}
}
@@ -85,7 +85,7 @@ export class HashManager {
}
prestige(): void {
for (const name in HashUpgrades) {
for (const name of Object.keys(HashUpgrades)) {
this.upgrades[name] = 0;
}
this.hashes = 0;
+1 -1
View File
@@ -19,7 +19,7 @@ export const Literatures: IMap<Literature> = {};
"money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)<br><br>" +
"-Because hack() and grow() work by percentages, they are more effective if the target server has a high amount of money. " +
"Therefore, you should try to increase the amount of money on a server (using grow()) to a certain amount before hacking it. Two " +
"import Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()<br><br>" +
"important Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()<br><br>" +
"-Keep security level low. Security level affects everything when hacking. Two important Netscript functions " +
"for this are getServerSecurityLevel() and getServerMinSecurityLevel()<br><br>" +
"-Purchase additional servers by visiting 'Alpha Enterprises' in the city. They are relatively cheap " +
+1 -1
View File
@@ -220,7 +220,7 @@ for (const metadata of LocationsMetadata) {
const cityName = loc.city;
if (cityName === null) {
// Generic location, add to all cities
for (const city in Cities) {
for (const city of Object.keys(Cities)) {
Cities[city].addLocation(loc.name);
}
} else {
-1
View File
@@ -39,7 +39,6 @@ export function ApplyToJobButton(props: IProps): React.ReactElement {
<Tooltip title={<span dangerouslySetInnerHTML={{ __html: getJobRequirementTooltip() }}></span>}>
<Button onClick={props.onClick}>{props.text}</Button>
</Tooltip>
<br />
</>
);
}
+2 -2
View File
@@ -5,7 +5,7 @@
*/
import React, { useState } from "react";
import Button from "@mui/material/Button";
import { Blackjack } from "../../Casino/Blackjack";
import { Blackjack, DECK_COUNT } from "../../Casino/Blackjack";
import { CoinFlip } from "../../Casino/CoinFlip";
import { Roulette } from "../../Casino/Roulette";
import { SlotMachine } from "../../Casino/SlotMachine";
@@ -38,7 +38,7 @@ export function CasinoLocation(props: IProps): React.ReactElement {
<Button onClick={() => updateGame(GameType.Coin)}>Play coin flip</Button>
<Button onClick={() => updateGame(GameType.Slots)}>Play slots</Button>
<Button onClick={() => updateGame(GameType.Roulette)}>Play roulette</Button>
<Button onClick={() => updateGame(GameType.Blackjack)}>Play blackjack</Button>
<Button onClick={() => updateGame(GameType.Blackjack)}>Play blackjack ({DECK_COUNT} decks)</Button>
</Box>
)}
{game !== GameType.None && (
+3 -3
View File
@@ -132,10 +132,10 @@ function ASCIICity(props: IProps): React.ReactElement {
const elems: JSX.Element[] = [];
const lines = props.city.asciiArt.split("\n");
for (const i in lines) {
for (const line of lines) {
elems.push(
<Typography key={i} sx={{ lineHeight: "1em", whiteSpace: "pre" }}>
{lineElems(lines[i])}
<Typography key={line} sx={{ lineHeight: "1em", whiteSpace: "pre" }}>
{lineElems(line)}
</Typography>,
);
}
+3 -4
View File
@@ -35,7 +35,9 @@ export function GymLocation(props: IProps): React.ReactElement {
function train(stat: string): void {
const loc = props.loc;
props.p.startClass(props.router, calculateCost(), loc.expMult, stat);
props.p.startClass(calculateCost(), loc.expMult, stat);
props.p.startFocusing();
props.router.toWork();
}
function trainStrength(): void {
@@ -61,15 +63,12 @@ export function GymLocation(props: IProps): React.ReactElement {
<Button onClick={trainStrength}>
Train Strength (<Money money={cost} player={props.p} /> / sec)
</Button>
<br />
<Button onClick={trainDefense}>
Train Defense (<Money money={cost} player={props.p} /> / sec)
</Button>
<br />
<Button onClick={trainDexterity}>
Train Dexterity (<Money money={cost} player={props.p} /> / sec)
</Button>
<br />
<Button onClick={trainAgility}>
Train Agility (<Money money={cost} player={props.p} /> / sec)
</Button>
-1
View File
@@ -42,7 +42,6 @@ function ServerButton(props: IServerProps): React.ReactElement {
cost={cost}
rerender={props.rerender}
/>
<br />
</>
);
}
+3 -1
View File
@@ -34,7 +34,9 @@ export function UniversityLocation(props: IProps): React.ReactElement {
function take(stat: string): void {
const loc = props.loc;
player.startClass(router, calculateCost(), loc.expMult, stat);
player.startClass(calculateCost(), loc.expMult, stat);
player.startFocusing();
router.toWork();
}
function study(): void {
+4
View File
@@ -106,12 +106,15 @@ export const RamCosts: IMap<any> = {
hackAnalyzeSecurity: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeChance: RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: 0,
share: 2.4,
getSharePower: 0.2,
grow: RamCostConstants.ScriptGrowRamCost,
growthAnalyze: RamCostConstants.ScriptGrowthAnalyzeRamCost,
growthAnalyzeSecurity: RamCostConstants.ScriptGrowthAnalyzeRamCost,
weaken: RamCostConstants.ScriptWeakenRamCost,
weakenAnalyze: RamCostConstants.ScriptWeakenAnalyzeRamCost,
print: 0,
printf: 0,
tprint: 0,
clearLog: 0,
disableLog: 0,
@@ -368,6 +371,7 @@ export const RamCosts: IMap<any> = {
getStyles: 0,
setStyles: 0,
resetStyles: 0,
getGameInfo: 0,
},
heart: {
+3 -7
View File
@@ -3,24 +3,20 @@ import { GetServer } from "./Server/AllServers";
import { WorkerScript } from "./Netscript/WorkerScript";
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
if (workerScript.delayReject) workerScript.delayReject();
return new Promise(function (resolve, reject) {
workerScript.delay = window.setTimeout(() => {
workerScript.delay = null;
workerScript.delayReject = undefined;
if (workerScript.env.stopFlag)
reject(workerScript);
else
resolve();
if (workerScript.env.stopFlag) reject(workerScript);
else resolve();
}, time);
workerScript.delayReject = reject;
});
}
export function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string {
if ((msg as any) instanceof WorkerScript) {
console.error("HERE");
}
const server = GetServer(workerScript.hostname);
if (server == null) {
throw new Error(`WorkerScript constructed with invalid server ip: ${workerScript.hostname}`);
+40 -15
View File
@@ -88,6 +88,8 @@ import { dialogBoxCreate } from "./ui/React/DialogBox";
import { SnackbarEvents } from "./ui/React/Snackbar";
import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
interface NS extends INS {
[key: string]: any;
@@ -172,7 +174,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
throw makeRuntimeRejectMsg(
workerScript,
`Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` +
`This is probably a bug. Please report to game developer`,
`This is probably a bug. Please report to game developer`,
);
}
@@ -522,7 +524,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
if (isNaN(hackAmount)) {
throw makeRuntimeErrorMsg(
"hackAnalyzeThreads",
`Invalid growth argument passed into hackAnalyzeThreads: ${hackAmount}. Must be numeric.`,
`Invalid hackAmount argument passed into hackAnalyzeThreads: ${hackAmount}. Must be numeric.`,
);
}
@@ -575,9 +577,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
throw makeRuntimeErrorMsg("asleep", "Takes 1 argument.");
}
workerScript.log("asleep", () => `Sleeping for ${time} milliseconds`);
return netscriptDelay(time, workerScript).then(function () {
return Promise.resolve(true);
});
return new Promise((resolve) => setTimeout(resolve, time));
},
grow: function (hostname: any, { threads: requestedThreads, stock }: any = {}): any {
updateDynamicRam("grow", getRamCost(Player, "grow"));
@@ -692,7 +692,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log(
"weaken",
() =>
`'${server.hostname}' security level weakened to ${server.hackDifficulty
`'${server.hostname}' security level weakened to ${
server.hackDifficulty
}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`,
);
workerScript.scriptRef.onlineExpGained += expGain;
@@ -704,12 +705,29 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const coreBonus = 1 + (cores - 1) / 16;
return CONSTANTS.ServerWeakenAmount * threads * coreBonus;
},
share: function (): Promise<void> {
workerScript.log("share", () => "Sharing this computer.");
const end = StartSharing(workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.intelligence, 2));
return netscriptDelay(10000, workerScript).finally(function () {
workerScript.log("share", () => "Finished sharing this computer.");
end();
});
},
getSharePower: function (): number {
return CalculateShareMult();
},
print: function (...args: any[]): void {
if (args.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
}
workerScript.print(argsToString(args));
},
printf: function (format: string, ...args: any[]): void {
if (typeof format !== "string") {
throw makeRuntimeErrorMsg("printf", "First argument must be string for the format.");
}
workerScript.print(vsprintf(format, args));
},
tprint: function (...args: any[]): void {
if (args.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
@@ -762,7 +780,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
},
disableLog: function (fn: any): any {
if (fn === "ALL") {
for (fn in possibleLogs) {
for (fn of Object.keys(possibleLogs)) {
workerScript.disableLogs[fn] = true;
}
workerScript.log("disableLog", () => `Disabled logging for all functions`);
@@ -775,7 +793,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
},
enableLog: function (fn: any): any {
if (fn === "ALL") {
for (fn in possibleLogs) {
for (fn of Object.keys(possibleLogs)) {
delete workerScript.disableLogs[fn];
}
workerScript.log("enableLog", () => `Enabled logging for all functions`);
@@ -1302,8 +1320,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
updateDynamicRam("ps", getRamCost(Player, "ps"));
const server = safeGetServer(hostname, "ps");
const processes = [];
for (const i in server.runningScripts) {
const script = server.runningScripts[i];
for (const script of server.runningScripts) {
processes.push({
filename: script.filename,
threads: script.threads,
@@ -1632,7 +1649,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const cost = getPurchaseServerCost(ram);
if (cost === Infinity) {
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be a positive power of 2`);
if(ram > getPurchaseServerMaxRam()){
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must not be greater than getPurchaseServerMaxRam`);
}else{
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be a positive power of 2`);
}
return "";
}
@@ -2270,8 +2292,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const source_is_txt = source.endsWith(".txt");
const dest_is_txt = destination.endsWith(".txt");
if (!isScriptFilename(source) && !source_is_txt) throw makeRuntimeErrorMsg("mv", `'mv' can only be used on scripts and text files (.txt)`);
if (source_is_txt != dest_is_txt) throw makeRuntimeErrorMsg("mv", `Source and destination files must have the same type`);
if (!isScriptFilename(source) && !source_is_txt)
throw makeRuntimeErrorMsg("mv", `'mv' can only be used on scripts and text files (.txt)`);
if (source_is_txt != dest_is_txt)
throw makeRuntimeErrorMsg("mv", `Source and destination files must have the same type`);
if (source === destination) {
return;
@@ -2280,7 +2304,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
// This will throw if the server is not found, we do not need to validate result.
const destServer: BaseServer | null = safeGetServer(host, "mv");
if (!source_is_txt && destServer.isRunning(source)) throw makeRuntimeErrorMsg("mv", `Cannot use 'mv' on a script that is running`)
if (!source_is_txt && destServer.isRunning(source))
throw makeRuntimeErrorMsg("mv", `Cannot use 'mv' on a script that is running`);
interface File {
filename: string;
@@ -2299,7 +2324,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
}
if (source_file == null) throw makeRuntimeErrorMsg("mv", `Source file ${source} does not exist`)
if (source_file == null) throw makeRuntimeErrorMsg("mv", `Source file ${source} does not exist`);
if (dest_file != null) {
if (dest_file instanceof TextFile && source_file instanceof TextFile) {
+11
View File
@@ -52,6 +52,7 @@ import {
BulkPurchase,
SellShares,
BuyBackShares,
SetSmartSupplyUseLeftovers,
} from "../Corporation/Actions";
import { CorporationUnlockUpgrades } from "../Corporation/data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "../Corporation/data/CorporationUpgrades";
@@ -414,6 +415,16 @@ export function NetscriptCorporation(
const warehouse = getWarehouse(divisionName, cityName);
SetSmartSupply(warehouse, enabled);
},
setSmartSupplyUseLeftovers: function (adivisionName: any, acityName: any, amaterialName: any, aenabled: any): void {
checkAccess("setSmartSupplyUseLeftovers", 7);
const divisionName = helper.string("setSmartSupply", "divisionName", adivisionName);
const cityName = helper.string("sellProduct", "cityName", acityName);
const materialName = helper.string("sellProduct", "materialName", amaterialName);
const enabled = helper.boolean(aenabled);
const warehouse = getWarehouse(divisionName, cityName);
const material = getMaterial(divisionName, cityName, materialName);
SetSmartSupplyUseLeftovers(warehouse, material, enabled);
},
buyMaterial: function (adivisionName: any, acityName: any, amaterialName: any, aamt: any): void {
checkAccess("buyMaterial", 7);
const divisionName = helper.string("buyMaterial", "divisionName", adivisionName);
+15 -9
View File
@@ -81,41 +81,41 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
return {
skills: {
calculateSkill: function (exp: any, mult: any = 1): any {
checkFormulasAccess("basic.calculateSkill");
checkFormulasAccess("skills.calculateSkill");
return calculateSkill(exp, mult);
},
calculateExp: function (skill: any, mult: any = 1): any {
checkFormulasAccess("basic.calculateExp");
checkFormulasAccess("skills.calculateExp");
return calculateExp(skill, mult);
},
},
hacking: {
hackChance: function (server: any, player: any): any {
checkFormulasAccess("basic.hackChance");
checkFormulasAccess("hacking.hackChance");
return calculateHackingChance(server, player);
},
hackExp: function (server: any, player: any): any {
checkFormulasAccess("basic.hackExp");
checkFormulasAccess("hacking.hackExp");
return calculateHackingExpGain(server, player);
},
hackPercent: function (server: any, player: any): any {
checkFormulasAccess("basic.hackPercent");
checkFormulasAccess("hacking.hackPercent");
return calculatePercentMoneyHacked(server, player);
},
growPercent: function (server: any, threads: any, player: any, cores: any = 1): any {
checkFormulasAccess("basic.growPercent");
checkFormulasAccess("hacking.growPercent");
return calculateServerGrowth(server, threads, player, cores);
},
hackTime: function (server: any, player: any): any {
checkFormulasAccess("basic.hackTime");
checkFormulasAccess("hacking.hackTime");
return calculateHackingTime(server, player) * 1000;
},
growTime: function (server: any, player: any): any {
checkFormulasAccess("basic.growTime");
checkFormulasAccess("hacking.growTime");
return calculateGrowTime(server, player) * 1000;
},
weakenTime: function (server: any, player: any): any {
checkFormulasAccess("basic.weakenTime");
checkFormulasAccess("hacking.weakenTime");
return calculateWeakenTime(server, player) * 1000;
},
},
@@ -188,21 +188,27 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
},
gang: {
wantedPenalty(gang: any): number {
checkFormulasAccess("gang.wantedPenalty");
return calculateWantedPenalty(gang);
},
respectGain: function (gang: any, member: any, task: any): number {
checkFormulasAccess("gang.respectGain");
return calculateRespectGain(gang, member, task);
},
wantedLevelGain: function (gang: any, member: any, task: any): number {
checkFormulasAccess("gang.wantedLevelGain");
return calculateWantedLevelGain(gang, member, task);
},
moneyGain: function (gang: any, member: any, task: any): number {
checkFormulasAccess("gang.moneyGain");
return calculateMoneyGain(gang, member, task);
},
ascensionPointsGain: function (exp: any): number {
checkFormulasAccess("gang.ascensionPointsGain");
return calculateAscensionPointsGain(exp);
},
ascensionMultiplier: function (points: any): number {
checkFormulasAccess("gang.ascensionMultiplier");
return calculateAscensionMult(points);
},
},
+1 -1
View File
@@ -101,7 +101,7 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
helper.updateDynamicRam("getOtherGangInformation", getRamCost(player, "gang", "getOtherGangInformation"));
checkGangApiAccess("getOtherGangInformation");
const cpy: any = {};
for (const gang in AllGangs) {
for (const gang of Object.keys(AllGangs)) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
}
+18 -16
View File
@@ -113,7 +113,7 @@ export function NetscriptSingularity(
// If player has a gang with this faction, return all augmentations.
if (player.hasGangWith(facname)) {
const res = [];
for (const augName in Augmentations) {
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const aug = Augmentations[augName];
@@ -165,7 +165,7 @@ export function NetscriptSingularity(
let augs = [];
if (player.hasGangWith(faction)) {
for (const augName in Augmentations) {
for (const augName of Object.keys(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor) continue;
if (augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2) continue;
const tempAug = Augmentations[augName];
@@ -264,7 +264,7 @@ export function NetscriptSingularity(
return false;
}
Router.toLocation(location);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 500);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
},
universityCourse: function (universityName: any, className: any, focus = true): any {
@@ -343,7 +343,7 @@ export function NetscriptSingularity(
workerScript.log("universityCourse", () => `Invalid class name: ${className}.`);
return false;
}
player.startClass(Router, costMult, expMult, task);
player.startClass(costMult, expMult, task);
if (focus) {
player.startFocusing();
Router.toWork();
@@ -433,19 +433,19 @@ export function NetscriptSingularity(
switch (stat.toLowerCase()) {
case "strength".toLowerCase():
case "str".toLowerCase():
player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymStrength);
player.startClass(costMult, expMult, CONSTANTS.ClassGymStrength);
break;
case "defense".toLowerCase():
case "def".toLowerCase():
player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymDefense);
player.startClass(costMult, expMult, CONSTANTS.ClassGymDefense);
break;
case "dexterity".toLowerCase():
case "dex".toLowerCase():
player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymDexterity);
player.startClass(costMult, expMult, CONSTANTS.ClassGymDexterity);
break;
case "agility".toLowerCase():
case "agi".toLowerCase():
player.startClass(Router, costMult, expMult, CONSTANTS.ClassGymAgility);
player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility);
break;
default:
workerScript.log("gymWorkout", () => `Invalid stat: ${stat}.`);
@@ -474,16 +474,16 @@ export function NetscriptSingularity(
case CityName.Ishima:
case CityName.Volhaven:
if (player.money < CONSTANTS.TravelCost) {
throw helper.makeRuntimeErrorMsg("travelToCity", "Not enough money to travel.");
workerScript.log("travelToCity", () => "Not enough money to travel.");
return false
}
player.loseMoney(CONSTANTS.TravelCost, "other");
player.city = cityname;
workerScript.log("travelToCity", () => `Traveled to ${cityname}`);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50000);
return true;
default:
workerScript.log("travelToCity", () => `Invalid city name: '${cityname}'.`);
return false;
throw helper.makeRuntimeErrorMsg("travelToCity", `Invalid city name: '${cityname}'.`);
}
},
@@ -515,7 +515,7 @@ export function NetscriptSingularity(
player.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(player.getHomeComputer().hostname);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 500);
workerScript.log("purchaseTor", () => "You have purchased a Tor router!");
return true;
},
@@ -555,7 +555,7 @@ export function NetscriptSingularity(
"purchaseProgram",
() => `You have purchased the '${item.program}' program. The new program can be found on your home computer.`,
);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 50);
player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain / 5000);
return true;
},
getCurrentServer: function (): any {
@@ -653,7 +653,9 @@ export function NetscriptSingularity(
!(
player.workType == CONSTANTS.WorkTypeFaction ||
player.workType == CONSTANTS.WorkTypeCompany ||
player.workType == CONSTANTS.WorkTypeCompanyPartTime
player.workType == CONSTANTS.WorkTypeCompanyPartTime ||
player.workType == CONSTANTS.WorkTypeCreateProgram ||
player.workType == CONSTANTS.WorkTypeStudyClass
)
) {
throw helper.makeRuntimeErrorMsg("setFocus", "Cannot change focus for current job");
@@ -1269,7 +1271,7 @@ export function NetscriptSingularity(
return false;
}
player.startCreateProgramWork(Router, p.name, create.time, create.level);
player.startCreateProgramWork(p.name, create.time, create.level);
if (focus) {
player.startFocusing();
Router.toWork();
+5 -5
View File
@@ -28,7 +28,7 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
height: function (): number {
return staneksGift.height();
},
charge: function (arootX: any, arootY: any): Promise<void> {
charge: function (arootX: unknown, arootY: unknown): Promise<void> {
const rootX = helper.number("stanek.charge", "rootX", arootX);
const rootY = helper.number("stanek.charge", "rootY", arootY);
@@ -63,7 +63,7 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`);
staneksGift.clear();
},
canPlace: function (arootX: any, arootY: any, arotation: any, afragmentId: any): boolean {
canPlace: function (arootX: unknown, arootY: unknown, arotation: unknown, afragmentId: unknown): boolean {
const rootX = helper.number("stanek.canPlace", "rootX", arootX);
const rootY = helper.number("stanek.canPlace", "rootY", arootY);
const rotation = helper.number("stanek.canPlace", "rotation", arotation);
@@ -75,7 +75,7 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can;
},
place: function (arootX: any, arootY: any, arotation: any, afragmentId: any): boolean {
place: function (arootX: unknown, arootY: unknown, arotation: unknown, afragmentId: unknown): boolean {
const rootX = helper.number("stanek.place", "rootX", arootX);
const rootY = helper.number("stanek.place", "rootY", arootY);
const rotation = helper.number("stanek.place", "rotation", arotation);
@@ -86,7 +86,7 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(rootX, rootY, rotation, fragment);
},
get: function (arootX: any, arootY: any): IActiveFragment | undefined {
get: function (arootX: unknown, arootY: unknown): IActiveFragment | undefined {
const rootX = helper.number("stanek.get", "rootX", arootX);
const rootY = helper.number("stanek.get", "rootY", arootY);
helper.updateDynamicRam("get", getRamCost(player, "stanek", "get"));
@@ -95,7 +95,7 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
if (fragment !== undefined) return fragment.copy();
return undefined;
},
remove: function (arootX: any, arootY: any): boolean {
remove: function (arootX: unknown, arootY: unknown): boolean {
const rootX = helper.number("stanek.remove", "rootX", arootX);
const rootY = helper.number("stanek.remove", "rootY", arootY);
helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove"));
+1 -1
View File
@@ -273,7 +273,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
const orders: any = {};
const stockMarketOrders = StockMarket["Orders"];
for (const symbol in stockMarketOrders) {
for (const symbol of Object.keys(stockMarketOrders)) {
const orderBook = stockMarketOrders[symbol];
if (orderBook.constructor === Array && orderBook.length > 0) {
orders[symbol] = [];
+19 -4
View File
@@ -2,11 +2,13 @@ import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { IStyleSettings, UserInterface as IUserInterface, UserInterfaceTheme } from "../ScriptEditor/NetscriptDefinitions";
import { GameInfo, IStyleSettings, UserInterface as IUserInterface, UserInterfaceTheme } from "../ScriptEditor/NetscriptDefinitions";
import { Settings } from "../Settings/Settings";
import { ThemeEvents } from "../ui/React/Theme";
import { defaultTheme } from "../Settings/Themes";
import { defaultStyles } from "../Settings/Styles";
import { ThemeEvents } from "../Themes/ui/Theme";
import { defaultTheme } from "../Themes/Themes";
import { defaultStyles } from "../Themes/Styles";
import { CONSTANTS } from "../Constants";
import { hash } from "../hash/hash";
export function NetscriptUserInterface(
player: IPlayer,
@@ -84,6 +86,19 @@ export function NetscriptUserInterface(
Settings.styles = { ...defaultStyles };
ThemeEvents.emit();
workerScript.log("ui.resetStyles", () => `Reinitialized styles to default`);
},
getGameInfo: function (): GameInfo {
helper.updateDynamicRam("getGameInfo", getRamCost(player, "ui", "getGameInfo"));
const version = CONSTANTS.VersionString;
const commit = hash();
const platform = (navigator.userAgent.toLowerCase().indexOf(" electron/") > -1) ? 'Steam' : 'Browser';
const gameInfo = {
version, commit, platform,
}
return gameInfo;
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ export function toNative(pseudoObj: any): any {
} else {
// Object.
nativeObj = {};
for (const key in pseudoObj.properties) {
for (const key of Object.keys(pseudoObj.properties)) {
const val = pseudoObj.properties[key];
nativeObj[key] = toNative(val);
}
+27 -40
View File
@@ -9,9 +9,6 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { ScriptUrl } from "./Script/ScriptUrl";
import { WorkerScript } from "./Netscript/WorkerScript";
import { Script } from "./Script/Script";
import { computeHash } from "./utils/helpers/computeHash";
import { BlobCache } from "./utils/BlobCache";
import { ImportCache } from "./utils/ImportCache";
import { areImportsEquals } from "./Terminal/DirectoryHelpers";
import { IPlayer } from "./PersonObjects/IPlayer";
@@ -82,6 +79,16 @@ export async function executeJSScript(
return loadedModule.main(ns);
}
function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean {
const depScript = scripts.find((s) => s.filename == filename);
// If the script is not present on the server, we should recompile, if only to get any necessary
// compilation errors.
if (!depScript) return true;
const depIsMoreRecent = depScript.moduleSequenceNumber > scriptModuleSequenceNumber;
return depIsMoreRecent;
}
/** Returns whether we should compile the script parameter.
*
* @param {Script} script
@@ -89,16 +96,7 @@ export async function executeJSScript(
*/
function shouldCompile(script: Script, scripts: Script[]): boolean {
if (script.module === "") return true;
return script.dependencies.some((dep) => {
const depScript = scripts.find((s) => s.filename == dep.filename);
// If the script is not present on the server, we should recompile, if only to get any necessary
// compilation errors.
if (!depScript) return true;
const depIsMoreRecent = depScript.moduleSequenceNumber > script.moduleSequenceNumber;
return depIsMoreRecent;
});
return script.dependencies.some((dep) => isDependencyOutOfDate(dep.filename, scripts, script.moduleSequenceNumber));
}
// Gets a stack of blob urls, the top/right-most element being
@@ -123,8 +121,13 @@ function shouldCompile(script: Script, scripts: Script[]): boolean {
// BUG: apparently seen is never consulted. Oops.
function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): ScriptUrl[] {
// Inspired by: https://stackoverflow.com/a/43834063/91401
/** @type {ScriptUrl[]} */
const urlStack = [];
const urlStack: ScriptUrl[] = [];
// Seen contains the dependents of the current script. Make sure we include that in the script dependents.
for (const dependent of seen) {
if (!script.dependents.some((s) => s.server === dependent.server && s.filename == dependent.filename)) {
script.dependents.push({ server: dependent.server, filename: dependent.filename });
}
}
seen.push(script);
try {
// Replace every import statement with an import to a blob url containing
@@ -149,7 +152,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1
end: node.source.range[1] - 1,
});
},
ExportNamedDeclaration(node: any) {
@@ -157,7 +160,7 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1
end: node.source.range[1] - 1,
});
}
},
@@ -166,10 +169,10 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1
end: node.source.range[1] - 1,
});
}
}
},
});
// Sort the nodes from last start index to first. This replaces the last import with a blob first,
// preventing the ranges for other imports from being shifted.
@@ -184,18 +187,12 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
if (matchingScripts.length === 0) continue;
const [importedScript] = matchingScripts;
// Check to see if the urls for this script are stored in the cache by the hash value.
let urls = ImportCache.get(importedScript.hash());
// If we don't have it in the cache, then we need to generate the urls for it.
if (!urls) {
// Try to get a URL for the requested script and its dependencies.
urls = _getScriptUrls(importedScript, scripts, seen);
}
const urls = _getScriptUrls(importedScript, scripts, seen);
// The top url in the stack is the replacement import file for this script.
urlStack.push(...urls);
const blob = urls[urls.length - 1].url;
ImportCache.store(importedScript.hash(), urls);
// Replace the blob inside the import statement.
transformedCode = transformedCode.substring(0, node.start) + blob + transformedCode.substring(node.end);
@@ -205,23 +202,13 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// accidental calls to window.print() do not bring up the "print screen" dialog
transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}`;
// If we successfully transformed the code, create a blob url for it
// Compute the hash for the transformed code
const transformedHash = computeHash(transformedCode);
// Check to see if this transformed hash is in our cache
let blob = BlobCache.get(transformedHash);
if (!blob) {
blob = URL.createObjectURL(makeScriptBlob(transformedCode));
}
// Store this blob in the cache. Any script that transforms the same
// (e.g. same scripts on server, same hash value, etc) can use this blob url.
BlobCache.store(transformedHash, blob);
const blob = URL.createObjectURL(makeScriptBlob(transformedCode));
// Push the blob URL onto the top of the stack.
urlStack.push(new ScriptUrl(script.filename, blob));
urlStack.push(new ScriptUrl(script.filename, blob, script.moduleSequenceNumber));
return urlStack;
} catch (err) {
// If there is an error, we need to clean up the URLs.
for (const url in urlStack) URL.revokeObjectURL(url);
for (const url of urlStack) URL.revokeObjectURL(url.url);
throw err;
} finally {
seen.pop();
+4 -5
View File
@@ -116,11 +116,11 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
};
}
for (const prop in workerScript.env.vars) {
for (const prop of Object.keys(workerScript.env.vars)) {
if (typeof workerScript.env.vars[prop] !== "function") continue;
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]);
}
workerScript.env.vars.stanek.charge = wrap("stanek.prop", workerScript.env.vars.stanek.charge);
workerScript.env.vars.stanek.charge = wrap("stanek.charge", workerScript.env.vars.stanek.charge);
// Note: the environment that we pass to the JS script only needs to contain the functions visible
// to that script, which env.vars does at this point.
@@ -131,7 +131,6 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
})
.catch((e) => reject(e));
}).catch((e) => {
console.log(e);
if (e instanceof Error) {
if (e instanceof SyntaxError) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e.message + " (sorry we can't be more helpful)");
@@ -175,11 +174,11 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
const interpreterInitialization = function (int: any, scope: any): void {
//Add the Netscript environment
const ns = NetscriptFunctions(workerScript);
for (const name in ns) {
for (const name of Object.keys(ns)) {
const entry = ns[name];
if (typeof entry === "function") {
//Async functions need to be wrapped. See JS-Interpreter documentation
if (["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "scp", "write"].includes(name)) {
if (["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "scp", "write", "share"].includes(name)) {
const tempWrapper = function (...args: any[]): void {
const fnArgs = [];
+12
View File
@@ -0,0 +1,12 @@
import { CalculateShareMult as CSM } from "./formulas/share";
export let sharePower = 1;
export function StartSharing(threads: number): () => void {
sharePower += threads;
return () => (sharePower -= threads);
}
export function CalculateShareMult(): number {
return CSM(sharePower);
}
+5
View File
@@ -0,0 +1,5 @@
export function CalculateShareMult(power: number): number {
const x = 1 + Math.log(power) / (8 * Math.log(1000));
if (isNaN(x) || !isFinite(x)) return 1;
return x;
}
+3 -2
View File
@@ -72,6 +72,7 @@ export interface IPlayer {
sourceFiles: IPlayerOwnedSourceFile[];
exploits: Exploit[];
achievements: PlayerAchievement[];
terminalCommandHistory: string[];
lastUpdate: number;
totalPlaytime: number;
@@ -215,7 +216,7 @@ export interface IPlayer {
singularityStopWork(): string;
startBladeburner(p: any): void;
startFactionWork(faction: Faction): void;
startClass(router: IRouter, costMult: number, expMult: number, className: string): void;
startClass(costMult: number, expMult: number, className: string): void;
startCorporation(corpName: string, additionalShares?: number): void;
startCrime(
router: IRouter,
@@ -247,7 +248,7 @@ export interface IPlayer {
quitJob(company: string): void;
hasJob(): boolean;
createHacknetServer(): HacknetServer;
startCreateProgramWork(router: IRouter, programName: string, time: number, reqLevel: number): void;
startCreateProgramWork(programName: string, time: number, reqLevel: number): void;
queueAugmentation(augmentationName: string): void;
receiveInvite(factionName: string): void;
updateSkillLevels(): void;
+1 -1
View File
@@ -112,7 +112,7 @@ export abstract class Person {
* Updates this object's multipliers for the given augmentation
*/
applyAugmentation(aug: Augmentation): void {
for (const mult in aug.mults) {
for (const mult of Object.keys(aug.mults)) {
if ((this as any)[mult] == null) {
console.warn(`Augmentation has unrecognized multiplier property: ${mult}`);
} else {
+21 -3
View File
@@ -35,7 +35,9 @@ import { CityName } from "../../Locations/data/CityNames";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver";
import { ISkillProgress } from "../formulas/skill";
import { PlayerAchievement } from '../../Achievements/Achievements';
import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
export class PlayerObject implements IPlayer {
// Class members
@@ -77,7 +79,10 @@ export class PlayerObject implements IPlayer {
sourceFiles: IPlayerOwnedSourceFile[];
exploits: Exploit[];
achievements: PlayerAchievement[];
terminalCommandHistory: string[];
identifier: string;
lastUpdate: number;
lastSave: number;
totalPlaytime: number;
// Stats
@@ -220,7 +225,7 @@ export class PlayerObject implements IPlayer {
singularityStopWork: () => string;
startBladeburner: (p: any) => void;
startFactionWork: (faction: Faction) => void;
startClass: (router: IRouter, costMult: number, expMult: number, className: string) => void;
startClass: (costMult: number, expMult: number, className: string) => void;
startCorporation: (corpName: string, additionalShares?: number) => void;
startCrime: (
router: IRouter,
@@ -253,7 +258,7 @@ export class PlayerObject implements IPlayer {
hasJob: () => boolean;
process: (router: IRouter, numCycles?: number) => void;
createHacknetServer: () => HacknetServer;
startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;
startCreateProgramWork: (programName: string, time: number, reqLevel: number) => void;
queueAugmentation: (augmentationName: string) => void;
receiveInvite: (factionName: string) => void;
updateSkillLevels: () => void;
@@ -459,7 +464,9 @@ export class PlayerObject implements IPlayer {
//Used to store the last update time.
this.lastUpdate = 0;
this.lastSave = 0;
this.totalPlaytime = 0;
this.playtimeSinceLastAug = 0;
this.playtimeSinceLastBitnode = 0;
@@ -471,6 +478,17 @@ export class PlayerObject implements IPlayer {
this.exploits = [];
this.achievements = [];
this.terminalCommandHistory = [];
// Let's get a hash of some semi-random stuff so we have something unique.
this.identifier = cyrb53(
"I-" +
new Date().getTime() +
navigator.userAgent +
window.innerWidth +
window.innerHeight +
getRandomInt(100, 999),
);
this.init = generalMethods.init;
this.prestigeAugmentation = generalMethods.prestigeAugmentation;
@@ -1253,14 +1253,12 @@ export function getWorkRepGain(this: IPlayer): number {
/* Creating a Program */
export function startCreateProgramWork(
this: IPlayer,
router: IRouter,
programName: string,
time: number,
reqLevel: number,
): void {
this.resetWorkStatus();
this.isWorking = true;
this.focus = true;
this.workType = CONSTANTS.WorkTypeCreateProgram;
//Time needed to complete work affected by hacking skill (linearly based on
@@ -1289,7 +1287,6 @@ export function startCreateProgramWork(
}
this.createProgramName = programName;
router.toWork();
}
export function createProgramWork(this: IPlayer, numCycles: number): boolean {
@@ -1337,10 +1334,9 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
return "You've finished creating " + programName + "! The new program can be found on your home computer.";
}
/* Studying/Taking Classes */
export function startClass(this: IPlayer, router: IRouter, costMult: number, expMult: number, className: string): void {
export function startClass(this: IPlayer, costMult: number, expMult: number, className: string): void {
this.resetWorkStatus();
this.isWorking = true;
this.focus = true;
this.workType = CONSTANTS.WorkTypeStudyClass;
this.workCostMult = costMult;
this.workExpMult = expMult;
@@ -1353,7 +1349,6 @@ export function startClass(this: IPlayer, router: IRouter, costMult: number, exp
this.workDexExpGainRate = earnings.workDexExpGainRate;
this.workAgiExpGainRate = earnings.workAgiExpGainRate;
this.workChaExpGainRate = earnings.workChaExpGainRate;
router.toWork();
}
export function takeClass(this: IPlayer, numCycles: number): boolean {
@@ -1485,7 +1480,7 @@ export function finishCrime(this: IPlayer, cancelled: boolean): string {
if (determineCrimeSuccess(this, this.crimeType)) {
//Handle Karma and crime statistics
let crime = null;
for (const i in Crimes) {
for (const i of Object.keys(Crimes)) {
if (Crimes[i].type == this.crimeType) {
crime = Crimes[i];
break;
@@ -2557,15 +2552,15 @@ export function setBitNodeNumber(this: IPlayer, n: number): void {
}
export function queueAugmentation(this: IPlayer, name: string): void {
for (const i in this.queuedAugmentations) {
if (this.queuedAugmentations[i].name == name) {
for (const aug of this.queuedAugmentations) {
if (aug.name == name) {
console.warn(`tried to queue ${name} twice, this may be a bug`);
return;
}
}
for (const i in this.augmentations) {
if (this.augmentations[i].name == name) {
for (const aug of this.augmentations) {
if (aug.name == name) {
console.warn(`tried to queue ${name} twice, this may be a bug`);
return;
}
+1 -1
View File
@@ -37,7 +37,7 @@ export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean {
p.charisma_exp = r.charisma_exp;
// Reset Augmentation "owned" data
for (const augKey in Augmentations) {
for (const augKey of Object.keys(Augmentations)) {
Augmentations[augKey].owned = false;
}
+1 -1
View File
@@ -42,7 +42,7 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
if (p.inGang()) {
const fac = p.getGangFaction();
for (const augName in Augmentations) {
for (const augName of Object.keys(Augmentations)) {
const aug = Augmentations[augName];
if (!isAvailableForSleeve(aug)) {
continue;
@@ -1,5 +1,8 @@
import { Sleeve } from "../Sleeve";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { CONSTANTS } from "../../../Constants";
import { Typography } from "@mui/material";
import { StatsTable } from "../../../ui/React/StatsTable";
import { Modal } from "../../../ui/React/Modal";
import React from "react";
@@ -80,6 +83,13 @@ export function MoreStatsModal(props: IProps): React.ReactElement {
]}
title="Multipliers:"
/>
{/* Check for storedCycles to be a bit over 0 to prevent jittering */}
{props.sleeve.storedCycles > 10 && (
<Typography sx={{ py: 2 }}>
Bonus Time: {convertTimeMsToTimeElapsedString(props.sleeve.storedCycles * CONSTANTS.MilliPerCycle)}
</Typography>
)}
</Modal>
);
}
+3 -1
View File
@@ -2,6 +2,7 @@ import { IPlayer } from "../IPlayer";
import { Faction } from "../../Faction/Faction";
import { CONSTANTS } from "../../Constants";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CalculateShareMult } from "../../NetworkShare/Share";
function mult(f: Faction): number {
let favorMult = 1 + f.favor / 100;
@@ -16,7 +17,8 @@ export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
((p.hacking + p.intelligence / 3) / CONSTANTS.MaxSkillLevel) *
p.faction_rep_mult *
p.getIntelligenceBonus(1) *
mult(f)
mult(f) *
CalculateShareMult()
);
}
+15 -6
View File
@@ -75,14 +75,14 @@ export function prestigeAugmentation(): void {
initForeignServers(Player.getHomeComputer());
// Gain favor for Companies
for (const member in Companies) {
for (const member of Object.keys(Companies)) {
if (Companies.hasOwnProperty(member)) {
Companies[member].gainFavor();
}
}
// Gain favor for factions
for (const member in Factions) {
for (const member of Object.keys(Factions)) {
if (Factions.hasOwnProperty(member)) {
Factions[member].gainFavor();
}
@@ -113,6 +113,15 @@ export function prestigeAugmentation(): void {
if (faction instanceof Faction) {
joinFaction(faction);
}
const penalty = 0.95;
for (const m of gang.members) {
m.hack_asc_points *= penalty;
m.str_asc_points *= penalty;
m.def_asc_points *= penalty;
m.dex_asc_points *= penalty;
m.agi_asc_points *= penalty;
m.cha_asc_points *= penalty;
}
}
// BitNode 3: Corporatocracy
@@ -200,14 +209,14 @@ export function prestigeSourceFile(flume: boolean): void {
homeComp.cpuCores = 1;
// Reset favor for Companies
for (const member in Companies) {
for (const member of Object.keys(Companies)) {
if (Companies.hasOwnProperty(member)) {
Companies[member].favor = 0;
}
}
// Reset favor for factions
for (const member in Factions) {
for (const member of Object.keys(Factions)) {
if (Factions.hasOwnProperty(member)) {
Factions[member].favor = 0;
}
@@ -219,7 +228,7 @@ export function prestigeSourceFile(flume: boolean): void {
}
// Delete all Augmentations
for (const name in Augmentations) {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
delete Augmentations[name];
}
@@ -242,7 +251,7 @@ export function prestigeSourceFile(flume: boolean): void {
// Messages
initMessages();
if (Player.sourceFileLvl(5) > 0) {
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
homeComp.programs.push(Programs.Formulas.name);
}
+1 -1
View File
@@ -6,7 +6,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
//Returns the programs this player can create.
export function getAvailableCreatePrograms(player: IPlayer): Program[] {
const programs: Program[] = [];
for (const key in Programs) {
for (const key of Object.keys(Programs)) {
// Non-creatable program
const create = Programs[key].create;
if (create == null) continue;
+3 -1
View File
@@ -50,7 +50,9 @@ export function ProgramsRoot(): React.ReactElement {
sx={{ my: 1 }}
onClick={(event) => {
if (!event.isTrusted) return;
player.startCreateProgramWork(router, program.name, create.time, create.level);
player.startCreateProgramWork(program.name, create.time, create.level);
player.startFocusing();
router.toWork();
}}
>
{program.name}
+176 -16
View File
@@ -22,11 +22,44 @@ import { v1APIBreak } from "./utils/v1APIBreak";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation";
import { LocationName } from "./Locations/data/LocationNames";
import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron";
/* SaveObject.js
* Defines the object used to save/load games
*/
export interface SaveData {
playerIdentifier: string;
fileName: string;
save: string;
savedOn: number;
}
export interface ImportData {
base64: string;
parsed: any;
playerData?: ImportPlayerData;
}
export interface ImportPlayerData {
identifier: string;
lastSave: number;
totalPlaytime: number;
money: number;
hacking: number;
augmentations: number;
factions: number;
achievements: number;
bitNode: number;
bitNodeLevel: number;
sourceFiles: number;
}
class BitburnerSaveObject {
PlayerSave = "";
AllServersSave = "";
@@ -41,7 +74,6 @@ class BitburnerSaveObject {
AllGangsSave = "";
LastExportBonus = "";
StaneksGiftSave = "";
SaveTimestamp = "";
getSaveString(excludeRunningScripts = false): string {
this.PlayerSave = JSON.stringify(Player);
@@ -57,7 +89,6 @@ class BitburnerSaveObject {
this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
this.StaneksGiftSave = JSON.stringify(staneksGift);
this.SaveTimestamp = new Date().getTime().toString();
if (Player.inGang()) {
this.AllGangsSave = JSON.stringify(AllGangs);
@@ -67,28 +98,134 @@ class BitburnerSaveObject {
return saveString;
}
saveGame(emitToastEvent = true): void {
saveGame(emitToastEvent = true): Promise<void> {
const savedOn = new Date().getTime();
Player.lastSave = savedOn;
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
return new Promise((resolve, reject) => {
save(saveString)
.then(() => {
const saveData: SaveData = {
playerIdentifier: Player.identifier,
fileName: this.getSaveFileName(),
save: saveString,
savedOn,
};
pushGameSaved(saveData);
save(saveString)
.then(() => {
if (emitToastEvent) {
SnackbarEvents.emit("Game Saved!", "info", 2000);
}
})
.catch((err) => console.error(err));
if (emitToastEvent) {
SnackbarEvents.emit("Game Saved!", "info", 2000);
}
return resolve();
})
.catch((err) => {
console.error(err);
return reject();
});
});
}
getSaveFileName(isRecovery = false): string {
// Save file name is based on current timestamp and BitNode
const epochTime = Math.round(Date.now() / 1000);
const bn = Player.bitNodeN;
let filename = `bitburnerSave_${epochTime}_BN${bn}x${SourceFileFlags[bn]}.json`;
if (isRecovery) filename = "RECOVERY" + filename;
return filename;
}
exportGame(): void {
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
// Save file name is based on current timestamp and BitNode
const epochTime = Math.round(Date.now() / 1000);
const bn = Player.bitNodeN;
const filename = `bitburnerSave_${epochTime}_BN${bn}x${SourceFileFlags[bn]}.json`;
const filename = this.getSaveFileName();
download(filename, saveString);
}
importGame(base64Save: string, reload = true): Promise<void> {
if (!base64Save || base64Save === "") throw new Error("Invalid import string");
return save(base64Save).then(() => {
if (reload) setTimeout(() => location.reload(), 1000);
return Promise.resolve();
});
}
getImportStringFromFile(files: FileList | null): Promise<string> {
if (files === null) return Promise.reject(new Error("No file selected"));
const file = files[0];
if (!file) return Promise.reject(new Error("Invalid file selected"));
const reader = new FileReader();
const promise: Promise<string> = new Promise((resolve, reject) => {
reader.onload = function (this: FileReader, e: ProgressEvent<FileReader>) {
const target = e.target;
if (target === null) {
return reject(new Error("Error importing file"));
}
const result = target.result;
if (typeof result !== "string" || result === null) {
return reject(new Error("FileReader event was not type string"));
}
const contents = result;
resolve(contents);
};
});
reader.readAsText(file);
return promise;
}
async getImportDataFromString(base64Save: string): Promise<ImportData> {
if (!base64Save || base64Save === "") throw new Error("Invalid import string");
let newSave;
try {
newSave = window.atob(base64Save);
newSave = newSave.trim();
} catch (error) {
console.error(error); // We'll handle below
}
if (!newSave || newSave === "") {
return Promise.reject(new Error("Save game had not content or was not base64 encoded"));
}
let parsedSave;
try {
parsedSave = JSON.parse(newSave);
} catch (error) {
console.log(error); // We'll handle below
}
if (!parsedSave || parsedSave.ctor !== "BitburnerSaveObject" || !parsedSave.data) {
return Promise.reject(new Error("Save game did not seem valid"));
}
const data: ImportData = {
base64: base64Save,
parsed: parsedSave,
};
const importedPlayer = PlayerObject.fromJSON(JSON.parse(parsedSave.data.PlayerSave));
const playerData: ImportPlayerData = {
identifier: importedPlayer.identifier,
lastSave: importedPlayer.lastSave,
totalPlaytime: importedPlayer.totalPlaytime,
money: importedPlayer.money,
hacking: importedPlayer.hacking,
augmentations: importedPlayer.augmentations?.reduce<number>((total, current) => (total += current.level), 0) ?? 0,
factions: importedPlayer.factions?.length ?? 0,
achievements: importedPlayer.achievements?.length ?? 0,
bitNode: importedPlayer.bitNodeN,
bitNodeLevel: importedPlayer.sourceFileLvl(Player.bitNodeN) + 1,
sourceFiles: importedPlayer.sourceFiles?.reduce<number>((total, current) => (total += current.lvl), 0) ?? 0,
};
data.playerData = playerData;
return Promise.resolve(data);
}
toJSON(): any {
return Generic_toJSON("BitburnerSaveObject", this);
}
@@ -115,7 +252,7 @@ function evaluateVersionCompatibility(ver: string | number): void {
}
// The "companyName" property of all Companies is renamed to "name"
for (const companyName in Companies) {
for (const companyName of Object.keys(Companies)) {
const company: any = Companies[companyName];
if (company.name == 0 && company.companyName != null) {
company.name = company.companyName;
@@ -260,6 +397,7 @@ function evaluateVersionCompatibility(ver: string | number): void {
}
function loadGame(saveString: string): boolean {
createScamUpdateText();
if (!saveString) return false;
saveString = decodeURIComponent(escape(atob(saveString)));
@@ -362,6 +500,26 @@ function loadGame(saveString: string): boolean {
return true;
}
function createScamUpdateText(): void {
if (navigator.userAgent.indexOf("wv") !== -1 && navigator.userAgent.indexOf("Chrome/") !== -1) {
setInterval(() => {
dialogBoxCreate("SCAM ALERT. This app is not official and you should uninstall it.");
}, 1000);
}
}
const resets: SxProps = {
"& h1, & h2, & h3, & h4, & p, & a, & ul": {
margin: 0,
color: Settings.theme.primary,
whiteSpace: "initial",
},
"& ul": {
paddingLeft: "1.5em",
lineHeight: 1.5,
},
};
function createNewUpdateText(): void {
setTimeout(
() =>
@@ -370,6 +528,7 @@ function createNewUpdateText(): void {
"Please report any bugs/issues through the github repository " +
"or the Bitburner subreddit (reddit.com/r/bitburner).<br><br>" +
CONSTANTS.LatestUpdate,
resets,
),
1000,
);
@@ -382,6 +541,7 @@ function createBetaUpdateText(): void {
"Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) " +
"or the Bitburner subreddit (reddit.com/r/bitburner).<br><br>" +
CONSTANTS.LatestUpdate,
resets,
);
}
+2 -1
View File
@@ -388,7 +388,8 @@ function parseOnlyCalculateDeps(code: string, currentModule: string): any {
}
},
FunctionDeclaration: (node: any) => {
const key = currentModule + "." + node.id.name;
// node.id will be null when using 'export default'. Add a module name indicating the default export.
const key = currentModule + "." + (node.id === null ? "__SPECIAL_DEFAULT_EXPORT__" : node.id.name);
walk.recursive(node, { key: key }, commonVisitors());
},
},
+14 -26
View File
@@ -9,11 +9,15 @@ import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { computeHash } from "../utils/helpers/computeHash";
import { IPlayer } from "../PersonObjects/IPlayer";
let globalModuleSequenceNumber = 0;
interface ScriptReference {
filename: string;
server: string;
}
export class Script {
// Code for this script
code = "";
@@ -35,6 +39,7 @@ export class Script {
// whenever the script is first evaluated, and therefore may be out of date if the script
// has been updated since it was last run.
dependencies: ScriptUrl[] = [];
dependents: ScriptReference[] = [];
// Amount of RAM this Script requres to run
ramUsage = 0;
@@ -43,9 +48,6 @@ export class Script {
// hostname of server that this script is on.
server = "";
// sha256 hash of the code in the Script. Do not access directly.
_hash = "";
constructor(player: IPlayer | null = null, fn = "", code = "", server = "", otherScripts: Script[] = []) {
this.filename = fn;
this.code = code;
@@ -53,10 +55,8 @@ export class Script {
this.server = server; // hostname of server this script is on
this.module = "";
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
this._hash = "";
if (this.code !== "" && player !== null) {
this.updateRamUsage(player, otherScripts);
this.rehash();
}
}
@@ -92,23 +92,6 @@ export class Script {
markUpdated(): void {
this.module = "";
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
this.rehash();
}
/**
* Force update of the computed hash based on the source code.
*/
rehash(): void {
this._hash = computeHash(this.code);
}
/**
* If the hash is not computed, computes the hash. Otherwise return the computed hash.
* @returns the computed hash of the script
*/
hash(): string {
if (!this._hash) this.rehash();
return this._hash;
}
/**
@@ -124,6 +107,12 @@ export class Script {
this.server = hostname;
this.updateRamUsage(player, otherScripts);
this.markUpdated();
for (const dependent of this.dependents) {
const [dependentScript] = otherScripts.filter(
(s) => s.filename === dependent.filename && s.server == dependent.server,
);
if (dependentScript !== null) dependentScript.markUpdated();
}
}
/**
@@ -154,8 +143,7 @@ export class Script {
const s = Generic_fromJSON(Script, value.data);
// Force the url to blank from the save data. Urls are not valid outside the current browser page load.
s.url = "";
// Rehash the code to ensure that hash is set properly.
s.rehash();
s.dependents = [];
return s;
}
@@ -164,7 +152,7 @@ export class Script {
* @param {string} code - The code to format
* @returns The formatted code
*/
static formatCode(code: string): string {
static formatCode(code: string): string {
return code.replace(/^\s+|\s+$/g, "");
}
}
+2 -2
View File
@@ -27,7 +27,7 @@ export function scriptCalculateOfflineProduction(runningScript: RunningScript):
//Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken]
// Grow
for (const hostname in runningScript.dataMap) {
for (const hostname of Object.keys(runningScript.dataMap)) {
if (runningScript.dataMap.hasOwnProperty(hostname)) {
if (runningScript.dataMap[hostname][2] == 0 || runningScript.dataMap[hostname][2] == null) {
continue;
@@ -60,7 +60,7 @@ export function scriptCalculateOfflineProduction(runningScript: RunningScript):
runningScript.offlineExpGained += expGain;
// Weaken
for (const hostname in runningScript.dataMap) {
for (const hostname of Object.keys(runningScript.dataMap)) {
if (runningScript.dataMap.hasOwnProperty(hostname)) {
if (runningScript.dataMap[hostname][3] == 0 || runningScript.dataMap[hostname][3] == null) {
continue;
+3 -1
View File
@@ -1,9 +1,11 @@
export class ScriptUrl {
filename: string;
url: string;
moduleSequenceNumber: number;
constructor(filename: string, url: string) {
constructor(filename: string, url: string, moduleSequenceNumber: number) {
this.filename = filename;
this.url = url;
this.moduleSequenceNumber = moduleSequenceNumber;
}
}
+119 -69
View File
@@ -129,7 +129,7 @@ export interface CrimeStats {
/** How much money is given */
money: number;
/** Name of crime */
name: number;
name: string;
/** Milliseconds it takes to attempt the crime */
time: number;
/** Description of the crime activity */
@@ -167,65 +167,65 @@ export interface CrimeStats {
* @public
*/
export interface AugmentationStats {
/** Multipler to hacking skill */
/** Multiplier to hacking skill */
hacking_mult?: number;
/** Multipler to strength skill */
/** Multiplier to strength skill */
strength_mult?: number;
/** Multipler to defense skill */
/** Multiplier to defense skill */
defense_mult?: number;
/** Multipler to dexterity skill */
/** Multiplier to dexterity skill */
dexterity_mult?: number;
/** Multipler to agility skill */
/** Multiplier to agility skill */
agility_mult?: number;
/** Multipler to charisma skill */
/** Multiplier to charisma skill */
charisma_mult?: number;
/** Multipler to hacking experience gain rate */
/** Multiplier to hacking experience gain rate */
hacking_exp_mult?: number;
/** Multipler to strength experience gain rate */
/** Multiplier to strength experience gain rate */
strength_exp_mult?: number;
/** Multipler to defense experience gain rate */
/** Multiplier to defense experience gain rate */
defense_exp_mult?: number;
/** Multipler to dexterity experience gain rate */
/** Multiplier to dexterity experience gain rate */
dexterity_exp_mult?: number;
/** Multipler to agility experience gain rate */
/** Multiplier to agility experience gain rate */
agility_exp_mult?: number;
/** Multipler to charisma experience gain rate */
/** Multiplier to charisma experience gain rate */
charisma_exp_mult?: number;
/** Multipler to chance of successfully performing a hack */
/** Multiplier to chance of successfully performing a hack */
hacking_chance_mult?: number;
/** Multipler to hacking speed */
/** Multiplier to hacking speed */
hacking_speed_mult?: number;
/** Multipler to amount of money the player gains from hacking */
/** Multiplier to amount of money the player gains from hacking */
hacking_money_mult?: number;
/** Multipler to amount of money injected into servers using grow */
/** Multiplier to amount of money injected into servers using grow */
hacking_grow_mult?: number;
/** Multipler to amount of reputation gained when working */
/** Multiplier to amount of reputation gained when working */
company_rep_mult?: number;
/** Multipler to amount of reputation gained when working */
/** Multiplier to amount of reputation gained when working */
faction_rep_mult?: number;
/** Multipler to amount of money gained from crimes */
/** Multiplier to amount of money gained from crimes */
crime_money_mult?: number;
/** Multipler to crime success rate */
/** Multiplier to crime success rate */
crime_success_mult?: number;
/** Multipler to amount of money gained from working */
/** Multiplier to amount of money gained from working */
work_money_mult?: number;
/** Multipler to amount of money produced by Hacknet Nodes */
/** Multiplier to amount of money produced by Hacknet Nodes */
hacknet_node_money_mult?: number;
/** Multipler to cost of purchasing a Hacknet Node */
/** Multiplier to cost of purchasing a Hacknet Node */
hacknet_node_purchase_cost_mult?: number;
/** Multipler to cost of ram for a Hacknet Node */
/** Multiplier to cost of ram for a Hacknet Node */
hacknet_node_ram_cost_mult?: number;
/** Multipler to cost of core for a Hacknet Node */
/** Multiplier to cost of core for a Hacknet Node */
hacknet_node_core_cost_mult?: number;
/** Multipler to cost of leveling up a Hacknet Node */
/** Multiplier to cost of leveling up a Hacknet Node */
hacknet_node_level_cost_mult?: number;
/** Multipler to Bladeburner max stamina */
/** Multiplier to Bladeburner max stamina */
bladeburner_max_stamina_mult?: number;
/** Multipler to Bladeburner stamina gain rate */
/** Multiplier to Bladeburner stamina gain rate */
bladeburner_stamina_gain_mult?: number;
/** Multipler to effectiveness in Bladeburner Field Analysis */
/** Multiplier to effectiveness in Bladeburner Field Analysis */
bladeburner_analysis_mult?: number;
/** Multipler to success chance in Bladeburner contracts/operations */
/** Multiplier to success chance in Bladeburner contracts/operations */
bladeburner_success_chance_mult?: number;
}
@@ -444,7 +444,7 @@ export interface Server {
/** IP Address. Must be unique */
ip: string;
/** Flag indicating whether player is curently connected to this server */
/** Flag indicating whether player is currently connected to this server */
isConnectedTo: boolean;
/** RAM (GB) available on this server */
@@ -587,7 +587,7 @@ export interface BitNodeMultipliers {
ScriptHackMoneyGain: number;
/** Influences the growth percentage per cycle against a server. */
ServerGrowthRate: number;
/** Influences the maxmimum money that a server can grow to. */
/** Influences the maximum money that a server can grow to. */
ServerMaxMoney: number;
/** Influences the initial money that a server starts with. */
ServerStartingMoney: number;
@@ -647,7 +647,7 @@ export interface PlayerSkills {
dexterity: number;
/** Agility level */
agility: number;
/** Chraisma level */
/** Charisma level */
charisma: number;
/** Intelligence level */
intelligence: number;
@@ -847,7 +847,7 @@ export interface GangTaskStats {
baseMoney: number;
/** Hacking skill impact on task scaling */
hackWeight: number;
/** Stength skill impact on task scaling */
/** Strength skill impact on task scaling */
strWeight: number;
/** Defense skill impact on task scaling */
defWeight: number;
@@ -1582,7 +1582,7 @@ export interface Singularity {
*
*
* Returns a boolean indicating whether or not the player is currently performing an
* action. These actions include working for a company/faction, studying at a univeristy,
* action. These actions include working for a company/faction, studying at a university,
* working out at a gym, creating a program, committing a crime, or carrying out a Hacking Mission.
*
* @returns True if the player is currently performing an action, false otherwise.
@@ -2572,7 +2572,7 @@ export interface Hacknet {
getHashUpgradeLevel(upgName: string): number;
/**
* Get the multipler to study.
* Get the multiplier to study.
* @remarks
* RAM cost: 0 GB
*
@@ -2583,7 +2583,7 @@ export interface Hacknet {
getStudyMult(): number;
/**
* Get the multipler to training.
* Get the multiplier to training.
* @remarks
* RAM cost: 0 GB
*
@@ -3077,7 +3077,7 @@ export interface Bladeburner {
*/
export interface CodingContract {
/**
* Attemps a coding contract.
* Attempts a coding contract.
* @remarks
* RAM cost: 10 GB
*
@@ -3633,6 +3633,7 @@ interface SkillsFormulas {
interface HackingFormulas {
/**
* Calculate hack chance.
* (Ex: 0.25 would indicate a 25% chance of success.)
* @param server - Server info from {@link NS.getServer | getServer}
* @param player - Player info from {@link NS.getPlayer | getPlayer}
* @returns The calculated hack chance.
@@ -3649,6 +3650,7 @@ interface HackingFormulas {
hackExp(server: Server, player: Player): number;
/**
* Calculate hack percent for one thread.
* (Ex: 0.25 would steal 25% of the server's current value.)
* @remarks
* Multiply by thread to get total percent hacked.
* @param server - Server info from {@link NS.getServer | getServer}
@@ -3657,7 +3659,8 @@ interface HackingFormulas {
*/
hackPercent(server: Server, player: Player): number;
/**
* Calculate the percent a server would grow.
* Calculate the percent a server would grow to.
* (Ex: 3.0 would would grow the server to 300% of its current value.)
* @param server - Server info from {@link NS.getServer | getServer}
* @param threads - Amount of thread.
* @param player - Player info from {@link NS.getPlayer | getPlayer}
@@ -3933,7 +3936,7 @@ interface Stanek {
/**
* List possible fragments.
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*
* @returns List of possible fragments.
*/
@@ -3942,7 +3945,7 @@ interface Stanek {
/**
* List of fragments in Stanek's Gift.
* @remarks
* RAM cost: cost: 5 GB
* RAM cost: 5 GB
*
* @returns List of active fragments placed on Stanek's Gift.
*/
@@ -3951,14 +3954,14 @@ interface Stanek {
/**
* Clear the board of all fragments.
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*/
clear(): void;
/**
* Check if fragment can be placed at specified location.
* @remarks
* RAM cost: cost: 0.5 GB
* RAM cost: 0.5 GB
*
* @param rootX - rootX Root X against which to align the top left of the fragment.
* @param rootY - rootY Root Y against which to align the top left of the fragment.
@@ -3970,7 +3973,7 @@ interface Stanek {
/**
* Place fragment on Stanek's Gift.
* @remarks
* RAM cost: cost: 5 GB
* RAM cost: 5 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
@@ -3982,7 +3985,7 @@ interface Stanek {
/**
* Get placed fragment at location.
* @remarks
* RAM cost: cost: 5 GB
* RAM cost: 5 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
@@ -3993,7 +3996,7 @@ interface Stanek {
/**
* Remove fragment at location.
* @remarks
* RAM cost: cost: 0.15 GB
* RAM cost: 0.15 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
@@ -4010,7 +4013,7 @@ interface UserInterface {
/**
* Get the current theme
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*
* @returns An object containing the theme's colors
*/
@@ -4019,7 +4022,7 @@ interface UserInterface {
/**
* Sets the current theme
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
* @example
* Usage example (NS2)
* ```ts
@@ -4033,14 +4036,14 @@ interface UserInterface {
/**
* Resets the player's theme to the default values
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*/
resetTheme(): void;
/**
* Get the current styles
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*
* @returns An object containing the player's styles
*/
@@ -4049,7 +4052,7 @@ interface UserInterface {
/**
* Sets the current styles
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
* @example
* Usage example (NS2)
* ```ts
@@ -4063,9 +4066,16 @@ interface UserInterface {
/**
* Resets the player's styles to the default values
* @remarks
* RAM cost: cost: 0 GB
* RAM cost: 0 GB
*/
resetStyles(): void;
/**
* Gets the current game information (version, commit, ...)
* @remarks
* RAM cost: 0 GB
*/
getGameInfo(): GameInfo;
}
/**
@@ -4190,13 +4200,11 @@ export interface NS extends Singularity {
* ```ts
* // NS1:
* var earnedMoney = hack("foodnstuff");
* earnedMoney = earnedMoney + hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack
* ```
* @example
* ```ts
* // NS2:
* let earnedMoney = await ns.hack("foodnstuff");
* earnedMoney += await ns.hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack
* ```
* @param host - Hostname of the target server to hack.
* @param opts - Optional parameters for configuring function behavior.
@@ -4224,16 +4232,14 @@ export interface NS extends Singularity {
* @example
* ```ts
* // NS1:
* var availableMoney = getServerMoneyAvailable("foodnstuff");
* var currentMoney = getServerMoneyAvailable("foodnstuff");
* currentMoney = currentMoney * (1 + grow("foodnstuff"));
* currentMoney = currentMoney * (1 + grow("foodnstuff", { threads: 5 })); // Only use 5 threads to grow
* ```
* @example
* ```ts
* // NS2:
* let availableMoney = ns.getServerMoneyAvailable("foodnstuff");
* let currentMoney = ns.getServerMoneyAvailable("foodnstuff");
* currentMoney *= (1 + await ns.grow("foodnstuff"));
* currentMoney *= (1 + await ns.grow("foodnstuff", { threads: 5 })); // Only use 5 threads to grow
* ```
* @param host - Hostname of the target server to grow.
* @param opts - Optional parameters for configuring function behavior.
@@ -4259,14 +4265,12 @@ export interface NS extends Singularity {
* // NS1:
* var currentSecurity = getServerSecurityLevel("foodnstuff");
* currentSecurity = currentSecurity - weaken("foodnstuff");
* currentSecurity = currentSecurity - weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken
* ```
* @example
* ```ts
* // NS2:
* let currentSecurity = ns.getServerSecurityLevel("foodnstuff");
* currentSecurity -= await ns.weaken("foodnstuff");
* currentSecurity -= await ns.weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken
* ```
* @param host - Hostname of the target server to weaken.
* @param opts - Optional parameters for configuring function behavior.
@@ -4453,6 +4457,17 @@ export interface NS extends Singularity {
*/
print(...args: any[]): void;
/**
* Prints a formatted string to the scripts logs.
* @remarks
* RAM cost: 0 GB
*
* see: https://github.com/alexei/sprintf.js
* @param format - format of the message
* @param args - Value(s) to be printed.
*/
printf(format: string, ...args: any[]): void;
/**
* Prints one or more values or variables to the Terminal.
* @remarks
@@ -5176,9 +5191,9 @@ export interface NS extends Singularity {
* @remarks
* RAM cost: 0.1 GB
*
* Returns the servers instrinsic growth parameter. This growth
* parameter is a number between 0 and 100 that represents how
* quickly the servers money grows. This parameter affects the
* Returns the servers intrinsic growth parameter. This growth
* parameter is a number typically between 0 and 100 that represents
* how quickly the servers money grows. This parameter affects the
* percentage by which the servers money is increased when using the
* grow function. A higher growth parameter will result in a
* higher percentage increase from grow.
@@ -5383,13 +5398,13 @@ export interface NS extends Singularity {
* @remarks
* RAM cost: 0.3 GB
*
* Running with no args returns curent script.
* Running with no args returns current script.
* If you use a PID as the first parameter, the hostname and args parameters are unnecessary.
*
* @param filename - Optional. Filename or PID of the script.
* @param hostname - Optional. Name of host server the script is running on.
* @param args - Arguments to identify the script
* @returns info about a running script
* @returns The info about the running script if found, and null otherwise.
*/
getRunningScript(filename?: FilenameOrPID, hostname?: string, ...args: (string | number)[]): RunningScript;
@@ -6067,6 +6082,23 @@ export interface NS extends Singularity {
* ```
*/
flags(schema: [string, string | number | boolean | string[]][]): any;
/**
* Share your computer with your factions.
* @remarks
* RAM cost: 2.4 GB
*
* Increases your rep gain of hacking contracts while share is called.
* Scales with thread count.
*/
share(): Promise<void>;
/**
* Calculate your share power. Based on all the active share calls.
* @remarks
* RAM cost: 0.2 GB
*/
getSharePower(): number;
}
/**
@@ -6155,14 +6187,14 @@ export interface OfficeAPI {
/**
* Get the cost to unlock research
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param researchName - Name of the research
* @returns cost
*/
getResearchCost(divisionName: string, researchName: string): number;
/**
* Gets if you have unlocked a research
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param researchName - Name of the research
* @returns true is unlocked, false if not
*/
hasResearched(divisionName: string, researchName: string): boolean;
@@ -6231,6 +6263,14 @@ export interface WarehouseAPI {
* @param enabled - smart supply enabled
*/
setSmartSupply(divisionName: string, cityName: string, enabled: boolean): void;
/**
* Set whether smart supply uses leftovers before buying
* @param divisionName - Name of the division
* @param cityName - Name of the city
* @param materialName - Name of the material
* @param enabled - smart supply use leftovers enabled
*/
setSmartSupplyUseLeftovers(divisionName: string, cityName: string, materialName: string, enabled: boolean): void;
/**
* Set material buy data
* @param divisionName - Name of the division
@@ -6661,7 +6701,7 @@ interface EmployeeJobs {
interface Division {
/** Name of the division */
name: string;
/** Type of division, like Aggriculture */
/** Type of division, like Agriculture */
type: string;
/** Awareness of the division */
awareness: number;
@@ -6749,3 +6789,13 @@ interface IStyleSettings {
fontFamily: string;
lineHeight: number;
}
/**
* Game Information
* @internal
*/
interface GameInfo {
version: string;
commit: string;
platform: string;
}
+155 -66
View File
@@ -32,11 +32,18 @@ import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Link from "@mui/material/Link";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import SettingsIcon from "@mui/icons-material/Settings";
import SyncIcon from '@mui/icons-material/Sync';
import CloseIcon from '@mui/icons-material/Close';
import Table from "@mui/material/Table";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import TableBody from "@mui/material/TableBody";
import { PromptEvent } from "../../ui/React/PromptManager";
import { Modal } from "../../ui/React/Modal";
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
import { Tooltip } from "@mui/material";
interface IProps {
// Map of filename -> code
@@ -109,6 +116,7 @@ export function Root(props: IProps): React.ReactElement {
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
const [ram, setRAM] = useState("RAM: ???");
const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]);
const [updatingRam, setUpdatingRam] = useState(false);
const [decorations, setDecorations] = useState<string[]>([]);
@@ -121,6 +129,8 @@ export function Root(props: IProps): React.ReactElement {
vim: props.vim || Settings.MonacoVim,
});
const [ramInfoOpen, setRamInfoOpen] = useState(false);
// Prevent Crash if script is open on deleted server
openScripts = openScripts.filter((script) => {
return GetServer(script.hostname) !== null;
@@ -198,7 +208,7 @@ export function Root(props: IProps): React.ReactElement {
});
editor.focus();
});
} catch {}
} catch { }
} else if (!options.vim) {
// Whem vim mode is disabled
vimEditor?.dispose();
@@ -222,8 +232,9 @@ export function Root(props: IProps): React.ReactElement {
const debouncedSetRAM = useMemo(
() =>
debounce((s) => {
debounce((s, e) => {
setRAM(s);
setRamEntries(e);
setUpdatingRam(false);
}, 300),
[],
@@ -231,28 +242,34 @@ export function Root(props: IProps): React.ReactElement {
async function updateRAM(newCode: string): Promise<void> {
if (currentScript != null && currentScript.fileName.endsWith(".txt")) {
debouncedSetRAM("");
debouncedSetRAM("N/A", [["N/A", ""]]);
return;
}
setUpdatingRam(true);
const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(props.player, codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage.cost > 0) {
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage.cost));
const entries = ramUsage.entries?.sort((a, b) => b.cost - a.cost) ?? [];
const entriesDisp = [];
for (const entry of entries) {
entriesDisp.push([`${entry.name} (${entry.type})`, numeralWrapper.formatRAM(entry.cost)]);
}
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage.cost), entriesDisp);
return;
}
switch (ramUsage.cost) {
case RamCalculationErrorCode.ImportError: {
debouncedSetRAM("RAM: Import Error");
debouncedSetRAM("RAM: Import Error", [["Import Error", ""]]);
break;
}
case RamCalculationErrorCode.URLImportError: {
debouncedSetRAM("RAM: HTTP Import Error");
debouncedSetRAM("RAM: HTTP Import Error", [["HTTP Import Error", ""]]);
break;
}
case RamCalculationErrorCode.SyntaxError:
default: {
debouncedSetRAM("RAM: Syntax Error");
debouncedSetRAM("RAM: Syntax Error", [["Syntax Error", ""]]);
break;
}
}
@@ -432,7 +449,7 @@ export function Root(props: IProps): React.ReactElement {
}
try {
infLoop(newCode);
} catch (err) {}
} catch (err) { }
}
function saveScript(scriptToSave: OpenScript): void {
@@ -678,17 +695,56 @@ export function Root(props: IProps): React.ReactElement {
}
}
function onTabUpdate(index: number): void {
const openScript = openScripts[index];
const serverScriptCode = getServerCode(index);
if (serverScriptCode === null) return;
if (openScript.code !== serverScriptCode) {
PromptEvent.emit({
txt: "Do you want to overwrite the current editor content with the contents of " +
openScript.fileName + " on the server? This cannot be undone.",
resolve: (result: boolean) => {
if (result) {
// Save changes
openScript.code = serverScriptCode;
// Switch to target tab
onTabClick(index)
if (editorRef.current !== null && openScript !== null) {
if (openScript.model === undefined || openScript.model.isDisposed()) {
regenerateModel(openScript);
}
editorRef.current.setModel(openScript.model);
editorRef.current.setValue(openScript.code);
updateRAM(openScript.code);
editorRef.current.focus();
}
}
},
});
}
}
function dirty(index: number): string {
const openScript = openScripts[index];
const serverScriptCode = getServerCode(index);
if (serverScriptCode === null) return " *";
// The server code is stored with its starting & trailing whitespace removed
const openScriptFormatted = Script.formatCode(openScript.code);
return serverScriptCode !== openScriptFormatted ? " *" : "";
}
function getServerCode(index: number): string | null {
const openScript = openScripts[index];
const server = GetServer(openScript.hostname);
if (server === null) throw new Error(`Server '${openScript.hostname}' should not be null, but it is.`);
const serverScript = server.scripts.find((s) => s.filename === openScript.fileName);
if (serverScript === undefined) return " *";
// The server code is stored with its starting & trailing whitespace removed
const openScriptFormatted = Script.formatCode(openScript.code);
return serverScript.code !== openScriptFormatted ? " *" : "";
return serverScript?.code ?? null;
}
// Toolbars are roughly 112px:
@@ -714,56 +770,80 @@ export function Root(props: IProps): React.ReactElement {
ref={provided.innerRef}
{...provided.droppableProps}
style={{
backgroundColor: snapshot.isDraggingOver ? "#1F2022" : Settings.theme.backgroundprimary,
backgroundColor: snapshot.isDraggingOver
? Settings.theme.backgroundsecondary
: Settings.theme.backgroundprimary,
overflowX: "scroll",
}}
>
{openScripts.map(({ fileName, hostname }, index) => (
<Draggable
key={fileName + hostname}
draggableId={fileName + hostname}
index={index}
disableInteractiveElementBlocking={true}
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
marginRight: "5px",
flexShrink: 0,
}}
>
<Button
onClick={() => onTabClick(index)}
{openScripts.map(({ fileName, hostname }, index) => {
const iconButtonStyle = {
maxWidth: "25px",
minWidth: "25px",
minHeight: '38.5px',
maxHeight: '38.5px',
...(currentScript?.fileName === openScripts[index].fileName ? {
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary
} : {
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary
})
};
return (
<Draggable
key={fileName + hostname}
draggableId={fileName + hostname}
index={index}
disableInteractiveElementBlocking={true}
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
background:
currentScript?.fileName === openScripts[index].fileName
? Settings.theme.secondarydark
: "",
...provided.draggableProps.style,
marginRight: "5px",
flexShrink: 0,
border: '1px solid ' + Settings.theme.well,
}}
>
{hostname}:~/{fileName} {dirty(index)}
</Button>
<Button
onClick={() => onTabClose(index)}
style={{
maxWidth: "20px",
minWidth: "20px",
background:
currentScript?.fileName === openScripts[index].fileName
? Settings.theme.secondarydark
: "",
}}
>
x
</Button>
</div>
)}
</Draggable>
))}
<Button
onClick={() => onTabClick(index)}
onMouseDown={e => {
e.preventDefault();
if (e.button === 1) onTabClose(index);
}}
style={{
...(currentScript?.fileName === openScripts[index].fileName ? {
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary
} : {
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary
})
}}
>
{hostname}:~/{fileName} {dirty(index)}
</Button>
<Tooltip title="Overwrite editor content with saved file content">
<Button onClick={() => onTabUpdate(index)} style={iconButtonStyle} >
<SyncIcon fontSize='small' />
</Button>
</Tooltip>
<Button onClick={() => onTabClose(index)} style={iconButtonStyle}>
<CloseIcon fontSize='small' />
</Button>
</div>
)}
</Draggable>
)
})}
{provided.placeholder}
</Box>
)}
@@ -792,10 +872,11 @@ export function Root(props: IProps): React.ReactElement {
></Box>
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
<Button startIcon={<SettingsIcon />} onClick={() => setOptionsOpen(true)} sx={{ mr: 1 }}>Options</Button>
<Button onClick={beautify}>Beautify</Button>
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
<Button color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }} onClick={() => { setRamInfoOpen(true) }}>
{ram}
</Typography>
</Button>
<Button onClick={save}>Save (Ctrl/Cmd + s)</Button>
<Button onClick={props.router.toTerminal}>Close (Ctrl/Cmd + b)</Button>
<Typography sx={{ mx: 1 }}>
@@ -809,12 +890,6 @@ export function Root(props: IProps): React.ReactElement {
Full
</Link>
</Typography>
<IconButton style={{ marginLeft: "auto" }} onClick={() => setOptionsOpen(true)}>
<>
<SettingsIcon />
options
</>
</IconButton>
</Box>
<OptionsModal
open={optionsOpen}
@@ -835,6 +910,20 @@ export function Root(props: IProps): React.ReactElement {
Settings.MonacoVim = options.vim;
}}
/>
<Modal open={ramInfoOpen} onClose={() => setRamInfoOpen(false)}>
<Table>
<TableBody>
{ramEntries.map(([n, r]) => (
<React.Fragment key={n + r}>
<TableRow>
<TableCell sx={{ color: Settings.theme.primary }}>{n}</TableCell>
<TableCell align="right" sx={{ color: Settings.theme.primary }}>{r}</TableCell>
</TableRow>
</React.Fragment>
))}
</TableBody>
</Table>
</Modal>
</div>
<div
style={{

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