Merge pull request #3678 from danielyxie/dev

v1.7.0
This commit is contained in:
hydroflame
2022-05-20 15:21:21 -04:00
committed by GitHub
153 changed files with 6949 additions and 6217 deletions

View File

@ -1,5 +1,7 @@
# DELETE THIS AFTER READING
# READ CONTRIBUTING.md
# PR title
Formatted as such:

4
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

42
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
doc/NEW_BN_GUIDELINE.md Normal file
View File

@ -0,0 +1,11 @@
Promote:
- New mechanic
- Coding problems based on NP problems. This makes solution that are easy to implement inefficient and solutions that are hard to implement efficent. (eg. Stanek)
- inter-mechanic synergy
- Simplicity (eg. Stanek, Hashnet. bad example: Corp)
Avoid:
- Failure conditions, it's very frustrating to revert several days worth of progress.
- Making existing mechanic harder. This makes it hard to port the content to other BNs.

1
doc/POTENTIAL_BN_1.md Normal file
View File

@ -0,0 +1 @@
Sleeves meet Screeps (That's all I got)

3
doc/POTENTIAL_BN_2.md Normal file
View File

@ -0,0 +1,3 @@
A game of risk from the point of view of a politician.
You allocate resources on a world map, trying to win elections.

View File

@ -336,12 +336,12 @@ The list contains the name of (i.e. the value returned by
| | | |
| | | You are given an LZ-encoded string. Decode it and output the original string. |
| | | |
| | | Example: decoding '5aaabc340533bca' chunk-by-chunk |
| | | 5aaabc -> aaabc |
| | | 5aaabc34 -> aaabcaab |
| | | 5aaabc340 -> aaabcaab |
| | | 5aaabc34053 -> aaabcaabaabaa |
| | | 5aaabc340533bca -> aaabcaabaabaabca |
| | | Example: decoding '5aaabb450723abb' chunk-by-chunk |
| | | 5aaabb -> aaabb |
| | | 5aaabb45 -> aaabbaaab |
| | | 5aaabb450 -> aaabbaaab |
| | | 5aaabb45072 -> aaabbaaababababa |
| | | 5aaabb450723abb -> aaabbaaababababaabb |
+-----------------------------------------+------------------------------------------------------------------------------------------+
| Compression III: LZ Compression | | Lempel-Ziv (LZ) compression is a data compression technique which encodes data using |
| | | references to earlier parts of the data. In this variant of LZ, data is encoded in two |
@ -361,12 +361,12 @@ The list contains the name of (i.e. the value returned by
| | | possible output length. |
| | | |
| | | Examples (some have other possible encodings of minimal length): |
| | | abracadabra -> 7abracad47 |
| | | mississippi -> 4miss433ppi |
| | | aAAaAAaAaAA -> 3aAA53035 |
| | | 2718281828 -> 627182844 |
| | | abcdefghijk -> 9abcdefghi02jk |
| | | aaaaaaaaaaa -> 1a911a |
| | | aaaaaaaaaaaa -> 1a912aa |
| | | aaaaaaaaaaaaa -> 1a91031 |
| | | abracadabra -> 7abracad47 |
| | | mississippi -> 4miss433ppi |
| | | aAAaAAaAaAA -> 3aAA53035 |
| | | 2718281828 -> 627182844 |
| | | abcdefghijk -> 9abcdefghi02jk |
| | | aaaaaaaaaaaa -> 3aaa91 |
| | | aaaaaaaaaaaaa -> 1a91031 |
| | | aaaaaaaaaaaaaa -> 1a91041 |
+-----------------------------------------+------------------------------------------------------------------------------------------+

View File

@ -1 +0,0 @@
I want the wiki here https://bitburner.fandom.com/wiki/Bitburner_Wiki taken down please.

1556
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "bitburner",
"license": "SEE LICENSE IN license.txt",
"version": "1.6.4",
"version": "1.7.0",
"main": "electron-main.js",
"author": {
"name": "Daniel Xie & Olivier Gagnon"
@ -57,6 +57,7 @@
"@types/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.3",
"@types/jest": "^27.4.1",
"@types/jquery": "^3.5.14",
"@types/lodash": "^4.14.168",
"@types/numeral": "^2.0.2",

View File

@ -1,8 +1,13 @@
// Defined by webpack on startup or compilation
declare let __COMMIT_HASH__: string;
declare const __COMMIT_HASH__: string;
// When using file-loader, we'll get a path to the resource
declare module "*.png" {
const value: string;
export default value;
}
// Achievements communicated back to Electron shell for Steam.
declare interface Document {
achievements: string[];
}

View File

@ -334,7 +334,7 @@
},
"BLADEBURNER_UNSPENT_100000": {
"ID": "BLADEBURNER_UNSPENT_100000",
"Name": "You should really spent those.",
"Name": "You should really spend those.",
"Description": "Have 100 000 unspent bladeburner skill points."
},
"4S": {

View File

@ -24,6 +24,7 @@ import { IMap } from "../types";
import * as data from "./AchievementData.json";
import { FactionNames } from "../Faction/data/FactionNames";
import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames";
import { ClassType } from "../utils/WorkType";
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
@ -391,12 +392,9 @@ export const achievements: IMap<Achievement> = {
...achievementData["WORKOUT"],
Icon: "WORKOUT",
Condition: () =>
[
CONSTANTS.ClassGymStrength,
CONSTANTS.ClassGymDefense,
CONSTANTS.ClassGymDexterity,
CONSTANTS.ClassGymAgility,
].includes(Player.className),
[ClassType.GymStrength, ClassType.GymDefense, ClassType.GymDexterity, ClassType.GymAgility].includes(
Player.className,
),
},
TOR: {
...achievementData["TOR"],
@ -799,5 +797,5 @@ export function calculateAchievements(): void {
// Write all player's achievements to document for Steam/Electron
// This could be replaced by "availableAchievements"
// if we don't want to grant the save game achievements to steam but only currently available
(document as any).achievements = [...Player.achievements.map((a) => a.ID)];
document.achievements = [...Player.achievements.map((a) => a.ID)];
}

View File

@ -9,6 +9,18 @@ import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AugmentationNames } from "./data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { StaticAugmentations } from "./StaticAugmentations";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { getBaseAugmentationPriceMultiplier, getGenericAugmentationPriceMultiplier } from "./AugmentationHelpers";
import { initSoAAugmentations } from "./data/AugmentationCreator";
export interface AugmentationCosts {
moneyCost: number;
repCost: number;
}
export interface IConstructorParams {
info: string | JSX.Element;
@ -410,10 +422,10 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
}
export class Augmentation {
// How much money this costs to buy
// How much money this costs to buy before multipliers
baseCost = 0;
// How much faction reputation is required to unlock this
// How much faction reputation is required to unlock this before multipliers
baseRepRequirement = 0;
// Description of what this Aug is and what it does
@ -425,9 +437,6 @@ export class Augmentation {
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
isSpecial = false;
// Augmentation level - for repeatable Augs like NeuroFlux Governor
level = 0;
// Name of Augmentation
name = "";
@ -438,12 +447,6 @@ export class Augmentation {
// The Player/Person classes
mults: IMap<number> = {};
// Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost = 0;
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
startingRepRequirement = 0;
// Factions that offer this aug.
factions: string[] = [];
@ -461,17 +464,15 @@ export class Augmentation {
this.prereqs = params.prereqs ? params.prereqs : [];
this.baseRepRequirement = params.repCost;
Object.freeze(this.baseRepRequirement);
this.baseCost = params.moneyCost;
this.startingCost = this.baseCost;
this.startingRepRequirement = this.baseRepRequirement;
Object.freeze(this.baseCost);
this.factions = params.factions;
if (params.isSpecial) {
this.isSpecial = true;
}
this.level = 0;
// Set multipliers
if (params.hacking_mult) {
this.mults.hacking_mult = params.hacking_mult;
@ -600,6 +601,62 @@ export class Augmentation {
}
}
getCost(player: IPlayer): AugmentationCosts {
const augmentationReference = StaticAugmentations[this.name];
let moneyCost = augmentationReference.baseCost;
let repCost = augmentationReference.baseRepRequirement;
if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) {
let nextLevel = this.getLevel(player);
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
moneyCost = augmentationReference.baseCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
moneyCost *= getBaseAugmentationPriceMultiplier();
}
} else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaMultiplier = Math.pow(
CONSTANTS.SoACostMult,
soaAugmentationNames.filter((augmentationName) => player.hasAugmentation(augmentationName)).length,
);
moneyCost = augmentationReference.baseCost * soaMultiplier;
if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) {
repCost = augmentationReference.baseRepRequirement * soaMultiplier;
}
} else {
moneyCost =
augmentationReference.baseCost *
getGenericAugmentationPriceMultiplier() *
BitNodeMultipliers.AugmentationMoneyCost;
repCost = augmentationReference.baseRepRequirement * BitNodeMultipliers.AugmentationRepCost;
}
return { moneyCost, repCost };
}
getLevel(player: IPlayer): number {
// Get current Neuroflux level based on Player's augmentations
if (this.name === AugmentationNames.NeuroFluxGovernor) {
let currLevel = 0;
for (let i = 0; i < player.augmentations.length; ++i) {
if (player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
if (player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
return 0;
}
// Adds this Augmentation to all Factions
addToAllFactions(): void {
for (const fac of Object.keys(Factions)) {

View File

@ -1,5 +1,5 @@
import { Augmentation } from "./Augmentation";
import { Augmentations } from "./Augmentations";
import { StaticAugmentations } from "./StaticAugmentations";
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
import { AugmentationNames } from "./data/AugmentationNames";
@ -20,30 +20,11 @@ import {
initNeuroFluxGovernor,
initUnstableCircadianModulator,
} from "./data/AugmentationCreator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void {
export function AddToStaticAugmentations(aug: Augmentation): void {
const name = aug.name;
Augmentations[name] = aug;
}
export function getNextNeuroFluxLevel(): number {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
StaticAugmentations[name] = aug;
}
function createAugmentations(): void {
@ -67,105 +48,54 @@ function resetFactionAugmentations(): void {
function initAugmentations(): void {
resetFactionAugmentations();
clearObject(Augmentations);
clearObject(StaticAugmentations);
createAugmentations();
updateAugmentationCosts();
Player.reapplyAllAugmentations();
}
function getBaseAugmentationPriceMultiplier(): number {
export function getBaseAugmentationPriceMultiplier(): number {
return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
export function getGenericAugmentationPriceMultiplier(): number {
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
}
function updateNeuroFluxGovernorCosts(neuroFluxGovernorAugmentation: Augmentation): void {
let nextLevel = getNextNeuroFluxLevel();
--nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
neuroFluxGovernorAugmentation.baseRepRequirement =
neuroFluxGovernorAugmentation.startingRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
neuroFluxGovernorAugmentation.baseCost =
neuroFluxGovernorAugmentation.startingCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier();
}
}
function updateSoACosts(soaAugmentation: Augmentation): void {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaAugCount = soaAugmentationNames.filter((augmentationName) =>
Player.hasAugmentation(augmentationName),
).length;
soaAugmentation.baseCost = soaAugmentation.startingCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
if (soaAugmentationNames.find((augmentationName) => augmentationName === soaAugmentation.name)) {
soaAugmentation.baseRepRequirement =
soaAugmentation.startingRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);
}
}
export function updateAugmentationCosts(): void {
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
const augmentationToUpdate = Augmentations[name];
if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) {
updateNeuroFluxGovernorCosts(augmentationToUpdate);
} else if (augmentationToUpdate.factions.includes(FactionNames.ShadowsOfAnarchy)) {
updateSoACosts(augmentationToUpdate);
} else {
augmentationToUpdate.baseCost =
augmentationToUpdate.startingCost *
getGenericAugmentationPriceMultiplier() *
BitNodeMultipliers.AugmentationMoneyCost;
}
}
}
}
//Resets an Augmentation during (re-initizliation)
function resetAugmentation(aug: Augmentation): void {
aug.addToFactions(aug.factions);
const name = aug.name;
if (augmentationExists(name)) {
delete Augmentations[name];
delete StaticAugmentations[name];
}
AddToAugmentations(aug);
AddToStaticAugmentations(aug);
}
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void {
const augObj = Augmentations[aug.name];
const staticAugmentation = StaticAugmentations[aug.name];
// Apply multipliers
for (const mult of Object.keys(augObj.mults)) {
const v = Player.getMult(mult) * augObj.mults[mult];
for (const mult of Object.keys(staticAugmentation.mults)) {
const v = Player.getMult(mult) * staticAugmentation.mults[mult];
Player.setMult(mult, v);
}
// Special logic for NeuroFlux Governor
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
if (!reapply) {
Augmentations[aug.name].level = aug.level;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
Player.augmentations[i].level = aug.level;
return;
// break;
}
}
}
}
// Special logic for Congruity Implant
if (aug.name === AugmentationNames.CongruityImplant && !reapply) {
Player.entropy = 0;
Player.applyEntropy(Player.entropy);
}
// Special logic for NeuroFlux Governor
const ownedNfg = Player.augmentations.find((pAug) => pAug.name === AugmentationNames.NeuroFluxGovernor);
if (aug.name === AugmentationNames.NeuroFluxGovernor && !reapply && ownedNfg) {
ownedNfg.level = aug.level;
return;
}
// Push onto Player's Augmentation list
if (!reapply) {
const ownedAug = new PlayerOwnedAugmentation(aug.name);
Player.augmentations.push(ownedAug);
}
}
@ -185,7 +115,7 @@ function installAugmentations(force?: boolean): boolean {
}
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
const ownedAug = Player.queuedAugmentations[i];
const aug = Augmentations[ownedAug.name];
const aug = StaticAugmentations[ownedAug.name];
if (aug == null) {
console.error(`Invalid augmentation: ${ownedAug.name}`);
continue;
@ -215,7 +145,7 @@ function installAugmentations(force?: boolean): boolean {
}
function augmentationExists(name: string): boolean {
return Augmentations.hasOwnProperty(name);
return StaticAugmentations.hasOwnProperty(name);
}
export function isRepeatableAug(aug: Augmentation): boolean {

View File

@ -1,4 +1,4 @@
import { Augmentation } from "./Augmentation";
import { IMap } from "../types";
export const Augmentations: IMap<Augmentation> = {};
export const StaticAugmentations: IMap<Augmentation> = {};

View File

@ -109,6 +109,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
rewards, reduced damage taken, etc.
</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -121,6 +122,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -129,6 +131,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6,
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -137,6 +140,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6,
info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -147,6 +151,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
"Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"make conversational partners more agreeable.",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -155,6 +160,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6,
info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.",
stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -163,6 +169,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6,
info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -176,6 +183,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
position.
</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
@ -184,6 +192,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
moneyCost: 1e6,
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
}),
];
@ -242,7 +251,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 1.15e8,
repCost: 2.75e4,
info: "The latest version of the 'Augmented Targeting' implant adds the ability to lock-on and track threats.",
prereqs: [AugmentationNames.Targeting2],
prereqs: [AugmentationNames.Targeting2, AugmentationNames.Targeting1],
dexterity_mult: 1.3,
factions: [
FactionNames.TheDarkArmy,
@ -339,7 +348,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info:
"The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " +
"improve muscle mass and physical performance while being safe and free of side effects.",
prereqs: [AugmentationNames.CombatRib2],
prereqs: [AugmentationNames.CombatRib2, AugmentationNames.CombatRib1],
strength_mult: 1.18,
defense_mult: 1.18,
factions: [
@ -673,7 +682,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"This upgraded firmware allows the Embedded Netburner Module to control information on " +
"a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " +
"packets.",
prereqs: [AugmentationNames.ENMCore],
prereqs: [AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05,
hacking_money_mult: 1.3,
hacking_chance_mult: 1.05,
@ -698,7 +707,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " +
"This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " +
"any device on a network.",
prereqs: [AugmentationNames.ENMCoreV2],
prereqs: [AugmentationNames.ENMCoreV2, AugmentationNames.ENMCore, AugmentationNames.ENM],
hacking_speed_mult: 1.05,
hacking_money_mult: 1.4,
hacking_chance_mult: 1.1,
@ -826,7 +835,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG2],
prereqs: [AugmentationNames.CranialSignalProcessorsG2, AugmentationNames.CranialSignalProcessorsG1],
hacking_speed_mult: 1.02,
hacking_money_mult: 1.15,
hacking_mult: 1.09,
@ -841,7 +850,11 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG3],
prereqs: [
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_speed_mult: 1.02,
hacking_money_mult: 1.2,
hacking_grow_mult: 1.25,
@ -856,7 +869,12 @@ export const initGeneralAugmentations = (): Augmentation[] => [
"are a set of specialized microprocessors that are attached to " +
"neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " +
"so that the brain doesn't have to.",
prereqs: [AugmentationNames.CranialSignalProcessorsG4],
prereqs: [
AugmentationNames.CranialSignalProcessorsG4,
AugmentationNames.CranialSignalProcessorsG3,
AugmentationNames.CranialSignalProcessorsG2,
AugmentationNames.CranialSignalProcessorsG1,
],
hacking_mult: 1.3,
hacking_money_mult: 1.25,
hacking_grow_mult: 1.75,
@ -1254,6 +1272,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 0,
info: "It's time to leave the cave.",
stats: null,
isSpecial: true,
factions: [FactionNames.Daedalus],
}),
new Augmentation({
@ -1952,7 +1971,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
"You will become greater than the sum of our parts. As One. Embrace your gift " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"of no longer suffering a stat penalty. ",
prereqs: [AugmentationNames.StaneksGift2],
prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1],
isSpecial: true,
hacking_chance_mult: 1 / 0.95,
hacking_speed_mult: 1 / 0.95,
@ -2003,6 +2022,7 @@ export function initNeuroFluxGovernor(): Augmentation {
multiplicatively.
</>
),
isSpecial: true,
hacking_chance_mult: 1.01 + donationBonus,
hacking_speed_mult: 1.01 + donationBonus,
hacking_money_mult: 1.01 + donationBonus,

View File

@ -114,17 +114,6 @@ export enum AugmentationNames {
StaneksGift2 = "Stanek's Gift - Awakening",
StaneksGift3 = "Stanek's Gift - Serenity",
/*
MightOfAres = "Might of Ares", // slash
WisdomOfAthena = "Wisdom of Athena", // bracket
TrickeryOfHermes = "Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "Beauty of Aphrodite", // bribe
ChaosOfDionysus = "Chaos of Dionysus", // reverse
FloodOfPoseidon = "Flood of Poseidon", // hex
HuntOfArtemis = "Hunt of Artemis", // mine
KnowledgeOfApollo = "Knowledge of Apollo", // wire
*/
// Infiltrators MiniGames
MightOfAres = "SoA - Might of Ares", // slash
WisdomOfAthena = "SoA - Wisdom of Athena", // bracket
@ -135,10 +124,4 @@ export enum AugmentationNames {
HuntOfArtemis = "SoA - Hunt of Artemis", // mine
KnowledgeOfApollo = "SoA - Knowledge of Apollo", // wire
WKSharmonizer = "SoA - phyzical WKS harmonizer",
//Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields
//PepBoyBlasts Generate high density plasma concussive blasts
//PepBoyDataStorage STore more data on pep boy,
}

View File

@ -22,7 +22,7 @@ import { Settings } from "../../Settings/Settings";
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AugmentationNames } from "../data/AugmentationNames";
import { Augmentations } from "../Augmentations";
import { StaticAugmentations } from "../StaticAugmentations";
import { CONSTANTS } from "../../Constants";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { Info } from "@mui/icons-material";
@ -39,7 +39,9 @@ const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
<Typography variant="h5" color={Settings.theme.info}>
NeuroFlux Governor - Level {level}
</Typography>
<Typography color={Settings.theme.info}>{Augmentations[AugmentationNames.NeuroFluxGovernor].stats}</Typography>
<Typography color={Settings.theme.info}>
{StaticAugmentations[AugmentationNames.NeuroFluxGovernor].stats}
</Typography>
</Paper>
) : (
<></>

View File

@ -13,7 +13,7 @@ import React, { useState } from "react";
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { Augmentations } from "../Augmentations";
import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames";
export function InstalledAugmentations(): React.ReactElement {
@ -77,7 +77,7 @@ export function InstalledAugmentations(): React.ReactElement {
</Typography>
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug.name];
const aug = StaticAugmentations[selectedAug.name];
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (

View File

@ -8,7 +8,7 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations } from "../Augmentations";
import { StaticAugmentations } from "../StaticAugmentations";
interface IAugmentedStats {
[index: string]: number;
@ -17,7 +17,7 @@ interface IAugmentedStats {
function calculateAugmentedStats(): IAugmentedStats {
const augP: IAugmentedStats = {};
for (const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name];
const augObj = StaticAugmentations[aug.name];
for (const mult of Object.keys(augObj.mults)) {
const v = augP[mult] ? augP[mult] : 1;
augP[mult] = v * augObj.mults[mult];
@ -45,13 +45,13 @@ function BitNodeModifiedStats(props: IBitNodeModifiedStatsProps): React.ReactEle
);
}
type MultiplierListItemData = [
multiplier: string,
currentValue: number,
augmentedValue: number,
bitNodeMultiplier: number,
color: string,
];
interface MultiplierListItemData {
mult: string;
current: number;
augmented: number;
bnMult?: number;
color?: string;
}
interface IMultiplierListProps {
rows: MultiplierListItemData[];
@ -60,23 +60,23 @@ interface IMultiplierListProps {
function MultiplierList(props: IMultiplierListProps): React.ReactElement {
const listItems = props.rows
.map((data) => {
const [multiplier, currentValue, augmentedValue, bitNodeMultiplier, color] = data;
const { mult, current, augmented, bnMult = 1, color = Settings.theme.primary } = data;
if (!isNaN(augmentedValue)) {
if (!isNaN(augmented)) {
return (
<ListItem key={multiplier} disableGutters sx={{ py: 0 }}>
<ListItem key={mult} disableGutters sx={{ py: 0 }}>
<ListItemText
sx={{ my: 0.1 }}
primary={
<Typography color={color}>
<b>{multiplier}</b>
<b>{mult}</b>
</Typography>
}
secondary={
<span style={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<BitNodeModifiedStats base={currentValue} mult={bitNodeMultiplier} color={color} />
<BitNodeModifiedStats base={current} mult={bnMult} color={color} />
<DoubleArrow fontSize="small" color="success" sx={{ mb: 0.5, mx: 1 }} />
<BitNodeModifiedStats base={augmentedValue} mult={bitNodeMultiplier} color={Settings.theme.success} />
<BitNodeModifiedStats base={augmented} mult={bnMult} color={Settings.theme.success} />
</span>
}
disableTypography
@ -94,177 +94,205 @@ function MultiplierList(props: IMultiplierListProps): React.ReactElement {
export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
// Column data is a bit janky, so it's set up here to allow for
// easier logic in setting up the layout
const leftColData: MultiplierListItemData[] = [
...[
["Hacking Chance ", Player.hacking_chance_mult, Player.hacking_chance_mult * mults.hacking_chance_mult, 1],
["Hacking Speed ", Player.hacking_speed_mult, Player.hacking_speed_mult * mults.hacking_speed_mult, 1],
["Hacking Money ", Player.hacking_money_mult, Player.hacking_money_mult * mults.hacking_money_mult, 1],
["Hacking Growth ", Player.hacking_grow_mult, Player.hacking_grow_mult * mults.hacking_grow_mult, 1],
[
"Hacking Level ",
Player.hacking_mult,
Player.hacking_mult * mults.hacking_mult,
BitNodeMultipliers.HackingLevelMultiplier,
],
[
"Hacking Experience ",
Player.hacking_exp_mult,
Player.hacking_exp_mult * mults.hacking_exp_mult,
BitNodeMultipliers.HackExpGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.hack])),
{
mult: "Hacking Chance",
current: Player.hacking_chance_mult,
augmented: Player.hacking_chance_mult * mults.hacking_chance_mult,
},
{
mult: "Hacking Speed",
current: Player.hacking_speed_mult,
augmented: Player.hacking_speed_mult * mults.hacking_speed_mult,
},
{
mult: "Hacking Money",
current: Player.hacking_money_mult,
augmented: Player.hacking_money_mult * mults.hacking_money_mult,
bnMult: BitNodeMultipliers.ScriptHackMoney,
},
{
mult: "Hacking Growth",
current: Player.hacking_grow_mult,
augmented: Player.hacking_grow_mult * mults.hacking_grow_mult,
},
{
mult: "Hacking Level",
current: Player.hacking_mult,
augmented: Player.hacking_mult * mults.hacking_mult,
bnMult: BitNodeMultipliers.HackingLevelMultiplier,
},
{
mult: "Hacking Experience",
current: Player.hacking_exp_mult,
augmented: Player.hacking_exp_mult * mults.hacking_exp_mult,
bnMult: BitNodeMultipliers.HackExpGain,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: Settings.theme.hack,
}),
),
...[
[
"Strength Level ",
Player.strength_mult,
Player.strength_mult * mults.strength_mult,
BitNodeMultipliers.StrengthLevelMultiplier,
],
["Strength Experience ", Player.strength_exp_mult, Player.strength_exp_mult * mults.strength_exp_mult, 1],
[
"Defense Level ",
Player.defense_mult,
Player.defense_mult * mults.defense_mult,
BitNodeMultipliers.DefenseLevelMultiplier,
],
["Defense Experience ", Player.defense_exp_mult, Player.defense_exp_mult * mults.defense_exp_mult, 1],
[
"Dexterity Level ",
Player.dexterity_mult,
Player.dexterity_mult * mults.dexterity_mult,
BitNodeMultipliers.DexterityLevelMultiplier,
],
["Dexterity Experience ", Player.dexterity_exp_mult, Player.dexterity_exp_mult * mults.dexterity_exp_mult, 1],
[
"Agility Level ",
Player.agility_mult,
Player.agility_mult * mults.agility_mult,
BitNodeMultipliers.AgilityLevelMultiplier,
],
["Agility Experience ", Player.agility_exp_mult, Player.agility_exp_mult * mults.agility_exp_mult, 1],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.combat])),
[
"Charisma Level ",
Player.charisma_mult,
Player.charisma_mult * mults.charisma_mult,
BitNodeMultipliers.CharismaLevelMultiplier,
Settings.theme.cha,
],
[
"Charisma Experience ",
Player.charisma_exp_mult,
Player.charisma_exp_mult * mults.charisma_exp_mult,
1,
Settings.theme.cha,
],
{
mult: "Strength Level",
current: Player.strength_mult,
augmented: Player.strength_mult * mults.strength_mult,
bnMult: BitNodeMultipliers.StrengthLevelMultiplier,
},
{
mult: "Strength Experience",
current: Player.strength_exp_mult,
augmented: Player.strength_exp_mult * mults.strength_exp_mult,
},
{
mult: "Defense Level",
current: Player.defense_mult,
augmented: Player.defense_mult * mults.defense_mult,
bnMult: BitNodeMultipliers.DefenseLevelMultiplier,
},
{
mult: "Defense Experience",
current: Player.defense_exp_mult,
augmented: Player.defense_exp_mult * mults.defense_exp_mult,
},
{
mult: "Dexterity Level",
current: Player.dexterity_mult,
augmented: Player.dexterity_mult * mults.dexterity_mult,
bnMult: BitNodeMultipliers.DexterityLevelMultiplier,
},
{
mult: "Dexterity Experience",
current: Player.dexterity_exp_mult,
augmented: Player.dexterity_exp_mult * mults.dexterity_exp_mult,
},
{
mult: "Agility Level",
current: Player.agility_mult,
augmented: Player.agility_mult * mults.agility_mult,
bnMult: BitNodeMultipliers.AgilityLevelMultiplier,
},
{
mult: "Agility Experience",
current: Player.agility_exp_mult,
augmented: Player.agility_exp_mult * mults.agility_exp_mult,
},
].map((data: MultiplierListItemData) =>
Object.defineProperty(data, "color", {
value: Settings.theme.combat,
}),
),
{
mult: "Charisma Level",
current: Player.charisma_mult,
augmented: Player.charisma_mult * mults.charisma_mult,
bnMult: BitNodeMultipliers.CharismaLevelMultiplier,
color: Settings.theme.cha,
},
{
mult: "Charisma Experience",
current: Player.charisma_exp_mult,
augmented: Player.charisma_exp_mult * mults.charisma_exp_mult,
color: Settings.theme.cha,
},
];
const rightColData: MultiplierListItemData[] = [
...[
[
"Hacknet Node production ",
Player.hacknet_node_money_mult,
Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
BitNodeMultipliers.HacknetNodeMoney,
],
[
"Hacknet Node purchase cost ",
Player.hacknet_node_purchase_cost_mult,
Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
1,
],
[
"Hacknet Node RAM upgrade cost ",
Player.hacknet_node_ram_cost_mult,
Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
1,
],
[
"Hacknet Node Core purchase cost ",
Player.hacknet_node_core_cost_mult,
Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
1,
],
[
"Hacknet Node level upgrade cost ",
Player.hacknet_node_level_cost_mult,
Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
1,
],
["Company reputation gain ", Player.company_rep_mult, Player.company_rep_mult * mults.company_rep_mult, 1],
[
"Faction reputation gain ",
Player.faction_rep_mult,
Player.faction_rep_mult * mults.faction_rep_mult,
BitNodeMultipliers.FactionWorkRepGain,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
[
"Salary ",
Player.work_money_mult,
Player.work_money_mult * mults.work_money_mult,
BitNodeMultipliers.CompanyWorkMoney,
Settings.theme.money,
],
[
"Crime success ",
Player.crime_success_mult,
Player.crime_success_mult * mults.crime_success_mult,
1,
Settings.theme.combat,
],
[
"Crime money ",
Player.crime_money_mult,
Player.crime_money_mult * mults.crime_money_mult,
BitNodeMultipliers.CrimeMoney,
Settings.theme.money,
],
{
mult: "Hacknet Node Production",
current: Player.hacknet_node_money_mult,
augmented: Player.hacknet_node_money_mult * mults.hacknet_node_money_mult,
bnMult: BitNodeMultipliers.HacknetNodeMoney,
},
{
mult: "Hacknet Node Purchase Cost",
current: Player.hacknet_node_purchase_cost_mult,
augmented: Player.hacknet_node_purchase_cost_mult * mults.hacknet_node_purchase_cost_mult,
},
{
mult: "Hacknet Node RAM Upgrade Cost",
current: Player.hacknet_node_ram_cost_mult,
augmented: Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult,
},
{
mult: "Hacknet Node Core Purchase Cost",
current: Player.hacknet_node_core_cost_mult,
augmented: Player.hacknet_node_core_cost_mult * mults.hacknet_node_core_cost_mult,
},
{
mult: "Hacknet Node Level Upgrade Cost",
current: Player.hacknet_node_level_cost_mult,
augmented: Player.hacknet_node_level_cost_mult * mults.hacknet_node_level_cost_mult,
},
{
mult: "Company Reputation Gain",
current: Player.company_rep_mult,
augmented: Player.company_rep_mult * mults.company_rep_mult,
},
{
mult: "Faction Reputation Gain",
current: Player.faction_rep_mult,
augmented: Player.faction_rep_mult * mults.faction_rep_mult,
bnMult: BitNodeMultipliers.FactionWorkRepGain,
},
{
mult: "Salary",
current: Player.work_money_mult,
augmented: Player.work_money_mult * mults.work_money_mult,
bnMult: BitNodeMultipliers.CompanyWorkMoney,
color: Settings.theme.money,
},
{
mult: "Crime Success Chance",
current: Player.crime_success_mult,
augmented: Player.crime_success_mult * mults.crime_success_mult,
color: Settings.theme.combat,
},
{
mult: "Crime Money",
current: Player.crime_money_mult,
augmented: Player.crime_money_mult * mults.crime_money_mult,
bnMult: BitNodeMultipliers.CrimeMoney,
color: Settings.theme.money,
},
];
if (Player.canAccessBladeburner()) {
rightColData.push(
...[
[
"Bladeburner Success Chance",
Player.bladeburner_success_chance_mult,
Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
1,
],
[
"Bladeburner Max Stamina",
Player.bladeburner_max_stamina_mult,
Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
1,
],
[
"Bladeburner Stamina Gain",
Player.bladeburner_stamina_gain_mult,
Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
1,
],
[
"Bladeburner Field Analysis",
Player.bladeburner_analysis_mult,
Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
1,
],
].map((data): MultiplierListItemData => (data as any).concat([Settings.theme.primary])),
{
mult: "Bladeburner Success Chance",
current: Player.bladeburner_success_chance_mult,
augmented: Player.bladeburner_success_chance_mult * mults.bladeburner_success_chance_mult,
},
{
mult: "Bladeburner Max Stamina",
current: Player.bladeburner_max_stamina_mult,
augmented: Player.bladeburner_max_stamina_mult * mults.bladeburner_max_stamina_mult,
},
{
mult: "Bladeburner Stamina Gain",
current: Player.bladeburner_stamina_gain_mult,
augmented: Player.bladeburner_stamina_gain_mult * mults.bladeburner_stamina_gain_mult,
},
{
mult: "Bladeburner Field Analysis",
current: Player.bladeburner_analysis_mult,
augmented: Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult,
},
);
}
const hasLeftImprovements = +!!(leftColData.filter((item) => item[2] !== 0).length > 0),
hasRightImprovements = +!!(rightColData.filter((item) => item[2] !== 0).length > 0);
return (
<Paper
sx={{
p: 1,
maxHeight: 400,
overflowY: "scroll",
display: "grid",
gridTemplateColumns: `repeat(${hasLeftImprovements + hasRightImprovements}, 1fr)`,
display: "flex",
flexDirection: "column",
flexWrap: "wrap",
gap: 1,
}}
>
<MultiplierList rows={leftColData} />

View File

@ -0,0 +1,265 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation";
import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { StaticAugmentations } from "../StaticAugmentations";
interface IPreReqsProps {
player: IPlayer;
aug: Augmentation;
}
const PreReqs = (props: IPreReqsProps): React.ReactElement => {
const ownedPreReqs = props.aug.prereqs.filter((aug) => props.player.hasAugmentation(aug));
const hasPreReqs = props.aug.prereqs.length > 0 && ownedPreReqs.length === props.aug.prereqs.length;
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation has the following pre-requisite(s):
</Typography>
{props.aug.prereqs.map((preAug) => (
<Requirement
fulfilled={props.player.hasAugmentation(preAug)}
value={preAug}
color={Settings.theme.money}
key={preAug}
/>
))}
</>
}
>
<Typography
variant="body2"
sx={{
display: "flex",
alignItems: "center",
color: hasPreReqs ? Settings.theme.successlight : Settings.theme.error,
}}
>
{hasPreReqs ? (
<>
<CheckCircle fontSize="small" sx={{ mr: 1 }} />
Pre-requisites Owned
</>
) : (
<>
<Report fontSize="small" sx={{ mr: 1 }} />
Missing {props.aug.prereqs.length - ownedPreReqs.length} pre-requisite(s)
</>
)}
</Typography>
</Tooltip>
);
};
interface IExclusiveProps {
player: IPlayer;
aug: Augmentation;
}
const Exclusive = (props: IExclusiveProps): React.ReactElement => {
return (
<Tooltip
title={
<>
<Typography sx={{ color: Settings.theme.money }}>
This Augmentation can only be acquired from the following source(s):
</Typography>
<ul>
<Typography sx={{ color: Settings.theme.money }}>
<li>
<b>{props.aug.factions[0]}</b> faction
</li>
{props.player.canAccessGang() && !props.aug.isSpecial && (
<li>
Certain <b>gangs</b>
</li>
)}
{props.player.canAccessGrafting() &&
!props.aug.isSpecial &&
props.aug.name !== AugmentationNames.TheRedPill && (
<li>
<b>Grafting</b>
</li>
)}
</Typography>
</ul>
</>
}
>
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
</Tooltip>
);
};
interface IReqProps {
value: string;
color: string;
fulfilled: boolean;
}
const Requirement = (props: IReqProps): React.ReactElement => {
return (
<Typography sx={{ display: "flex", alignItems: "center", color: props.color }}>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};
interface IPurchasableAugsProps {
augNames: string[];
ownedAugNames: string[];
player: IPlayer;
canPurchase: (player: IPlayer, aug: Augmentation) => boolean;
purchaseAugmentation: (player: IPlayer, aug: Augmentation, showModal: (open: boolean) => void) => void;
rep?: number;
sleeveAugs?: boolean;
faction?: Faction;
}
export const PurchasableAugmentations = (props: IPurchasableAugsProps): React.ReactElement => {
return (
<Container
maxWidth="lg"
disableGutters
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 1 }}
>
{props.augNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={false} />
))}
{props.ownedAugNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={true} />
))}
</Container>
);
};
interface IPurchasableAugProps {
parent: IPurchasableAugsProps;
augName: string;
owned: boolean;
}
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = StaticAugmentations[props.augName];
const augCosts = aug.getCost(props.parent.player);
const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost;
const repCost = augCosts.repCost;
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const description = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return (
<Paper
sx={{
p: 1,
display: "grid",
gridTemplateColumns: "minmax(0, 4fr) 1fr",
gap: 1,
opacity: props.owned ? 0.75 : 1,
}}
>
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button
onClick={() =>
props.parent.purchaseAugmentation(props.parent.player, aug, (open): void => {
setOpen(open);
})
}
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned}
sx={{ width: "48px", height: "48px", float: "left", clear: "none", mr: 1 }}
>
{props.owned ? "Owned" : "Buy"}
</Button>
<Box sx={{ maxWidth: props.owned ? "100%" : "85%" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip
title={
<>
<Typography variant="h5">
{props.augName}
{props.augName === AugmentationNames.NeuroFluxGovernor &&
` - Level ${aug.getLevel(props.parent.player)}`}
</Typography>
<Typography>{description}</Typography>
</>
}
>
<Info sx={{ mr: 1 }} color="info" />
</Tooltip>
<Typography
variant="h6"
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
color: props.owned ? Settings.theme.disabled : Settings.theme.primary,
}}
>
{aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`}
</Typography>
{aug.factions.length === 1 && !props.parent.sleeveAugs && (
<Exclusive player={props.parent.player} aug={aug} />
)}
</Box>
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
</Box>
{props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Requirement
fulfilled={cost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)}
color={Settings.theme.money}
/>
{props.parent.rep !== undefined && (
<Requirement
fulfilled={props.parent.rep >= repCost}
value={`${numeralWrapper.formatReputation(repCost)} rep`}
color={Settings.theme.rep}
/>
)}
</Box>
)}
{Settings.SuppressBuyAugmentationConfirmation || (
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
faction={props.parent.faction}
aug={aug}
/>
)}
</>
</Paper>
);
}

View File

@ -1,9 +1,9 @@
import React from "react";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Faction } from "../Faction";
import { purchaseAugmentation } from "../FactionHelpers";
import { isRepeatableAug } from "../../Augmentation/AugmentationHelpers";
import { Augmentation } from "../Augmentation";
import { Faction } from "../../Faction/Faction";
import { purchaseAugmentation } from "../../Faction/FactionHelpers";
import { isRepeatableAug } from "../AugmentationHelpers";
import { Money } from "../../ui/React/Money";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
@ -13,21 +13,23 @@ import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
faction: Faction;
aug: Augmentation;
rerender: () => void;
faction?: Faction;
aug?: Augmentation;
}
export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
if (typeof props.aug === "undefined" || typeof props.faction === "undefined") {
return <></>;
}
const player = use.Player();
function buy(): void {
if (!isRepeatableAug(props.aug) && player.hasAugmentation(props.aug)) {
if (!isRepeatableAug(props.aug as Augmentation) && player.hasAugmentation(props.aug as Augmentation)) {
return;
}
purchaseAugmentation(props.aug, props.faction);
props.rerender();
purchaseAugmentation(props.aug as Augmentation, props.faction as Faction);
props.onClose();
}
@ -42,7 +44,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
<br />
<br />
Would you like to purchase the {props.aug.name} Augmentation for&nbsp;
<Money money={props.aug.baseCost} />?
<Money money={props.aug.getCost(player).moneyCost} />?
<br />
<br />
</Typography>

View File

@ -5,7 +5,7 @@
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
import * as React from "react";
import { Player } from "../../Player";
import { Augmentations } from "../Augmentations";
import { StaticAugmentations } from "../StaticAugmentations";
import { AugmentationNames } from "../data/AugmentationNames";
export function PurchasedAugmentations(): React.ReactElement {
@ -23,7 +23,7 @@ export function PurchasedAugmentations(): React.ReactElement {
let displayName = ownedAug.name;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name];
const aug = StaticAugmentations[ownedAug.name];
let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {

View File

@ -488,7 +488,7 @@ export const defaultMultipliers: IBitNodeMultipliers = {
FourSigmaMarketDataApiCost: 1,
CorporationValuation: 1,
CorporationSoftCap: 1,
CorporationSoftcap: 1,
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
@ -504,6 +504,8 @@ export const defaultMultipliers: IBitNodeMultipliers = {
WorldDaemonDifficulty: 1,
};
Object.freeze(defaultMultipliers);
export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultipliers {
const mults = Object.assign({}, defaultMultipliers);
switch (n) {
@ -523,7 +525,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: -6,
PurchasedServerSoftcap: 1.3,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 5,
});
}
@ -609,7 +611,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
@ -637,7 +639,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.9,
StaneksGiftExtraSize: -1,
GangSoftcap: 0.7,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.2,
});
@ -657,7 +659,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftExtraSize: -99,
PurchasedServerSoftcap: 4,
GangSoftcap: 0,
CorporationSoftCap: 0,
CorporationSoftcap: 0,
GangUniqueAugs: 0,
});
}
@ -685,7 +687,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 0.5,
StaneksGiftExtraSize: 2,
GangSoftcap: 0.8,
CorporationSoftCap: 0.7,
CorporationSoftcap: 0.7,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
@ -717,7 +719,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftExtraSize: -3,
PurchasedServerSoftcap: 1.1,
GangSoftcap: 0.9,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 2,
GangUniqueAugs: 0.25,
});
@ -741,7 +743,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
FourSigmaMarketDataCost: 4,
FourSigmaMarketDataApiCost: 4,
PurchasedServerSoftcap: 2,
CorporationSoftCap: 0.9,
CorporationSoftcap: 0.9,
WorldDaemonDifficulty: 1.5,
GangUniqueAugs: 0.75,
});
@ -809,7 +811,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: inc,
StaneksGiftExtraSize: inc,
GangSoftcap: 0.8,
CorporationSoftCap: 0.8,
CorporationSoftcap: 0.8,
WorldDaemonDifficulty: inc,
GangUniqueAugs: dec,
@ -854,7 +856,7 @@ export function getBitNodeMultipliers(n: number, lvl: number): IBitNodeMultiplie
StaneksGiftPowerMultiplier: 2,
StaneksGiftExtraSize: 1,
GangSoftcap: 0.3,
CorporationSoftCap: 0.3,
CorporationSoftcap: 0.3,
WorldDaemonDifficulty: 3,
GangUniqueAugs: 0.1,
});

View File

@ -242,7 +242,7 @@ export interface IBitNodeMultipliers {
/**
* Influences corporation dividends.
*/
CorporationSoftCap: number;
CorporationSoftcap: number;
// Index signature
[key: string]: number;
@ -252,4 +252,4 @@ export interface IBitNodeMultipliers {
* The multipliers that are influenced by current Bitnode progression.
*/
// tslint:disable-next-line:variable-name
export const BitNodeMultipliers = defaultMultipliers;
export const BitNodeMultipliers = Object.assign({}, defaultMultipliers);

View File

@ -1,554 +1,335 @@
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Typography } from "@mui/material";
import ExpandMore from "@mui/icons-material/ExpandMore";
import { Box, Collapse, ListItemButton, ListItemText, Paper, Table, TableBody, Typography } from "@mui/material";
import { uniqueId } from "lodash";
import React from "react";
import { SpecialServers } from "../../Server/data/SpecialServers";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { StatsRow } from "../../ui/React/StatsRow";
import { defaultMultipliers, getBitNodeMultipliers } from "../BitNode";
import { IBitNodeMultipliers } from "../BitNodeMultipliers";
import { SpecialServers } from "../../Server/data/SpecialServers";
interface IProps {
n: number;
level?: number;
}
export function BitnodeMultiplierDescription({ n }: IProps): React.ReactElement {
const player = use.Player();
export function BitnodeMultiplierDescription({ n, level }: IProps): React.ReactElement {
const [open, setOpen] = React.useState(false);
const mults = getBitNodeMultipliers(n, player.sourceFileLvl(n));
if (n === 1) return <></>;
return (
<>
<br />
<Box component={Paper}>
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography>Bitnode multipliers:</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Box mx={2}>
<Collapse in={open}>
<GeneralMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
<br />
</Collapse>
</Box>
</Box>
</>
<Box component={Paper} sx={{ mt: 1, p: 1 }}>
<ListItemButton disableGutters onClick={() => setOpen((old) => !old)}>
<ListItemText primary={<Typography variant="h6">Bitnode Multipliers</Typography>} />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
<Collapse in={open}>
<BitNodeMultipliersDisplay n={n} level={level} />
</Collapse>
</Box>
);
}
export const BitNodeMultipliersDisplay = ({ n, level }: IProps): React.ReactElement => {
const player = use.Player();
// If a level argument has been provided, use that as the multiplier level
// If not, then we have to assume that we want the next level up from the
// current node's source file, so we get the min of that, the SF's max level,
// or if it's BN12, ∞
const maxSfLevel = n === 12 ? Infinity : 3;
const mults = getBitNodeMultipliers(n, level ?? Math.min(player.sourceFileLvl(n) + 1, maxSfLevel));
return (
<Box sx={{ columnCount: 2, columnGap: 1, mb: -2 }}>
<GeneralMults n={n} mults={mults} />
<SkillMults n={n} mults={mults} />
<FactionMults n={n} mults={mults} />
<AugmentationMults n={n} mults={mults} />
<HackingMults n={n} mults={mults} />
<PurchasedServersMults n={n} mults={mults} />
<StockMults n={n} mults={mults} />
<CrimeMults n={n} mults={mults} />
<InfiltrationMults n={n} mults={mults} />
<CompanyMults n={n} mults={mults} />
<GangMults n={n} mults={mults} />
<CorporationMults n={n} mults={mults} />
<BladeburnerMults n={n} mults={mults} />
<StanekMults n={n} mults={mults} />
</Box>
);
};
interface IBNMultRows {
[mult: string]: {
name: string;
content?: string;
color?: string;
};
}
interface IBNMultTableProps {
sectionName: string;
rowData: IBNMultRows;
mults: IBitNodeMultipliers;
}
const BNMultTable = (props: IBNMultTableProps): React.ReactElement => {
const rowsArray = Object.entries(props.rowData)
.filter(([key, _value]) => props.mults[key] !== defaultMultipliers[key])
.map(([key, value]) => (
<StatsRow
key={uniqueId()}
name={value.name}
data={{ content: value.content ?? `${(props.mults[key] * 100).toFixed(3)}%` }}
color={value.color ?? Settings.theme.primary}
/>
));
return rowsArray.length > 0 ? (
<span style={{ display: "inline-block", width: "100%", marginBottom: "16px" }}>
<Typography variant="h6">{props.sectionName}</Typography>
<Table>
<TableBody>{rowsArray}</TableBody>
</Table>
</span>
) : (
<></>
);
};
interface IMultsProps {
n: number;
mults: IBitNodeMultipliers;
}
function GeneralMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ClassGymExpGain === defaultMultipliers.ClassGymExpGain &&
mults.CodingContractMoney === defaultMultipliers.CodingContractMoney &&
mults.DaedalusAugsRequirement === defaultMultipliers.DaedalusAugsRequirement &&
mults.WorldDaemonDifficulty === defaultMultipliers.WorldDaemonDifficulty &&
mults.HacknetNodeMoney === defaultMultipliers.HacknetNodeMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>General:</Typography>
<Box mx={1}>
{mults.WorldDaemonDifficulty !== defaultMultipliers.WorldDaemonDifficulty ? (
<Typography>
{SpecialServers.WorldDaemon} difficulty: x{mults.WorldDaemonDifficulty.toFixed(3)}
</Typography>
) : (
<></>
)}
{mults.DaedalusAugsRequirement !== defaultMultipliers.DaedalusAugsRequirement ? (
<Typography>Daedalus aug req.: {mults.DaedalusAugsRequirement}</Typography>
) : (
<></>
)}
{mults.HacknetNodeMoney !== defaultMultipliers.HacknetNodeMoney ? (
<Typography>Hacknet production: x{mults.HacknetNodeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CodingContractMoney !== defaultMultipliers.CodingContractMoney ? (
<Typography>Coding contract reward: x{mults.CodingContractMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ClassGymExpGain !== defaultMultipliers.ClassGymExpGain ? (
<Typography>Class/Gym exp: x{mults.ClassGymExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
WorldDaemonDifficulty: { name: `${SpecialServers.WorldDaemon} Difficulty` },
DaedalusAugsRequirement: {
name: "Daedalus Augs Requirement",
content: String(mults.DaedalusAugsRequirement),
},
HacknetNodeMoney: { name: "Hacknet Production" },
CodingContractMoney: { name: "Coding Contract Reward" },
ClassGymExpGain: { name: "Class/Gym Exp" },
};
return <BNMultTable sectionName="General" rowData={rows} mults={mults} />;
}
function AugmentationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.AugmentationMoneyCost === defaultMultipliers.AugmentationMoneyCost &&
mults.AugmentationRepCost === defaultMultipliers.AugmentationRepCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Augmentations:</Typography>
<Box mx={1}>
{mults.AugmentationMoneyCost !== defaultMultipliers.AugmentationMoneyCost ? (
<Typography>Cost: x{mults.AugmentationMoneyCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AugmentationRepCost !== defaultMultipliers.AugmentationRepCost ? (
<Typography>Reputation: x{mults.AugmentationRepCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
AugmentationMoneyCost: { name: "Money Cost" },
AugmentationRepCost: {
name: "Reputation Cost",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Augmentations" rowData={rows} mults={mults} />;
}
function CompanyMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.CompanyWorkExpGain === defaultMultipliers.CompanyWorkExpGain &&
mults.CompanyWorkMoney === defaultMultipliers.CompanyWorkMoney
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Company:</Typography>
<Box mx={1}>
{mults.CompanyWorkMoney !== defaultMultipliers.CompanyWorkMoney ? (
<Typography>Money: x{mults.CompanyWorkMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CompanyWorkExpGain !== defaultMultipliers.CompanyWorkExpGain ? (
<Typography>Exp: x{mults.CompanyWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CompanyWorkMoney: {
name: "Work Money",
color: Settings.theme.money,
},
CompanyWorkExpGain: { name: "Work Exp" },
};
return <BNMultTable sectionName="Company" rowData={rows} mults={mults} />;
}
function StockMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FourSigmaMarketDataApiCost === defaultMultipliers.FourSigmaMarketDataApiCost &&
mults.FourSigmaMarketDataCost === defaultMultipliers.FourSigmaMarketDataCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Stock market:</Typography>
<Box mx={1}>
{mults.FourSigmaMarketDataCost !== defaultMultipliers.FourSigmaMarketDataCost ? (
<Typography>Market data cost: x{mults.FourSigmaMarketDataCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FourSigmaMarketDataApiCost !== defaultMultipliers.FourSigmaMarketDataApiCost ? (
<Typography>Market data API cost: x{mults.FourSigmaMarketDataApiCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
FourSigmaMarketDataCost: { name: "Market Data Cost" },
FourSigmaMarketDataApiCost: { name: "Market Data API Cost" },
};
return <BNMultTable sectionName="Stock Market" rowData={rows} mults={mults} />;
}
function FactionMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.FactionPassiveRepGain === defaultMultipliers.FactionPassiveRepGain &&
mults.FactionWorkExpGain === defaultMultipliers.FactionWorkExpGain &&
mults.FactionWorkRepGain === defaultMultipliers.FactionWorkRepGain &&
mults.RepToDonateToFaction === defaultMultipliers.RepToDonateToFaction
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Faction:</Typography>
<Box mx={1}>
{mults.RepToDonateToFaction !== defaultMultipliers.RepToDonateToFaction ? (
<Typography>Favor to donate: x{mults.RepToDonateToFaction.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkRepGain !== defaultMultipliers.FactionWorkRepGain ? (
<Typography>Work rep: x{mults.FactionWorkRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionWorkExpGain !== defaultMultipliers.FactionWorkExpGain ? (
<Typography>Work exp: x{mults.FactionWorkExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.FactionPassiveRepGain !== defaultMultipliers.FactionPassiveRepGain ? (
<Typography>Passive rep: x{mults.FactionPassiveRepGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
RepToDonateToFaction: { name: "Favor to Donate" },
FactionWorkRepGain: {
name: "Work Reputation",
color: Settings.theme.rep,
},
FactionWorkExpGain: { name: "Work Exp" },
FactionPassiveRepGain: {
name: "Passive Rep",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Faction" rowData={rows} mults={mults} />;
}
function CrimeMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (mults.CrimeExpGain === defaultMultipliers.CrimeExpGain && mults.CrimeMoney === defaultMultipliers.CrimeMoney)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Crime:</Typography>
<Box mx={1}>
{mults.CrimeExpGain !== defaultMultipliers.CrimeExpGain ? (
<Typography>Exp: x{mults.CrimeExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CrimeMoney !== defaultMultipliers.CrimeMoney ? (
<Typography>Money: x{mults.CrimeMoney.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CrimeExpGain: {
name: "Crime Exp",
color: Settings.theme.combat,
},
CrimeMoney: {
name: "Crime Money",
color: Settings.theme.combat,
},
};
return <BNMultTable sectionName="Crime" rowData={rows} mults={mults} />;
}
function SkillMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.HackingLevelMultiplier === defaultMultipliers.HackingLevelMultiplier &&
mults.AgilityLevelMultiplier === defaultMultipliers.AgilityLevelMultiplier &&
mults.DefenseLevelMultiplier === defaultMultipliers.DefenseLevelMultiplier &&
mults.DexterityLevelMultiplier === defaultMultipliers.DexterityLevelMultiplier &&
mults.StrengthLevelMultiplier === defaultMultipliers.StrengthLevelMultiplier &&
mults.CharismaLevelMultiplier === defaultMultipliers.CharismaLevelMultiplier
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Skills:</Typography>
<Box mx={1}>
{mults.HackingLevelMultiplier !== defaultMultipliers.HackingLevelMultiplier ? (
<Typography>Hacking: x{mults.HackingLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.AgilityLevelMultiplier !== defaultMultipliers.AgilityLevelMultiplier ? (
<Typography>Agility: x{mults.AgilityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DefenseLevelMultiplier !== defaultMultipliers.DefenseLevelMultiplier ? (
<Typography>Defense: x{mults.DefenseLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.DexterityLevelMultiplier !== defaultMultipliers.DexterityLevelMultiplier ? (
<Typography>Dexterity: x{mults.DexterityLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.StrengthLevelMultiplier !== defaultMultipliers.StrengthLevelMultiplier ? (
<Typography>Strength: x{mults.StrengthLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CharismaLevelMultiplier !== defaultMultipliers.CharismaLevelMultiplier ? (
<Typography>Charisma: x{mults.CharismaLevelMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
HackingLevelMultiplier: {
name: "Hacking Level",
color: Settings.theme.hack,
},
StrengthLevelMultiplier: {
name: "Strength Level",
color: Settings.theme.combat,
},
DefenseLevelMultiplier: {
name: "Defense Level",
color: Settings.theme.combat,
},
DexterityLevelMultiplier: {
name: "Dexterity Level",
color: Settings.theme.combat,
},
AgilityLevelMultiplier: {
name: "Agility Level",
color: Settings.theme.combat,
},
CharismaLevelMultiplier: {
name: "Charisma Level",
color: Settings.theme.cha,
},
};
return <BNMultTable sectionName="Skills" rowData={rows} mults={mults} />;
}
function HackingMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.ServerGrowthRate === defaultMultipliers.ServerGrowthRate &&
mults.ServerMaxMoney === defaultMultipliers.ServerMaxMoney &&
mults.ServerStartingMoney === defaultMultipliers.ServerStartingMoney &&
mults.ServerStartingSecurity === defaultMultipliers.ServerStartingSecurity &&
mults.ServerWeakenRate === defaultMultipliers.ServerWeakenRate &&
mults.ManualHackMoney === defaultMultipliers.ManualHackMoney &&
mults.ScriptHackMoney === defaultMultipliers.ScriptHackMoney &&
mults.ScriptHackMoneyGain === defaultMultipliers.ScriptHackMoneyGain &&
mults.HackExpGain === defaultMultipliers.HackExpGain
)
return <></>;
const rows: IBNMultRows = {
HackExpGain: {
name: "Hacking Exp",
color: Settings.theme.hack,
},
ServerGrowthRate: { name: "Server Growth Rate" },
ServerMaxMoney: { name: "Server Max Money" },
ServerStartingMoney: { name: "Server Starting Money" },
ServerStartingSecurity: { name: "Server Starting Security" },
ServerWeakenRate: { name: "Server Weaken Rate" },
ManualHackMoney: {
name: "Manual Hack Money",
color: Settings.theme.money,
},
ScriptHackMoney: {
name: "Script Hack Money",
color: Settings.theme.money,
},
ScriptHackMoneyGain: {
name: "Money Gained From Hack",
color: Settings.theme.money,
},
};
return (
<>
<br />
<Typography variant={"h5"}>Hacking:</Typography>
<Box mx={1}>
{mults.HackExpGain !== defaultMultipliers.HackExpGain ? (
<Typography>Exp: x{mults.HackExpGain.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerGrowthRate !== defaultMultipliers.ServerGrowthRate ? (
<Typography>Growth rate: x{mults.ServerGrowthRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerMaxMoney !== defaultMultipliers.ServerMaxMoney ? (
<Typography>Max money: x{mults.ServerMaxMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingMoney !== defaultMultipliers.ServerStartingMoney ? (
<Typography>Starting money: x{mults.ServerStartingMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerStartingSecurity !== defaultMultipliers.ServerStartingSecurity ? (
<Typography>Starting security: x{mults.ServerStartingSecurity.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ServerWeakenRate !== defaultMultipliers.ServerWeakenRate ? (
<Typography>Weaken rate: x{mults.ServerWeakenRate.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ManualHackMoney !== defaultMultipliers.ManualHackMoney ? (
<Typography>Manual hack money: x{mults.ManualHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoney !== defaultMultipliers.ScriptHackMoney ? (
<Typography>Hack money stolen: x{mults.ScriptHackMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.ScriptHackMoneyGain !== defaultMultipliers.ScriptHackMoneyGain ? (
<Typography>Money gained from hack: x{mults.ScriptHackMoneyGain.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
return <BNMultTable sectionName="Hacking" rowData={rows} mults={mults} />;
}
function PurchasedServersMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.PurchasedServerCost === defaultMultipliers.PurchasedServerCost &&
mults.PurchasedServerSoftcap === defaultMultipliers.PurchasedServerSoftcap &&
mults.PurchasedServerLimit === defaultMultipliers.PurchasedServerLimit &&
mults.PurchasedServerMaxRam === defaultMultipliers.PurchasedServerMaxRam &&
mults.HomeComputerRamCost === defaultMultipliers.HomeComputerRamCost
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Purchased servers:</Typography>
<Box mx={1}>
{mults.PurchasedServerCost !== defaultMultipliers.PurchasedServerCost ? (
<Typography>Base cost: {mults.PurchasedServerCost.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerSoftcap !== defaultMultipliers.PurchasedServerSoftcap ? (
<Typography>Softcap cost: {mults.PurchasedServerSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerLimit !== defaultMultipliers.PurchasedServerLimit ? (
<Typography>Limit: x{mults.PurchasedServerLimit.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.PurchasedServerMaxRam !== defaultMultipliers.PurchasedServerMaxRam ? (
<Typography>Max ram: x{mults.PurchasedServerMaxRam.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.HomeComputerRamCost !== defaultMultipliers.HomeComputerRamCost ? (
<Typography>Home ram cost: x{mults.HomeComputerRamCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
PurchasedServerCost: {
name: "Base Cost",
content: mults.PurchasedServerCost.toFixed(3),
},
PurchasedServerSoftcap: {
name: "Softcap Cost",
content: mults.PurchasedServerSoftcap.toFixed(3),
},
PurchasedServerLimit: { name: "Server Limit" },
PurchasedServerMaxRam: { name: "Max RAM" },
HomeComputerRamCost: { name: "Home RAM Cost" },
};
return <BNMultTable sectionName="Purchased Servers" rowData={rows} mults={mults} />;
}
function InfiltrationMults({ mults }: IMultsProps): React.ReactElement {
// is it empty check
if (
mults.InfiltrationMoney === defaultMultipliers.InfiltrationMoney &&
mults.InfiltrationRep === defaultMultipliers.InfiltrationRep
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Infiltration:</Typography>
<Box mx={1}>
{mults.InfiltrationMoney !== defaultMultipliers.InfiltrationMoney ? (
<Typography>Money: {mults.InfiltrationMoney.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.InfiltrationRep !== defaultMultipliers.InfiltrationRep ? (
<Typography>Reputation: x{mults.InfiltrationRep.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
InfiltrationMoney: {
name: "Infiltration Money",
color: Settings.theme.money,
},
InfiltrationRep: {
name: "Infiltration Reputation",
color: Settings.theme.rep,
},
};
return <BNMultTable sectionName="Infiltration" rowData={rows} mults={mults} />;
}
function BladeburnerMults({ n, mults }: IMultsProps): React.ReactElement {
function BladeburnerMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 6 && n !== 7 && player.sourceFileLvl(6) === 0) return <></>;
//default mults check
if (mults.BladeburnerRank === 1 && mults.BladeburnerSkillCost === 1) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Bladeburner:</Typography>
<Box mx={1}>
{mults.BladeburnerRank !== 1 ? <Typography>Rank gain: x{mults.BladeburnerRank.toFixed(3)}</Typography> : <></>}
{mults.BladeburnerSkillCost !== 1 ? (
<Typography>Skill cost: x{mults.BladeburnerSkillCost.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (!player.canAccessBladeburner()) return <></>;
const rows: IBNMultRows = {
BladeburnerRank: { name: "Rank Gain" },
BladeburnerSkillCost: { name: "Skill Cost" },
};
return <BNMultTable sectionName="Bladeburner" rowData={rows} mults={mults} />;
}
function StanekMults({ n, mults }: IMultsProps): React.ReactElement {
function StanekMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 13 && player.sourceFileLvl(13) === 0) return <></>;
//default mults check
if (
mults.StaneksGiftExtraSize === defaultMultipliers.StaneksGiftExtraSize &&
mults.StaneksGiftPowerMultiplier === defaultMultipliers.StaneksGiftPowerMultiplier
)
return <></>;
if (!player.canAccessCotMG()) return <></>;
const s = mults.StaneksGiftExtraSize;
return (
<>
<br />
<Typography variant={"h5"}>Stanek's Gift:</Typography>
<Box mx={1}>
{mults.StaneksGiftPowerMultiplier !== defaultMultipliers.StaneksGiftPowerMultiplier ? (
<Typography>Gift power: x{mults.StaneksGiftPowerMultiplier.toFixed(3)}</Typography>
) : (
<></>
)}
{s !== defaultMultipliers.StaneksGiftExtraSize ? (
<Typography>Base size modifier: {s > defaultMultipliers.StaneksGiftExtraSize ? `+${s}` : s}</Typography>
) : (
<></>
)}
</Box>
</>
);
const extraSize = mults.StaneksGiftExtraSize.toFixed(3);
const rows: IBNMultRows = {
StnakesGiftPowerMultiplier: { name: "Gift Power" },
StaneksGiftExtraSize: {
name: "Base Size Modifier",
content: `${mults.StaneksGiftExtraSize > defaultMultipliers.StaneksGiftExtraSize ? `+${extraSize}` : extraSize}`,
},
};
return <BNMultTable sectionName="Stanek's Gift" rowData={rows} mults={mults} />;
}
function GangMults({ n, mults }: IMultsProps): React.ReactElement {
function GangMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 2 && player.sourceFileLvl(2) === 0) return <></>;
// is it empty check
if (
mults.GangSoftcap === defaultMultipliers.GangSoftcap &&
mults.GangUniqueAugs === defaultMultipliers.GangUniqueAugs
)
return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Gang:</Typography>
<Box mx={1}>
{mults.GangSoftcap !== defaultMultipliers.GangSoftcap ? (
<Typography>Softcap: {mults.GangSoftcap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.GangUniqueAugs !== defaultMultipliers.GangUniqueAugs ? (
<Typography>Unique augs: x{mults.GangUniqueAugs.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
if (player.bitNodeN !== 2 && player.sourceFileLvl(2) <= 0) return <></>;
const rows: IBNMultRows = {
GangSoftcap: {
name: "Gang Softcap",
content: mults.GangSoftcap.toFixed(3),
},
GangUniqueAugs: { name: "Unique Augmentations" },
};
return <BNMultTable sectionName="Gang" rowData={rows} mults={mults} />;
}
function CorporationMults({ n, mults }: IMultsProps): React.ReactElement {
function CorporationMults({ mults }: IMultsProps): React.ReactElement {
const player = use.Player();
// access check
if (n !== 3 && player.sourceFileLvl(3) === 0) return <></>;
// is it empty check
if (
mults.CorporationSoftCap === defaultMultipliers.CorporationSoftCap &&
mults.CorporationValuation === defaultMultipliers.CorporationValuation
)
return <></>;
if (!player.canAccessCorporation()) return <></>;
return (
<>
<br />
<Typography variant={"h5"}>Corporation:</Typography>
<Box mx={1}>
{mults.CorporationSoftCap !== defaultMultipliers.CorporationSoftCap ? (
<Typography>Softcap: {mults.CorporationSoftCap.toFixed(3)}</Typography>
) : (
<></>
)}
{mults.CorporationValuation !== defaultMultipliers.CorporationValuation ? (
<Typography>Valuation: x{mults.CorporationValuation.toFixed(3)}</Typography>
) : (
<></>
)}
</Box>
</>
);
const rows: IBNMultRows = {
CorporationSoftcap: {
name: "Corporation Softcap",
content: mults.CorporationSoftcap.toFixed(3),
},
CorporationValuation: { name: "Valuation" },
};
return <BNMultTable sectionName="Corporation" rowData={rows} mults={mults} />;
}

View File

@ -41,7 +41,7 @@ export function PortalModal(props: IProps): React.ReactElement {
<br />
<br />
<Typography>{bitNode.info}</Typography>
<BitnodeMultiplierDescription n={props.n} />
<BitnodeMultiplierDescription n={props.n} level={newLevel} />
<br />
<br />
<Button

View File

@ -697,7 +697,7 @@ export class Bladeburner implements IBladeburner {
// Set variables
if (args.length === 4) {
const variable = args[1];
const variable = args[1].toLowerCase(); // allows Action Type to be with or without capitalisation.
const val = args[2];
let highLow = false; // True for high, false for low
@ -1919,7 +1919,7 @@ export class Bladeburner implements IBladeburner {
}
// If the Player starts doing some other actions, set action to idle and alert
if (player.hasAugmentation(AugmentationNames.BladesSimulacrum) === false && player.isWorking) {
if (!player.hasAugmentation(AugmentationNames.BladesSimulacrum, true) && player.isWorking) {
if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) {

View File

@ -63,26 +63,6 @@ export const CONSTANTS: {
GameCyclesPerQuarterHour: number;
MillisecondsPerFiveMinutes: number;
GameCyclesPerFiveMinutes: number;
FactionWorkHacking: string;
FactionWorkField: string;
FactionWorkSecurity: string;
WorkTypeCompany: string;
WorkTypeCompanyPartTime: string;
WorkTypeFaction: string;
WorkTypeCreateProgram: string;
WorkTypeStudyClass: string;
WorkTypeCrime: string;
WorkTypeGraftAugmentation: string;
ClassStudyComputerScience: string;
ClassDataStructures: string;
ClassNetworks: string;
ClassAlgorithms: string;
ClassManagement: string;
ClassLeadership: string;
ClassGymStrength: string;
ClassGymDefense: string;
ClassGymDexterity: string;
ClassGymAgility: string;
ClassDataStructuresBaseCost: number;
ClassNetworksBaseCost: number;
ClassAlgorithmsBaseCost: number;
@ -95,18 +75,6 @@ export const CONSTANTS: {
ClassAlgorithmsBaseExp: number;
ClassManagementBaseExp: number;
ClassLeadershipBaseExp: number;
CrimeShoplift: string;
CrimeRobStore: string;
CrimeMug: string;
CrimeLarceny: string;
CrimeDrugs: string;
CrimeBondForgery: string;
CrimeTraffickArms: string;
CrimeHomicide: string;
CrimeGrandTheftAuto: string;
CrimeKidnap: string;
CrimeAssassination: string;
CrimeHeist: string;
CodingContractBaseFactionRepGain: number;
CodingContractBaseCompanyRepGain: number;
CodingContractBaseMoneyGain: number;
@ -116,11 +84,12 @@ export const CONSTANTS: {
SoARepMult: number;
EntropyEffect: number;
TotalNumBitNodes: number;
InfiniteLoopLimit: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string;
} = {
VersionString: "1.6.4",
VersionNumber: 15,
VersionString: "1.7.0",
VersionNumber: 18,
// Speed (in ms) at which the main loop is updated
_idleSpeed: 200,
@ -223,28 +192,6 @@ export const CONSTANTS: {
// Player Work & Action
BaseFocusBonus: 0.8,
FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work",
WorkTypeCompany: "Working for Company",
WorkTypeCompanyPartTime: "Working for Company part-time",
WorkTypeFaction: "Working for Faction",
WorkTypeCreateProgram: "Working on Create a Program",
WorkTypeStudyClass: "Studying or Taking a class at university",
WorkTypeCrime: "Committing a crime",
WorkTypeGraftAugmentation: "Grafting an Augmentation",
ClassStudyComputerScience: "studying Computer Science",
ClassDataStructures: "taking a Data Structures course",
ClassNetworks: "taking a Networks course",
ClassAlgorithms: "taking an Algorithms course",
ClassManagement: "taking a Management course",
ClassLeadership: "taking a Leadership course",
ClassGymStrength: "training your strength at a gym",
ClassGymDefense: "training your defense at a gym",
ClassGymDexterity: "training your dexterity at a gym",
ClassGymAgility: "training your agility at a gym",
ClassDataStructuresBaseCost: 40,
ClassNetworksBaseCost: 80,
@ -260,19 +207,6 @@ export const CONSTANTS: {
ClassManagementBaseExp: 2,
ClassLeadershipBaseExp: 4,
CrimeShoplift: "shoplift",
CrimeRobStore: "rob a store",
CrimeMug: "mug someone",
CrimeLarceny: "commit larceny",
CrimeDrugs: "deal drugs",
CrimeBondForgery: "forge corporate bonds",
CrimeTraffickArms: "traffick illegal arms",
CrimeHomicide: "commit homicide",
CrimeGrandTheftAuto: "commit grand theft auto",
CrimeKidnap: "kidnap someone for ransom",
CrimeAssassination: "assassinate a high-profile target",
CrimeHeist: "pull off the ultimate heist",
// Coding Contract
// TODO: Move this into Coding contract implementation?
CodingContractBaseFactionRepGain: 2500,
@ -293,22 +227,173 @@ export const CONSTANTS: {
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
Donations: 6,
InfiniteLoopLimit: 1000,
Donations: 7,
LatestUpdate: `
v1.6.3 - 2022-04-01 Few stanek fixes
----------------------------
## [draft] v1.7.0 - 2022-04-13 to 2022-05-20
Stanek Gift
#### Information
* Has a minimum size of 2x3
* Active Fragment property 'avgCharge' renamed to 'highestCharge'
* Formula for fragment effect updated to make 561% more sense.
Now you can charge to your heart content.
* Logs for the 'chargeFragment' function updated.
Modifications included between **2022-04-13** and **2022-05-20** 'b5e4d70' to '0fbe4a1').
Misc.
_[See Pull Requests on GitHub](https://github.com/search?q=user%3Adanielyxie%20repo%3Abitburner%20is%3Apr%20is%3Amerged%20merged%3A%222022-04-13T16%3A32%3A26.000Z..2022-05-20T06%3A08%3A51.000Z%22)_
#### Merged Pull Requests
- [Feature] Monaco Theme Editor (by @nickofolas) #[3438](https://github.com/danielyxie/bitburner/pull/3438)
- [Fix] Dummy Stanek grid width (by @nickofolas) #[3442](https://github.com/danielyxie/bitburner/pull/3442)
- [Fix] Theme browser assets not loading (by @nickofolas) #[3446](https://github.com/danielyxie/bitburner/pull/3446)
- Accept valid JSON arrays in coding contracts (by @Savlik) #[3247](https://github.com/danielyxie/bitburner/pull/3247)
- another dark theme? (by @hydroflame) #[3450](https://github.com/danielyxie/bitburner/pull/3450)
- API: Add repFromDonation() to the Formula API (by @Hoekstraa) #[3461](https://github.com/danielyxie/bitburner/pull/3461)
- API: Add safeguard to ns.killall(), preventing killing itself by default (by @Hoekstraa) #[3607](https://github.com/danielyxie/bitburner/pull/3607)
- API: FIX #2993 sleeve.travel with invalid city names (by @TheMas3212) #[3458](https://github.com/danielyxie/bitburner/pull/3458)
- API: Fix inconsistent return value in 'ns.grafting.getAugmentationGraftTime' (by @nickofolas) #[3539](https://github.com/danielyxie/bitburner/pull/3539)
- API: Fix leak of real Employee object in hireEmployee (by @TheMas3212) #[3483](https://github.com/danielyxie/bitburner/pull/3483)
- API: replace a number of references to workerscript.log with \_ctx.log (by @TheMas3212) #[3470](https://github.com/danielyxie/bitburner/pull/3470)
- API: Terminal screen can now be cleared from within scripts with ns.ui.clearTerminal() (by @Hoekstraa) #[3618](https://github.com/danielyxie/bitburner/pull/3618)
- AUGMENTATIONS: Fix 'isSpecial' filter in helper (Removes NeuroFlux, Stanek's Gift, etc from gangs) (by @nickofolas) #[3565](https://github.com/danielyxie/bitburner/pull/3565)
- AUGMENTATIONS: Fix Augmentation rep req not being properly influenced by BitNode multipliers (by @nickofolas) #[3652](https://github.com/danielyxie/bitburner/pull/3652)
- AUGMENTATIONS: Fix NeuroFlux being applied improperly and migrate broken saves (by @nickofolas) #[3613](https://github.com/danielyxie/bitburner/pull/3613)
- AUGMENTATIONS: Fix reputation check for faction augs (by @nickofolas) #[3609](https://github.com/danielyxie/bitburner/pull/3609)
- AUGMENTATIONS: Tweak a couple small UI elements (by @nickofolas) #[3614](https://github.com/danielyxie/bitburner/pull/3614)
- basic doc no longer hacker themed (by @hydroflame) #[3449](https://github.com/danielyxie/bitburner/pull/3449)
- BITNODE: FIX #3546 BitVerse now shows proper BN level when accessed via flume (by @nickofolas) #[3550](https://github.com/danielyxie/bitburner/pull/3550)
- BLADEBURNER: fixes #3648 : Automate console command capitalisation inconsistent (by @Vic1970) #[3647](https://github.com/danielyxie/bitburner/pull/3647)
- BLADEBURNER: Fix #3594 Blade's Simulacrum worked without being installed (by @Undeemiss) #[3639](https://github.com/danielyxie/bitburner/pull/3639)
- blood (by @hydroflame) #[3495](https://github.com/danielyxie/bitburner/pull/3495)
- BUGFIX: getAugmentationCost response backwards (by @phyzical) #[3617](https://github.com/danielyxie/bitburner/pull/3617)
- BUGFIX: Handle edge case in LZ compression code and fix docs (by @stalefishies) #[3581](https://github.com/danielyxie/bitburner/pull/3581)
- BUGFIX: make bonustime for gang in miliseconds (by @phyzical) #[3578](https://github.com/danielyxie/bitburner/pull/3578)
- BUGFIX: sleeve stale object refence during augmentation (by @phyzical) #[3601](https://github.com/danielyxie/bitburner/pull/3601)
- Bugfix/corp updates (by @phyzical) #[3321](https://github.com/danielyxie/bitburner/pull/3321)
- Bump async from 2.6.3 to 2.6.4 (by @dependabot[bot]) #[3463](https://github.com/danielyxie/bitburner/pull/3463)
- CODINGCONTRACT: Fix #3391 Double contract reward exploit (by @Undeemiss) #[3646](https://github.com/danielyxie/bitburner/pull/3646)
- CODINGCONTRACT: FIX #3484 BREAKING Fixed capitalization in contract name (by @Undeemiss) #[3537](https://github.com/danielyxie/bitburner/pull/3537)
- CODINGCONTRACT: New "Proper 2-Coloring of a Graph" contract (by @Undeemiss) #[3530](https://github.com/danielyxie/bitburner/pull/3530)
- CODINGCONTRACT: Three new compression contracts (by @stalefishies) #[3541](https://github.com/danielyxie/bitburner/pull/3541)
- CODINGCONTRACT: Typo & clarity fixes to description of Encoded Binary to Integer contract (by @ActuallyCurtis) #[3469](https://github.com/danielyxie/bitburner/pull/3469)
- CODINGCONTRACT: Updated description of 2-coloring contract (by @Undeemiss) #[3531](https://github.com/danielyxie/bitburner/pull/3531)
- COMPANY: Fix #3551 Applying for a new job will not change active employer if player is performing company work (by @Snarling) #[3552](https://github.com/danielyxie/bitburner/pull/3552)
- CORPORATIONS: Expose makeProducts on NSDivision interface (by @DavidGrinberg) #[3570](https://github.com/danielyxie/bitburner/pull/3570)
- CORPORATIONS: Expose sales cost on NSMaterial interface (by @DavidGrinberg) #[3574](https://github.com/danielyxie/bitburner/pull/3574)
- Corrected example grids found in Stanek help (by @Undeemiss) #[3441](https://github.com/danielyxie/bitburner/pull/3441)
- Create program action no longer creates duplicates (by @Undeemiss) #[3436](https://github.com/danielyxie/bitburner/pull/3436)
- DOCUMENTATION: Add descriptions for compression contracts (by @stalefishies) #[3559](https://github.com/danielyxie/bitburner/pull/3559)
- DOCUMENTATION: Add new coding contract descriptions (by @stalefishies) #[3542](https://github.com/danielyxie/bitburner/pull/3542)
- DOCUMENTATION: Clarify definition for installAugmentations() (by @PSEUDOSTAGE) #[3560](https://github.com/danielyxie/bitburner/pull/3560)
- DOCUMENTATION: FIX #3516 "cannot" misspelled as "cannnot" (by @Undeemiss) #[3533](https://github.com/danielyxie/bitburner/pull/3533)
- EDITOR: FIX #3502 Editor theme migration crash (by @nickofolas) #[3503](https://github.com/danielyxie/bitburner/pull/3503)
- FEATURE: added logic to allow quitJob to be called from singularity (by @phyzical) #[3577](https://github.com/danielyxie/bitburner/pull/3577)
- fix #3395 donating to special factions possible via singularity (by @TheMas3212) #[3456](https://github.com/danielyxie/bitburner/pull/3456)
- fix b1tflum3 and destroyW0r1dD43m0n singularity functions to check for sf4 (by @TheMas3212) #[3443](https://github.com/danielyxie/bitburner/pull/3443)
- Fix inconsistancy with trying to work for gang factions while running a gang (by @TheMas3212) #[3454](https://github.com/danielyxie/bitburner/pull/3454)
- Fix infiltration rep BN mult calculation (by @trambelus) #[3632](https://github.com/danielyxie/bitburner/pull/3632)
- Fix script editor settings. (by @hydroflame) #[3504](https://github.com/danielyxie/bitburner/pull/3504)
- Fix test/jest/Netscript/DynamicRamCalculation.test.js (by @TheMas3212) #[3455](https://github.com/danielyxie/bitburner/pull/3455)
- GRAFTING: Fix Grafting not being handled in singularity stop work (by @nickofolas) #[3568](https://github.com/danielyxie/bitburner/pull/3568)
- GRAFTING: Implement sorting options (by @nickofolas) #[3654](https://github.com/danielyxie/bitburner/pull/3654)
- INFILTRATION: Added new faction called infiltrators that provide infiltration specific augs. (by @phyzical) #[3241](https://github.com/danielyxie/bitburner/pull/3241)
- INFILTRATION: Fix minigame cycle (by @nickofolas) #[3549](https://github.com/danielyxie/bitburner/pull/3549)
- INFILTRATION: Fix phyzical WKS aug effects being applied before aug is installed (by @nickofolas) #[3555](https://github.com/danielyxie/bitburner/pull/3555)
- INFILTRATION: Fix rep reward being substantially higher than intended (by @nickofolas) #[3562](https://github.com/danielyxie/bitburner/pull/3562)
- INFILTRATION: New faction, Shadows of Anarchy, provides various augs to help infiltrations. (by @hydroflame) #[3543](https://github.com/danielyxie/bitburner/pull/3543)
- INFILTRATION: Update gameplay UI (by @nickofolas) #[3587](https://github.com/danielyxie/bitburner/pull/3587)
- keeping up to date (by @hydroflame) #[3432](https://github.com/danielyxie/bitburner/pull/3432)
- Keeping up to date. (by @hydroflame) #[3561](https://github.com/danielyxie/bitburner/pull/3561)
- Make .lit and .msg files clickable (by @Chris380) #[3453](https://github.com/danielyxie/bitburner/pull/3453)
- MESSAGES: Added the name of NiteSec's server to their .msg (by @Undeemiss) #[3466](https://github.com/danielyxie/bitburner/pull/3466)
- MISC: add better typing to Electron.tsx (by @taralx) #[3540](https://github.com/danielyxie/bitburner/pull/3540)
- MISC: Added NS function closeTail to close tail windows (by @Undeemiss) #[3666](https://github.com/danielyxie/bitburner/pull/3666)
- MISC: Adjust deps to current usage (by @taralx) #[3519](https://github.com/danielyxie/bitburner/pull/3519)
- MISC: Close some GitHub issues that do not need action (by @Undeemiss) #[3640](https://github.com/danielyxie/bitburner/pull/3640)
- MISC: Closing more GitHub issues I missed last time (by @Undeemiss) #[3665](https://github.com/danielyxie/bitburner/pull/3665)
- MISC: Correct BB Skill point achievement name (by @Undeemiss) #[3571](https://github.com/danielyxie/bitburner/pull/3571)
- MISC: Correct typos in getScriptRam docs. (by @nzdjb) #[3590](https://github.com/danielyxie/bitburner/pull/3590)
- MISC: Fix #3125 BREAKING Renamed BN mult CorporationSoftCap to CorporationSoftcap (by @Undeemiss) #[3638](https://github.com/danielyxie/bitburner/pull/3638)
- MISC: FIX #3593 Float errors can no longer prevent full usage of a server's available ram. (by @Snarling) #[3619](https://github.com/danielyxie/bitburner/pull/3619)
- MISC: fix typing conflict between jest and cypress (by @taralx) #[3518](https://github.com/danielyxie/bitburner/pull/3518)
- MISC: fix typing conflict between jest and cypress (by @taralx) #[3644](https://github.com/danielyxie/bitburner/pull/3644)
- MISC: Fixed typo in exceptionAlert.ts (by @Undeemiss) #[3572](https://github.com/danielyxie/bitburner/pull/3572)
- MISC: Fixed typos in game options (by @notacompsciguy) #[3584](https://github.com/danielyxie/bitburner/pull/3584)
- MISC: HammingCodingContracts need rework (by @Hedrauta) #[3479](https://github.com/danielyxie/bitburner/pull/3479)
- MISC: Implemented infinite loop safety net. (by @hydroflame) #[3624](https://github.com/danielyxie/bitburner/pull/3624)
- MISC: make jQuery use explicit (by @taralx) #[3517](https://github.com/danielyxie/bitburner/pull/3517)
- MISC: Make tutorial explain ns1 vs ns2 better (by @hydroflame) #[3586](https://github.com/danielyxie/bitburner/pull/3586)
- MISC: Remove comments that describe nonexistent augs (by @Undeemiss) #[3569](https://github.com/danielyxie/bitburner/pull/3569)
- MISC: update @types/numeral and fix type errors (by @taralx) #[3521](https://github.com/danielyxie/bitburner/pull/3521)
- MISC: Update logic for stats page BitNode level (by @nickofolas) #[3512](https://github.com/danielyxie/bitburner/pull/3512)
- MISC: upgrade to eslint v8 (by @taralx) #[3523](https://github.com/danielyxie/bitburner/pull/3523)
- MISC: Wrap most of the API in the new api wrapper (by @hydroflame) #[3627](https://github.com/danielyxie/bitburner/pull/3627)
- OPTIONS: Fix sliders not sliding correctly (by @nickofolas) #[3642](https://github.com/danielyxie/bitburner/pull/3642)
- REFACTOR: augmentation cost, rep cost and level to be calculated in place (by @phyzical) #[3544](https://github.com/danielyxie/bitburner/pull/3544)
- REFACTOR: augmentation isSpecial adjustments (by @phyzical) #[3564](https://github.com/danielyxie/bitburner/pull/3564)
- Reran npm format and lint to fix formatting (by @Undeemiss) #[3434](https://github.com/danielyxie/bitburner/pull/3434)
- Revert "MISC: fix typing conflict between jest and cypress" (by @hydroflame) #[3608](https://github.com/danielyxie/bitburner/pull/3608)
- Revert "MISC: HammingCodingContracts need rework" (by @hydroflame) #[3500](https://github.com/danielyxie/bitburner/pull/3500)
- revert theme (by @hydroflame) #[3451](https://github.com/danielyxie/bitburner/pull/3451)
- Singularity: Fix #3489 Disable checkTixApiAccess for purchase4SMarketData (by @DavidGrinberg) #[3490](https://github.com/danielyxie/bitburner/pull/3490)
- SLEEVES: Fix issues with Sleeve UI crashing when Sleeve task faction becomes gang faction (by @nickofolas) #[3557](https://github.com/danielyxie/bitburner/pull/3557)
- STANEK: Fix #3196 Charging booster fragments throws an error (by @Undeemiss) #[3637](https://github.com/danielyxie/bitburner/pull/3637)
- STANEK: FIX #3277 Can no longer overlap rotated fragments (by @Undeemiss) #[3460](https://github.com/danielyxie/bitburner/pull/3460)
- STANEK: FIX #3282 Added NS function stanek.acceptGift (by @Undeemiss) #[3513](https://github.com/danielyxie/bitburner/pull/3513)
- STANEK: Properly reapply entropy in Stanek's Gift (by @nickofolas) #[3673](https://github.com/danielyxie/bitburner/pull/3673)
- STANEK: Stanek NS functions correctly throw errors when stanek not installed (by @Undeemiss) #[3660](https://github.com/danielyxie/bitburner/pull/3660)
- Started collecting lore so that additions to it are simpler (by @Undeemiss) #[3465](https://github.com/danielyxie/bitburner/pull/3465)
- TERMINAL: FIX #3492 Allow cd .. even when destination directory is empty (by @Snarling) #[3525](https://github.com/danielyxie/bitburner/pull/3525)
- TERMINAL: FIX #3651 Make directory name regex more flexible (by @Dane-Horn) #[3653](https://github.com/danielyxie/bitburner/pull/3653)
- TOOLING: Add GitHub action to validate PR titles (by @MartinFournier) #[3471](https://github.com/danielyxie/bitburner/pull/3471)
- UI FIX #3485 - Allow bulk purchasing when smart supply is enabled (by @phyzical) #[3486](https://github.com/danielyxie/bitburner/pull/3486)
- UI: Change text color of Augmentations page backup button (by @nickofolas) #[3511](https://github.com/danielyxie/bitburner/pull/3511)
- UI: FIX #1754 Stanek effect summary & slight tweak. (by @borisflagell) #[3622](https://github.com/danielyxie/bitburner/pull/3622)
- UI: FIX #2228,#2958 Fix tab highlights and highlight files not on home. (by @phyzical) #[2989](https://github.com/danielyxie/bitburner/pull/2989)
- UI: FIX #2256 Hacknet server's upgrade tooltip were not handling RAM (by @borisflagell) #[3532](https://github.com/danielyxie/bitburner/pull/3532)
- UI: FIX #2741 Allow using modifier keys inside the typing infiltration (by @Dane-Horn) #[3634](https://github.com/danielyxie/bitburner/pull/3634)
- UI: FIX #2829 Remove defeated NPC gangs from territory page (by @Dane-Horn) #[3633](https://github.com/danielyxie/bitburner/pull/3633)
- UI: FIX #3313 Streamline the GraftingRoot page by making it rerender. (by @borisflagell) #[3558](https://github.com/danielyxie/bitburner/pull/3558)
- UI: FIX #3341 Enable touch-clicks in react-draggable (by @Snarling) #[3488](https://github.com/danielyxie/bitburner/pull/3488)
- UI: FIX #3415 Tweak Manage Gang button visibility (by @borisflagell) #[3528](https://github.com/danielyxie/bitburner/pull/3528)
- UI: FIX #3457 autocomplete suggestions no longer require hovering terminal input (by @Snarling) #[3493](https://github.com/danielyxie/bitburner/pull/3493)
- UI: FIX #3473 'mv' now says destination script is running instead of returning an error (by @Hoekstraa) #[3474](https://github.com/danielyxie/bitburner/pull/3474)
- UI: FIX #3522 realigned autocomplete popup (by @Snarling) #[3524](https://github.com/danielyxie/bitburner/pull/3524)
- UI: FIX #3592 Sidebar and bash shortcuts now work on MacOS with US-like layouts (by @Hoekstraa) #[3605](https://github.com/danielyxie/bitburner/pull/3605)
- UI: Fix Agility BitNode multiplier not appearing in UI (by @nickofolas) #[3662](https://github.com/danielyxie/bitburner/pull/3662)
- UI: Fix exclusive augs not always showing as purchasable through gangs when they should (by @nickofolas) #[3676](https://github.com/danielyxie/bitburner/pull/3676)
- UI: Fix the achievement covenant icon was not shown (by @Risenafis) #[3510](https://github.com/danielyxie/bitburner/pull/3510)
- UI: Fix z-index of modals overriding everything (by @nickofolas) #[3620](https://github.com/danielyxie/bitburner/pull/3620)
- UI: lightweight description update on "increase maximum money" hash spending option. (by @borisflagell) #[3547](https://github.com/danielyxie/bitburner/pull/3547)
- UI: Minor improvements to log boxes (by @nickofolas) #[3641](https://github.com/danielyxie/bitburner/pull/3641)
- UI: Overhaul GameOptions UI (by @nickofolas) #[3505](https://github.com/danielyxie/bitburner/pull/3505)
- UI: Positioning improved for tail titlebar buttons, and tail window has minimum size constraints. (by @Snarling) #[3548](https://github.com/danielyxie/bitburner/pull/3548)
- UI: Redesign purchasable Augmentations (by @nickofolas) #[3545](https://github.com/danielyxie/bitburner/pull/3545)
- UI: Refactor and redesign WorkInProgress interface (by @nickofolas) #[3611](https://github.com/danielyxie/bitburner/pull/3611)
- UI: Refactors, redesigns, and new section to stats page (by @nickofolas) #[3626](https://github.com/danielyxie/bitburner/pull/3626)
- UI: Sort and color Graft Augmentation list (by @jaype87) #[3616](https://github.com/danielyxie/bitburner/pull/3616)
- UI: Update Factions list interface (by @nickofolas) #[3675](https://github.com/danielyxie/bitburner/pull/3675)
- WORK: FIX #3435 Quitting the active job now sets first remaining job as active (by @Snarling) #[3507](https://github.com/danielyxie/bitburner/pull/3507)
- WORK: Refactor work types to use 'enum's instead of constants (by @nickofolas) #[3612](https://github.com/danielyxie/bitburner/pull/3612)
#### Other Changes
- increase donation counter (by @hydroflame) - [8456410](https://github.com/danielyxie/bitburner/commit/84564100e90c46ae4b816853c2cdea0bc309af4d)
- allbuild commit 7f9e3775 (by @hydroflame) - [791c19c](https://github.com/danielyxie/bitburner/commit/791c19c4fe447c9231bfb423b9fc48114e783b43)
- allbuild commit bcbda22a (by @hydroflame) - [032c440](https://github.com/danielyxie/bitburner/commit/032c440eaeb069eecd720ec2f8e069f705a0c1b4)
- fix documentation for getDarkwebPrograms (by @hydroflame) - [4056956](https://github.com/danielyxie/bitburner/commit/4056956c2ada37946333bdad44cb0b6eb3909bf8)
- support ASNI (by @hydroflame) - [36c7ef1](https://github.com/danielyxie/bitburner/commit/36c7ef1ad7ea8bb69fca23bce5883a3c2e23f1e0)
- allbuild commit 22b6d0d5 (by @hydroflame) - [b46718d](https://github.com/danielyxie/bitburner/commit/b46718d188880ecf716ae045861d81d61e00af4b)
- allbuild commit 36c7ef1a (by @hydroflame) - [d0ebf5e](https://github.com/danielyxie/bitburner/commit/d0ebf5e14e0498cb063fde35d63c9f59f2c01e35)
- Update documentation for employee (by @hydroflame) - [100e81c](https://github.com/danielyxie/bitburner/commit/100e81c8ab4a408f74cc9bd9ffe2b8bad3d03462)
- allbuild commit c799b291 (by @hydroflame) - [f5f5879](https://github.com/danielyxie/bitburner/commit/f5f5879fc380678d978e2b0a29ba7b6f0b4c9ec0)
- ideas (by @hydroflame) - [0121fee](https://github.com/danielyxie/bitburner/commit/0121fee6e4c690d01650d1e68a80ea363bb48bce)
- allbuild commit 0121fee6 (by @hydroflame) - [5c417e9](https://github.com/danielyxie/bitburner/commit/5c417e9b4df236df8bf3e2f8262b7bce87c934df)
- Update codebase for stanek (by @hydroflame) - [c2b4a5b](https://github.com/danielyxie/bitburner/commit/c2b4a5b52a2162d2e49c7317b0a60a349984eb47)
- fix lint (by @hydroflame) - [4cc518f](https://github.com/danielyxie/bitburner/commit/4cc518f37723aafb3168b64cd689408afdb74877)
- Fix (by @hydroflame) - [9af553f](https://github.com/danielyxie/bitburner/commit/9af553f63cb1380795550648b0134b608564fab8)
- Fix stanek leaking classes (by @hydroflame) - [fda3f02](https://github.com/danielyxie/bitburner/commit/fda3f02d73dba27034128c9be5e810a51e475e38)
- fix conflicts (by @hydroflame) - [ca1a2aa](https://github.com/danielyxie/bitburner/commit/ca1a2aad333fa838b6d0e57f89e1cedba086a4a0)
- Nerf noodle bar.
* Nerf noodle bar.
`,
};

View File

@ -159,7 +159,7 @@ export class Corporation {
if (this.unlockUpgrades[6] === 1) {
upgrades += 0.1;
}
return Math.pow(dividends, BitNodeMultipliers.CorporationSoftCap + upgrades);
return Math.pow(dividends, BitNodeMultipliers.CorporationSoftcap + upgrades);
}
determineValuation(): number {

View File

@ -162,11 +162,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
<Tooltip
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
>
<Button
color={tutorial ? "error" : "primary"}
onClick={() => setPurchaseMaterialOpen(true)}
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
>
<Button color={tutorial ? "error" : "primary"} onClick={() => setPurchaseMaterialOpen(true)}>
{purchaseButtonText}
</Button>
</Tooltip>
@ -174,6 +170,9 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
mat={mat}
warehouse={warehouse}
open={purchaseMaterialOpen}
disablePurchaseLimit={
props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)
}
onClose={() => setPurchaseMaterialOpen(false)}
/>

View File

@ -106,6 +106,7 @@ interface IProps {
onClose: () => void;
mat: Material;
warehouse: Warehouse;
disablePurchaseLimit: boolean;
}
// Create a popup that lets the player purchase a Material
@ -143,6 +144,7 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
<Typography>
Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes
constantly.
{props.disablePurchaseLimit ? "Note: Purchase amount is disabled as smart supply is enabled" : ""}
</Typography>
<TextField
value={buyAmt}
@ -150,10 +152,15 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
autoFocus={true}
placeholder="Purchase amount"
type="number"
disabled={props.disablePurchaseLimit}
onKeyDown={onKeyDown}
/>
<Button onClick={purchaseMaterial}>Confirm</Button>
<Button onClick={clearPurchase}>Clear Purchase</Button>
<Button disabled={props.disablePurchaseLimit} onClick={purchaseMaterial}>
Confirm
</Button>
<Button disabled={props.disablePurchaseLimit} onClick={clearPurchase}>
Clear Purchase
</Button>
{division.hasResearch("Bulk Purchasing") && (
<BulkPurchaseSection onClose={props.onClose} mat={props.mat} warehouse={props.warehouse} />
)}

View File

@ -68,13 +68,7 @@ export class ActiveFragment {
}
copy(): ActiveFragment {
// We have to do a round trip because the constructor.
const fragment = FragmentById(this.id);
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
const c = new ActiveFragment({ x: this.x, y: this.y, rotation: this.rotation, fragment: fragment });
c.highestCharge = this.highestCharge;
c.numCharge = this.numCharge;
return c;
return Object.assign({}, this);
}
/**

View File

@ -76,13 +76,7 @@ export class Fragment {
}
copy(): Fragment {
return new Fragment(
this.id,
this.shape.map((a) => a.slice()),
this.type,
this.power,
this.limit,
);
return Object.assign({}, this);
}
}

View File

@ -136,8 +136,9 @@ export class StaneksGift implements IStaneksGift {
}
updateMults(p: IPlayer): void {
p.reapplyAllAugmentations(true);
p.reapplyAllSourceFiles();
// applyEntropy also reapplies all augmentations and source files
// This wraps up the reset nicely
p.applyEntropy(p.entropy);
for (const aFrag of this.fragments) {
const fragment = aFrag.fragment();

View File

@ -1,5 +0,0 @@
import { StanekConstants } from "../data/Constants";
export function CalculateCharge(ram: number): number {
return ram * Math.pow(1 + Math.log2(ram) * StanekConstants.RAMBonus, 0.7);
}

View File

@ -1,9 +1,9 @@
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
export function CalculateEffect(avgCharge: number, numCharge: number, power: number, boost: number): number {
export function CalculateEffect(highestCharge: number, numCharge: number, power: number, boost: number): number {
return (
1 +
(Math.log(avgCharge + 1) / (Math.log(1.8) * 100)) *
(Math.log(highestCharge + 1) / 60) *
Math.pow((numCharge + 1) / 5, 0.07) *
power *
boost *

View File

@ -0,0 +1,88 @@
import React from "react";
import { ActiveFragment } from "../ActiveFragment";
import { IStaneksGift } from "../IStaneksGift";
import { FragmentType, Effect } from "../FragmentType";
import { numeralWrapper } from "../../ui/numeralFormat";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import Table from "@mui/material/Table";
import { TableBody, TableCell, TableRow } from "@mui/material";
type IProps = {
gift: IStaneksGift;
};
function formatEffect(effect: number, type: FragmentType): string {
if (Effect(type).includes("+x%")) {
return Effect(type).replace(/-*x%/, numeralWrapper.formatPercentage(effect - 1));
} else if (Effect(type).includes("-x%")) {
const perc = numeralWrapper.formatPercentage(1 - 1 / effect);
return Effect(type).replace(/-x%/, perc);
} else {
return Effect(type);
}
}
export function ActiveFragmentSummary(props: IProps): React.ReactElement {
const summary: { coordinate: { x: number; y: number }[]; effect: number; type: FragmentType }[] = [];
// Iterate through Active Fragment
props.gift.fragments.forEach((fragment: ActiveFragment) => {
const f = fragment.fragment();
// Discard ToolBrush and Booster.
if (![FragmentType.Booster, FragmentType.None, FragmentType.Delete].includes(f.type)) {
// Check for an existing entry in summary for this fragment's type
const entry = summary.find((e) => {
return e.type === f.type;
});
if (entry) {
// If there's one, update the existing entry
entry.effect *= props.gift.effect(fragment);
entry.coordinate.push({ x: fragment.x, y: fragment.y });
} else {
// If there's none, create a new entry
summary.push({
coordinate: [{ x: fragment.x, y: fragment.y }],
effect: props.gift.effect(fragment),
type: f.type,
});
}
}
});
return (
<Paper sx={{ mb: 1 }}>
<Typography variant="h5">Summary of active fragments:</Typography>
<Table sx={{ display: "table", width: "100%" }}>
<TableBody>
<TableRow>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>Coordinate</Typography>
</TableCell>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>Effect</Typography>
</TableCell>
</TableRow>
{summary.map((entry) => {
return (
<TableRow>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>
{entry.coordinate.map((coord) => {
return "[" + coord.x + "," + coord.y + "]";
})}
</Typography>
</TableCell>
<TableCell sx={{ borderBottom: "none", p: 0, m: 0 }}>
<Typography>{formatEffect(entry.effect, entry.type)}</Typography>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Paper>
);
}

View File

@ -25,23 +25,21 @@ export function FragmentInspector(props: IProps): React.ReactElement {
if (props.fragment === undefined) {
return (
<Paper>
<Paper sx={{ flexGrow: 1 }}>
<Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: N/A
<br />
Effect: N/A
<br />
Magnitude: N/A
Base Power: N/A
<br />
Charge: N/A
<br />
Heat: N/A
root [X, Y] N/A
<br />
Effect: N/A
<br />
[X, Y] N/A
<br />
[X, Y] {props.x}, {props.y}
</Typography>
</Paper>
);
@ -63,8 +61,11 @@ export function FragmentInspector(props: IProps): React.ReactElement {
}
return (
<Paper>
<Paper sx={{ flexGrow: 1 }}>
<Typography>
[X, Y] {props.x}, {props.y}
<br />
<br />
ID: {props.fragment.id}
<br />
Effect: {effect}
@ -73,10 +74,8 @@ export function FragmentInspector(props: IProps): React.ReactElement {
<br />
Charge: {charge}
<br />
<br />
root [X, Y] {props.fragment.x}, {props.fragment.y}
<br />
[X, Y] {props.x}, {props.y}
</Typography>
</Paper>
);

View File

@ -9,6 +9,9 @@ import Button from "@mui/material/Button";
import { Table } from "../../ui/React/Table";
import { Grid } from "./Grid";
import { zeros, calculateGrid } from "../Helper";
import { ActiveFragmentSummary } from "./ActiveFragmentSummary";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
interface IProps {
gift: IStaneksGift;
@ -84,9 +87,8 @@ export function MainBoard(props: IProps): React.ReactElement {
return (
<>
<Button onClick={clear}>Clear</Button>
<Box display="flex">
<Table>
<Box display="flex" sx={{ mb: 1 }}>
<Table sx={{ mr: 1 }}>
<Grid
width={props.gift.width()}
height={props.gift.height()}
@ -99,7 +101,22 @@ export function MainBoard(props: IProps): React.ReactElement {
</Table>
<FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} />
</Box>
<FragmentSelector gift={props.gift} selectFragment={updateSelectedFragment} />
<Box display="flex" sx={{ mb: 1 }}>
<FragmentSelector gift={props.gift} selectFragment={updateSelectedFragment} />
</Box>
<ActiveFragmentSummary gift={props.gift} />
<Tooltip
title={
<Typography>
WARNING : This will remove all active fragment from the grid. <br />
All cumulated charges will be lost.
</Typography>
}
>
<Button onClick={clear}>Clear grid</Button>
</Tooltip>
</>
);
}

View File

@ -10,6 +10,7 @@ import Typography from "@mui/material/Typography";
import { ActiveFragment } from "../ActiveFragment";
import { Fragments } from "../Fragment";
import { DummyGrid } from "./DummyGrid";
import Container from "@mui/material/Container";
type IProps = {
staneksGift: IStaneksGift;
@ -22,7 +23,7 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
}
useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
return (
<>
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">
Stanek's Gift
<Info
@ -184,18 +185,18 @@ export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
/>
</Typography>
<Typography>
<Typography sx={{ mb: 1 }}>
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
in order to become useful. The other kind of fragments are called booster fragments. They increase the
efficiency of the neighboring fragments (not diagonally). Use Q/E to rotate fragments.
</Typography>
{staneksGift.storedCycles > 5 && (
<Typography>
<Typography sx={{ mb: 1 }}>
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
</Typography>
)}
<MainBoard gift={staneksGift} />
</>
</Container>
);
}

View File

@ -3,6 +3,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
import { CrimeType } from "../utils/WorkType";
interface IConstructorParams {
hacking_success_weight?: number;
@ -42,7 +43,7 @@ export class Crime {
time = 0;
// Corresponding type in CONSTANTS. Contains a description for the crime activity
type = "";
type: CrimeType;
// Weighting factors that determine how stats affect the success rate of this crime
hacking_success_weight = 0;
@ -61,7 +62,15 @@ export class Crime {
charisma_exp = 0;
intelligence_exp = 0;
constructor(name = "", type = "", time = 0, money = 0, difficulty = 0, karma = 0, params: IConstructorParams = {}) {
constructor(
name = "",
type: CrimeType,
time = 0,
money = 0,
difficulty = 0,
karma = 0,
params: IConstructorParams = {},
) {
this.name = name;
this.type = type;
this.time = time;

View File

@ -9,7 +9,7 @@ export function determineCrimeSuccess(p: IPlayer, type: string): boolean {
let found = false;
for (const i of Object.keys(Crimes)) {
const crime = Crimes[i];
if (crime.type == type) {
if (crime.type === type) {
chance = crime.successRate(p);
found = true;
break;

View File

@ -3,8 +3,10 @@ import { Crime } from "./Crime";
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
import { CrimeType } from "../utils/WorkType";
export const Crimes: IMap<Crime> = {
Shoplift: new Crime("Shoplift", CONSTANTS.CrimeShoplift, 2e3, 15e3, 1 / 20, 0.1, {
Shoplift: new Crime("Shoplift", CrimeType.Shoplift, 2e3, 15e3, 1 / 20, 0.1, {
dexterity_success_weight: 1,
agility_success_weight: 1,
@ -12,7 +14,7 @@ export const Crimes: IMap<Crime> = {
agility_exp: 2,
}),
RobStore: new Crime("Rob Store", CONSTANTS.CrimeRobStore, 60e3, 400e3, 1 / 5, 0.5, {
RobStore: new Crime("Rob Store", CrimeType.RobStore, 60e3, 400e3, 1 / 5, 0.5, {
hacking_exp: 30,
dexterity_exp: 45,
agility_exp: 45,
@ -24,7 +26,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Mug: new Crime("Mug", CONSTANTS.CrimeMug, 4e3, 36e3, 1 / 5, 0.25, {
Mug: new Crime("Mug", CrimeType.Mug, 4e3, 36e3, 1 / 5, 0.25, {
strength_exp: 3,
defense_exp: 3,
dexterity_exp: 3,
@ -36,7 +38,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 0.5,
}),
Larceny: new Crime("Larceny", CONSTANTS.CrimeLarceny, 90e3, 800e3, 1 / 3, 1.5, {
Larceny: new Crime("Larceny", CrimeType.Larceny, 90e3, 800e3, 1 / 3, 1.5, {
hacking_exp: 45,
dexterity_exp: 60,
agility_exp: 60,
@ -48,7 +50,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
DealDrugs: new Crime("Deal Drugs", CONSTANTS.CrimeDrugs, 10e3, 120e3, 1, 0.5, {
DealDrugs: new Crime("Deal Drugs", CrimeType.Drugs, 10e3, 120e3, 1, 0.5, {
dexterity_exp: 5,
agility_exp: 5,
charisma_exp: 10,
@ -58,7 +60,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1,
}),
BondForgery: new Crime("Bond Forgery", CONSTANTS.CrimeBondForgery, 300e3, 4.5e6, 1 / 2, 0.1, {
BondForgery: new Crime("Bond Forgery", CrimeType.BondForgery, 300e3, 4.5e6, 1 / 2, 0.1, {
hacking_exp: 100,
dexterity_exp: 150,
charisma_exp: 15,
@ -69,7 +71,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
TraffickArms: new Crime("Traffick Arms", CONSTANTS.CrimeTraffickArms, 40e3, 600e3, 2, 1, {
TraffickArms: new Crime("Traffick Arms", CrimeType.TraffickArms, 40e3, 600e3, 2, 1, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
@ -83,7 +85,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1,
}),
Homicide: new Crime("Homicide", CONSTANTS.CrimeHomicide, 3e3, 45e3, 1, 3, {
Homicide: new Crime("Homicide", CrimeType.Homicide, 3e3, 45e3, 1, 3, {
strength_exp: 2,
defense_exp: 2,
dexterity_exp: 2,
@ -97,7 +99,7 @@ export const Crimes: IMap<Crime> = {
kills: 1,
}),
GrandTheftAuto: new Crime("Grand Theft Auto", CONSTANTS.CrimeGrandTheftAuto, 80e3, 1.6e6, 8, 5, {
GrandTheftAuto: new Crime("Grand Theft Auto", CrimeType.GrandTheftAuto, 80e3, 1.6e6, 8, 5, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
@ -113,7 +115,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Kidnap: new Crime("Kidnap", CONSTANTS.CrimeKidnap, 120e3, 3.6e6, 5, 6, {
Kidnap: new Crime("Kidnap", CrimeType.Kidnap, 120e3, 3.6e6, 5, 6, {
strength_exp: 80,
defense_exp: 80,
dexterity_exp: 80,
@ -128,7 +130,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Assassination: new Crime("Assassination", CONSTANTS.CrimeAssassination, 300e3, 12e6, 8, 10, {
Assassination: new Crime("Assassination", CrimeType.Assassination, 300e3, 12e6, 8, 10, {
strength_exp: 300,
defense_exp: 300,
dexterity_exp: 300,
@ -143,7 +145,7 @@ export const Crimes: IMap<Crime> = {
kills: 1,
}),
Heist: new Crime("Heist", CONSTANTS.CrimeHeist, 600e3, 120e6, 18, 15, {
Heist: new Crime("Heist", CrimeType.Heist, 600e3, 120e6, 18, 15, {
hacking_exp: 450,
strength_exp: 450,
defense_exp: 450,

View File

@ -11,11 +11,41 @@ import { exportScripts } from "./Terminal/commands/download";
import { CONSTANTS } from "./Constants";
import { hash } from "./hash/hash";
interface IReturnWebStatus extends IReturnStatus {
data?: Record<string, unknown>;
}
declare global {
interface Window {
appNotifier: {
terminal: (message: string, type?: string) => void;
toast: (message: string, type: ToastVariant, duration?: number) => void;
};
appSaveFns: {
triggerSave: () => Promise<void>;
triggerGameExport: () => void;
triggerScriptsExport: () => void;
getSaveData: () => { save: string; fileName: string };
getSaveInfo: (base64save: string) => Promise<ImportPlayerData | undefined>;
pushSaveData: (base64save: string, automatic?: boolean) => void;
};
electronBridge: {
send: (channel: string, data?: unknown) => void;
receive: (channel: string, func: (...args: any[]) => void) => void;
};
}
interface Document {
getFiles: () => IReturnWebStatus;
deleteFile: (filename: string) => IReturnWebStatus;
saveFile: (filename: string, code: string) => IReturnWebStatus;
}
}
export function initElectron(): void {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf(" electron/") > -1) {
// Electron-specific code
(document as any).achievements = [];
document.achievements = [];
initWebserver();
initAppNotifier();
initSaveFunctions();
@ -24,11 +54,6 @@ export function initElectron(): void {
}
function initWebserver(): void {
interface IReturnWebStatus extends IReturnStatus {
data?: {
[propName: string]: any;
};
}
function normalizeFileName(filename: string): string {
filename = filename.replace(/\/\/+/g, "/");
filename = removeLeadingSlash(filename);
@ -38,7 +63,7 @@ function initWebserver(): void {
return filename;
}
(document as any).getFiles = function (): IReturnWebStatus {
document.getFiles = function (): IReturnWebStatus {
const home = GetServer("home");
if (home === null) {
return {
@ -58,7 +83,7 @@ function initWebserver(): void {
};
};
(document as any).deleteFile = function (filename: string): IReturnWebStatus {
document.deleteFile = function (filename: string): IReturnWebStatus {
filename = normalizeFileName(filename);
const home = GetServer("home");
if (home === null) {
@ -70,7 +95,7 @@ function initWebserver(): void {
return home.removeFile(filename);
};
(document as any).saveFile = function (filename: string, code: string): IReturnWebStatus {
document.saveFile = function (filename: string, code: string): IReturnWebStatus {
filename = normalizeFileName(filename);
code = Buffer.from(code, "base64").toString();
@ -115,7 +140,7 @@ function initAppNotifier(): void {
};
// Will be consumud by the electron wrapper.
(window as any).appNotifier = funcs;
window.appNotifier = funcs;
}
function initSaveFunctions(): void {
@ -149,38 +174,38 @@ function initSaveFunctions(): void {
};
// Will be consumud by the electron wrapper.
(window as any).appSaveFns = funcs;
window.appSaveFns = funcs;
}
function initElectronBridge(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.receive("get-save-data-request", () => {
const data = (window as any).appSaveFns.getSaveData();
const data = window.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);
const data = await window.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);
window.appSaveFns.pushSaveData(save, automatic);
});
bridge.receive("trigger-save", () => {
return (window as any).appSaveFns
return window.appSaveFns
.triggerSave()
.then(() => {
bridge.send("save-completed");
})
.catch((error: any) => {
.catch((error: unknown) => {
console.log(error);
SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000);
});
});
bridge.receive("trigger-game-export", () => {
try {
(window as any).appSaveFns.triggerGameExport();
window.appSaveFns.triggerGameExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
@ -188,7 +213,7 @@ function initElectronBridge(): void {
});
bridge.receive("trigger-scripts-export", () => {
try {
(window as any).appSaveFns.triggerScriptsExport();
window.appSaveFns.triggerScriptsExport();
} catch (error) {
console.log(error);
SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000);
@ -197,14 +222,14 @@ function initElectronBridge(): void {
}
export function pushGameSaved(data: SaveData): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-game-saved", data);
}
export function pushGameReady(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
// Send basic information to the electron wrapper
@ -222,7 +247,7 @@ export function pushGameReady(): void {
}
export function pushImportResult(wasImported: boolean): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-import-result", { wasImported });
@ -230,7 +255,7 @@ export function pushImportResult(wasImported: boolean): void {
}
export function pushDisableRestore(): void {
const bridge = (window as any).electronBridge as any;
const bridge = window.electronBridge;
if (!bridge) return;
bridge.send("push-disable-restore", { duration: 1000 * 60 });

View File

@ -1,4 +1,4 @@
import { Augmentations } from "../Augmentation/Augmentations";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
@ -18,7 +18,6 @@ import {
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal";
import { FactionNames } from "./data/FactionNames";
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
import { SFC32RNG } from "../Casino/RNG";
export function inviteToFaction(faction: Faction): void {
@ -54,63 +53,41 @@ export function joinFaction(faction: Faction): void {
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
export function hasAugmentationPrereqs(aug: Augmentation): boolean {
let hasPrereqs = true;
if (aug.prereqs && aug.prereqs.length > 0) {
for (let i = 0; i < aug.prereqs.length; ++i) {
const prereqAug = Augmentations[aug.prereqs[i]];
if (prereqAug == null) {
console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`);
continue;
}
if (Player.hasAugmentation(prereqAug, true) === false) {
hasPrereqs = false;
// Check if the aug is purchased
for (let j = 0; j < Player.queuedAugmentations.length; ++j) {
if (Player.queuedAugmentations[j].name === prereqAug.name) {
hasPrereqs = true;
break;
}
}
}
}
}
return hasPrereqs;
return aug.prereqs.every((aug) => Player.hasAugmentation(aug));
}
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
const hasPrereqs = hasAugmentationPrereqs(aug);
const augCosts = aug.getCost(Player);
if (!hasPrereqs) {
const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`;
const txt = `You must first purchase or install ${aug.prereqs
.filter((req) => !Player.hasAugmentation(req))
.join(",")} before you can purchase this one.`;
if (sing) {
return txt;
} else {
dialogBoxCreate(txt);
}
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost) {
} else if (augCosts.moneyCost !== 0 && Player.money < augCosts.moneyCost) {
const txt = "You don't have enough money to purchase " + aug.name;
if (sing) {
return txt;
}
dialogBoxCreate(txt);
} else if (fac.playerReputation < aug.baseRepRequirement) {
} else if (fac.playerReputation < augCosts.repCost) {
const txt = "You don't have enough faction reputation to purchase " + aug.name;
if (sing) {
return txt;
}
dialogBoxCreate(txt);
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
} else if (augCosts.moneyCost === 0 || Player.money >= augCosts.moneyCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeuroFluxLevel();
queuedAugmentation.level = aug.getLevel(Player);
}
Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost, "augmentations");
updateAugmentationCosts();
Player.loseMoney(augCosts.moneyCost, "augmentations");
if (sing) {
return "You purchased " + aug.name;
@ -162,16 +139,14 @@ export function processPassiveFactionRepGain(numCycles: number): void {
export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Faction): string[] => {
// If player has a gang with this faction, return (almost) all augmentations
if (player.hasGangWith(faction.name)) {
let augs = Object.values(Augmentations);
let augs = Object.values(StaticAugmentations);
// Remove special augs
augs = augs.filter((a) => !a.isSpecial);
augs = augs.filter((a) => !a.isSpecial && a.name !== AugmentationNames.CongruityImplant);
const blacklist: string[] = [AugmentationNames.NeuroFluxGovernor, AugmentationNames.CongruityImplant];
if (player.bitNodeN !== 2) {
if (player.bitNodeN === 2) {
// TRP is not available outside of BN2 for Gangs
blacklist.push(AugmentationNames.TheRedPill);
augs.push(StaticAugmentations[AugmentationNames.TheRedPill]);
}
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
@ -190,9 +165,6 @@ export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Factio
};
augs = augs.filter(uniqueFilter);
// Remove blacklisted augs
augs = augs.filter((a) => !blacklist.includes(a.name));
return augs.map((a) => a.name);
}

View File

@ -1,30 +1,22 @@
/**
* Root React Component for displaying a faction's "Purchase Augmentations" page
*/
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
import React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { hasAugmentationPrereqs, getFactionAugmentationsFiltered } from "../FactionHelpers";
import { use } from "../../ui/Context";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { numeralWrapper } from "../../ui/numeralFormat";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import TableBody from "@mui/material/TableBody";
import Table from "@mui/material/Table";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { Favor } from "../../ui/React/Favor";
import { Reputation } from "../../ui/React/Reputation";
import { FactionNames } from "../data/FactionNames";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
type IProps = {
faction: Faction;
@ -63,13 +55,13 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByCost(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
const aug1 = StaticAugmentations[augName1],
aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
});
return augs;
@ -78,31 +70,32 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByPurchasable(): string[] {
const augs = getAugs();
function canBuy(augName: string): boolean {
const aug = Augmentations[augName];
const repCost = aug.baseRepRequirement;
const aug = StaticAugmentations[augName];
const augCosts = aug.getCost(player);
const repCost = augCosts.repCost;
const hasReq = props.faction.playerReputation >= repCost;
const hasRep = hasAugmentationPrereqs(aug);
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost;
const hasCost = augCosts.moneyCost !== 0 && player.money > augCosts.moneyCost;
return hasCost && hasReq && hasRep;
}
const buy = augs.filter(canBuy).sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
const aug1 = StaticAugmentations[augName1],
aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseCost - aug2.baseCost;
return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
});
const cantBuy = augs
.filter((aug) => !canBuy(aug))
.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
const aug1 = StaticAugmentations[augName1],
aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
});
return buy.concat(cantBuy);
@ -111,12 +104,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
function getAugsSortedByReputation(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
const aug1 = StaticAugmentations[augName1],
aug2 = StaticAugmentations[augName2];
if (aug1 == null || aug2 == null) {
throw new Error("Invalid Augmentation Names");
}
return aug1.baseRepRequirement - aug2.baseRepRequirement;
return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
});
return augs;
@ -137,38 +130,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
);
const purchaseableAugmentation = (aug: string, owned = false): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={props.faction}
key={aug}
p={player}
rerender={rerender}
owned={owned}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
<Typography variant="h4">Purchased Augmentations</Typography>
<Typography>This faction also offers these augmentations but you already own them.</Typography>
{owned.map((aug) => purchaseableAugmentation(aug, true))}
</>
);
}
const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
<b>Price multiplier:</b> x {numeralWrapper.formatReallyBigNumber(getGenericAugmentationPriceMultiplier())}
</Typography>
) : (
<></>
@ -176,42 +143,78 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
return (
<>
<Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations</Typography>
<Typography>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities.
<br />
Reputation: <Reputation reputation={props.faction.playerReputation} /> Favor:{" "}
<Favor favor={Math.floor(props.faction.favor)} />
</Typography>
<Box display="flex">
<Tooltip
title={
<Container disableGutters maxWidth="lg" sx={{ mx: 0 }}>
<Button onClick={props.routeToMainPage}>Back</Button>
<Typography variant="h4">Faction Augmentations - {props.faction.name}</Typography>
<Paper sx={{ p: 1, mb: 1 }}>
<Typography>
These are all of the Augmentations that are available to purchase from <b>{props.faction.name}</b>.
Augmentations are powerful upgrades that will enhance your abilities.
<br />
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${props.faction.name === FactionNames.ShadowsOfAnarchy ? "2" : "3"}, 1fr)`,
justifyItems: "center",
my: 1,
}}
>
<Tooltip
title={
<Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you
install them.
</Typography>
}
>
{multiplierComponent}
</Tooltip>
<Typography>
The price of every Augmentation increases for every queued Augmentation and it is reset when you install
them.
<b>Reputation:</b> <Reputation reputation={props.faction.playerReputation} />
</Typography>
<Typography>
<b>Favor:</b> <Favor favor={Math.floor(props.faction.favor)} />
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }}>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>
Sort by Reputation
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable
</Button>
</Box>
</Paper>
</Container>
<PurchasableAugmentations
augNames={purchasable}
ownedAugNames={owned}
player={player}
canPurchase={(player, aug) => {
const costs = aug.getCost(player);
return (
hasAugmentationPrereqs(aug) &&
props.faction.playerReputation >= costs.repCost &&
(costs.moneyCost === 0 || player.money > costs.moneyCost)
);
}}
purchaseAugmentation={(player, aug, showModal) => {
if (!Settings.SuppressBuyAugmentationConfirmation) {
showModal(true);
} else {
purchaseAugmentation(aug, props.faction);
rerender();
}
>
{multiplierComponent}
</Tooltip>
</Box>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}>Sort by Reputation</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>Sort by Default Order</Button>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Purchasable)}>
Sort by Purchasable
</Button>
<br />
<Table size="small" padding="none">
<TableBody>{augListElems}</TableBody>
</Table>
<Table size="small" padding="none">
<TableBody>{ownedElem}</TableBody>
</Table>
}}
rep={props.faction.playerReputation}
faction={props.faction}
/>
</>
);
}

View File

@ -16,12 +16,10 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../Faction";
import { use } from "../../ui/Context";
import { CreateGangModal } from "./CreateGangModal";
import { Box, Paper, Typography, Button, Tooltip } from "@mui/material";
import { Typography, Button } from "@mui/material";
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
import { FactionNames } from "../data/FactionNames";
import { GangConstants } from "../../Gang/data/Constants";
import { GangButton } from "./GangButton";
type IProps = {
@ -30,7 +28,6 @@ type IProps = {
};
// Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and faction reputation";
const hackingContractsInfo =
"Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +

View File

@ -1,170 +0,0 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { Augmentation as AugFormat } from "../../ui/React/Augmentation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers";
interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (!props.hasReq) {
return (
<TableCell key={1} colSpan={2}>
<Typography color="error">
Requires{" "}
{aug.prereqs.map((aug, i) => (
<AugFormat key={i} name={aug} />
))}
</Typography>
</TableCell>
);
}
return (
<React.Fragment key="f">
<TableCell key={1}>
<Typography>
<Money money={props.cost} player={props.p} />
</Typography>
</TableCell>
<TableCell key={2}>
<Typography color={props.hasRep ? "primary" : "error"}>
Requires <Reputation reputation={props.rep} /> faction reputation
</Typography>
</TableCell>
</React.Fragment>
);
}
interface IProps {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
return <></>;
}
const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeuroFluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
function handleClick(): void {
if (color === "error") return;
if (!Settings.SuppressBuyAugmentationConfirmation) {
setOpen(true);
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
return (
<TableRow>
{!props.owned && (
<TableCell key={0}>
<Button onClick={handleClick} color={color}>
Buy
</Button>
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
aug={aug}
faction={props.faction}
rerender={props.rerender}
/>
</TableCell>
)}
<TableCell key={1}>
<Box display="flex">
<Tooltip title={<Typography>{tooltip}</Typography>} placement="top">
<Typography>{btnTxt}</Typography>
</Tooltip>
</Box>
</TableCell>
{!props.owned && (
<Requirements
key={2}
augName={props.augName}
p={props.p}
cost={moneyCost}
rep={repCost}
hasReq={hasReq}
hasRep={hasRep}
hasCost={hasCost}
/>
)}
</TableRow>
);
}

View File

@ -70,7 +70,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
<>
<OptionsSlider
label=".script exec time (ms)"
value={execTime}
initialValue={execTime}
callback={handleExecTimeChange}
step={1}
min={5}
@ -84,7 +84,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Recently killed scripts size"
value={recentScriptsSize}
initialValue={recentScriptsSize}
callback={handleRecentScriptsSizeChange}
step={25}
min={0}
@ -98,7 +98,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Netscript log size"
value={logSize}
initialValue={logSize}
callback={handleLogSizeChange}
step={20}
min={20}
@ -112,7 +112,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Netscript port size"
value={portSize}
initialValue={portSize}
callback={handlePortSizeChange}
step={1}
min={20}
@ -126,7 +126,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Terminal capacity"
value={terminalSize}
initialValue={terminalSize}
callback={handleTerminalSizeChange}
step={50}
min={50}
@ -141,7 +141,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
/>
<OptionsSlider
label="Autosave interval (s)"
value={autosaveInterval}
initialValue={autosaveInterval}
callback={handleAutosaveIntervalChange}
step={30}
min={0}
@ -179,6 +179,12 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
</>
}
/>
<OptionSwitch
checked={Settings.InfinityLoopSafety}
onChange={(newValue) => (Settings.InfinityLoopSafety = newValue)}
text="Script infinite loop safety net"
tooltip={<>If this is set the game will attempt to automatically kill scripts stuck in infinite loops.</>}
/>
</GameOptionsPage>
),
[GameOptionsTab.INTERFACE]: (

View File

@ -189,9 +189,9 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
title={
<Typography>
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game.
After using this, save the game and then reload the page. This is different then normal kill in that
After using this, save the game and then reload the page. This is different than normal kill in that
normal kill will tell the script to shut down while force kill just removes the references to it (and it
should crash on it's own). This will not remove the files on your computer. Just forcefully kill all
should crash on its own). This will not remove the files on your computer, just forcefully kill all
running instances of all scripts.
</Typography>
}
@ -210,7 +210,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
title={
<Typography>
If your save file is extremely big you can use this button to view a map of all the files on every server.
Be careful there might be spoilers.
Be careful: there might be spoilers.
</Typography>
}
>

View File

@ -1,8 +1,8 @@
import { Slider, Tooltip, Typography, Box } from "@mui/material";
import React from "react";
import React, { useState } from "react";
interface IProps {
value: any;
initialValue: any;
callback: (event: any, newValue: number | number[]) => void;
step: number;
min: number;
@ -13,14 +13,21 @@ interface IProps {
}
export const OptionsSlider = (props: IProps): React.ReactElement => {
const [value, setValue] = useState(props.initialValue);
const onChange = (_evt: Event, newValue: number | Array<number>): void => {
setValue(newValue);
};
return (
<Box>
<Tooltip title={<Typography>{props.tooltip}</Typography>}>
<Typography>{props.label}</Typography>
</Tooltip>
<Slider
value={props.value}
onChange={props.callback}
value={value}
onChange={onChange}
onChangeCommitted={props.callback}
step={props.step}
min={props.min}
max={props.max}

View File

@ -85,9 +85,15 @@ export function TerritorySubpage(): React.ReactElement {
</Typography>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }}>
{gangNames.map((name) => (
<OtherGangTerritory key={name} name={name} />
))}
{gangNames
.sort((a, b) => {
if (AllGangs[a].territory <= 0 && AllGangs[b].territory > 0) return 1;
if (AllGangs[a].territory > 0 && AllGangs[b].territory <= 0) return -1;
return 0;
})
.map((name) => (
<OtherGangTerritory key={name} name={name} />
))}
</Box>
<TerritoryInfoModal open={infoOpen} onClose={() => setInfoOpen(false)} />
</Container>
@ -114,14 +120,16 @@ function OtherGangTerritory(props: ITerritoryProps): React.ReactElement {
const playerPower = AllGangs[gang.facName].power;
const power = AllGangs[props.name].power;
const clashVictoryChance = playerPower / (power + playerPower);
const territory = AllGangs[props.name].territory;
const opacity = territory ? 1 : 0.75;
return (
<Box component={Paper} sx={{ p: 1 }}>
<Box component={Paper} sx={{ p: 1, opacity }}>
<Typography variant="h6" sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
{props.name}
</Typography>
<Typography>
<b>Power:</b> {formatNumber(power, 3)} <br />
<b>Territory:</b> {formatTerritory(AllGangs[props.name].territory)}% <br />
<b>Territory:</b> {formatTerritory(territory)}% <br />
<b>Clash Win Chance:</b> {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
</Typography>
</Box>

View File

@ -60,15 +60,22 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const increase =
const base_increase =
calculateHashGainRate(node.level + multiplier, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult) -
node.hashRate;
calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
const modded_increase = (base_increase * (node.maxRam - node.ramUsed)) / node.maxRam;
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);
upgradeLevelButton = (
<Tooltip
title={
<Typography>
+<HashRate hashes={increase} />
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
<br />
<span style={{ opacity: 0.5 }}>
+<HashRate hashes={base_increase} />
</span>{" "}
(base increase, attained when no script is running)
</Typography>
}
>
@ -109,20 +116,36 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const increase =
const base_increase =
calculateHashGainRate(
node.level,
0,
node.maxRam * Math.pow(2, multiplier),
node.cores,
props.player.hacknet_node_money_mult,
) - node.hashRate;
) - calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
const modded_increase =
calculateHashGainRate(
node.level,
node.ramUsed,
node.maxRam * Math.pow(2, multiplier),
node.cores,
props.player.hacknet_node_money_mult,
) -
calculateHashGainRate(node.level, node.ramUsed, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);
upgradeRamButton = (
<Tooltip
title={
<Typography>
+<HashRate hashes={increase} />
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
<br />
<span style={{ opacity: 0.5 }}>
+<HashRate hashes={base_increase} />
</span>{" "}
(base increase, attained when no script is running)
</Typography>
}
>
@ -155,15 +178,22 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const increase =
const base_increase =
calculateHashGainRate(node.level, 0, node.maxRam, node.cores + multiplier, props.player.hacknet_node_money_mult) -
node.hashRate;
calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
const modded_increase = (base_increase * (node.maxRam - node.ramUsed)) / node.maxRam;
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);
upgradeCoresButton = (
<Tooltip
title={
<Typography>
+<HashRate hashes={increase} />
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
<br />
<span style={{ opacity: 0.5 }}>
+<HashRate hashes={base_increase} />
</span>{" "}
(base increase, attained when no script is running)
</Typography>
}
>
@ -232,9 +262,31 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
<Typography>Production:</Typography>
</TableCell>
<TableCell colSpan={2}>
<Typography>
<Hashes hashes={node.totalHashesGenerated} /> (<HashRate hashes={node.hashRate} />)
</Typography>
<Tooltip
title={
<Typography>
<Hashes hashes={node.totalHashesGenerated} /> hashes produced by this server since last augment
installation.
<br />
<HashRate hashes={node.hashRate} /> current production rate.
<br />
<span style={{ opacity: 0.5 }}>
<HashRate hashes={(node.hashRate * node.maxRam) / (node.maxRam - node.ramUsed)} />
</span>{" "}
max production rate. (achieved when 100% RAM is allocated to it)
<br />
{numeralWrapper.formatRAM(node.ramUsed)} / {numeralWrapper.formatRAM(node.maxRam)} (
{Math.round((100 * node.ramUsed) / node.maxRam)}%) RAM allocated to script.
<br />
{numeralWrapper.formatRAM(node.maxRam - node.ramUsed)} / {numeralWrapper.formatRAM(node.maxRam)} (
{Math.round((100 * (node.maxRam - node.ramUsed)) / node.maxRam)}%) RAM allocated to hash production.
</Typography>
}
>
<Typography>
<Hashes hashes={node.totalHashesGenerated} /> (<HashRate hashes={node.hashRate} />)
</Typography>
</Tooltip>
</TableCell>
</TableRow>
<TableRow>

View File

@ -31,12 +31,12 @@ export function calculateTradeInformationRepReward(
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
Math.pow(reward + 1, 1.1) *
Math.pow(difficulty, 1.2) *
30 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer, true) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
BitNodeMultipliers.InfiltrationRep
);
}

View File

@ -1,15 +1,14 @@
import { Paper, Typography } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { KEY } from "../../utils/helpers/keyCodes";
import { random } from "../utils";
import { BlinkingCursor } from "./BlinkingCursor";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty {
[key: string]: number;
@ -38,9 +37,13 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function ignorableKeyboardEvent(event: KeyboardEvent): boolean {
return event.key === KEY.BACKSPACE || (event.shiftKey && event.key === "Shift") || event.ctrlKey || event.altKey;
}
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
if (event.key === KEY.BACKSPACE) return;
if (ignorableKeyboardEvent(event)) return;
const nextGuess = guess + event.key.toUpperCase();
if (!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess();
@ -48,24 +51,18 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
}
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
<Typography variant="h4">Type it{!hasAugment ? " backward" : ""}</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)", marginLeft: hasAugment ? "50%" : "none" }}>
{answer}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)" }}>{answer}</Typography>
<Typography>
{guess}
<BlinkingCursor />
</Typography>
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
export function BlinkingCursor(): React.ReactElement {
const [on, setOn] = useState(true);
@ -6,5 +6,5 @@ export function BlinkingCursor(): React.ReactElement {
const i = setInterval(() => setOn((old) => !old), 1000);
return () => clearInterval(i);
});
return <>{on ? "|" : ""}</>;
return <>{on ? "|" : <>&nbsp;</>}</>;
}

View File

@ -1,15 +1,14 @@
import { Paper, Typography } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { KEY } from "../../utils/helpers/keyCodes";
import { random } from "../utils";
import { BlinkingCursor } from "./BlinkingCursor";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { KEY } from "../../utils/helpers/keyCodes";
interface Difficulty {
[key: string]: number;
@ -84,16 +83,16 @@ export function BracketGame(props: IMinigameProps): React.ReactElement {
}
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center" }}>
<Typography variant="h4">Close the brackets</Typography>
<Typography style={{ fontSize: "5em" }}>
{`${left}${right}`}
<BlinkingCursor />
</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,15 +1,14 @@
import { Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { KEY } from "../../utils/helpers/keyCodes";
import { downArrowSymbol, upArrowSymbol } from "../utils";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
interface Difficulty {
[key: string]: number;
@ -88,13 +87,11 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
}
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Say something nice about the guard.</Typography>
<Paper sx={{ display: "grid", justifyItems: "center" }}>
<Typography variant="h4">Say something nice about the guard</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
<Grid item xs={6}>
<Typography variant="h5" color={upColor}>
{upArrowSymbol}
</Typography>
@ -104,8 +101,8 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
<Typography variant="h5" color={downColor}>
{downArrowSymbol}
</Typography>
</Grid>
</Grid>
</Paper>
</>
);
}
@ -154,6 +151,7 @@ const positive = [
"patient",
"dynamic",
"loyal",
"based",
];
const negative = [
@ -177,4 +175,5 @@ const negative = [
"picky",
"tactless",
"thoughtless",
"cringe",
];

View File

@ -1,21 +1,20 @@
import { Paper, Typography } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import {
random,
downArrowSymbol,
getArrow,
getInverseArrow,
leftArrowSymbol,
random,
rightArrowSymbol,
upArrowSymbol,
downArrowSymbol,
} from "../utils";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
interface Difficulty {
[key: string]: number;
@ -55,14 +54,14 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
}
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center" }}>
<Typography variant="h4">Enter the Code!</Typography>
<Typography variant="h4">{code[index]}</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react";
import Grid from "@mui/material/Grid";
import { Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import Typography from "@mui/material/Typography";
interface IProps {
onFinish: () => void;
}
@ -13,17 +12,13 @@ export function Countdown(props: IProps): React.ReactElement {
props.onFinish();
return;
}
setTimeout(() => setX(x - 1), 200);
setTimeout(() => setX(x - 1), 300);
});
return (
<>
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h4">Get Ready!</Typography>
<Typography variant="h4">{x}</Typography>
</Grid>
</Grid>
</>
<Paper sx={{ p: 1, textAlign: "center" }}>
<Typography variant="h4">Get Ready!</Typography>
<Typography variant="h4">{x}</Typography>
</Paper>
);
}

View File

@ -1,15 +1,14 @@
import { Paper, Typography, Box } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Settings } from "../../Settings/Settings";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { KEY } from "../../utils/helpers/keyCodes";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
interface Difficulty {
[key: string]: number;
@ -19,6 +18,12 @@ interface Difficulty {
symbols: number;
}
interface GridItem {
content: string;
color: string;
selected?: boolean;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
@ -76,18 +81,33 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
}
}
const flatGrid: GridItem[] = [];
grid.map((line, y) =>
line.map((cell, x) => {
const isCorrectAnswer = cell === answers[currentAnswerIndex];
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
if (x === pos[0] && y === pos[1]) {
flatGrid.push({ color: optionColor, content: cell, selected: true });
return;
}
flatGrid.push({ color: optionColor, content: cell });
}),
);
const fontSize = "2em";
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
<Typography variant="h4">Match the symbols!</Typography>
<Typography variant="h5" color={Settings.theme.primary}>
Targets:{" "}
{answers.map((a, i) => {
if (i == currentAnswerIndex)
return (
<span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}>
<span key={`${i}`} style={{ fontSize: "1em", color: Settings.theme.infolight }}>
{a}&nbsp;
</span>
);
@ -99,34 +119,30 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
})}
</Typography>
<br />
{grid.map((line, y) => (
<div key={y}>
<Typography>
{line.map((cell, x) => {
const isCorrectAnswer = cell === answers[currentAnswerIndex];
if (x == pos[0] && y == pos[1]) {
return (
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: "blue" }}>
{cell}&nbsp;
</span>
);
}
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: optionColor }}>
{cell}&nbsp;
</span>
);
})}
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${Math.round(difficulty.width)}, 1fr)`,
gap: 1,
}}
>
{flatGrid.map((item) => (
<Typography
sx={{
fontSize: fontSize,
color: item.color,
border: item.selected ? `2px solid ${Settings.theme.infolight}` : "unset",
lineHeight: "unset",
p: item.selected ? "2px" : "4px",
}}
>
{item.content}
</Typography>
<br />
</div>
))}
))}
</Box>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,19 +1,17 @@
import { use } from "../../ui/Context";
import { Button, Container, Paper, Typography } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button";
import { Countdown } from "./Countdown";
import { BracketGame } from "./BracketGame";
import { SlashGame } from "./SlashGame";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { use } from "../../ui/Context";
import { BackwardGame } from "./BackwardGame";
import { BracketGame } from "./BracketGame";
import { BribeGame } from "./BribeGame";
import { CheatCodeGame } from "./CheatCodeGame";
import { Countdown } from "./Countdown";
import { Cyberpunk2077Game } from "./Cyberpunk2077Game";
import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame";
import { SlashGame } from "./SlashGame";
import { Victory } from "./Victory";
import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { WireCuttingGame } from "./WireCuttingGame";
interface IProps {
StartingDifficulty: number;
@ -139,22 +137,20 @@ export function Game(props: IProps): React.ReactElement {
}
return (
<>
<Grid container spacing={3}>
<Grid item xs={3}>
<Button onClick={cancel}>Cancel</Button>
</Grid>
<Grid item xs={3}>
<Typography>
Level: {level}&nbsp;/&nbsp;{props.MaxLevel}
</Typography>
<Progress />
</Grid>
<Container>
<Paper sx={{ p: 1, mb: 1, display: "grid", justifyItems: "center", gap: 1 }}>
{stage !== Stage.Sell && (
<Button sx={{ width: "100%" }} onClick={cancel}>
Cancel Infiltration
</Button>
)}
<Typography variant="h5">
Level {level} / {props.MaxLevel}
</Typography>
<Progress />
</Paper>
<Grid item xs={12}>
{stageComponent}
</Grid>
</Grid>
</>
{stageComponent}
</Container>
);
}

View File

@ -1,24 +1,13 @@
import LinearProgress from "@mui/material/LinearProgress";
import React, { useState, useEffect } from "react";
import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles";
import Grid from "@mui/material/Grid";
import { use } from "../../ui/Context";
import { Paper } from "@mui/material";
import React, { useEffect, useState } from "react";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
const TimerProgress = withStyles((theme: Theme) => ({
root: {
backgroundColor: theme.palette.background.paper,
},
bar: {
transition: "none",
backgroundColor: theme.palette.primary.main,
},
}))(LinearProgress);
import { use } from "../../ui/Context";
import { ProgressBar } from "../../ui/React/Progress";
interface IProps {
millis: number;
onExpire: () => void;
noPaper?: boolean;
}
export function GameTimer(props: IProps): React.ReactElement {
@ -42,9 +31,11 @@ export function GameTimer(props: IProps): React.ReactElement {
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
// TODO(hydroflame): there's like a bug where it triggers the end before the
// bar physically reaches the end
return (
<Grid item xs={12}>
<TimerProgress variant="determinate" value={v} color="primary" />
</Grid>
return props.noPaper ? (
<ProgressBar variant="determinate" value={v} color="primary" />
) : (
<Paper sx={{ p: 1, mb: 1 }}>
<ProgressBar variant="determinate" value={v} color="primary" />
</Paper>
);
}

View File

@ -1,9 +1,9 @@
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
import { Location } from "../../Locations/Location";
import { use } from "../../ui/Context";
import { calculateDifficulty, calculateReward } from "../formulas/game";
import { Game } from "./Game";
import { Intro } from "./Intro";
interface IProps {
location: Location;
}
@ -22,24 +22,24 @@ export function InfiltrationRoot(props: IProps): React.ReactElement {
router.toCity();
}
if (!start) {
return (
<Intro
Location={props.location}
Difficulty={difficulty}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
StartingDifficulty={startingSecurityLevel}
Difficulty={difficulty}
Reward={reward}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
/>
<div style={{ display: "flex", alignItems: "center", height: "calc(100vh - 16px)" }}>
{start ? (
<Game
StartingDifficulty={startingSecurityLevel}
Difficulty={difficulty}
Reward={reward}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
/>
) : (
<Intro
Location={props.location}
Difficulty={difficulty}
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
start={() => setStart(true)}
cancel={cancel}
/>
)}
</div>
);
}

View File

@ -1,8 +1,8 @@
import { Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React from "react";
import { Location } from "../../Locations/Location";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
interface IProps {
@ -41,9 +41,9 @@ function coloredArrow(difficulty: number): JSX.Element {
} else {
return (
<>
{arrowPart("white", difficulty * 13)}
{arrowPart("orange", (difficulty - 1) * 13)}
{arrowPart("red", (difficulty - 2) * 13)}
{arrowPart(Settings.theme.primary, difficulty * 13)}
{arrowPart(Settings.theme.warning, (difficulty - 1) * 13)}
{arrowPart(Settings.theme.error, (difficulty - 2) * 13)}
</>
);
}
@ -51,65 +51,84 @@ function coloredArrow(difficulty: number): JSX.Element {
export function Intro(props: IProps): React.ReactElement {
return (
<>
<Grid container spacing={3}>
<Grid item xs={10}>
<Typography variant="h4">Infiltrating {props.Location.name}</Typography>
</Grid>
<Grid item xs={10}>
<Typography variant="h5" color="primary">
Maximum level: {props.MaxLevel}
</Typography>
</Grid>
<Grid item xs={10}>
<Typography variant="h5" color="primary">
Difficulty: {numeralWrapper.format(props.Difficulty * 33.3333, "0")} / 100
</Typography>
</Grid>
<Container sx={{ alignItems: "center" }}>
<Paper sx={{ p: 1, mb: 1, display: "grid", justifyItems: "center" }}>
<Typography variant="h4">
Infiltrating <b>{props.Location.name}</b>
</Typography>
<Typography variant="h6">
<b>Maximum Level: </b>
{props.MaxLevel}
</Typography>
<Typography
variant="h6"
sx={{
color:
props.Difficulty > 2
? Settings.theme.error
: props.Difficulty > 1
? Settings.theme.warning
: Settings.theme.primary,
display: "flex",
alignItems: "center",
}}
>
<b>Difficulty:&nbsp;</b>
{numeralWrapper.format(props.Difficulty * 33.3333, "0")} / 100
{props.Difficulty > 1.5 && (
<Tooltip
title={
<Typography color="error">
This location is too heavily guarded for your current stats. It is recommended that you try training,
or finding an easier location.
</Typography>
}
>
<Report sx={{ ml: 1 }} />
</Tooltip>
)}
</Typography>
{props.Difficulty > 1.5 && (
<Grid item xs={10}>
<Typography variant="h5" color="primary">
Warning: This location is too heavily guarded for your current stats, try training or finding an easier
location.
</Typography>
</Grid>
)}
<Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>[{coloredArrow(props.Difficulty)}]</Typography>
<Typography
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
>{`▲ ▲ ▲ ▲`}</Typography>
<Typography
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
>{` Trivial Normal Hard Impossible`}</Typography>
</Paper>
<Grid item xs={10}>
<Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>[{coloredArrow(props.Difficulty)}]</Typography>
<Typography
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
>{` ^ ^ ^ ^`}</Typography>
<Typography
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
>{` Trivial Normal Hard Impossible`}</Typography>
</Grid>
<Grid item xs={10}>
<Paper sx={{ p: 1, display: "grid", justifyItems: "center" }}>
<Typography sx={{ width: "75%", textAlign: "center" }}>
<b>Infiltration</b> is a series of short minigames that get progressively harder. You take damage for failing
them. Reaching the maximum level rewards you with intel that you can trade for money or reputation.
<br />
<br />
<b>Gameplay:</b>
</Typography>
<ul>
<Typography>
Infiltration is a series of short minigames that get progressively harder. You take damage for failing them.
Reaching the maximum level rewards you with intel you can trade for money or reputation.
<li>
The minigames you play are randomly selected.
<br />
It might take you a few tries to get used to them.
</li>
<li>No game requires use of the mouse.</li>
<li>
<b>Spacebar</b> is the default action/confirm button.
</li>
<li>
The <b>arrow keys</b> and <b>WASD</b> can be used interchangeably.
</li>
<li>Sometimes the rest of the keyboard is used.</li>
</Typography>
<br />
<Typography>
The minigames you play are randomly selected. It might take you few tries to get used to them.
</Typography>
<br />
<Typography>No game require use of the mouse.</Typography>
<br />
<Typography>Spacebar is the default action/confirm button.</Typography>
<br />
<Typography>Everything that uses arrow can also use WASD</Typography>
<br />
<Typography>Sometimes the rest of the keyboard is used.</Typography>
</Grid>
<Grid item xs={3}>
</ul>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", width: "100%" }}>
<Button onClick={props.start}>Start</Button>
</Grid>
<Grid item xs={3}>
<Button onClick={props.cancel}>Cancel</Button>
</Grid>
</Grid>
</>
</Box>
</Paper>
</Container>
);
}

View File

@ -1,14 +1,16 @@
import React, { useState, useEffect } from "react";
import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Close, Flag, Report } from "@mui/icons-material";
import { Box, Paper, Typography } from "@mui/material";
import { uniqueId } from "lodash";
import React, { useEffect, useState } from "react";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { KEY } from "../../utils/helpers/keyCodes";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
interface Difficulty {
[key: string]: number;
@ -81,32 +83,77 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
return () => clearInterval(id);
}, []);
const flatGrid: { flagged?: boolean; current?: boolean; marked?: boolean }[] = [];
minefield.map((line, y) =>
line.map((cell, x) => {
if (memoryPhase) {
flatGrid.push({ flagged: Boolean(minefield[y][x]) });
return;
} else if (x === pos[0] && y === pos[1]) {
flatGrid.push({ current: true });
} else if (answer[y][x]) {
flatGrid.push({ marked: true });
} else if (hasAugment && minefield[y][x]) {
flatGrid.push({ flagged: true });
} else {
flatGrid.push({});
}
}),
);
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
<Typography variant="h4">{memoryPhase ? "Remember all the mines!" : "Mark all the mines!"}</Typography>
{minefield.map((line, y) => (
<div key={y}>
<Typography>
{line.map((cell, x) => {
if (memoryPhase) {
if (minefield[y][x]) return <span key={x}>[?]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>;
} else {
if (x == pos[0] && y == pos[1]) return <span key={x}>[X]&nbsp;</span>;
if (answer[y][x]) return <span key={x}>[.]&nbsp;</span>;
if (hasAugment && minefield[y][x]) return <span key={x}>[?]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>;
}
})}
</Typography>
<br />
</div>
))}
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${Math.round(difficulty.width)}, 1fr)`,
gridTemplateRows: `repeat(${Math.round(difficulty.height)}, 1fr)`,
gap: 1,
}}
>
{flatGrid.map((item) => {
let color: string;
let icon: React.ReactElement;
if (item.marked) {
color = Settings.theme.warning;
icon = <Flag />;
} else if (item.current) {
color = Settings.theme.infolight;
icon = <Close />;
} else if (item.flagged) {
color = Settings.theme.error;
icon = <Report />;
} else {
color = Settings.theme.primary;
icon = <></>;
}
return (
<Typography
key={`${item}${uniqueId()}`}
sx={{
color: color,
border: `2px solid ${item.current ? Settings.theme.infolight : Settings.theme.primary}`,
height: "32px",
width: "32px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{icon}
</Typography>
);
})}
</Box>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,13 +1,12 @@
import React, { useState, useEffect } from "react";
import Grid from "@mui/material/Grid";
import { Box, Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { KEY } from "../../utils/helpers/keyCodes";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty {
[key: string]: number;
@ -59,23 +58,25 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
}, []);
return (
<Grid container spacing={3}>
<>
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<Paper sx={{ display: "grid", justifyItems: "center" }}>
<Typography variant="h4">Slash when his guard is down!</Typography>
{hasAugment ? (
<>
<Typography variant="h4">Guard will drop in...</Typography>
<GameTimer millis={timeUntilAttacking} onExpire={props.onFailure} />
</>
<Box sx={{ my: 1 }}>
<Typography variant="h5">Guard will drop in...</Typography>
<GameTimer millis={timeUntilAttacking} onExpire={() => null} noPaper />
</Box>
) : (
<></>
)}
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
{phase === 1 && <Typography variant="h4">Preparing?</Typography>}
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -1,21 +1,17 @@
import { Factions } from "../../Faction/Factions";
import { Box, Button, MenuItem, Paper, Select, SelectChangeEvent, Typography } from "@mui/material";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { FactionNames } from "../../Faction/data/FactionNames";
import { inviteToFaction } from "../../Faction/FactionHelpers";
import { Factions } from "../../Faction/Factions";
import { use } from "../../ui/Context";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { FactionNames } from "../../Faction/data/FactionNames";
import { formatNumber } from "../../utils/StringHelperFunctions";
import {
calculateInfiltratorsRepReward,
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../formulas/victory";
import { inviteToFaction } from "../../Faction/FactionHelpers";
interface IProps {
StartingDifficulty: number;
@ -66,24 +62,22 @@ export function Victory(props: IProps): React.ReactElement {
}
return (
<>
<Grid container spacing={3}>
<Grid item xs={10}>
<Typography variant="h4">Infiltration successful!</Typography>
</Grid>
<Grid item xs={10}>
<Typography variant="h5" color="primary">
You{" "}
{isMemberOfInfiltrators ? (
<>
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
</>
) : (
<></>
)}
can trade the confidential information you found for money or reputation.
</Typography>
<Select value={faction} onChange={changeDropdown}>
<Paper sx={{ p: 1, textAlign: "center", display: "flex", alignItems: "center", flexDirection: "column" }}>
<Typography variant="h4">Infiltration successful!</Typography>
<Typography variant="h5" color="primary" width="75%">
You{" "}
{isMemberOfInfiltrators ? (
<>
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
</>
) : (
<></>
)}
can trade the confidential information you found for money or reputation.
</Typography>
<Box sx={{ width: "fit-content" }}>
<Box sx={{ width: "100%" }}>
<Select value={faction} onChange={changeDropdown} sx={{ mr: 1 }}>
<MenuItem key={"none"} value={"none"}>
{"none"}
</MenuItem>
@ -98,17 +92,15 @@ export function Victory(props: IProps): React.ReactElement {
<Button onClick={trade}>
Trade for <Reputation reputation={repGain} /> reputation
</Button>
</Grid>
<Grid item xs={3}>
<Button onClick={sell}>
Sell for&nbsp;
<Money money={moneyGain} />
</Button>
</Grid>
<Grid item xs={3}>
<Button onClick={quitInfiltration}>Quit</Button>
</Grid>
</Grid>
</>
</Box>
<Button onClick={sell} sx={{ width: "100%" }}>
Sell for&nbsp;
<Money money={moneyGain} />
</Button>
</Box>
<Button onClick={quitInfiltration} sx={{ width: "100%", mt: 1 }}>
Quit
</Button>
</Paper>
);
}

View File

@ -1,15 +1,14 @@
import { Box, Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { KEY } from "../../utils/helpers/keyCodes";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { GameTimer } from "./GameTimer";
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
interface Difficulty {
[key: string]: number;
@ -102,46 +101,53 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
}
return (
<Grid container spacing={3}>
<>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<Typography variant="h4">Cut the wires with the following properties! (keyboard 1 to 9)</Typography>
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
<Typography variant="h4" sx={{ width: "75%", textAlign: "center" }}>
Cut the wires with the following properties! (keyboard 1 to 9)
</Typography>
{questions.map((question, i) => (
<Typography key={i}>{question.toString()}</Typography>
))}
<Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: `repeat(${wires.length}, 1fr)`,
columnGap: 3,
justifyItems: "center",
}}
>
{new Array(wires.length).fill(0).map((_, i) => {
const isCorrectWire = checkWire(i + 1);
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={i} style={{ color: color }}>
&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;
</span>
<Typography key={i} style={{ color: color }}>
{i + 1}
</Typography>
);
})}
</Typography>
{new Array(8).fill(0).map((_, i) => (
<div key={i}>
<Typography>
{new Array(8).fill(0).map((_, i) => (
<React.Fragment key={i}>
{wires.map((wire, j) => {
if ((i === 3 || i === 4) && cutWires[j]) {
return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>;
return <Typography key={j}></Typography>;
}
const isCorrectWire = checkWire(j + 1);
const wireColor =
hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length];
return (
<span key={j} style={{ color: wireColor }}>
|{wire.tpe}|&nbsp;&nbsp;&nbsp;
</span>
<Typography key={j} style={{ color: wireColor }}>
|{wire.tpe}|
</Typography>
);
})}
</Typography>
</div>
))}
</React.Fragment>
))}
</Box>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid>
</Grid>
</Paper>
</>
);
}

View File

@ -7,6 +7,7 @@ import { ITutorialEvents } from "./ui/InteractiveTutorial/ITutorialEvents";
// Ordered array of keys to Interactive Tutorial Steps
enum iTutorialSteps {
Start,
NSSelection,
GoToCharacterPage, // Click on 'Stats' page
CharacterPage, // Introduction to 'Stats' page
CharacterGoToTerminalPage, // Go back to Terminal
@ -43,6 +44,7 @@ const ITutorial: {
isRunning: boolean;
stepIsDone: {
[iTutorialSteps.Start]: boolean;
[iTutorialSteps.NSSelection]: boolean;
[iTutorialSteps.GoToCharacterPage]: boolean;
[iTutorialSteps.CharacterPage]: boolean;
[iTutorialSteps.CharacterGoToTerminalPage]: boolean;
@ -80,6 +82,7 @@ const ITutorial: {
// Keeps track of whether each step has been done
stepIsDone: {
[iTutorialSteps.Start]: false,
[iTutorialSteps.NSSelection]: false,
[iTutorialSteps.GoToCharacterPage]: false,
[iTutorialSteps.CharacterPage]: false,
[iTutorialSteps.CharacterGoToTerminalPage]: false,

View File

@ -2,11 +2,11 @@
* Location and traveling-related helper functions.
* Mostly used for UI
*/
import { SpecialServers } from "../Server/data/SpecialServers";
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { AddToAllServers, createUniqueRandomIp } from "../Server/AllServers";
import { safetlyCreateUniqueServer } from "../Server/ServerHelpers";
import { GetServer } from "../Server/AllServers";
import { dialogBoxCreate } from "../ui/React/DialogBox";
@ -25,19 +25,14 @@ export function purchaseTorRouter(p: IPlayer): void {
}
p.loseMoney(CONSTANTS.TorRouterCost, "other");
const darkweb = safetlyCreateUniqueServer({
ip: createUniqueRandomIp(),
hostname: "darkweb",
organizationName: "",
isConnectedTo: false,
adminRights: false,
purchasedByPlayer: false,
maxRam: 1,
});
AddToAllServers(darkweb);
const darkweb = GetServer(SpecialServers.DarkWeb);
if (!darkweb) {
throw new Error("Dark web is not a server.");
}
p.getHomeComputer().serversOnNetwork.push(darkweb.hostname);
darkweb.serversOnNetwork.push(p.getHomeComputer().hostname);
console.log(darkweb);
dialogBoxCreate(
"You have purchased a TOR router!<br>" +
"You now have access to the dark web from your home computer.<br>" +

View File

@ -17,6 +17,7 @@ import { Money } from "../../ui/React/Money";
import { IRouter } from "../../ui/Router";
import { serverMetadata } from "../../Server/data/servers";
import { Box } from "@mui/material";
import { ClassType } from "../../utils/WorkType";
type IProps = {
loc: Location;
@ -33,7 +34,7 @@ export function GymLocation(props: IProps): React.ReactElement {
return props.loc.costMult * discount;
}
function train(stat: string): void {
function train(stat: ClassType): void {
const loc = props.loc;
props.p.startClass(calculateCost(), loc.expMult, stat);
props.p.startFocusing();
@ -41,19 +42,19 @@ export function GymLocation(props: IProps): React.ReactElement {
}
function trainStrength(): void {
train(CONSTANTS.ClassGymStrength);
train(ClassType.GymStrength);
}
function trainDefense(): void {
train(CONSTANTS.ClassGymDefense);
train(ClassType.GymDefense);
}
function trainDexterity(): void {
train(CONSTANTS.ClassGymDexterity);
train(ClassType.GymDexterity);
}
function trainAgility(): void {
train(CONSTANTS.ClassGymAgility);
train(ClassType.GymAgility);
}
const cost = CONSTANTS.ClassGymBaseCost * calculateCost();

View File

@ -32,7 +32,7 @@ export function RamButton(props: IProps): React.ReactElement {
}
const bnMult = BitNodeMultipliers.HomeComputerRamCost === 1 ? "" : `\\cdot ${BitNodeMultipliers.HomeComputerRamCost}`;
console.log(BitNodeMultipliers.HomeComputerRamCost);
return (
<Tooltip
title={

View File

@ -317,7 +317,7 @@ export function SpecialLocation(props: IProps): React.ReactElement {
return renderGrafting();
}
case LocationName.Sector12CityHall: {
return (BitNodeMultipliers.CorporationSoftCap < 0.15 && <></>) || <CreateCorporation />;
return (BitNodeMultipliers.CorporationSoftcap < 0.15 && <></>) || <CreateCorporation />;
}
case LocationName.Sector12NSA: {
return renderBladeburner();

View File

@ -17,6 +17,8 @@ import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
import { Box } from "@mui/material";
import { ClassType } from "../../utils/WorkType";
type IProps = {
loc: Location;
};
@ -32,7 +34,7 @@ export function UniversityLocation(props: IProps): React.ReactElement {
return props.loc.costMult * discount;
}
function take(stat: string): void {
function take(stat: ClassType): void {
const loc = props.loc;
player.startClass(calculateCost(), loc.expMult, stat);
player.startFocusing();
@ -40,27 +42,27 @@ export function UniversityLocation(props: IProps): React.ReactElement {
}
function study(): void {
take(CONSTANTS.ClassStudyComputerScience);
take(ClassType.StudyComputerScience);
}
function dataStructures(): void {
take(CONSTANTS.ClassDataStructures);
take(ClassType.DataStructures);
}
function networks(): void {
take(CONSTANTS.ClassNetworks);
take(ClassType.Networks);
}
function algorithms(): void {
take(CONSTANTS.ClassAlgorithms);
take(ClassType.Algorithms);
}
function management(): void {
take(CONSTANTS.ClassManagement);
take(ClassType.Management);
}
function leadership(): void {
take(CONSTANTS.ClassLeadership);
take(ClassType.Leadership);
}
const costMult: number = calculateCost();

View File

@ -5,6 +5,8 @@ import type { WorkerScript } from "./WorkerScript";
import { makeRuntimeRejectMsg } from "../NetscriptEvaluator";
import { Player } from "../Player";
import { CityName } from "src/Locations/data/CityNames";
import { Settings } from "../Settings/Settings";
import { CONSTANTS } from "../Constants";
type ExternalFunction = (...args: any[]) => any;
type ExternalAPI = {
@ -91,8 +93,14 @@ function wrapFunction(
getValidPort: (port: any) => helpers.getValidPort(functionPath, port),
},
};
const safetyEnabled = Settings.InfinityLoopSafety;
function wrappedFunction(...args: unknown[]): unknown {
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
if (safetyEnabled) workerScript.infiniteLoopSafetyCounter++;
if (workerScript.infiniteLoopSafetyCounter > CONSTANTS.InfiniteLoopLimit)
throw new Error(
`Infinite loop without sleep detected. ${CONSTANTS.InfiniteLoopLimit} ns functions were called without sleep. This will cause your UI to hang.`,
);
return func(ctx)(...args);
}
const parent = getNestedProperty(wrappedAPI, ...tree);

View File

@ -51,6 +51,7 @@ export const RamCostConstants: IMap<number> = {
ScriptCodingContractBaseRamCost: 10,
ScriptSleeveBaseRamCost: 4,
ScriptGetOwnedSourceFiles: 5,
ScriptClearTerminalCost: 0.2,
ScriptSingularityFn1RamCost: 2,
ScriptSingularityFn2RamCost: 3,
@ -156,6 +157,7 @@ const singularity: IMap<any> = {
getUpgradeHomeCoresCost: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 2),
workForCompany: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
applyToCompany: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
quitJob: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
getCompanyRep: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 3),
getCompanyFavor: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 3),
getCompanyFavorGain: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 4),
@ -357,6 +359,7 @@ export const RamCosts: IMap<any> = {
enableLog: 0,
isLogEnabled: 0,
getScriptLogs: 0,
clearTerminal: RamCostConstants.ScriptClearTerminalCost,
nuke: RamCostConstants.ScriptPortProgramRamCost,
brutessh: RamCostConstants.ScriptPortProgramRamCost,
ftpcrack: RamCostConstants.ScriptPortProgramRamCost,

View File

@ -111,6 +111,11 @@ export class WorkerScript {
*/
atExit: any;
/**
* Once this counter reaches it's limit the script crashes. It is reset when a promise completes.
*/
infiniteLoopSafetyCounter = 0;
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) {
this.name = runningScriptObj.filename;
this.hostname = runningScriptObj.server;

View File

@ -14,6 +14,7 @@ export function netscriptDelay(time: number, workerScript: WorkerScript): Promis
workerScript.delay = null;
workerScript.delayReject = undefined;
workerScript.infiniteLoopSafetyCounter = 0;
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);

View File

@ -55,7 +55,7 @@ import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads
import { numeralWrapper } from "./ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { LogBoxEvents } from "./ui/React/LogBoxManager";
import { LogBoxEvents, LogBoxCloserEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString";
@ -82,6 +82,7 @@ import {
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
Sleeve as ISleeve,
Infiltration as IInfiltration,
RunningScript as IRunningScript,
RecentScript as IRecentScript,
@ -93,6 +94,12 @@ import {
BitNodeMultipliers as IBNMults,
Server as IServerDef,
RunningScript as IRunningScriptDef,
Grafting as IGrafting,
UserInterface as IUserInterface,
TIX as ITIX,
Corporation as ICorporation,
CodingContract as ICodingContract,
Hacknet as IHacknet,
// ToastVariant,
} from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
@ -360,7 +367,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
};
const hack = function (
const hack = async function (
hostname: string,
manual: boolean,
{ threads: requestedThreads, stock }: any = {},
@ -524,23 +531,35 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
},
};
const gang = NetscriptGang(Player, workerScript, helper);
const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const gang = wrapAPI(helper, {}, workerScript, NetscriptGang(Player, workerScript), "gang").gang as unknown as IGang;
const sleeve = wrapAPI(helper, {}, workerScript, NetscriptSleeve(Player), "sleeve").sleeve as unknown as ISleeve;
const hacknet = wrapAPI(helper, {}, workerScript, NetscriptHacknet(Player, workerScript), "hacknet")
.hacknet as unknown as IHacknet;
const bladeburner = wrapAPI(helper, {}, workerScript, NetscriptBladeburner(Player, workerScript), "bladeburner")
.bladeburner as unknown as IBladeburner;
const codingcontract = wrapAPI(
helper,
{},
workerScript,
NetscriptCodingContract(Player, workerScript),
"codingcontract",
).codingcontract as unknown as ICodingContract;
const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
const codingcontract = NetscriptCodingContract(Player, workerScript, helper);
const corporation = NetscriptCorporation(Player, workerScript, helper);
const formulas = NetscriptFormulas(Player, workerScript, helper);
const corporation = wrapAPI(helper, {}, workerScript, NetscriptCorporation(Player, workerScript), "corporation")
.corporation as unknown as ICorporation;
const singularity = wrapAPI(helper, {}, workerScript, NetscriptSingularity(Player, workerScript), "singularity")
.singularity as unknown as ISingularity;
const stockmarket = NetscriptStockMarket(Player, workerScript, helper);
const ui = NetscriptUserInterface(Player, workerScript, helper);
const grafting = NetscriptGrafting(Player, workerScript, helper);
const stockmarket = wrapAPI(helper, {}, workerScript, NetscriptStockMarket(Player, workerScript), "stock")
.stock as unknown as ITIX;
const ui = wrapAPI(helper, {}, workerScript, NetscriptUserInterface(), "ui").ui as unknown as IUserInterface;
const grafting = wrapAPI(helper, {}, workerScript, NetscriptGrafting(Player), "grafting")
.grafting as unknown as IGrafting;
const base: INS = {
...singularity,
@ -988,6 +1007,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
LogBoxEvents.emit(runningScriptObj);
},
closeTail: function (_pid: unknown = workerScript.scriptRef.pid): void {
updateDynamicRam("closeTail", getRamCost(Player, "closeTail"));
const pid = helper.number("closeTail", "pid", _pid);
//Emit an event to tell the game to close the tail window if it exists
LogBoxCloserEvents.emit(pid);
},
nuke: function (_hostname: unknown): boolean {
updateDynamicRam("nuke", getRamCost(Player, "nuke"));
const hostname = helper.string("tail", "hostname", _hostname);
@ -1233,16 +1258,21 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return false;
}
},
killall: function (_hostname: unknown = workerScript.hostname): boolean {
killall: function (_hostname: unknown = workerScript.hostname, _safetyguard: unknown = true): boolean {
updateDynamicRam("killall", getRamCost(Player, "killall"));
const hostname = helper.string("killall", "hostname", _hostname);
const safetyguard = helper.boolean(_safetyguard);
if (hostname === undefined) {
throw makeRuntimeErrorMsg("killall", "Takes 1 argument");
throw makeRuntimeErrorMsg("killall", "Usage: killall(hostname, [safetyguard boolean])");
}
const server = safeGetServer(hostname, "killall");
const scriptsRunning = server.runningScripts.length > 0;
let scriptsKilled = 0;
for (let i = server.runningScripts.length - 1; i >= 0; --i) {
if (safetyguard === true && server.runningScripts[i].pid == workerScript.pid) continue;
killWorkerScript(server.runningScripts[i], server.hostname, false);
++scriptsKilled;
}
WorkerScriptStartStopEventEmitter.emit();
workerScript.log(
@ -1250,7 +1280,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
() => `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`,
);
return scriptsRunning;
return scriptsKilled > 0;
},
exit: function (): void {
updateDynamicRam("exit", getRamCost(Player, "exit"));

View File

@ -1,18 +1,13 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Bladeburner } from "../Bladeburner/Bladeburner";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Bladeburner as INetscriptBladeburner, BladeburnerCurAction } from "../ScriptEditor/NetscriptDefinitions";
import { IAction } from "src/Bladeburner/IAction";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptBladeburner(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): INetscriptBladeburner {
const checkBladeburnerAccess = function (func: string, skipjoined = false): void {
export function NetscriptBladeburner(player: IPlayer, workerScript: WorkerScript): InternalAPI<INetscriptBladeburner> {
const checkBladeburnerAccess = function (ctx: NetscriptContext, skipjoined = false): void {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
const apiAccess =
@ -22,353 +17,354 @@ export function NetscriptBladeburner(
});
if (!apiAccess) {
const apiDenied = `You do not currently have access to the Bladeburner API. You must either be in BitNode-7 or have Source-File 7.`;
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, apiDenied);
throw ctx.makeRuntimeErrorMsg(apiDenied);
}
if (!skipjoined) {
const bladeburnerAccess = bladeburner instanceof Bladeburner;
if (!bladeburnerAccess) {
const bladeburnerDenied = `You must be a member of the Bladeburner division to use this API.`;
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, bladeburnerDenied);
throw ctx.makeRuntimeErrorMsg(bladeburnerDenied);
}
}
};
const checkBladeburnerCity = function (func: string, city: string): void {
const checkBladeburnerCity = function (ctx: NetscriptContext, city: string): void {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
if (!bladeburner.cities.hasOwnProperty(city)) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid city: ${city}`);
throw ctx.makeRuntimeErrorMsg(`Invalid city: ${city}`);
}
};
const getBladeburnerActionObject = function (func: string, type: string, name: string): IAction {
const getBladeburnerActionObject = function (ctx: NetscriptContext, type: string, name: string): IAction {
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Must have joined bladeburner");
const actionId = bladeburner.getActionIdFromTypeAndName(type, name);
if (!actionId) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid action type='${type}', name='${name}'`);
}
const actionObj = bladeburner.getActionObject(actionId);
if (!actionObj) {
throw helper.makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid action type='${type}', name='${name}'`);
}
return actionObj;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "bladeburner", funcName));
return {
getContractNames: function (): string[] {
updateRam("getContractNames");
checkBladeburnerAccess("getContractNames");
getContractNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getContractNamesNetscriptFn();
},
getOperationNames: function (): string[] {
updateRam("getOperationNames");
checkBladeburnerAccess("getOperationNames");
getOperationNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getOperationNamesNetscriptFn();
},
getBlackOpNames: function (): string[] {
updateRam("getBlackOpNames");
checkBladeburnerAccess("getBlackOpNames");
getBlackOpNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getBlackOpNamesNetscriptFn();
},
getBlackOpRank: function (_blackOpName: unknown): number {
updateRam("getBlackOpRank");
const blackOpName = helper.string("getBlackOpRank", "blackOpName", _blackOpName);
checkBladeburnerAccess("getBlackOpRank");
const action: any = getBladeburnerActionObject("getBlackOpRank", "blackops", blackOpName);
return action.reqdRank;
},
getGeneralActionNames: function (): string[] {
updateRam("getGeneralActionNames");
checkBladeburnerAccess("getGeneralActionNames");
getBlackOpRank:
(ctx: NetscriptContext) =>
(_blackOpName: unknown): number => {
const blackOpName = ctx.helper.string("blackOpName", _blackOpName);
checkBladeburnerAccess(ctx);
const action: any = getBladeburnerActionObject(ctx, "blackops", blackOpName);
return action.reqdRank;
},
getGeneralActionNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getGeneralActionNamesNetscriptFn();
},
getSkillNames: function (): string[] {
updateRam("getSkillNames");
checkBladeburnerAccess("getSkillNames");
getSkillNames: (ctx: NetscriptContext) => (): string[] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getSkillNamesNetscriptFn();
},
startAction: function (_type: unknown, _name: unknown): boolean {
updateRam("startAction");
const type = helper.string("startAction", "type", _type);
const name = helper.string("startAction", "name", _name);
checkBladeburnerAccess("startAction");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.startActionNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.startAction", e);
}
},
stopBladeburnerAction: function (): void {
updateRam("stopBladeburnerAction");
checkBladeburnerAccess("stopBladeburnerAction");
startAction:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): boolean => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.startActionNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
stopBladeburnerAction: (ctx: NetscriptContext) => (): void => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.resetAction();
},
getCurrentAction: function (): BladeburnerCurAction {
updateRam("getCurrentAction");
checkBladeburnerAccess("getCurrentAction");
getCurrentAction: (ctx: NetscriptContext) => (): BladeburnerCurAction => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.getTypeAndNameFromActionId(bladeburner.action);
},
getActionTime: function (_type: unknown, _name: unknown): number {
updateRam("getActionTime");
const type = helper.string("getActionTime", "type", _type);
const name = helper.string("getActionTime", "name", _name);
checkBladeburnerAccess("getActionTime");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionTimeNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e);
}
},
getActionEstimatedSuccessChance: function (_type: unknown, _name: unknown): [number, number] {
updateRam("getActionEstimatedSuccessChance");
const type = helper.string("getActionEstimatedSuccessChance", "type", _type);
const name = helper.string("getActionEstimatedSuccessChance", "name", _name);
checkBladeburnerAccess("getActionEstimatedSuccessChance");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
}
},
getActionRepGain: function (_type: unknown, _name: unknown, _level: unknown): number {
updateRam("getActionRepGain");
const type = helper.string("getActionRepGain", "type", _type);
const name = helper.string("getActionRepGain", "name", _name);
const level = helper.number("getActionRepGain", "level", _level);
checkBladeburnerAccess("getActionRepGain");
const action = getBladeburnerActionObject("getActionRepGain", type, name);
let rewardMultiplier;
if (level == null || isNaN(level)) {
rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
} else {
rewardMultiplier = Math.pow(action.rewardFac, level - 1);
}
getActionTime:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionTimeNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionEstimatedSuccessChance:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): [number, number] => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionRepGain:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _level: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const level = ctx.helper.number("level", _level);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
let rewardMultiplier;
if (level == null || isNaN(level)) {
rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
} else {
rewardMultiplier = Math.pow(action.rewardFac, level - 1);
}
return action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank;
},
getActionCountRemaining: function (_type: unknown, _name: unknown): number {
updateRam("getActionCountRemaining");
const type = helper.string("getActionCountRemaining", "type", _type);
const name = helper.string("getActionCountRemaining", "name", _name);
checkBladeburnerAccess("getActionCountRemaining");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionCountRemainingNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionCountRemaining", e);
}
},
getActionMaxLevel: function (_type: unknown, _name: unknown): number {
updateRam("getActionMaxLevel");
const type = helper.string("getActionMaxLevel", "type", _type);
const name = helper.string("getActionMaxLevel", "name", _name);
checkBladeburnerAccess("getActionMaxLevel");
const action = getBladeburnerActionObject("getActionMaxLevel", type, name);
return action.maxLevel;
},
getActionCurrentLevel: function (_type: unknown, _name: unknown): number {
updateRam("getActionCurrentLevel");
const type = helper.string("getActionCurrentLevel", "type", _type);
const name = helper.string("getActionCurrentLevel", "name", _name);
checkBladeburnerAccess("getActionCurrentLevel");
const action = getBladeburnerActionObject("getActionCurrentLevel", type, name);
return action.level;
},
getActionAutolevel: function (_type: unknown, _name: unknown): boolean {
updateRam("getActionAutolevel");
const type = helper.string("getActionAutolevel", "type", _type);
const name = helper.string("getActionAutolevel", "name", _name);
checkBladeburnerAccess("getActionAutolevel");
const action = getBladeburnerActionObject("getActionCurrentLevel", type, name);
return action.autoLevel;
},
setActionAutolevel: function (_type: unknown, _name: unknown, _autoLevel: unknown = true): void {
updateRam("setActionAutolevel");
const type = helper.string("setActionAutolevel", "type", _type);
const name = helper.string("setActionAutolevel", "name", _name);
const autoLevel = helper.boolean(_autoLevel);
checkBladeburnerAccess("setActionAutolevel");
const action = getBladeburnerActionObject("setActionAutolevel", type, name);
action.autoLevel = autoLevel;
},
setActionLevel: function (_type: unknown, _name: unknown, _level: unknown = 1): void {
updateRam("setActionLevel");
const type = helper.string("setActionLevel", "type", _type);
const name = helper.string("setActionLevel", "name", _name);
const level = helper.number("setActionLevel", "level", _level);
checkBladeburnerAccess("setActionLevel");
const action = getBladeburnerActionObject("setActionLevel", type, name);
if (level < 1 || level > action.maxLevel) {
throw helper.makeRuntimeErrorMsg(
"bladeburner.setActionLevel",
`Level must be between 1 and ${action.maxLevel}, is ${level}`,
);
}
action.level = level;
},
getRank: function (): number {
updateRam("getRank");
checkBladeburnerAccess("getRank");
return action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank;
},
getActionCountRemaining:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionCountRemainingNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionMaxLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.maxLevel;
},
getActionCurrentLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.level;
},
getActionAutolevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): boolean => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
return action.autoLevel;
},
setActionAutolevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _autoLevel: unknown = true): void => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const autoLevel = ctx.helper.boolean(_autoLevel);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
action.autoLevel = autoLevel;
},
setActionLevel:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _level: unknown = 1): void => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const level = ctx.helper.number("level", _level);
checkBladeburnerAccess(ctx);
const action = getBladeburnerActionObject(ctx, type, name);
if (level < 1 || level > action.maxLevel) {
ctx.helper.makeRuntimeErrorMsg(`Level must be between 1 and ${action.maxLevel}, is ${level}`);
}
action.level = level;
},
getRank: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.rank;
},
getSkillPoints: function (): number {
updateRam("getSkillPoints");
checkBladeburnerAccess("getSkillPoints");
getSkillPoints: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.skillPoints;
},
getSkillLevel: function (_skillName: unknown): number {
updateRam("getSkillLevel");
const skillName = helper.string("getSkillLevel", "skillName", _skillName);
checkBladeburnerAccess("getSkillLevel");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillLevelNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getSkillLevel", e);
}
},
getSkillUpgradeCost: function (_skillName: unknown): number {
updateRam("getSkillUpgradeCost");
const skillName = helper.string("getSkillUpgradeCost", "skillName", _skillName);
checkBladeburnerAccess("getSkillUpgradeCost");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillUpgradeCostNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getSkillUpgradeCost", e);
}
},
upgradeSkill: function (_skillName: unknown): boolean {
updateRam("upgradeSkill");
const skillName = helper.string("upgradeSkill", "skillName", _skillName);
checkBladeburnerAccess("upgradeSkill");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.upgradeSkillNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.upgradeSkill", e);
}
},
getTeamSize: function (_type: unknown, _name: unknown): number {
updateRam("getTeamSize");
const type = helper.string("getTeamSize", "type", _type);
const name = helper.string("getTeamSize", "name", _name);
checkBladeburnerAccess("getTeamSize");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getTeamSizeNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getTeamSize", e);
}
},
setTeamSize: function (_type: unknown, _name: unknown, _size: unknown): number {
updateRam("setTeamSize");
const type = helper.string("setTeamSize", "type", _type);
const name = helper.string("setTeamSize", "name", _name);
const size = helper.number("setTeamSize", "size", _size);
checkBladeburnerAccess("setTeamSize");
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.setTeamSizeNetscriptFn(type, name, size, workerScript);
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.setTeamSize", e);
}
},
getCityEstimatedPopulation: function (_cityName: unknown): number {
updateRam("getCityEstimatedPopulation");
const cityName = helper.string("getCityEstimatedPopulation", "cityName", _cityName);
checkBladeburnerAccess("getCityEstimatedPopulation");
checkBladeburnerCity("getCityEstimatedPopulation", cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].popEst;
},
getCityCommunities: function (_cityName: unknown): number {
updateRam("getCityCommunities");
const cityName = helper.string("getCityCommunities", "cityName", _cityName);
checkBladeburnerAccess("getCityCommunities");
checkBladeburnerCity("getCityCommunities", cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].comms;
},
getCityChaos: function (_cityName: unknown): number {
updateRam("getCityChaos");
const cityName = helper.string("getCityChaos", "cityName", _cityName);
checkBladeburnerAccess("getCityChaos");
checkBladeburnerCity("getCityChaos", cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].chaos;
},
getCity: function (): string {
updateRam("getCity");
checkBladeburnerAccess("getCityChaos");
getSkillLevel:
(ctx: NetscriptContext) =>
(_skillName: unknown): number => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillLevelNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getSkillUpgradeCost:
(ctx: NetscriptContext) =>
(_skillName: unknown): number => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getSkillUpgradeCostNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
upgradeSkill:
(ctx: NetscriptContext) =>
(_skillName: unknown): boolean => {
const skillName = ctx.helper.string("skillName", _skillName);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.upgradeSkillNetscriptFn(skillName, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getTeamSize:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getTeamSizeNetscriptFn(type, name, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
setTeamSize:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown, _size: unknown): number => {
const type = ctx.helper.string("type", _type);
const name = ctx.helper.string("name", _name);
const size = ctx.helper.number("size", _size);
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.setTeamSizeNetscriptFn(type, name, size, workerScript);
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getCityEstimatedPopulation:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].popEst;
},
getCityCommunities:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].comms;
},
getCityChaos:
(ctx: NetscriptContext) =>
(_cityName: unknown): number => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.cities[cityName].chaos;
},
getCity: (ctx: NetscriptContext) => (): string => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.city;
},
switchCity: function (_cityName: unknown): boolean {
updateRam("switchCity");
const cityName = helper.string("switchCity", "cityName", _cityName);
checkBladeburnerAccess("switchCity");
checkBladeburnerCity("switchCity", cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
bladeburner.city = cityName;
return true;
},
getStamina: function (): [number, number] {
updateRam("getStamina");
checkBladeburnerAccess("getStamina");
switchCity:
(ctx: NetscriptContext) =>
(_cityName: unknown): boolean => {
const cityName = ctx.helper.string("cityName", _cityName);
checkBladeburnerAccess(ctx);
checkBladeburnerCity(ctx, cityName);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
bladeburner.city = cityName;
return true;
},
getStamina: (ctx: NetscriptContext) => (): [number, number] => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return [bladeburner.stamina, bladeburner.maxStamina];
},
joinBladeburnerFaction: function (): boolean {
updateRam("joinBladeburnerFaction");
checkBladeburnerAccess("joinBladeburnerFaction", true);
joinBladeburnerFaction: (ctx: NetscriptContext) => (): boolean => {
checkBladeburnerAccess(ctx, true);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return bladeburner.joinBladeburnerFactionNetscriptFn(workerScript);
},
joinBladeburnerDivision: function (): boolean {
updateRam("joinBladeburnerDivision");
joinBladeburnerDivision: (ctx: NetscriptContext) => (): boolean => {
if (player.bitNodeN === 7 || player.sourceFileLvl(7) > 0) {
if (player.bitNodeN === 8) {
return false;
@ -382,22 +378,18 @@ export function NetscriptBladeburner(
player.agility >= 100
) {
player.bladeburner = new Bladeburner(player);
workerScript.log("joinBladeburnerDivision", () => "You have been accepted into the Bladeburner division");
ctx.log(() => "You have been accepted into the Bladeburner division");
return true;
} else {
workerScript.log(
"joinBladeburnerDivision",
() => "You do not meet the requirements for joining the Bladeburner division",
);
ctx.log(() => "You do not meet the requirements for joining the Bladeburner division");
return false;
}
}
return false;
},
getBonusTime: function (): number {
updateRam("getBonusTime");
checkBladeburnerAccess("getBonusTime");
getBonusTime: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
return Math.round(bladeburner.storedCycles / 5) * 1000;

View File

@ -1,132 +1,124 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { is2DArray } from "../utils/helpers/is2DArray";
import { CodingContract } from "../CodingContracts";
import { CodingAttemptOptions, CodingContract as ICodingContract } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptCodingContract(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): ICodingContract {
const getCodingContract = function (func: string, hostname: string, filename: string): CodingContract {
const server = helper.getServer(hostname, func);
export function NetscriptCodingContract(player: IPlayer, workerScript: WorkerScript): InternalAPI<ICodingContract> {
const getCodingContract = function (
ctx: NetscriptContext,
func: string,
hostname: string,
filename: string,
): CodingContract {
const server = ctx.helper.getServer(hostname);
const contract = server.getContract(filename);
if (contract == null) {
throw helper.makeRuntimeErrorMsg(
`codingcontract.${func}`,
`Cannot find contract '${filename}' on server '${hostname}'`,
);
throw ctx.makeRuntimeErrorMsg(`Cannot find contract '${filename}' on server '${hostname}'`);
}
return contract;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "codingcontract", funcName));
return {
attempt: function (
answer: any,
_filename: unknown,
_hostname: unknown = workerScript.hostname,
{ returnReward }: CodingAttemptOptions = { returnReward: false },
): boolean | string {
updateRam("attempt");
const filename = helper.string("attempt", "filename", _filename);
const hostname = helper.string("attempt", "hostname", _hostname);
const contract = getCodingContract("attempt", hostname, filename);
attempt:
(ctx: NetscriptContext) =>
(
answer: any,
_filename: unknown,
_hostname: unknown = workerScript.hostname,
{ returnReward }: CodingAttemptOptions = { returnReward: false },
): boolean | string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "attempt", hostname, filename);
// Convert answer to string. If the answer is a 2D array, then we have to
// manually add brackets for the inner arrays
if (is2DArray(answer)) {
const answerComponents = [];
for (let i = 0; i < answer.length; ++i) {
answerComponents.push(["[", answer[i].toString(), "]"].join(""));
}
answer = answerComponents.join(",");
} else {
answer = String(answer);
}
const creward = contract.reward;
if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward");
const serv = helper.getServer(hostname, "codingcontract.attempt");
if (contract.isSolution(answer)) {
const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
workerScript.log(
"codingcontract.attempt",
() => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`,
);
serv.removeContract(filename);
return returnReward ? reward : true;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
workerScript.log(
"codingcontract.attempt",
() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`,
);
serv.removeContract(filename);
} else {
workerScript.log(
"codingcontract.attempt",
() =>
`Coding Contract attempt '${filename}' failed. ${
contract.getMaxNumTries() - contract.tries
} attempts remaining.`,
);
}
return returnReward ? "" : false;
}
},
getContractType: function (_filename: unknown, _hostname: unknown = workerScript.hostname): string {
updateRam("getContractType");
const filename = helper.string("getContractType", "filename", _filename);
const hostname = helper.string("getContractType", "hostname", _hostname);
const contract = getCodingContract("getContractType", hostname, filename);
return contract.getType();
},
getData: function (_filename: unknown, _hostname: unknown = workerScript.hostname): any {
updateRam("getData");
const filename = helper.string("getContractType", "filename", _filename);
const hostname = helper.string("getContractType", "hostname", _hostname);
const contract = getCodingContract("getData", hostname, filename);
const data = contract.getData();
if (data.constructor === Array) {
// For two dimensional arrays, we have to copy the internal arrays using
// slice() as well. As of right now, no contract has arrays that have
// more than two dimensions
const copy = data.slice();
for (let i = 0; i < copy.length; ++i) {
if (data[i].constructor === Array) {
copy[i] = data[i].slice();
// Convert answer to string. If the answer is a 2D array, then we have to
// manually add brackets for the inner arrays
if (is2DArray(answer)) {
const answerComponents = [];
for (let i = 0; i < answer.length; ++i) {
answerComponents.push(["[", answer[i].toString(), "]"].join(""));
}
answer = answerComponents.join(",");
} else {
answer = String(answer);
}
return copy;
} else {
return data;
}
},
getDescription: function (_filename: unknown, _hostname: unknown = workerScript.hostname): string {
updateRam("getDescription");
const filename = helper.string("getDescription", "filename", _filename);
const hostname = helper.string("getDescription", "hostname", _hostname);
const contract = getCodingContract("getDescription", hostname, filename);
return contract.getDescription();
},
getNumTriesRemaining: function (_filename: unknown, _hostname: unknown = workerScript.hostname): number {
updateRam("getNumTriesRemaining");
const filename = helper.string("getNumTriesRemaining", "filename", _filename);
const hostname = helper.string("getNumTriesRemaining", "hostname", _hostname);
const contract = getCodingContract("getNumTriesRemaining", hostname, filename);
return contract.getMaxNumTries() - contract.tries;
},
const creward = contract.reward;
if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward");
const serv = ctx.helper.getServer(hostname);
if (contract.isSolution(answer)) {
const reward = player.gainCodingContractReward(creward, contract.getDifficulty());
ctx.log(() => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
serv.removeContract(filename);
return returnReward ? reward : true;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
ctx.log(() => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`);
serv.removeContract(filename);
} else {
ctx.log(
() =>
`Coding Contract attempt '${filename}' failed. ${
contract.getMaxNumTries() - contract.tries
} attempts remaining.`,
);
}
return returnReward ? "" : false;
}
},
getContractType:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getContractType", hostname, filename);
return contract.getType();
},
getData:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): any => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getData", hostname, filename);
const data = contract.getData();
if (data.constructor === Array) {
// For two dimensional arrays, we have to copy the internal arrays using
// slice() as well. As of right now, no contract has arrays that have
// more than two dimensions
const copy = data.slice();
for (let i = 0; i < copy.length; ++i) {
if (data[i].constructor === Array) {
copy[i] = data[i].slice();
}
}
return copy;
} else {
return data;
}
},
getDescription:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): string => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getDescription", hostname, filename);
return contract.getDescription();
},
getNumTriesRemaining:
(ctx: NetscriptContext) =>
(_filename: unknown, _hostname: unknown = workerScript.hostname): number => {
const filename = ctx.helper.string("filename", _filename);
const hostname = ctx.helper.string("hostname", _hostname);
const contract = getCodingContract(ctx, "getNumTriesRemaining", hostname, filename);
return contract.getMaxNumTries() - contract.tries;
},
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,6 @@
import { FactionNames } from "../Faction/data/FactionNames";
import { GangConstants } from "../Gang/data/Constants";
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { Gang } from "../Gang/Gang";
import { AllGangs } from "../Gang/AllGangs";
import { GangMemberTasks } from "../Gang/GangMemberTasks";
@ -20,63 +18,60 @@ import {
EquipmentStats,
GangTaskStats,
} from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGang {
const checkGangApiAccess = function (func: string): void {
export function NetscriptGang(player: IPlayer, workerScript: WorkerScript): InternalAPI<IGang> {
const checkGangApiAccess = function (ctx: NetscriptContext): void {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
const hasAccess = gang instanceof Gang;
if (!hasAccess) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `You do not currently have a Gang`);
throw ctx.makeRuntimeErrorMsg(`You do not currently have a Gang`);
}
};
const getGangMember = function (func: string, name: string): GangMember {
const getGangMember = function (ctx: NetscriptContext, name: string): GangMember {
const gang = player.gang;
if (gang === null) throw new Error("Must have joined gang");
for (const member of gang.members) if (member.name === name) return member;
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid gang member: '${name}'`);
};
const getGangTask = function (func: string, name: string): GangMemberTask {
const getGangTask = function (ctx: NetscriptContext, name: string): GangMemberTask {
const task = GangMemberTasks[name];
if (!task) {
throw helper.makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid task: '${name}'`);
}
return task;
};
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "gang", funcName));
return {
createGang: function (_faction: unknown): boolean {
updateRam("createGang");
const faction = helper.string("createGang", "faction", _faction);
// this list is copied from Faction/ui/Root.tsx
createGang:
(ctx: NetscriptContext) =>
(_faction: unknown): boolean => {
const faction = ctx.helper.string("faction", _faction);
// this list is copied from Faction/ui/Root.tsx
if (!player.canAccessGang() || !GangConstants.Names.includes(faction)) return false;
if (player.inGang()) return false;
if (!player.factions.includes(faction)) return false;
if (!player.canAccessGang() || !GangConstants.Names.includes(faction)) return false;
if (player.inGang()) return false;
if (!player.factions.includes(faction)) return false;
const isHacking = faction === FactionNames.NiteSec || faction === FactionNames.TheBlackHand;
player.startGang(faction, isHacking);
return true;
},
inGang: function (): boolean {
updateRam("inGang");
const isHacking = faction === FactionNames.NiteSec || faction === FactionNames.TheBlackHand;
player.startGang(faction, isHacking);
return true;
},
inGang: () => (): boolean => {
return player.inGang();
},
getMemberNames: function (): string[] {
updateRam("getMemberNames");
checkGangApiAccess("getMemberNames");
getMemberNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.members.map((member) => member.name);
},
getGangInformation: function (): GangGenInfo {
updateRam("getGangInformation");
checkGangApiAccess("getGangInformation");
getGangInformation: (ctx: NetscriptContext) => (): GangGenInfo => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return {
@ -94,9 +89,8 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
wantedPenalty: gang.getWantedPenalty(),
};
},
getOtherGangInformation: function (): GangOtherInfo {
updateRam("getOtherGangInformation");
checkGangApiAccess("getOtherGangInformation");
getOtherGangInformation: (ctx: NetscriptContext) => (): GangOtherInfo => {
checkGangApiAccess(ctx);
const cpy: any = {};
for (const gang of Object.keys(AllGangs)) {
cpy[gang] = Object.assign({}, AllGangs[gang]);
@ -104,245 +98,254 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
return cpy;
},
getMemberInformation: function (_memberName: unknown): GangMemberInfo {
updateRam("getMemberInformation");
const memberName = helper.string("getMemberInformation", "memberName", _memberName);
checkGangApiAccess("getMemberInformation");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getMemberInformation", memberName);
return {
name: member.name,
task: member.task,
earnedRespect: member.earnedRespect,
hack: member.hack,
str: member.str,
def: member.def,
dex: member.dex,
agi: member.agi,
cha: member.cha,
getMemberInformation:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberInfo => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember(ctx, memberName);
return {
name: member.name,
task: member.task,
earnedRespect: member.earnedRespect,
hack: member.hack,
str: member.str,
def: member.def,
dex: member.dex,
agi: member.agi,
cha: member.cha,
hack_exp: member.hack_exp,
str_exp: member.str_exp,
def_exp: member.def_exp,
dex_exp: member.dex_exp,
agi_exp: member.agi_exp,
cha_exp: member.cha_exp,
hack_exp: member.hack_exp,
str_exp: member.str_exp,
def_exp: member.def_exp,
dex_exp: member.dex_exp,
agi_exp: member.agi_exp,
cha_exp: member.cha_exp,
hack_mult: member.hack_mult,
str_mult: member.str_mult,
def_mult: member.def_mult,
dex_mult: member.dex_mult,
agi_mult: member.agi_mult,
cha_mult: member.cha_mult,
hack_mult: member.hack_mult,
str_mult: member.str_mult,
def_mult: member.def_mult,
dex_mult: member.dex_mult,
agi_mult: member.agi_mult,
cha_mult: member.cha_mult,
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points),
str_asc_mult: member.calculateAscensionMult(member.str_asc_points),
def_asc_mult: member.calculateAscensionMult(member.def_asc_points),
dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points),
agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points),
cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points),
hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points,
hack_asc_points: member.hack_asc_points,
str_asc_points: member.str_asc_points,
def_asc_points: member.def_asc_points,
dex_asc_points: member.dex_asc_points,
agi_asc_points: member.agi_asc_points,
cha_asc_points: member.cha_asc_points,
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
respectGain: member.calculateRespectGain(gang),
wantedLevelGain: member.calculateWantedLevelGain(gang),
moneyGain: member.calculateMoneyGain(gang),
};
},
canRecruitMember: function (): boolean {
updateRam("canRecruitMember");
checkGangApiAccess("canRecruitMember");
respectGain: member.calculateRespectGain(gang),
wantedLevelGain: member.calculateWantedLevelGain(gang),
moneyGain: member.calculateMoneyGain(gang),
};
},
canRecruitMember: (ctx: NetscriptContext) => (): boolean => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return gang.canRecruitMember();
},
recruitMember: function (_memberName: unknown): boolean {
updateRam("recruitMember");
const memberName = helper.string("recruitMember", "memberName", _memberName);
checkGangApiAccess("recruitMember");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(memberName);
if (recruited) {
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`);
} else {
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`);
}
recruitMember:
(ctx: NetscriptContext) =>
(_memberName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const recruited = gang.recruitMember(memberName);
if (recruited) {
workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`);
} else {
workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`);
}
return recruited;
},
getTaskNames: function (): string[] {
updateRam("getTaskNames");
checkGangApiAccess("getTaskNames");
return recruited;
},
getTaskNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const tasks = gang.getAllTaskNames();
tasks.unshift("Unassigned");
return tasks;
},
setMemberTask: function (_memberName: unknown, _taskName: unknown): boolean {
updateRam("setMemberTask");
const memberName = helper.string("setMemberTask", "memberName", _memberName);
const taskName = helper.string("setMemberTask", "taskName", _taskName);
checkGangApiAccess("setMemberTask");
const member = getGangMember("setMemberTask", memberName);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (!gang.getAllTaskNames().includes(taskName)) {
workerScript.log(
"gang.setMemberTask",
() =>
`Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`,
);
return member.assignToTask("Unassigned");
}
const success = member.assignToTask(taskName);
if (success) {
workerScript.log(
"gang.setMemberTask",
() => `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`,
);
} else {
workerScript.log(
"gang.setMemberTask",
() => `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
setMemberTask:
(ctx: NetscriptContext) =>
(_memberName: unknown, _taskName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
const taskName = ctx.helper.string("taskName", _taskName);
checkGangApiAccess(ctx);
const member = getGangMember(ctx, memberName);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (!gang.getAllTaskNames().includes(taskName)) {
workerScript.log(
"gang.setMemberTask",
() =>
`Failed to assign Gang Member '${memberName}' to Invalid task '${taskName}'. '${memberName}' is now Unassigned`,
);
return member.assignToTask("Unassigned");
}
const success = member.assignToTask(taskName);
if (success) {
workerScript.log(
"gang.setMemberTask",
() => `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`,
);
} else {
workerScript.log(
"gang.setMemberTask",
() =>
`Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`,
);
}
return success;
},
getTaskStats: function (_taskName: unknown): GangTaskStats {
updateRam("getTaskStats");
const taskName = helper.string("getTaskStats", "taskName", _taskName);
checkGangApiAccess("getTaskStats");
const task = getGangTask("getTaskStats", taskName);
const copy = Object.assign({}, task);
copy.territory = Object.assign({}, task.territory);
return copy;
},
getEquipmentNames: function (): string[] {
updateRam("getEquipmentNames");
checkGangApiAccess("getEquipmentNames");
return success;
},
getTaskStats:
(ctx: NetscriptContext) =>
(_taskName: unknown): GangTaskStats => {
const taskName = ctx.helper.string("taskName", _taskName);
checkGangApiAccess(ctx);
const task = getGangTask(ctx, taskName);
const copy = Object.assign({}, task);
copy.territory = Object.assign({}, task.territory);
return copy;
},
getEquipmentNames: (ctx: NetscriptContext) => (): string[] => {
checkGangApiAccess(ctx);
return Object.keys(GangMemberUpgrades);
},
getEquipmentCost: function (_equipName: any): number {
updateRam("getEquipmentCost");
const equipName = helper.string("getEquipmentCost", "equipName", _equipName);
checkGangApiAccess("getEquipmentCost");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const upg = GangMemberUpgrades[equipName];
if (upg === null) return Infinity;
return gang.getUpgradeCost(upg);
},
getEquipmentType: function (_equipName: unknown): string {
updateRam("getEquipmentType");
const equipName = helper.string("getEquipmentType", "equipName", _equipName);
checkGangApiAccess("getEquipmentType");
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
return upg.getType();
},
getEquipmentStats: function (_equipName: unknown): EquipmentStats {
updateRam("getEquipmentStats");
const equipName = helper.string("getEquipmentStats", "equipName", _equipName);
checkGangApiAccess("getEquipmentStats");
const equipment = GangMemberUpgrades[equipName];
if (!equipment) {
throw helper.makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`);
}
const typecheck: EquipmentStats = equipment.mults;
return Object.assign({}, typecheck) as any;
},
purchaseEquipment: function (_memberName: unknown, _equipName: unknown): boolean {
updateRam("purchaseEquipment");
const memberName = helper.string("purchaseEquipment", "memberName", _memberName);
const equipName = helper.string("purchaseEquipment", "equipName", _equipName);
checkGangApiAccess("purchaseEquipment");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("purchaseEquipment", memberName);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) return false;
const res = member.buyUpgrade(equipment, player, gang);
if (res) {
workerScript.log("gang.purchaseEquipment", () => `Purchased '${equipName}' for Gang member '${memberName}'`);
} else {
workerScript.log(
"gang.purchaseEquipment",
() => `Failed to purchase '${equipName}' for Gang member '${memberName}'`,
);
}
getEquipmentCost:
(ctx: NetscriptContext) =>
(_equipName: any): number => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const upg = GangMemberUpgrades[equipName];
if (upg === null) return Infinity;
return gang.getUpgradeCost(upg);
},
getEquipmentType:
(ctx: NetscriptContext) =>
(_equipName: unknown): string => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const upg = GangMemberUpgrades[equipName];
if (upg == null) return "";
return upg.getType();
},
getEquipmentStats:
(ctx: NetscriptContext) =>
(_equipName: unknown): EquipmentStats => {
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) {
throw ctx.makeRuntimeErrorMsg(`Invalid equipment: ${equipName}`);
}
const typecheck: EquipmentStats = equipment.mults;
return Object.assign({}, typecheck) as any;
},
purchaseEquipment:
(ctx: NetscriptContext) =>
(_memberName: unknown, _equipName: unknown): boolean => {
const memberName = ctx.helper.string("memberName", _memberName);
const equipName = ctx.helper.string("equipName", _equipName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember(ctx, memberName);
const equipment = GangMemberUpgrades[equipName];
if (!equipment) return false;
const res = member.buyUpgrade(equipment, player, gang);
if (res) {
workerScript.log("gang.purchaseEquipment", () => `Purchased '${equipName}' for Gang member '${memberName}'`);
} else {
workerScript.log(
"gang.purchaseEquipment",
() => `Failed to purchase '${equipName}' for Gang member '${memberName}'`,
);
}
return res;
},
ascendMember: function (_memberName: unknown): GangMemberAscension | undefined {
updateRam("ascendMember");
const memberName = helper.string("ascendMember", "memberName", _memberName);
checkGangApiAccess("ascendMember");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("ascendMember", memberName);
if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript);
},
getAscensionResult: function (_memberName: unknown): GangMemberAscension | undefined {
updateRam("getAscensionResult");
const memberName = helper.string("getAscensionResult", "memberName", _memberName);
checkGangApiAccess("getAscensionResult");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember("getAscensionResult", memberName);
if (!member.canAscend()) return;
return {
respect: member.earnedRespect,
...member.getAscensionResults(),
};
},
setTerritoryWarfare: function (_engage: unknown): void {
updateRam("setTerritoryWarfare");
const engage = helper.boolean(_engage);
checkGangApiAccess("setTerritoryWarfare");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (engage) {
gang.territoryWarfareEngaged = true;
workerScript.log("gang.setTerritoryWarfare", () => "Engaging in Gang Territory Warfare");
} else {
gang.territoryWarfareEngaged = false;
workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare");
}
},
getChanceToWinClash: function (_otherGang: unknown): number {
updateRam("getChanceToWinClash");
const otherGang = helper.string("getChanceToWinClash", "otherGang", _otherGang);
checkGangApiAccess("getChanceToWinClash");
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw helper.makeRuntimeErrorMsg(`gang.getChanceToWinClash`, `Invalid gang: ${otherGang}`);
}
return res;
},
ascendMember:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberAscension | undefined => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember(ctx, memberName);
if (!member.canAscend()) return;
return gang.ascendMember(member, workerScript);
},
getAscensionResult:
(ctx: NetscriptContext) =>
(_memberName: unknown): GangMemberAscension | undefined => {
const memberName = ctx.helper.string("memberName", _memberName);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
const member = getGangMember(ctx, memberName);
if (!member.canAscend()) return;
return {
respect: member.earnedRespect,
...member.getAscensionResults(),
};
},
setTerritoryWarfare:
(ctx: NetscriptContext) =>
(_engage: unknown): void => {
const engage = ctx.helper.boolean(_engage);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (engage) {
gang.territoryWarfareEngaged = true;
workerScript.log("gang.setTerritoryWarfare", () => "Engaging in Gang Territory Warfare");
} else {
gang.territoryWarfareEngaged = false;
workerScript.log("gang.setTerritoryWarfare", () => "Disengaging in Gang Territory Warfare");
}
},
getChanceToWinClash:
(ctx: NetscriptContext) =>
(_otherGang: unknown): number => {
const otherGang = ctx.helper.string("otherGang", _otherGang);
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
if (AllGangs[otherGang] == null) {
throw ctx.makeRuntimeErrorMsg(`Invalid gang: ${otherGang}`);
}
const playerPower = AllGangs[gang.facName].power;
const otherPower = AllGangs[otherGang].power;
const playerPower = AllGangs[gang.facName].power;
const otherPower = AllGangs[otherGang].power;
return playerPower / (otherPower + playerPower);
},
getBonusTime: function (): number {
updateRam("getBonusTime");
checkGangApiAccess("getBonusTime");
return playerPower / (otherPower + playerPower);
},
getBonusTime: (ctx: NetscriptContext) => (): number => {
checkGangApiAccess(ctx);
const gang = player.gang;
if (gang === null) throw new Error("Should not be called without Gang");
return Math.round(gang.storedCycles / 5);
return Math.round(gang.storedCycles / 5) * 1000;
},
};
}

View File

@ -1,104 +1,97 @@
import { Augmentations } from "../Augmentation/Augmentations";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { hasAugmentationPrereqs } from "../Faction/FactionHelpers";
import { CityName } from "../Locations/data/CityNames";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { WorkerScript } from "../Netscript/WorkerScript";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot";
import { INetscriptHelper } from "./INetscriptHelper";
export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IGrafting {
const checkGraftingAPIAccess = (func: string): void => {
export function NetscriptGrafting(player: IPlayer): InternalAPI<IGrafting> {
const checkGraftingAPIAccess = (ctx: NetscriptContext): void => {
if (!player.canAccessGrafting()) {
throw helper.makeRuntimeErrorMsg(
`grafting.${func}`,
throw ctx.makeRuntimeErrorMsg(
"You do not currently have access to the Grafting API. This is either because you are not in BitNode 10 or because you do not have Source-File 10",
);
}
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "grafting", funcName));
return {
getAugmentationGraftPrice: (_augName: unknown): number => {
updateRam("getAugmentationGraftPrice");
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftPrice");
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
return graftableAug.cost;
},
getAugmentationGraftPrice:
(ctx: NetscriptContext) =>
(_augName: unknown): number => {
const augName = ctx.helper.string("augName", _augName);
checkGraftingAPIAccess(ctx);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return graftableAug.cost;
},
getAugmentationGraftTime: (_augName: string): number => {
updateRam("getAugmentationGraftTime");
const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
checkGraftingAPIAccess("getAugmentationGraftTime");
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
return calculateGraftingTimeWithBonus(player, graftableAug);
},
getAugmentationGraftTime:
(ctx: NetscriptContext) =>
(_augName: string): number => {
const augName = ctx.helper.string("augName", _augName);
checkGraftingAPIAccess(ctx);
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
return calculateGraftingTimeWithBonus(player, graftableAug);
},
getGraftableAugmentations: (): string[] => {
updateRam("getGraftableAugmentations");
checkGraftingAPIAccess("getGraftableAugmentations");
getGraftableAugmentations: (ctx: NetscriptContext) => (): string[] => {
checkGraftingAPIAccess(ctx);
const graftableAugs = getGraftingAvailableAugs(player);
return graftableAugs;
},
graftAugmentation: (_augName: string, _focus: unknown = true): boolean => {
updateRam("graftAugmentation");
const augName = helper.string("graftAugmentation", "augName", _augName);
const focus = helper.boolean(_focus);
checkGraftingAPIAccess("graftAugmentation");
if (player.city !== CityName.NewTokyo) {
throw helper.makeRuntimeErrorMsg(
"grafting.graftAugmentation",
"You must be in New Tokyo to begin grafting an Augmentation.",
);
}
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
return false;
}
graftAugmentation:
(ctx: NetscriptContext) =>
(_augName: string, _focus: unknown = true): boolean => {
const augName = ctx.helper.string("augName", _augName);
const focus = ctx.helper.boolean(_focus);
checkGraftingAPIAccess(ctx);
if (player.city !== CityName.NewTokyo) {
throw ctx.makeRuntimeErrorMsg("You must be in New Tokyo to begin grafting an Augmentation.");
}
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
ctx.log(() => `Invalid aug: ${augName}`);
return false;
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
workerScript.log("graftAugmentation", () => txt);
}
const wasFocusing = player.focus;
if (player.isWorking) {
const txt = player.singularityStopWork();
ctx.log(() => txt);
}
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
if (player.money < craftableAug.cost) {
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
return false;
}
const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
if (player.money < craftableAug.cost) {
ctx.log(() => `You don't have enough money to craft ${augName}`);
return false;
}
if (!hasAugmentationPrereqs(craftableAug.augmentation)) {
workerScript.log("grafting.graftAugmentation", () => `You don't have the pre-requisites for ${augName}`);
return false;
}
if (!hasAugmentationPrereqs(craftableAug.augmentation)) {
ctx.log(() => `You don't have the pre-requisites for ${augName}`);
return false;
}
player.loseMoney(craftableAug.cost, "augmentations");
player.startGraftAugmentationWork(augName, craftableAug.time);
player.loseMoney(craftableAug.cost, "augmentations");
player.startGraftAugmentationWork(augName, craftableAug.time);
if (focus) {
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
if (focus) {
player.startFocusing();
Router.toWork();
} else if (wasFocusing) {
player.stopFocusing();
Router.toTerminal();
}
workerScript.log("grafting.graftAugmentation", () => `Began grafting Augmentation ${augName}.`);
return true;
},
ctx.log(() => `Began grafting Augmentation ${augName}.`);
return true;
},
};
}

View File

@ -1,4 +1,3 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServerConstants } from "../Hacknet/data/Constants";
@ -21,12 +20,13 @@ import { HashUpgrade } from "../Hacknet/HashUpgrade";
import { GetServer } from "../Server/AllServers";
import { Hacknet as IHacknet, NodeStats } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IHacknet {
export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript): InternalAPI<IHacknet> {
// Utility function to get Hacknet Node object
const getHacknetNode = function (i: number, callingFn = ""): HacknetNode | HacknetServer {
const getHacknetNode = function (ctx: NetscriptContext, i: number): HacknetNode | HacknetServer {
if (i < 0 || i >= player.hacknetNodes.length) {
throw helper.makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
throw ctx.makeRuntimeErrorMsg("Index specified for Hacknet Node is out-of-bounds: " + i);
}
if (hasHacknetServers(player)) {
@ -35,8 +35,7 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
const hserver = GetServer(hi);
if (!(hserver instanceof HacknetServer)) throw new Error("hacknet server was not actually hacknet server");
if (hserver == null) {
throw helper.makeRuntimeErrorMsg(
callingFn,
throw ctx.makeRuntimeErrorMsg(
`Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`,
);
}
@ -50,162 +49,186 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
};
return {
numNodes: function (): number {
numNodes: () => (): number => {
return player.hacknetNodes.length;
},
maxNumNodes: function (): number {
maxNumNodes: () => (): number => {
if (hasHacknetServers(player)) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
},
purchaseNode: function (): number {
purchaseNode: () => (): number => {
return purchaseHacknet(player);
},
getPurchaseNodeCost: function (): number {
getPurchaseNodeCost: () => (): number => {
if (hasHacknetServers(player)) {
return getCostOfNextHacknetServer(player);
} else {
return getCostOfNextHacknetNode(player);
}
},
getNodeStats: function (_i: unknown): NodeStats {
const i = helper.number("getNodeStats", "i", _i);
const node = getHacknetNode(i, "getNodeStats");
const hasUpgraded = hasHacknetServers(player);
const res: any = {
name: node instanceof HacknetServer ? node.hostname : node.name,
level: node.level,
ram: node instanceof HacknetServer ? node.maxRam : node.ram,
ramUsed: node instanceof HacknetServer ? node.ramUsed : undefined,
cores: node.cores,
production: node instanceof HacknetServer ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node instanceof HacknetServer ? node.totalHashesGenerated : node.totalMoneyGenerated,
};
getNodeStats:
(ctx: NetscriptContext) =>
(_i: unknown): NodeStats => {
const i = ctx.helper.number("i", _i);
const node = getHacknetNode(ctx, i);
const hasUpgraded = hasHacknetServers(player);
const res: any = {
name: node instanceof HacknetServer ? node.hostname : node.name,
level: node.level,
ram: node instanceof HacknetServer ? node.maxRam : node.ram,
ramUsed: node instanceof HacknetServer ? node.ramUsed : undefined,
cores: node.cores,
production: node instanceof HacknetServer ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node instanceof HacknetServer ? node.totalHashesGenerated : node.totalMoneyGenerated,
};
if (hasUpgraded && node instanceof HacknetServer) {
res.cache = node.cache;
res.hashCapacity = node.hashCapacity;
}
if (hasUpgraded && node instanceof HacknetServer) {
res.cache = node.cache;
res.hashCapacity = node.hashCapacity;
}
return res;
},
upgradeLevel: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeLevel", "i", _i);
const n = helper.number("upgradeLevel", "n", _n);
const node = getHacknetNode(i, "upgradeLevel");
return purchaseLevelUpgrade(player, node, n);
},
upgradeRam: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeRam", "i", _i);
const n = helper.number("upgradeRam", "n", _n);
const node = getHacknetNode(i, "upgradeRam");
return purchaseRamUpgrade(player, node, n);
},
upgradeCore: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeCore", "i", _i);
const n = helper.number("upgradeCore", "n", _n);
const node = getHacknetNode(i, "upgradeCore");
return purchaseCoreUpgrade(player, node, n);
},
upgradeCache: function (_i: unknown, _n: unknown = 1): boolean {
const i = helper.number("upgradeCache", "i", _i);
const n = helper.number("upgradeCache", "n", _n);
if (!hasHacknetServers(player)) {
return false;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.upgradeCache", () => "Can only be called on hacknet servers");
return false;
}
const res = purchaseCacheUpgrade(player, node, n);
if (res) {
updateHashManagerCapacity(player);
}
return res;
},
getLevelUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getLevelUpgradeCost", "i", _i);
const n = helper.number("getLevelUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeLevel");
return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult);
},
getRamUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getRamUpgradeCost", "i", _i);
const n = helper.number("getRamUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeRam");
return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult);
},
getCoreUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getCoreUpgradeCost", "i", _i);
const n = helper.number("getCoreUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeCore");
return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult);
},
getCacheUpgradeCost: function (_i: unknown, _n: unknown = 1): number {
const i = helper.number("getCacheUpgradeCost", "i", _i);
const n = helper.number("getCacheUpgradeCost", "n", _n);
if (!hasHacknetServers(player)) {
return Infinity;
}
const node = getHacknetNode(i, "upgradeCache");
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.getCacheUpgradeCost", () => "Can only be called on hacknet servers");
return -1;
}
return node.calculateCacheUpgradeCost(n);
},
numHashes: function (): number {
return res;
},
upgradeLevel:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseLevelUpgrade(player, node, n);
},
upgradeRam:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseRamUpgrade(player, node, n);
},
upgradeCore:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return purchaseCoreUpgrade(player, node, n);
},
upgradeCache:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): boolean => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
if (!hasHacknetServers(player)) {
return false;
}
const node = getHacknetNode(ctx, i);
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.upgradeCache", () => "Can only be called on hacknet servers");
return false;
}
const res = purchaseCacheUpgrade(player, node, n);
if (res) {
updateHashManagerCapacity(player);
}
return res;
},
getLevelUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult);
},
getRamUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult);
},
getCoreUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
const node = getHacknetNode(ctx, i);
return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult);
},
getCacheUpgradeCost:
(ctx: NetscriptContext) =>
(_i: unknown, _n: unknown = 1): number => {
const i = ctx.helper.number("i", _i);
const n = ctx.helper.number("n", _n);
if (!hasHacknetServers(player)) {
return Infinity;
}
const node = getHacknetNode(ctx, i);
if (!(node instanceof HacknetServer)) {
workerScript.log("hacknet.getCacheUpgradeCost", () => "Can only be called on hacknet servers");
return -1;
}
return node.calculateCacheUpgradeCost(n);
},
numHashes: () => (): number => {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.hashes;
},
hashCapacity: function (): number {
hashCapacity: () => (): number => {
if (!hasHacknetServers(player)) {
return 0;
}
return player.hashManager.capacity;
},
hashCost: function (_upgName: unknown): number {
const upgName = helper.string("hashCost", "upgName", _upgName);
if (!hasHacknetServers(player)) {
return Infinity;
}
hashCost:
(ctx: NetscriptContext) =>
(_upgName: unknown): number => {
const upgName = ctx.helper.string("upgName", _upgName);
if (!hasHacknetServers(player)) {
return Infinity;
}
return player.hashManager.getUpgradeCost(upgName);
},
spendHashes: function (_upgName: unknown, _upgTarget: unknown = ""): boolean {
const upgName = helper.string("spendHashes", "upgName", _upgName);
const upgTarget = helper.string("spendHashes", "upgTarget", _upgTarget);
if (!hasHacknetServers(player)) {
return false;
}
return purchaseHashUpgrade(player, upgName, upgTarget);
},
getHashUpgrades: function (): string[] {
return player.hashManager.getUpgradeCost(upgName);
},
spendHashes:
(ctx: NetscriptContext) =>
(_upgName: unknown, _upgTarget: unknown = ""): boolean => {
const upgName = ctx.helper.string("upgName", _upgName);
const upgTarget = ctx.helper.string("upgTarget", _upgTarget);
if (!hasHacknetServers(player)) {
return false;
}
return purchaseHashUpgrade(player, upgName, upgTarget);
},
getHashUpgrades: () => (): string[] => {
if (!hasHacknetServers(player)) {
return [];
}
return Object.values(HashUpgrades).map((upgrade: HashUpgrade) => upgrade.name);
},
getHashUpgradeLevel: function (_upgName: unknown): number {
const upgName = helper.string("getHashUpgradeLevel", "upgName", _upgName);
const level = player.hashManager.upgrades[upgName];
if (level === undefined) {
throw helper.makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`);
}
return level;
},
getStudyMult: function (): number {
getHashUpgradeLevel:
(ctx: NetscriptContext) =>
(_upgName: unknown): number => {
const upgName = ctx.helper.string("upgName", _upgName);
const level = player.hashManager.upgrades[upgName];
if (level === undefined) {
throw ctx.makeRuntimeErrorMsg(`Invalid Hash Upgrade: ${upgName}`);
}
return level;
},
getStudyMult: () => (): number => {
if (!hasHacknetServers(player)) {
return 1;
}
return player.hashManager.getStudyMult();
},
getTrainingMult: function (): number {
getTrainingMult: () => (): number => {
if (!hasHacknetServers(player)) {
return 1;
}

View File

@ -3,7 +3,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { purchaseAugmentation, joinFaction, getFactionAugmentationsFiltered } from "../Faction/FactionHelpers";
import { startWorkerScript } from "../NetscriptWorker";
import { Augmentation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { killWorkerScript } from "../Netscript/killWorkerScript";
@ -49,6 +49,7 @@ import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames";
import { enterBitNode } from "../RedPill";
import { FactionNames } from "../Faction/data/FactionNames";
import { ClassType, WorkType } from "../utils/WorkType";
export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI<ISingularity> {
const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation {
@ -56,7 +57,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid augmentation: '${name}'`);
}
return Augmentations[name];
return StaticAugmentations[name];
};
const getFaction = function (_ctx: NetscriptContext, name: string): Faction {
@ -83,7 +84,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
if (script.filename === cbScript) {
const ramUsage = script.ramUsage;
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable) {
if (ramUsage > ramAvailable + 0.001) {
return; // Not enough RAM
}
const runningScriptObj = new RunningScript(script, []); // No args
@ -122,7 +123,8 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return [aug.baseRepRequirement, aug.baseCost];
const costs = aug.getCost(player);
return [costs.repCost, costs.moneyCost];
},
getAugmentationPrereq: (_ctx: NetscriptContext) =>
function (_augName: unknown): string[] {
@ -136,14 +138,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return aug.baseCost;
return aug.getCost(player).moneyCost;
},
getAugmentationRepReq: (_ctx: NetscriptContext) =>
function (_augName: unknown): number {
_ctx.helper.checkSingularityAccess();
const augName = _ctx.helper.string("augName", _augName);
const aug = getAugmentation(_ctx, augName);
return aug.baseRepRequirement;
return aug.getCost(player).repCost;
},
getAugmentationStats: (_ctx: NetscriptContext) =>
function (_augName: unknown): AugmentationStats {
@ -183,7 +185,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
}
if (fac.playerReputation < aug.baseRepRequirement) {
if (fac.playerReputation < aug.getCost(player).repCost) {
_ctx.log(() => `You do not have enough reputation with '${fac.name}'.`);
return false;
}
@ -298,25 +300,25 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
let task = "";
let task: ClassType;
switch (className.toLowerCase()) {
case "Study Computer Science".toLowerCase():
task = CONSTANTS.ClassStudyComputerScience;
task = ClassType.StudyComputerScience;
break;
case "Data Structures".toLowerCase():
task = CONSTANTS.ClassDataStructures;
task = ClassType.DataStructures;
break;
case "Networks".toLowerCase():
task = CONSTANTS.ClassNetworks;
task = ClassType.Networks;
break;
case "Algorithms".toLowerCase():
task = CONSTANTS.ClassAlgorithms;
task = ClassType.Algorithms;
break;
case "Management".toLowerCase():
task = CONSTANTS.ClassManagement;
task = ClassType.Management;
break;
case "Leadership".toLowerCase():
task = CONSTANTS.ClassLeadership;
task = ClassType.Leadership;
break;
default:
_ctx.log(() => `Invalid class name: ${className}.`);
@ -415,19 +417,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
switch (stat.toLowerCase()) {
case "strength".toLowerCase():
case "str".toLowerCase():
player.startClass(costMult, expMult, CONSTANTS.ClassGymStrength);
player.startClass(costMult, expMult, ClassType.GymStrength);
break;
case "defense".toLowerCase():
case "def".toLowerCase():
player.startClass(costMult, expMult, CONSTANTS.ClassGymDefense);
player.startClass(costMult, expMult, ClassType.GymDefense);
break;
case "dexterity".toLowerCase():
case "dex".toLowerCase():
player.startClass(costMult, expMult, CONSTANTS.ClassGymDexterity);
player.startClass(costMult, expMult, ClassType.GymDexterity);
break;
case "agility".toLowerCase():
case "agi".toLowerCase():
player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility);
player.startClass(costMult, expMult, ClassType.GymAgility);
break;
default:
_ctx.log(() => `Invalid stat: ${stat}.`);
@ -650,11 +652,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
if (
!(
player.workType == CONSTANTS.WorkTypeFaction ||
player.workType == CONSTANTS.WorkTypeCompany ||
player.workType == CONSTANTS.WorkTypeCompanyPartTime ||
player.workType == CONSTANTS.WorkTypeCreateProgram ||
player.workType == CONSTANTS.WorkTypeStudyClass
player.workType === WorkType.Faction ||
player.workType === WorkType.Company ||
player.workType === WorkType.CompanyPartTime ||
player.workType === WorkType.CreateProgram ||
player.workType === WorkType.StudyClass
)
) {
throw _ctx.helper.makeRuntimeErrorMsg("Cannot change focus for current job");
@ -947,6 +949,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
}
return res;
},
quitJob: (_ctx: NetscriptContext) =>
function (_companyName: unknown): void {
_ctx.helper.checkSingularityAccess();
const companyName = _ctx.helper.string("companyName", _companyName);
player.quitJob(companyName);
},
getCompanyRep: (_ctx: NetscriptContext) =>
function (_companyName: unknown): number {
_ctx.helper.checkSingularityAccess();
@ -1079,7 +1087,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.log(() => `Invalid work type: '${type}`);
return false;
}
return true;
},
getFactionRep: (_ctx: NetscriptContext) =>
function (_facName: unknown): number {

View File

@ -1,11 +1,8 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { WorkerScript } from "../Netscript/WorkerScript";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { Augmentations } from "../Augmentation/Augmentations";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { CityName } from "../Locations/data/CityNames";
import { findCrime } from "../Crime/CrimeHelpers";
@ -17,22 +14,22 @@ import {
SleeveTask,
} from "../ScriptEditor/NetscriptDefinitions";
import { checkEnum } from "../utils/helpers/checkEnum";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve {
const checkSleeveAPIAccess = function (func: string): void {
export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
const checkSleeveAPIAccess = function (ctx: NetscriptContext): void {
if (player.bitNodeN !== 10 && !player.sourceFileLvl(10)) {
throw helper.makeRuntimeErrorMsg(
`sleeve.${func}`,
throw ctx.makeRuntimeErrorMsg(
"You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10",
);
}
};
const checkSleeveNumber = function (func: string, sleeveNumber: number): void {
const checkSleeveNumber = function (ctx: NetscriptContext, sleeveNumber: number): void {
if (sleeveNumber >= player.sleeves.length || sleeveNumber < 0) {
const msg = `Invalid sleeve number: ${sleeveNumber}`;
workerScript.log(func, () => msg);
throw helper.makeRuntimeErrorMsg(`sleeve.${func}`, msg);
ctx.log(() => msg);
throw ctx.makeRuntimeErrorMsg(msg);
}
};
@ -50,265 +47,268 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
};
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "sleeve", funcName));
return {
getNumSleeves: function (): number {
updateRam("getNumSleeves");
checkSleeveAPIAccess("getNumSleeves");
getNumSleeves: (ctx: NetscriptContext) => (): number => {
checkSleeveAPIAccess(ctx);
return player.sleeves.length;
},
setToShockRecovery: function (_sleeveNumber: unknown): boolean {
updateRam("setToShockRecovery");
const sleeveNumber = helper.number("setToShockRecovery", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("setToShockRecovery");
checkSleeveNumber("setToShockRecovery", sleeveNumber);
return player.sleeves[sleeveNumber].shockRecovery(player);
},
setToSynchronize: function (_sleeveNumber: unknown): boolean {
updateRam("setToSynchronize");
const sleeveNumber = helper.number("setToSynchronize", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("setToSynchronize");
checkSleeveNumber("setToSynchronize", sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player);
},
setToCommitCrime: function (_sleeveNumber: unknown, _crimeRoughName: unknown): boolean {
updateRam("setToCommitCrime");
const sleeveNumber = helper.number("setToCommitCrime", "sleeveNumber", _sleeveNumber);
const crimeRoughName = helper.string("setToCommitCrime", "crimeName", _crimeRoughName);
checkSleeveAPIAccess("setToCommitCrime");
checkSleeveNumber("setToCommitCrime", sleeveNumber);
const crime = findCrime(crimeRoughName);
if (crime === null) {
return false;
}
return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
},
setToUniversityCourse: function (_sleeveNumber: unknown, _universityName: unknown, _className: unknown): boolean {
updateRam("setToUniversityCourse");
const sleeveNumber = helper.number("setToUniversityCourse", "sleeveNumber", _sleeveNumber);
const universityName = helper.string("setToUniversityCourse", "universityName", _universityName);
const className = helper.string("setToUniversityCourse", "className", _className);
checkSleeveAPIAccess("setToUniversityCourse");
checkSleeveNumber("setToUniversityCourse", sleeveNumber);
return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className);
},
travel: function (_sleeveNumber: unknown, _cityName: unknown): boolean {
updateRam("travel");
const sleeveNumber = helper.number("travel", "sleeveNumber", _sleeveNumber);
const cityName = helper.string("travel", "cityName", _cityName);
checkSleeveAPIAccess("travel");
checkSleeveNumber("travel", sleeveNumber);
if (checkEnum(CityName, cityName)) {
return player.sleeves[sleeveNumber].travel(player, cityName);
} else {
throw helper.makeRuntimeErrorMsg("sleeve.setToCompanyWork", `Invalid city name: '${cityName}'.`);
}
},
setToCompanyWork: function (_sleeveNumber: unknown, acompanyName: unknown): boolean {
updateRam("setToCompanyWork");
const sleeveNumber = helper.number("setToCompanyWork", "sleeveNumber", _sleeveNumber);
const companyName = helper.string("setToCompanyWork", "companyName", acompanyName);
checkSleeveAPIAccess("setToCompanyWork");
checkSleeveNumber("setToCompanyWork", sleeveNumber);
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
setToShockRecovery:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].shockRecovery(player);
},
setToSynchronize:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].synchronize(player);
},
setToCommitCrime:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _crimeRoughName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const crimeRoughName = ctx.helper.string("crimeName", _crimeRoughName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const crime = findCrime(crimeRoughName);
if (crime === null) {
return false;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToCompanyWork",
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
return player.sleeves[sleeveNumber].commitCrime(player, crime.name);
},
setToUniversityCourse:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _universityName: unknown, _className: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const universityName = ctx.helper.string("universityName", _universityName);
const className = ctx.helper.string("className", _className);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].takeUniversityCourse(player, universityName, className);
},
travel:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _cityName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const cityName = ctx.helper.string("cityName", _cityName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
if (checkEnum(CityName, cityName)) {
return player.sleeves[sleeveNumber].travel(player, cityName);
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid city name: '${cityName}'.`);
}
},
setToCompanyWork:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, acompanyName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const companyName = ctx.helper.string("companyName", acompanyName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot work at the same company that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) {
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`,
);
}
}
return player.sleeves[sleeveNumber].workForCompany(player, companyName);
},
setToFactionWork:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _factionName: unknown, _workType: unknown): boolean | undefined => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const factionName = ctx.helper.string("factionName", _factionName);
const workType = ctx.helper.string("workType", _workType);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`,
);
}
}
if (player.gang && player.gang.facName == factionName) {
throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because you have started a gang with them.`,
);
}
}
return player.sleeves[sleeveNumber].workForCompany(player, companyName);
},
setToFactionWork: function (
_sleeveNumber: unknown,
_factionName: unknown,
_workType: unknown,
): boolean | undefined {
updateRam("setToFactionWork");
const sleeveNumber = helper.number("setToFactionWork", "sleeveNumber", _sleeveNumber);
const factionName = helper.string("setToFactionWork", "factionName", _factionName);
const workType = helper.string("setToFactionWork", "workType", _workType);
checkSleeveAPIAccess("setToFactionWork");
checkSleeveNumber("setToFactionWork", sleeveNumber);
return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType);
},
setToGymWorkout:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const gymName = ctx.helper.string("gymName", _gymName);
const stat = ctx.helper.string("stat", _stat);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
// Cannot work at the same faction that another sleeve is working at
for (let i = 0; i < player.sleeves.length; ++i) {
if (i === sleeveNumber) {
continue;
return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat);
},
getSleeveStats:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveSkills => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return getSleeveStats(sleeveNumber);
},
getTask:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveTask => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveInformation => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
tor: false,
city: sl.city,
hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(player.jobs),
maxHp: sl.max_hp,
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
charisma: sl.charisma_mult,
charismaExp: sl.charisma_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(player),
};
},
getSleeveAugmentations:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): string[] => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const augs = [];
for (let i = 0; i < player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(player.sleeves[sleeveNumber].augmentations[i].name);
}
const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`,
);
return augs;
},
getSleevePurchasableAugs:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): AugmentPair[] => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(player.sleeves[sleeveNumber], player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.baseCost,
});
}
}
if (player.gang && player.gang.facName == factionName) {
throw helper.makeRuntimeErrorMsg(
"sleeve.setToFactionWork",
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because you have started a gang with them.`,
);
}
return augs;
},
purchaseSleeveAug:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown, _augName: unknown): boolean => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
const augName = ctx.helper.string("augName", _augName);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
return player.sleeves[sleeveNumber].workForFaction(player, factionName, workType);
},
setToGymWorkout: function (_sleeveNumber: unknown, _gymName: unknown, _stat: unknown): boolean {
updateRam("setToGymWorkout");
const sleeveNumber = helper.number("setToGymWorkout", "sleeveNumber", _sleeveNumber);
const gymName = helper.string("setToGymWorkout", "gymName", _gymName);
const stat = helper.string("setToGymWorkout", "stat", _stat);
checkSleeveAPIAccess("setToGymWorkout");
checkSleeveNumber("setToGymWorkout", sleeveNumber);
if (getSleeveStats(sleeveNumber).shock > 0) {
throw ctx.makeRuntimeErrorMsg(`Sleeve shock too high: Sleeve ${sleeveNumber}`);
}
return player.sleeves[sleeveNumber].workoutAtGym(player, gymName, stat);
},
getSleeveStats: function (_sleeveNumber: unknown): SleeveSkills {
updateRam("getSleeveStats");
const sleeveNumber = helper.number("getSleeveStats", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleeveStats");
checkSleeveNumber("getSleeveStats", sleeveNumber);
return getSleeveStats(sleeveNumber);
},
getTask: function (_sleeveNumber: unknown): SleeveTask {
updateRam("getTask");
const sleeveNumber = helper.number("getTask", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getTask");
checkSleeveNumber("getTask", sleeveNumber);
const aug = StaticAugmentations[augName];
if (!aug) {
throw ctx.makeRuntimeErrorMsg(`Invalid aug: ${augName}`);
}
const sl = player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
};
},
getInformation: function (_sleeveNumber: unknown): SleeveInformation {
updateRam("getInformation");
const sleeveNumber = helper.number("getInformation", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getInformation");
checkSleeveNumber("getInformation", sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
tor: false,
city: sl.city,
hp: sl.hp,
jobs: Object.keys(player.jobs), // technically sleeves have the same jobs as the player.
jobTitle: Object.values(player.jobs),
maxHp: sl.max_hp,
mult: {
agility: sl.agility_mult,
agilityExp: sl.agility_exp_mult,
charisma: sl.charisma_mult,
charismaExp: sl.charisma_exp_mult,
companyRep: sl.company_rep_mult,
crimeMoney: sl.crime_money_mult,
crimeSuccess: sl.crime_success_mult,
defense: sl.defense_mult,
defenseExp: sl.defense_exp_mult,
dexterity: sl.dexterity_mult,
dexterityExp: sl.dexterity_exp_mult,
factionRep: sl.faction_rep_mult,
hacking: sl.hacking_mult,
hackingExp: sl.hacking_exp_mult,
strength: sl.strength_mult,
strengthExp: sl.strength_exp_mult,
workMoney: sl.work_money_mult,
},
timeWorked: sl.currentTaskTime,
earningsForSleeves: {
workHackExpGain: sl.earningsForSleeves.hack,
workStrExpGain: sl.earningsForSleeves.str,
workDefExpGain: sl.earningsForSleeves.def,
workDexExpGain: sl.earningsForSleeves.dex,
workAgiExpGain: sl.earningsForSleeves.agi,
workChaExpGain: sl.earningsForSleeves.cha,
workMoneyGain: sl.earningsForSleeves.money,
},
earningsForPlayer: {
workHackExpGain: sl.earningsForPlayer.hack,
workStrExpGain: sl.earningsForPlayer.str,
workDefExpGain: sl.earningsForPlayer.def,
workDexExpGain: sl.earningsForPlayer.dex,
workAgiExpGain: sl.earningsForPlayer.agi,
workChaExpGain: sl.earningsForPlayer.cha,
workMoneyGain: sl.earningsForPlayer.money,
},
earningsForTask: {
workHackExpGain: sl.earningsForTask.hack,
workStrExpGain: sl.earningsForTask.str,
workDefExpGain: sl.earningsForTask.def,
workDexExpGain: sl.earningsForTask.dex,
workAgiExpGain: sl.earningsForTask.agi,
workChaExpGain: sl.earningsForTask.cha,
workMoneyGain: sl.earningsForTask.money,
},
workRepGain: sl.getRepGain(player),
};
},
getSleeveAugmentations: function (_sleeveNumber: unknown): string[] {
updateRam("getSleeveAugmentations");
const sleeveNumber = helper.number("getSleeveAugmentations", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleeveAugmentations");
checkSleeveNumber("getSleeveAugmentations", sleeveNumber);
const augs = [];
for (let i = 0; i < player.sleeves[sleeveNumber].augmentations.length; i++) {
augs.push(player.sleeves[sleeveNumber].augmentations[i].name);
}
return augs;
},
getSleevePurchasableAugs: function (_sleeveNumber: unknown): AugmentPair[] {
updateRam("getSleevePurchasableAugs");
const sleeveNumber = helper.number("getSleevePurchasableAugs", "sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess("getSleevePurchasableAugs");
checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber);
const purchasableAugs = findSleevePurchasableAugs(player.sleeves[sleeveNumber], player);
const augs = [];
for (let i = 0; i < purchasableAugs.length; i++) {
const aug = purchasableAugs[i];
augs.push({
name: aug.name,
cost: aug.startingCost,
});
}
return augs;
},
purchaseSleeveAug: function (_sleeveNumber: unknown, _augName: unknown): boolean {
updateRam("purchaseSleeveAug");
const sleeveNumber = helper.number("purchaseSleeveAug", "sleeveNumber", _sleeveNumber);
const augName = helper.string("purchaseSleeveAug", "augName", _augName);
checkSleeveAPIAccess("purchaseSleeveAug");
checkSleeveNumber("purchaseSleeveAug", sleeveNumber);
if (getSleeveStats(sleeveNumber).shock > 0) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`);
}
const aug = Augmentations[augName];
if (!aug) {
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
}
return player.sleeves[sleeveNumber].tryBuyAugmentation(player, aug);
},
return player.sleeves[sleeveNumber].tryBuyAugmentation(player, aug);
},
};
}

View File

@ -5,6 +5,7 @@ import { netscriptDelay } from "../NetscriptEvaluator";
import { staneksGift } from "../CotMG/Helper";
import { Fragments, FragmentById } from "../CotMG/Fragment";
import { FragmentType } from "../CotMG/FragmentType";
import {
Fragment as IFragment,
@ -25,7 +26,7 @@ export function NetscriptStanek(
): InternalAPI<IStanek> {
function checkStanekAPIAccess(func: string): void {
if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) {
helper.makeRuntimeErrorMsg(func, "Requires Stanek's Gift installed.");
throw helper.makeRuntimeErrorMsg(func, "Stanek's Gift is not installed");
}
}
@ -42,15 +43,23 @@ export function NetscriptStanek(
},
chargeFragment: (_ctx: NetscriptContext) =>
function (_rootX: unknown, _rootY: unknown): Promise<void> {
//Get the fragment object using the given coordinates
const rootX = _ctx.helper.number("rootX", _rootX);
const rootY = _ctx.helper.number("rootY", _rootY);
checkStanekAPIAccess("chargeFragment");
const fragment = staneksGift.findFragment(rootX, rootY);
//Check whether the selected fragment can ge charged
if (!fragment) throw _ctx.makeRuntimeErrorMsg(`No fragment with root (${rootX}, ${rootY}).`);
if (fragment.fragment().type == FragmentType.Booster) {
throw _ctx.makeRuntimeErrorMsg(
`The fragment with root (${rootX}, ${rootY}) is a Booster Fragment and thus cannot be charged.`,
);
}
//Charge the fragment
const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () {
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment for ${charge} charge.`);
staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
_ctx.log(() => `Charged fragment with ${_ctx.workerScript.scriptRef.threads} threads.`);
return Promise.resolve();
});
},

View File

@ -1,7 +1,5 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import { StockMarket, SymbolToStockMap, placeOrder, cancelOrder, initStockMarketFn } from "../StockMarket/StockMarket";
import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/StockMarketHelpers";
@ -16,301 +14,286 @@ import {
} from "../StockMarket/StockMarketCosts";
import { Stock } from "../StockMarket/Stock";
import { TIX } from "../ScriptEditor/NetscriptDefinitions";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): TIX {
export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript): InternalAPI<TIX> {
/**
* Checks if the player has TIX API access. Throws an error if the player does not
*/
const checkTixApiAccess = function (callingFn: string): void {
const checkTixApiAccess = function (ctx: NetscriptContext): void {
if (!player.hasWseAccount) {
throw helper.makeRuntimeErrorMsg(callingFn, `You don't have WSE Access! Cannot use ${callingFn}()`);
throw ctx.makeRuntimeErrorMsg(`You don't have WSE Access! Cannot use ${ctx.function}()`);
}
if (!player.hasTixApiAccess) {
throw helper.makeRuntimeErrorMsg(callingFn, `You don't have TIX API Access! Cannot use ${callingFn}()`);
throw ctx.makeRuntimeErrorMsg(`You don't have TIX API Access! Cannot use ${ctx.function}()`);
}
};
const getStockFromSymbol = function (symbol: string, callingFn: string): Stock {
const getStockFromSymbol = function (ctx: NetscriptContext, symbol: string): Stock {
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helper.makeRuntimeErrorMsg(callingFn, `Invalid stock symbol: '${symbol}'`);
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: '${symbol}'`);
}
return stock;
};
const updateRam = (funcName: string): void =>
helper.updateDynamicRam(funcName, getRamCost(player, "stock", funcName));
return {
getSymbols: function (): string[] {
updateRam("getSymbols");
checkTixApiAccess("getSymbols");
getSymbols: (ctx: NetscriptContext) => (): string[] => {
checkTixApiAccess(ctx);
return Object.values(StockSymbols);
},
getPrice: function (_symbol: unknown): number {
updateRam("getPrice");
const symbol = helper.string("getPrice", "symbol", _symbol);
checkTixApiAccess("getPrice");
const stock = getStockFromSymbol(symbol, "getPrice");
getPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.price;
},
getAskPrice: function (_symbol: unknown): number {
updateRam("getAskPrice");
const symbol = helper.string("getAskPrice", "symbol", _symbol);
checkTixApiAccess("getAskPrice");
const stock = getStockFromSymbol(symbol, "getAskPrice");
return stock.price;
},
getAskPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getAskPrice();
},
getBidPrice: function (_symbol: unknown): number {
updateRam("getBidPrice");
const symbol = helper.string("getBidPrice", "symbol", _symbol);
checkTixApiAccess("getBidPrice");
const stock = getStockFromSymbol(symbol, "getBidPrice");
return stock.getAskPrice();
},
getBidPrice:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return stock.getBidPrice();
},
getPosition: function (_symbol: unknown): [number, number, number, number] {
updateRam("getPosition");
const symbol = helper.string("getPosition", "symbol", _symbol);
checkTixApiAccess("getPosition");
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw helper.makeRuntimeErrorMsg("getPosition", `Invalid stock symbol: ${symbol}`);
}
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
},
getMaxShares: function (_symbol: unknown): number {
updateRam("getMaxShares");
const symbol = helper.string("getMaxShares", "symbol", _symbol);
checkTixApiAccess("getMaxShares");
const stock = getStockFromSymbol(symbol, "getMaxShares");
return stock.maxShares;
},
getPurchaseCost: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
updateRam("getPurchaseCost");
const symbol = helper.string("getPurchaseCost", "symbol", _symbol);
let shares = helper.number("getPurchaseCost", "shares", _shares);
const posType = helper.string("getPurchaseCost", "posType", _posType);
checkTixApiAccess("getPurchaseCost");
const stock = getStockFromSymbol(symbol, "getPurchaseCost");
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return Infinity;
}
const res = getBuyTransactionCost(stock, shares, pos);
if (res == null) {
return Infinity;
}
return res;
},
getSaleGain: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
updateRam("getSaleGain");
const symbol = helper.string("getSaleGain", "symbol", _symbol);
let shares = helper.number("getSaleGain", "shares", _shares);
const posType = helper.string("getSaleGain", "posType", _posType);
checkTixApiAccess("getSaleGain");
const stock = getStockFromSymbol(symbol, "getSaleGain");
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return 0;
}
const res = getSellTransactionGain(stock, shares, pos);
if (res == null) {
return 0;
}
return res;
},
buy: function (_symbol: unknown, _shares: unknown): number {
updateRam("buy");
const symbol = helper.string("buy", "symbol", _symbol);
const shares = helper.number("buy", "shares", _shares);
checkTixApiAccess("buy");
const stock = getStockFromSymbol(symbol, "buy");
const res = buyStock(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
sell: function (_symbol: unknown, _shares: unknown): number {
updateRam("sell");
const symbol = helper.string("sell", "symbol", _symbol);
const shares = helper.number("sell", "shares", _shares);
checkTixApiAccess("sell");
const stock = getStockFromSymbol(symbol, "sell");
const res = sellStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
short: function (_symbol: unknown, _shares: unknown): number {
updateRam("short");
const symbol = helper.string("short", "symbol", _symbol);
const shares = helper.number("short", "shares", _shares);
checkTixApiAccess("short");
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw helper.makeRuntimeErrorMsg(
"short",
"You must either be in BitNode-8 or you must have Source-File 8 Level 2.",
);
return stock.getBidPrice();
},
getPosition:
(ctx: NetscriptContext) =>
(_symbol: unknown): [number, number, number, number] => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = SymbolToStockMap[symbol];
if (stock == null) {
throw ctx.makeRuntimeErrorMsg(`Invalid stock symbol: ${symbol}`);
}
}
const stock = getStockFromSymbol(symbol, "short");
const res = shortStock(stock, shares, workerScript, {});
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
},
getMaxShares:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
return res ? stock.getBidPrice() : 0;
},
sellShort: function (_symbol: unknown, _shares: unknown): number {
updateRam("sellShort");
const symbol = helper.string("sellShort", "symbol", _symbol);
const shares = helper.number("sellShort", "shares", _shares);
checkTixApiAccess("sellShort");
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw helper.makeRuntimeErrorMsg(
"sellShort",
"You must either be in BitNode-8 or you must have Source-File 8 Level 2.",
);
return stock.maxShares;
},
getPurchaseCost:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return Infinity;
}
}
const stock = getStockFromSymbol(symbol, "sellShort");
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
placeOrder: function (_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean {
updateRam("placeOrder");
const symbol = helper.string("placeOrder", "symbol", _symbol);
const shares = helper.number("placeOrder", "shares", _shares);
const price = helper.number("placeOrder", "price", _price);
const type = helper.string("placeOrder", "type", _type);
const pos = helper.string("placeOrder", "pos", _pos);
checkTixApiAccess("placeOrder");
const res = getBuyTransactionCost(stock, shares, pos);
if (res == null) {
return Infinity;
}
return res;
},
getSaleGain:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _posType: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
let shares = ctx.helper.number("shares", _shares);
const posType = ctx.helper.string("posType", _posType);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
shares = Math.round(shares);
let pos;
const sanitizedPosType = posType.toLowerCase();
if (sanitizedPosType.includes("l")) {
pos = PositionTypes.Long;
} else if (sanitizedPosType.includes("s")) {
pos = PositionTypes.Short;
} else {
return 0;
}
const res = getSellTransactionGain(stock, shares, pos);
if (res == null) {
return 0;
}
return res;
},
buy:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = buyStock(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
sell:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
const stock = getStockFromSymbol(ctx, symbol);
const res = sellStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
short:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = shortStock(stock, shares, workerScript, {});
return res ? stock.getBidPrice() : 0;
},
sellShort:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 1) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 2.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
const res = sellShort(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0;
},
placeOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
},
cancelOrder:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean => {
const symbol = ctx.helper.string("symbol", _symbol);
const shares = ctx.helper.number("shares", _shares);
const price = ctx.helper.number("price", _price);
const type = ctx.helper.string("type", _type);
const pos = ctx.helper.string("pos", _pos);
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or you must have Source-File 8 Level 3.");
}
}
const stock = getStockFromSymbol(ctx, symbol);
if (isNaN(shares) || isNaN(price)) {
throw ctx.makeRuntimeErrorMsg(`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`);
}
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw ctx.makeRuntimeErrorMsg(`Invalid position type: ${pos}`);
}
const params = {
stock: stock,
shares: shares,
price: price,
type: orderType,
pos: orderPos,
};
return cancelOrder(params, workerScript);
},
getOrders: (ctx: NetscriptContext) => (): any => {
checkTixApiAccess(ctx);
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"placeOrder",
"You must either be in BitNode-8 or you must have Source-File 8 Level 3.",
);
}
}
const stock = getStockFromSymbol(symbol, "placeOrder");
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("placeOrder", `Invalid position type: ${pos}`);
}
return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
},
cancelOrder: function (
_symbol: unknown,
_shares: unknown,
_price: unknown,
_type: unknown,
_pos: unknown,
): boolean {
updateRam("cancelOrder");
const symbol = helper.string("cancelOrder", "symbol", _symbol);
const shares = helper.number("cancelOrder", "shares", _shares);
const price = helper.number("cancelOrder", "price", _price);
const type = helper.string("cancelOrder", "type", _type);
const pos = helper.string("cancelOrder", "pos", _pos);
checkTixApiAccess("cancelOrder");
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"cancelOrder",
"You must either be in BitNode-8 or you must have Source-File 8 Level 3.",
);
}
}
const stock = getStockFromSymbol(symbol, "cancelOrder");
if (isNaN(shares) || isNaN(price)) {
throw helper.makeRuntimeErrorMsg(
"cancelOrder",
`Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`,
);
}
let orderType;
let orderPos;
const ltype = type.toLowerCase();
if (ltype.includes("limit") && ltype.includes("buy")) {
orderType = OrderTypes.LimitBuy;
} else if (ltype.includes("limit") && ltype.includes("sell")) {
orderType = OrderTypes.LimitSell;
} else if (ltype.includes("stop") && ltype.includes("buy")) {
orderType = OrderTypes.StopBuy;
} else if (ltype.includes("stop") && ltype.includes("sell")) {
orderType = OrderTypes.StopSell;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid order type: ${type}`);
}
const lpos = pos.toLowerCase();
if (lpos.includes("l")) {
orderPos = PositionTypes.Long;
} else if (lpos.includes("s")) {
orderPos = PositionTypes.Short;
} else {
throw helper.makeRuntimeErrorMsg("cancelOrder", `Invalid position type: ${pos}`);
}
const params = {
stock: stock,
shares: shares,
price: price,
type: orderType,
pos: orderPos,
};
return cancelOrder(params, workerScript);
},
getOrders: function (): any {
updateRam("getOrders");
checkTixApiAccess("getOrders");
if (player.bitNodeN !== 8) {
if (player.sourceFileLvl(8) <= 2) {
throw helper.makeRuntimeErrorMsg(
"getOrders",
"You must either be in BitNode-8 or have Source-File 8 Level 3.",
);
throw ctx.makeRuntimeErrorMsg("You must either be in BitNode-8 or have Source-File 8 Level 3.");
}
}
@ -334,103 +317,95 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return orders;
},
getVolatility: function (_symbol: unknown): number {
updateRam("getVolatility");
const symbol = helper.string("getVolatility", "symbol", _symbol);
if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(symbol, "getVolatility");
getVolatility:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(ctx, symbol);
return stock.mv / 100; // Convert from percentage to decimal
},
getForecast: function (_symbol: unknown): number {
updateRam("getForecast");
const symbol = helper.string("getForecast", "symbol", _symbol);
if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(symbol, "getForecast");
let forecast = 50;
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal
},
purchase4SMarketData: function (): boolean {
updateRam("purchase4SMarketData");
return stock.mv / 100; // Convert from percentage to decimal
},
getForecast:
(ctx: NetscriptContext) =>
(_symbol: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);
if (!player.has4SDataTixApi) {
throw ctx.makeRuntimeErrorMsg("You don't have 4S Market Data TIX API Access!");
}
const stock = getStockFromSymbol(ctx, symbol);
let forecast = 50;
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal
},
purchase4SMarketData: (ctx: NetscriptContext) => (): boolean => {
if (player.has4SData) {
workerScript.log("stock.purchase4SMarketData", () => "Already purchased 4S Market Data.");
ctx.log(() => "Already purchased 4S Market Data.");
return true;
}
if (player.money < getStockMarket4SDataCost()) {
workerScript.log("stock.purchase4SMarketData", () => "Not enough money to purchase 4S Market Data.");
ctx.log(() => "Not enough money to purchase 4S Market Data.");
return false;
}
player.has4SData = true;
player.loseMoney(getStockMarket4SDataCost(), "stock");
workerScript.log("stock.purchase4SMarketData", () => "Purchased 4S Market Data");
ctx.log(() => "Purchased 4S Market Data");
return true;
},
purchase4SMarketDataTixApi: function (): boolean {
updateRam("purchase4SMarketDataTixApi");
checkTixApiAccess("purchase4SMarketDataTixApi");
purchase4SMarketDataTixApi: (ctx: NetscriptContext) => (): boolean => {
checkTixApiAccess(ctx);
if (player.has4SDataTixApi) {
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Already purchased 4S Market Data TIX API");
ctx.log(() => "Already purchased 4S Market Data TIX API");
return true;
}
if (player.money < getStockMarket4STixApiCost()) {
workerScript.log(
"stock.purchase4SMarketDataTixApi",
() => "Not enough money to purchase 4S Market Data TIX API",
);
ctx.log(() => "Not enough money to purchase 4S Market Data TIX API");
return false;
}
player.has4SDataTixApi = true;
player.loseMoney(getStockMarket4STixApiCost(), "stock");
workerScript.log("stock.purchase4SMarketDataTixApi", () => "Purchased 4S Market Data TIX API");
ctx.log(() => "Purchased 4S Market Data TIX API");
return true;
},
purchaseWseAccount: function (): boolean {
updateRam("PurchaseWseAccount");
purchaseWseAccount: (ctx: NetscriptContext) => (): boolean => {
if (player.hasWseAccount) {
workerScript.log("stock.purchaseWseAccount", () => "Already purchased WSE Account");
ctx.log(() => "Already purchased WSE Account");
return true;
}
if (player.money < getStockMarketWseCost()) {
workerScript.log("stock.purchaseWseAccount", () => "Not enough money to purchase WSE Account Access");
ctx.log(() => "Not enough money to purchase WSE Account Access");
return false;
}
player.hasWseAccount = true;
initStockMarketFn();
player.loseMoney(getStockMarketWseCost(), "stock");
workerScript.log("stock.purchaseWseAccount", () => "Purchased WSE Account Access");
ctx.log(() => "Purchased WSE Account Access");
return true;
},
purchaseTixApi: function (): boolean {
updateRam("purchaseTixApi");
purchaseTixApi: (ctx: NetscriptContext) => (): boolean => {
if (player.hasTixApiAccess) {
workerScript.log("stock.purchaseTixApi", () => "Already purchased TIX API");
ctx.log(() => "Already purchased TIX API");
return true;
}
if (player.money < getStockMarketTixApiCost()) {
workerScript.log("stock.purchaseTixApi", () => "Not enough money to purchase TIX API Access");
ctx.log(() => "Not enough money to purchase TIX API Access");
return false;
}
player.hasTixApiAccess = true;
player.loseMoney(getStockMarketTixApiCost(), "stock");
workerScript.log("stock.purchaseTixApi", () => "Purchased TIX API");
ctx.log(() => "Purchased TIX API");
return true;
},
};

View File

@ -1,7 +1,3 @@
import { INetscriptHelper } from "./INetscriptHelper";
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { getRamCost } from "../Netscript/RamCostGenerator";
import {
GameInfo,
IStyleSettings,
@ -14,88 +10,81 @@ import { defaultTheme } from "../Themes/Themes";
import { defaultStyles } from "../Themes/Styles";
import { CONSTANTS } from "../Constants";
import { hash } from "../hash/hash";
import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { Terminal } from "../../src/Terminal";
export function NetscriptUserInterface(
player: IPlayer,
workerScript: WorkerScript,
helper: INetscriptHelper,
): IUserInterface {
const updateRam = (funcName: string): void => helper.updateDynamicRam(funcName, getRamCost(player, "ui", funcName));
export function NetscriptUserInterface(): InternalAPI<IUserInterface> {
return {
getTheme: function (): UserInterfaceTheme {
updateRam("getTheme");
getTheme: () => (): UserInterfaceTheme => {
return { ...Settings.theme };
},
getStyles: function (): IStyleSettings {
updateRam("getStyles");
getStyles: () => (): IStyleSettings => {
return { ...Settings.styles };
},
setTheme: function (newTheme: UserInterfaceTheme): void {
updateRam("setTheme");
const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/;
const currentTheme = { ...Settings.theme };
const errors: string[] = [];
for (const key of Object.keys(newTheme)) {
if (!currentTheme[key]) {
// Invalid key
errors.push(`Invalid key "${key}"`);
} else if (!hex.test(newTheme[key] ?? "")) {
errors.push(`Invalid color "${key}": ${newTheme[key]}`);
} else {
currentTheme[key] = newTheme[key];
setTheme:
(ctx: NetscriptContext) =>
(newTheme: UserInterfaceTheme): void => {
const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/;
const currentTheme = { ...Settings.theme };
const errors: string[] = [];
for (const key of Object.keys(newTheme)) {
if (!currentTheme[key]) {
// Invalid key
errors.push(`Invalid key "${key}"`);
} else if (!hex.test(newTheme[key] ?? "")) {
errors.push(`Invalid color "${key}": ${newTheme[key]}`);
} else {
currentTheme[key] = newTheme[key];
}
}
}
if (errors.length === 0) {
Object.assign(Settings.theme, currentTheme);
ThemeEvents.emit();
workerScript.log("ui.setTheme", () => `Successfully set theme`);
} else {
workerScript.log("ui.setTheme", () => `Failed to set theme. Errors: ${errors.join(", ")}`);
}
},
setStyles: function (newStyles: IStyleSettings): void {
updateRam("setStyles");
const currentStyles = { ...Settings.styles };
const errors: string[] = [];
for (const key of Object.keys(newStyles)) {
if (!(currentStyles as any)[key]) {
// Invalid key
errors.push(`Invalid key "${key}"`);
if (errors.length === 0) {
Object.assign(Settings.theme, currentTheme);
ThemeEvents.emit();
ctx.log(() => `Successfully set theme`);
} else {
(currentStyles as any)[key] = (newStyles as any)[key];
ctx.log(() => `Failed to set theme. Errors: ${errors.join(", ")}`);
}
}
},
if (errors.length === 0) {
Object.assign(Settings.styles, currentStyles);
ThemeEvents.emit();
workerScript.log("ui.setStyles", () => `Successfully set styles`);
} else {
workerScript.log("ui.setStyles", () => `Failed to set styles. Errors: ${errors.join(", ")}`);
}
},
setStyles:
(ctx: NetscriptContext) =>
(newStyles: IStyleSettings): void => {
const currentStyles = { ...Settings.styles };
const errors: string[] = [];
for (const key of Object.keys(newStyles)) {
if (!(currentStyles as any)[key]) {
// Invalid key
errors.push(`Invalid key "${key}"`);
} else {
(currentStyles as any)[key] = (newStyles as any)[key];
}
}
resetTheme: function (): void {
updateRam("resetTheme");
if (errors.length === 0) {
Object.assign(Settings.styles, currentStyles);
ThemeEvents.emit();
ctx.log(() => `Successfully set styles`);
} else {
ctx.log(() => `Failed to set styles. Errors: ${errors.join(", ")}`);
}
},
resetTheme: (ctx: NetscriptContext) => (): void => {
Settings.theme = { ...defaultTheme };
ThemeEvents.emit();
workerScript.log("ui.resetTheme", () => `Reinitialized theme to default`);
ctx.log(() => `Reinitialized theme to default`);
},
resetStyles: function (): void {
updateRam("resetStyles");
resetStyles: (ctx: NetscriptContext) => (): void => {
Settings.styles = { ...defaultStyles };
ThemeEvents.emit();
workerScript.log("ui.resetStyles", () => `Reinitialized styles to default`);
ctx.log(() => `Reinitialized styles to default`);
},
getGameInfo: function (): GameInfo {
updateRam("getGameInfo");
getGameInfo: () => (): GameInfo => {
const version = CONSTANTS.VersionString;
const commit = hash();
const platform = navigator.userAgent.toLowerCase().indexOf(" electron/") > -1 ? "Steam" : "Browser";
@ -108,5 +97,10 @@ export function NetscriptUserInterface(
return gameInfo;
},
clearTerminal: (ctx: NetscriptContext) => (): void => {
ctx.log(() => `Clearing terminal`);
Terminal.clear();
},
};
}

View File

@ -532,7 +532,7 @@ function createAndAddWorkerScript(
const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj);
const ramUsage = roundToTwo(oneRamUsage * threads);
const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable) {
if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate(
`Not enough RAM to run script ${runningScriptObj.filename} with args ` +
`${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` +
@ -750,7 +750,7 @@ export function runScriptFromScript(
if (server.hasAdminRights == false) {
workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`);
return 0;
} else if (ramUsage > ramAvailable) {
} else if (ramUsage > ramAvailable + 0.001) {
workerScript.log(
caller,
() =>

View File

@ -18,7 +18,7 @@ export class GraftableAugmentation {
}
get cost(): number {
return this.augmentation.startingCost * CONSTANTS.AugmentationGraftingCostMult;
return this.augmentation.baseCost * CONSTANTS.AugmentationGraftingCostMult;
}
get time(): number {

View File

@ -1,14 +1,12 @@
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
import { GraftableAugmentation } from "./GraftableAugmentation";
import { IPlayer } from "../IPlayer";
export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
const augs: string[] = [];
for (const [augName, aug] of Object.entries(Augmentations)) {
if (augName === AugmentationNames.NeuroFluxGovernor || augName === AugmentationNames.TheRedPill || aug.isSpecial)
continue;
for (const [augName, aug] of Object.entries(StaticAugmentations)) {
if (aug.isSpecial) continue;
augs.push(augName);
}

View File

@ -1,13 +1,14 @@
import { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material";
import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material";
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { Augmentations } from "../../../Augmentation/Augmentations";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
import { CONSTANTS } from "../../../Constants";
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
import { LocationName } from "../../../Locations/data/LocationNames";
import { Locations } from "../../../Locations/Locations";
import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums";
import { Settings } from "../../../Settings/Settings";
import { IMap } from "../../../types";
import { use } from "../../../ui/Context";
@ -15,8 +16,8 @@ import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money";
import { convertTimeMsToTimeElapsedString, formatNumber } from "../../../utils/StringHelperFunctions";
import { IPlayer } from "../../IPlayer";
import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../GraftingHelpers";
import { GraftableAugmentation } from "../GraftableAugmentation";
import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers";
const GraftableAugmentations: IMap<GraftableAugmentation> = {};
@ -54,7 +55,7 @@ export const GraftingRoot = (): React.ReactElement => {
const player = use.Player();
const router = use.Router();
for (const aug of Object.values(Augmentations)) {
for (const aug of Object.values(StaticAugmentations)) {
const name = aug.name;
const graftableAug = new GraftableAugmentation(aug);
GraftableAugmentations[name] = graftableAug;
@ -62,12 +63,28 @@ export const GraftingRoot = (): React.ReactElement => {
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false);
const selectedAugmentation = StaticAugmentations[selectedAug];
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const getAugsSorted = (): string[] => {
const augs = getGraftingAvailableAugs(player);
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost:
return augs.sort((a, b) => GraftableAugmentations[a].cost - GraftableAugmentations[b].cost);
default:
return augs;
}
};
const switchSortOrder = (newOrder: PurchaseAugmentationsOrderSetting): void => {
Settings.PurchaseAugmentationsOrder = newOrder;
rerender();
};
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
@ -90,13 +107,31 @@ export const GraftingRoot = (): React.ReactElement => {
</Typography>
<Box sx={{ my: 3 }}>
<Typography variant="h5">Graft Augmentations</Typography>
<Paper sx={{ p: 1 }}>
<Typography variant="h5">Graft Augmentations</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button sx={{ width: "100%" }} onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>
Sort by Cost
</Button>
<Button sx={{ width: "100%" }} onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}>
Sort by Default Order
</Button>
</Box>
</Paper>
{getGraftingAvailableAugs(player).length > 0 ? (
<Paper sx={{ my: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<Paper sx={{ mb: 1, width: "fit-content", display: "grid", gridTemplateColumns: "1fr 3fr" }}>
<List sx={{ height: 400, overflowY: "scroll", borderRight: `1px solid ${Settings.theme.welllight}` }}>
{getGraftingAvailableAugs(player).map((k, i) => (
{getAugsSorted().map((k, i) => (
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography>{k}</Typography>
<Typography
sx={{
color: canGraft(player, GraftableAugmentations[k])
? Settings.theme.primary
: Settings.theme.disabled,
}}
>
{k}
</Typography>
</ListItemButton>
))}
</List>
@ -139,34 +174,41 @@ export const GraftingRoot = (): React.ReactElement => {
</>
}
/>
<Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString(
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]),
<Box sx={{ maxHeight: 330, overflowY: "scroll" }}>
<Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString(
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]),
)}
{/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography>
{selectedAugmentation.prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={selectedAugmentation} />
)}
{/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography>
{Augmentations[selectedAug].prereqs.length > 0 && (
<AugPreReqsChecklist player={player} aug={Augmentations[selectedAug]} />
)}
<br />
<Typography sx={{ maxHeight: 305, overflowY: "scroll" }}>
{(() => {
const aug = Augmentations[selectedAug];
<br />
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
const tooltip = (
<>
{info}
<br />
<br />
{aug.stats}
</>
);
return tooltip;
})()}
</Typography>
<Typography>
{(() => {
const info =
typeof selectedAugmentation.info === "string" ? (
<span>{selectedAugmentation.info}</span>
) : (
selectedAugmentation.info
);
const tooltip = (
<>
{info}
<br />
<br />
{selectedAugmentation.stats}
</>
);
return tooltip;
})()}
</Typography>
</Box>
</Box>
</Paper>
) : (

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