Compare commits

...

1 Commits

Author SHA1 Message Date
hydroflame
6d8e1a6d24 Factory squash 2025-08-14 15:01:51 -04:00
52 changed files with 2755 additions and 85 deletions

View File

@@ -192,9 +192,9 @@ async function pushSaveDataToSteamCloud(saveData, currentPlayerId) {
*
* Instead of implementing it, the old code (encoding in base64) is used here for backward compatibility.
*/
const content = Buffer.from(saveData).toString("base64");
log.debug(`saveData: ${saveData.length} bytes`);
log.debug(`Base64 string of saveData: ${content.length} bytes`);
const content = saveData.toString("base64");
log.debug(`Uncompressed: ${saveData.length} bytes`);
log.debug(`Compressed: ${content.length} bytes`);
log.debug(`Saving to Steam Cloud as ${steamSaveName}`);
try {

View File

@@ -2,6 +2,7 @@ import React from "react";
import { Player } from "@player";
import { CityName, FactionName } from "@enums";
import { BitNodeMultipliers, replaceCurrentNodeMults } from "./BitNodeMultipliers";
import { CorruptableText } from "../ui/React/CorruptableText";
class BitNode {
// A short description, or tagline, about the BitNode
@@ -480,6 +481,19 @@ export function initBitNodes() {
</>
),
);
BitNodes.BitNode19 = new BitNode(
19,
2,
"MyrianOS",
"l̷i̵g̵h̴t̵ ̴a̷t̸ ̶t̵h̵e̸ ̶e̷n̵d̶ ̶o̸f̶ ̸t̴h̸e̴ ̸t̷u̶n̸n̸e̷l̵.̷",
(
<CorruptableText
content={`yNjHLAgecI ASW1fQdKx5 n9DQ3rmHp3 mnv0XEdwH2 sBkAlBOPhx NohIDL9eRy TbIl8U3WKz 1wjnJ9iuwS VML36vYLNH K06StviNvI cRboTarefZ 7BSNntPpJj DfayVbfxU6 46xvOPQd2Y Ogyj2gnyLr FIND THE GLITCH IN ISHIMA S6E0Vpmxk6 GTF9dWvE6n EEGg7xvtYR Um8YIC0Qww PG4vauBKBk JWG8V1j5Z5 bfYYTTFnBY 7uoicoqIaV IeUu0F42aA EhTF7Fkxyt OBYgGSu0es bJQpenVoO6 L9cL39tRhh xfLroUMvY8 xmMckUHLSQ`}
spoiler={false}
/>
),
);
}
export const defaultMultipliers = new BitNodeMultipliers();
@@ -987,6 +1001,64 @@ export function getBitNodeMultipliers(n: number, lvl: number): BitNodeMultiplier
WorldDaemonDifficulty: 5,
});
}
case 19: {
return new BitNodeMultipliers({
MyrianPower: 10,
AgilityLevelMultiplier: 0.01,
AugmentationMoneyCost: 100,
AugmentationRepCost: 100,
BladeburnerRank: 0.01,
BladeburnerSkillCost: 100,
CharismaLevelMultiplier: 0.01,
ClassGymExpGain: 0.01,
CodingContractMoney: 0.01,
CompanyWorkExpGain: 0.01,
CompanyWorkMoney: 0.01,
CompanyWorkRepGain: 0.01,
CorporationValuation: 0.01,
CrimeExpGain: 0.01,
CrimeMoney: 0.01,
CrimeSuccessRate: 0.01,
DaedalusAugsRequirement: 100,
DefenseLevelMultiplier: 0.01,
DexterityLevelMultiplier: 0.01,
FactionPassiveRepGain: 0.01,
FactionWorkExpGain: 0.01,
FactionWorkRepGain: 0.01,
FourSigmaMarketDataApiCost: 100,
FourSigmaMarketDataCost: 100,
GangSoftcap: 0.01,
GangUniqueAugs: 0.01,
GoPower: 0.01,
HackExpGain: 0.01,
HackingLevelMultiplier: 0.01,
HackingSpeedMultiplier: 0.01,
HacknetNodeMoney: 0.01,
HomeComputerRamCost: 100,
InfiltrationMoney: 0.01,
InfiltrationRep: 0.01,
ManualHackMoney: 0.01,
PurchasedServerCost: 100,
PurchasedServerSoftcap: 100,
PurchasedServerLimit: 0.01,
PurchasedServerMaxRam: 0.01,
RepToDonateToFaction: 10000,
ScriptHackMoney: 0.01,
ScriptHackMoneyGain: 0.01,
ServerGrowthRate: 0.01,
ServerMaxMoney: 0.01,
ServerStartingMoney: 0.01,
ServerStartingSecurity: 100,
ServerWeakenRate: 0.01,
StrengthLevelMultiplier: 0.01,
StaneksGiftPowerMultiplier: 0.01,
StaneksGiftExtraSize: -100,
WorldDaemonDifficulty: 100,
CorporationSoftcap: 0.01,
CorporationDivisions: 0.01,
});
}
default: {
throw new Error("Invalid BitNodeN");
}

View File

@@ -114,6 +114,9 @@ export class BitNodeMultipliers {
*/
ManualHackMoney = 1;
/** Influence how strongly Myrian improves bitnode multipliers */
MyrianPower = 1;
/** Influence how much it costs to purchase a server */
PurchasedServerCost = 1;

View File

@@ -236,7 +236,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | <BitNodePortal n={19} level={n(19)} flume={props.flume} destroyedBitNode={destroyed} /> / | O | | O | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | <BitNodePortal n={14} level={n(14)} flume={props.flume} destroyedBitNode={destroyed} /> | | O__/ | / \__ | | O | | | O </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>

View File

@@ -156,48 +156,10 @@ export const CONSTANTS: {
// Also update doc/source/changelog.rst
LatestUpdate: `
## v2.6.2 dev - Last update 4 June 2024
## v2.6.2 dev - Last update 22 May 2024
See 2.6.1 changelog at https://github.com/bitburner-official/bitburner-src/blob/v2.6.1/src/Documentation/doc/changelog.md
### CHANGES
- Hotfix (also backported to 2.6.1): Fixed an issue with invalid format on steam cloud save (@catloversg)
- Augmentations: Adjusted handling of augmentations that affect starting money or programs (@jjclark1982)
- Coding Contracts: Improved the performance of the All Valid Math Expressions contract checker (@yichizhng)
- Coding Contracts: Simplified the Shortest Path contract checker (@gmcew)
- Coding Contracts: Clarification on HammingCodes: Encoded Binary to Integer description (@gmcew)
- Faction: Fixed some edge cases around Favor overflow (@catloversg)
- Faction Invites: Code refactoring, all available invites are sent at once (@catloversg)
- Faction UI: show which skills are relevant for each type of Faction work (@gmcew)
- Font: Embedded the JetBrains Mono font as "JetBrainsMono" (@catloversg)
- Go: Support playing manually as white against your own scripts (@ficocelliguy)
- Go: Save a full game history to prevent repeat moves (@ficocelliguy)
- Infiltration: Updated Slash game text to be less confusing (@catloversg)
- Netscript API docs: Fixed some invalid usage issues + general type improvements (@catloversg, @ficocelliguy)
- Programs UI: Changed time elapsed display to time left (@TheAimMan)
- Servers: Game servers can now start with more than 1 core (@TheAimMan)
- Scripts: Relative imports should now work correctly (@Caldwell-74)
- Script Editor: Improved detection of possible infinite loops (@G4mingJon4s)
- Script Editor: should now remember cursor location when switching tabs or game pages (@catloversg)
- Skill XP: Fix an issue where in some cases, too much experience was needed to raise a skill from 1 to 2 (@catloversg)
- Terminal: Improved autocompletion code for mixed case strings (@yichizhng)
- Codebase: Partial migration away from outdated mui/styles (@Caldwell-74)
### SPOILER CHANGES
- Bladeburner: Added a button to stop the current action (@Kelenius)
- Bladeburner UI: Display Black Operations in the expected order (@catloversg)
- Corporation: Allow mass discarding products by selling for 0 (@gmcew)
- Grafting: Fixed a spacing issue (@Sphyxis)
- Grafting/Hacknet: Fixed an issue that could cause hacknet node production to be inaccurrate when combined with Grafting (@catloversg)
- Grafting: Fixed an issue that could cause inaccurate HP after Grafting (@catloversg)
- Hashnet: Clarified effect of hacknet multipliers in in documentation (@catloversg)
- Sleeve: Sleeve travel can no longer be performed if the player has insufficient funds (@gmcew)
- Sleeve: Added a missing availability check when installing augmentations on Sleeves (@yichizhng)
- Sleeve API: Fix an issue in ns.sleeve.setToBladeburnerAction that prevented setting sleeves to contract work (@Sphyxis)
### OTHER
- Nerf noodle bar
No changes yet since 2.6.1 release
`,
};

View File

@@ -14,3 +14,4 @@ export * from "./Programs/Enums";
export * from "./StockMarket/Enums";
export * from "./ui/Enums";
export * from "./Work/Enums";
export * from "./Myrian/Enums";

View File

@@ -300,11 +300,36 @@ export function SpecialLocation(props: SpecialLocationProps): React.ReactElement
}
function renderGlitch(): React.ReactElement {
const onClick = () => {
dialogBoxCreate(
"Hexabytes of information are streamed to your mind. Completely drained one thing remained clear as crystal. You now understand how to connect directly to the machine running the bitnodes. Myrian.",
);
Router.toPage(Page.MyrianOS);
Player.myrianConnection = true;
};
if (!Player.canAccessMyrian())
return (
<>
<Typography>
<CorruptableText
content={"An eerie aura surrounds this area. You feel you should leave."}
spoiler={false}
/>
</Typography>
</>
);
return (
<>
<Typography>
<CorruptableText content={"An eerie aura surrounds this area. You feel you should leave."} spoiler={false} />
You find yourself standing in a small, unremarkable alley. Despite the lack of air you do not feel the need to
breathe. There is no light, yet you can see every details of every objects in the alley. A rat walking in the
alley completely stop in it's track as if frozen in time. You know what this means. This location has fallen
through the crack of the Enders.
<br />
In the middle of the alley is a personal link port. You can connect your personal link to the anomaly.
</Typography>
<Button onClick={onClick}>Connect your personal link to the anomaly</Button>
</>
);
}

100
src/Myrian/Enums.ts Normal file
View File

@@ -0,0 +1,100 @@
export enum DeviceType {
Bus = "bus",
ISocket = "isocket",
OSocket = "osocket",
Reducer = "reducer",
Cache = "cache",
Lock = "lock",
Battery = "battery",
}
export enum Component {
// tier 0
R0 = "r0",
G0 = "g0",
B0 = "b0",
// tier 1
R1 = "r1",
G1 = "g1",
B1 = "b1",
Y1 = "y1",
C1 = "c1",
M1 = "m1",
// tier 2
R2 = "r2",
G2 = "g2",
B2 = "b2",
Y2 = "y2",
C2 = "c2",
M2 = "m2",
W2 = "w2",
// tier 3
R3 = "r3",
G3 = "g3",
B3 = "b3",
Y3 = "y3",
C3 = "c3",
M3 = "m3",
W3 = "w3",
// tier 4
R4 = "r4",
G4 = "g4",
B4 = "b4",
Y4 = "y4",
C4 = "c4",
M4 = "m4",
W4 = "w4",
// tier 5
R5 = "r5",
G5 = "g5",
B5 = "b5",
Y5 = "y5",
C5 = "c5",
M5 = "m5",
W5 = "w5",
// tier 6
Y6 = "y6",
C6 = "c6",
M6 = "m6",
W6 = "w6",
// tier 7
W7 = "w7",
}
export enum Glitch {
// Locks spawn at random
Segmentation = "segmentation",
// ISockets and OSockets move around on their own
Roaming = "roaming",
// OSocket ask for more complicated components
Encryption = "encryption",
// Energy starts being consumed (level 0 is no consumption)
Magnetism = "magnetism",
// Hidden tiles on the board, when stepped on the bus loses upgrades
Rust = "rust",
// Move slows down
Friction = "friction",
// Transfer components and charging slows down
Isolation = "isolation",
// Install/Uninstall slows down
Virtualization = "virtualization",
// Reduce slows down
Jamming = "jamming",
}

87
src/Myrian/Myrian.ts Normal file
View File

@@ -0,0 +1,87 @@
import { Device, DeviceID } from "@nsdefs";
import { DeviceType, Component, Glitch } from "@enums";
import { glitchMult } from "./formulas/glitches";
import { isDeviceISocket, pickOne } from "./utils";
import { componentTiers } from "./formulas/components";
import { NewBus, NewISocket, NewOSocket } from "./NewDevices";
import { startRoaming } from "./glitches/roaming";
import { startRust } from "./glitches/rust";
import { startSegmentation } from "./glitches/segmentation";
import { startBattery } from "./glitches/battery";
export interface Myrian {
vulns: number;
totalVulns: number;
devices: Device[];
glitches: Record<Glitch, number>;
rust: Record<string, boolean>;
}
export const myrianSize = 12;
const defaultGlitches = Object.values(Glitch).reduce((acc, g) => ({ ...acc, [g]: 0 }), {}) as Record<Glitch, number>;
export const myrian: Myrian = {
vulns: 0,
totalVulns: 0,
devices: [],
glitches: { ...defaultGlitches },
rust: {},
};
export const loadMyrian = (save: string) => {
resetMyrian();
startRoaming();
startRust();
startSegmentation();
startBattery();
if (!save) return;
const savedMyrian = JSON.parse(save);
Object.assign(myrian, savedMyrian);
myrian.devices.forEach((d) => (d.isBusy = false));
myrian.devices.filter(isDeviceISocket).forEach((d) => (d.content = new Array(d.maxContent).fill(d.emitting)));
};
export const inMyrianBounds = (x: number, y: number) => x >= 0 && x < myrianSize && y >= 0 && y < myrianSize;
export const findDevice = (id: DeviceID, type?: DeviceType): Device | undefined =>
myrian.devices.find(
(e) => (typeof id === "string" ? e.name === id : e.x === id[0] && e.y === id[1]) && (!type || type === e.type),
);
export const removeDevice = (id: DeviceID, type?: DeviceType) => {
myrian.devices = myrian.devices.filter(
(e) => !((typeof id === "string" ? e.name === id : e.x === id[0] && e.y === id[1]) && (!type || type === e.type)),
);
};
export const getTotalGlitchMult = () =>
Object.entries(myrian.glitches).reduce((acc, [glitch, lvl]) => {
return acc * glitchMult(glitch as Glitch, lvl);
}, 1);
export const getNextOSocketRequest = (tier: number) => {
const potential = componentTiers.slice(0, tier + 1).flat();
return new Array(Math.floor(Math.pow(Math.random() * tier, 0.75) + 1)).fill(null).map(() => pickOne(potential));
};
export const countDevices = (type: DeviceType) =>
myrian.devices.reduce((acc, d) => (d.type === type ? acc + 1 : acc), 0);
export const resetMyrian = () => {
myrian.vulns = 0;
myrian.totalVulns = 0;
myrian.devices = [];
myrian.glitches = { ...defaultGlitches };
myrian.rust = {};
NewBus("alice", Math.floor(myrianSize / 2), Math.floor(myrianSize / 2));
NewISocket("isocket0", Math.floor(myrianSize / 4), 0, Component.R0);
NewISocket("isocket1", Math.floor(myrianSize / 2), 0, Component.G0);
NewISocket("isocket2", Math.floor((myrianSize * 3) / 4), 0, Component.B0);
NewOSocket("osocket0", Math.floor(myrianSize / 4), Math.floor(myrianSize - 1));
NewOSocket("osocket1", Math.floor(myrianSize / 2), Math.floor(myrianSize - 1));
NewOSocket("osocket2", Math.floor((myrianSize * 3) / 4), Math.floor(myrianSize - 1));
};

110
src/Myrian/NewDevices.ts Normal file
View File

@@ -0,0 +1,110 @@
import { Battery, Bus, Cache, ISocket, Lock, OSocket, Reducer } from "@nsdefs";
import { Component, DeviceType } from "@enums";
import { myrian } from "./Myrian";
import { getNextOSocketRequest } from "./Myrian";
export const NewBus = (name: string, x: number, y: number) => {
const bus: Bus = {
name,
type: DeviceType.Bus,
isBusy: false,
x,
y,
content: [],
maxContent: 1,
moveLvl: 0,
transferLvl: 0,
reduceLvl: 0,
installLvl: 0,
energy: 16,
maxEnergy: 16,
};
myrian.devices.push(bus);
};
export const NewCache = (name: string, x: number, y: number) => {
const cache: Cache = {
name,
type: DeviceType.Cache,
isBusy: false,
content: [],
maxContent: 1,
x,
y,
};
myrian.devices.push(cache);
return cache;
};
export const NewReducer = (name: string, x: number, y: number) => {
const reducer: Reducer = {
name,
type: DeviceType.Reducer,
isBusy: false,
x,
y,
content: [],
maxContent: 2,
tier: 1,
};
myrian.devices.push(reducer);
return reducer;
};
export const NewISocket = (name: string, x: number, y: number, emitting: Component) => {
const isocket: ISocket = {
name,
type: DeviceType.ISocket,
isBusy: false,
x,
y,
emitting: emitting,
emissionLvl: 0,
cooldownUntil: 0,
content: [emitting],
maxContent: 1,
};
myrian.devices.push(isocket);
};
export const NewOSocket = (name: string, x: number, y: number) => {
const osocket: OSocket = {
name,
type: DeviceType.OSocket,
isBusy: false,
x,
y,
currentRequest: getNextOSocketRequest(0),
content: [],
maxContent: 1,
};
myrian.devices.push(osocket);
return osocket;
};
export const NewLock = (name: string, x: number, y: number) => {
const lock: Lock = {
name,
type: DeviceType.Lock,
isBusy: false,
x,
y,
};
myrian.devices.push(lock);
return lock;
};
export const NewBattery = (name: string, x: number, y: number) => {
const battery: Battery = {
name,
type: DeviceType.Battery,
isBusy: false,
x,
y,
tier: 0,
energy: 64,
maxEnergy: 64,
};
myrian.devices.push(battery);
};

View File

@@ -0,0 +1,12 @@
import { Component } from "@enums";
export const componentTiers = [
[Component.R0, Component.G0, Component.B0],
[Component.R1, Component.G1, Component.B1, Component.Y1, Component.C1, Component.M1],
[Component.R2, Component.G2, Component.B2, Component.Y2, Component.C2, Component.M2, Component.W2],
[Component.R3, Component.G3, Component.B3, Component.Y3, Component.C3, Component.M3, Component.W3],
[Component.R4, Component.G4, Component.B4, Component.Y4, Component.C4, Component.M4, Component.W4],
[Component.R5, Component.G5, Component.B5, Component.Y5, Component.C5, Component.M5, Component.W5],
[Component.Y6, Component.C6, Component.M6, Component.W6],
[Component.W7],
];

View File

@@ -0,0 +1,67 @@
import { DeviceType } from "@enums";
// parameters for a exponential formula, a^(b*X+c)+d
type ExponentialFormulaParams = [number, number, number, number];
// Parameters for a cost that shouldn't be available. Such as upgrading the max energy of a isocket.
const NA: ExponentialFormulaParams = [Infinity, Infinity, Infinity, Infinity];
type DeviceScale = Record<DeviceType, ExponentialFormulaParams>;
// Default scale for each device type, helps simplify code.
const defaultScale = Object.keys(DeviceType).reduce((acc, type) => ({ ...acc, [type]: NA }), {}) as DeviceScale;
// Exponential formula, a^(b*X+c)+d
const exp = (p: ExponentialFormulaParams, x: number): number => Math.pow(p[0], p[1] * x + p[2]) + p[3];
// Wrap exp with a specific scale for each device type.
const makeExpFunction = (p: Partial<DeviceScale>) => {
const scale = { ...defaultScale, ...p };
return (type: DeviceType, x: number) => exp(scale[type], x);
};
export const upgradeMaxContentCost = makeExpFunction({
[DeviceType.Bus]: [8, 0.5, 2, 0],
[DeviceType.ISocket]: [4, 1, 5, 0],
[DeviceType.Reducer]: [256, 1, -3, 512],
[DeviceType.Cache]: [1.2, 10, 0, 63],
});
export const upgradeTierCost = makeExpFunction({
[DeviceType.Reducer]: [1.5, 1, 2, 0],
[DeviceType.Battery]: [2, 1, 3, 0],
});
export const upgradeEmissionCost = makeExpFunction({
[DeviceType.ISocket]: [2, 1, 3, 0],
});
export const upgradeMoveLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeTransferLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeReduceLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeInstallLvlCost = makeExpFunction({
[DeviceType.Bus]: [2, 1, 3, 0],
});
export const upgradeMaxEnergyCost = makeExpFunction({
[DeviceType.Bus]: [1.1, 1, -8, 16],
[DeviceType.Battery]: [1.1, 1, -16, 8],
});
export const installDeviceCost = makeExpFunction({
[DeviceType.Bus]: [4, 0.5, 2, 0],
[DeviceType.ISocket]: [2, 1, 4, 0],
[DeviceType.OSocket]: [4, 1, 3, 0],
[DeviceType.Reducer]: [5, 0.5, 1, 0],
[DeviceType.Cache]: [1.2, 5, 0, 18],
[DeviceType.Battery]: [1.2, 10, 0, 63],
});

View File

@@ -0,0 +1,46 @@
import { Glitch } from "@enums";
export const glitchMaxLvl: Record<Glitch, number> = {
[Glitch.Segmentation]: 10,
[Glitch.Roaming]: 10,
[Glitch.Encryption]: 7,
[Glitch.Magnetism]: 10,
[Glitch.Rust]: 10,
[Glitch.Friction]: 3,
[Glitch.Isolation]: 3,
[Glitch.Virtualization]: 3,
[Glitch.Jamming]: 3,
};
export const giltchMultCoefficients: Record<Glitch, number> = {
[Glitch.Segmentation]: 1,
[Glitch.Roaming]: 1,
[Glitch.Encryption]: 0.1,
[Glitch.Magnetism]: 0.2,
[Glitch.Rust]: 1,
[Glitch.Friction]: 0.2,
[Glitch.Isolation]: 0.2,
[Glitch.Virtualization]: 0.2,
[Glitch.Jamming]: 0.2,
};
// vulns mult by glitch lvl
export const glitchMult = (glitch: Glitch, lvl: number) => 1 + lvl * giltchMultCoefficients[glitch];
// move hinderance
export const frictionMult = (lvl: number) => Math.pow(2.5, lvl);
// transfer slow down
export const isolationMult = (lvl: number) => Math.pow(8, lvl);
// install/uninstall slow down
export const virtualizationMult = (lvl: number) => Math.pow(5, lvl);
// reduce slow down
export const jammingMult = (lvl: number) => Math.pow(2.5, lvl);
// energy loss
export const magnetismLoss = (lvl: number) => lvl;
// How often isocket/osocke move
export const roamingTime = (lvl: number) => 30000 * Math.pow(0.7, lvl);

View File

@@ -0,0 +1,83 @@
import { Recipe } from "@nsdefs";
import { Component } from "@enums";
const make = (input: Component[], output: Component): Recipe => ({ input, output });
export const Tier1Recipes: Recipe[] = [
make([Component.R0, Component.R0], Component.R1),
make([Component.G0, Component.G0], Component.G1),
make([Component.B0, Component.B0], Component.B1),
make([Component.R0, Component.G0], Component.Y1),
make([Component.G0, Component.B0], Component.C1),
make([Component.B0, Component.R0], Component.M1),
];
export const Tier2Recipes: Recipe[] = [
make([Component.R1, Component.R1], Component.R2),
make([Component.G1, Component.G1], Component.G2),
make([Component.B1, Component.B1], Component.B2),
make([Component.R1, Component.G1], Component.Y2),
make([Component.G1, Component.B1], Component.C2),
make([Component.B1, Component.R1], Component.M2),
make([Component.Y1, Component.C1, Component.M1], Component.W2),
];
export const Tier3Recipes: Recipe[] = [
make([Component.R2, Component.R2], Component.R3),
make([Component.G2, Component.G2], Component.G3),
make([Component.B2, Component.B2], Component.B3),
make([Component.R2, Component.G2], Component.Y3),
make([Component.G2, Component.B2], Component.C3),
make([Component.B2, Component.R2], Component.M3),
make([Component.Y2, Component.C2, Component.M2], Component.W3),
];
export const Tier4Recipes: Recipe[] = [
make([Component.R3, Component.R3], Component.R4),
make([Component.G3, Component.G3], Component.G4),
make([Component.B3, Component.B3], Component.B4),
make([Component.R3, Component.G3], Component.Y4),
make([Component.G3, Component.B3], Component.C4),
make([Component.B3, Component.R3], Component.M4),
make([Component.Y3, Component.C3, Component.M3], Component.W4),
];
export const Tier5Recipes: Recipe[] = [
make([Component.R4, Component.R4], Component.R5),
make([Component.G4, Component.G4], Component.G5),
make([Component.B4, Component.B4], Component.B5),
make([Component.R4, Component.G4], Component.Y5),
make([Component.G4, Component.B4], Component.C5),
make([Component.B4, Component.R4], Component.M5),
make([Component.Y4, Component.C4, Component.M4], Component.W5),
];
export const Tier6Recipes: Recipe[] = [
make([Component.R5, Component.G5], Component.Y6),
make([Component.G5, Component.B5], Component.C6),
make([Component.B5, Component.R5], Component.M6),
make([Component.Y5, Component.C5, Component.M5], Component.W6),
];
export const Tier7Recipes: Recipe[] = [make([Component.Y6, Component.C6, Component.M6], Component.W7)];
export const recipes: Recipe[][] = [
[],
Tier1Recipes,
Tier2Recipes,
Tier3Recipes,
Tier4Recipes,
Tier5Recipes,
Tier6Recipes,
Tier7Recipes,
];

View File

@@ -0,0 +1,14 @@
// speed to move between 2 tiles
export const moveSpeed = (level: number) => 1000 / (level + 10);
// speed to reduce components
export const reduceSpeed = (level: number) => 50000 / (level + 10);
// speed to transfer components between devices
export const transferSpeed = (level: number) => 4000 / (level + 10);
// speed to install / uninstall devices and tweak ISockets
export const installSpeed = (level: number) => 100000 / (level + 10);
// time until ISocket refreshes
export const emissionSpeed = (level: number) => 100000 / (level + 10);

View File

@@ -0,0 +1,12 @@
import { myrian } from "../Myrian";
import { isDeviceBattery } from "../utils";
const applyBattery = () => {
myrian.devices.forEach((device) => {
if (!isDeviceBattery(device)) return;
const up = Math.pow(2, device.tier + 1);
device.energy = Math.min(device.energy + up, device.maxEnergy);
});
};
export const startBattery = () => setInterval(applyBattery, 1000);

View File

@@ -0,0 +1,74 @@
import { DeviceType, Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { findDevice, inMyrianBounds } from "../Myrian";
import { roamingTime } from "../formulas/glitches";
const clamp = (min: number, v: number, max: number) => Math.min(Math.max(v, min), max);
let globalOffset = 0;
const dirDiff = (v: number): number => {
globalOffset++;
const r = Math.random();
const d = v - (myrianSize - 1) / 2;
const h = d > 0 ? -1 : 1;
const dv = Math.floor(r * 3 + h * Math.random() * Math.sin(globalOffset * 0.05) * Math.abs(d)) - 1;
return clamp(-1, dv, 1);
};
const isEmpty = (x: number, y: number) => {
if (!inMyrianBounds(x, y)) return false;
return !findDevice([x, y]);
};
const dirs = [
[0, 1],
[0, -1],
[1, 0],
[-1, 0],
];
const applyRoaming = () => {
const roaming = myrian.glitches[Glitch.Roaming];
setTimeout(applyRoaming, roamingTime(roaming));
if (roaming === 0) return;
myrian.devices.forEach((device) => {
if (device.type !== DeviceType.OSocket && device.type !== DeviceType.ISocket) return;
if (device.isBusy) return;
let canMove = false;
for (const dir of dirs) {
const [dx, dy] = dir;
if (isEmpty(device.x + dx, device.y + dy)) {
canMove = true;
break;
}
}
let x = -1;
let y = -1;
if (canMove) {
let dx = dirDiff(device.x);
let dy = dirDiff(device.y);
if (dx !== 0 && dy !== 0) {
if (Math.random() > 0.5) {
dx = 0;
} else {
dy = 0;
}
}
x = device.x + dx;
y = device.y + dy;
} else {
x = Math.floor(Math.random() * myrianSize);
y = Math.floor(Math.random() * myrianSize);
}
if (findDevice([x, y])) return;
if (!inMyrianBounds(x, y)) return;
device.x = x;
device.y = y;
});
};
export const startRoaming = () => setTimeout(applyRoaming, 0);

View File

@@ -0,0 +1,30 @@
import { Bus } from "@nsdefs";
import { Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { pickOne } from "../utils";
const applyRust = () => {
myrian.rust = {};
const rust = myrian.glitches[Glitch.Rust];
for (let i = 0; i < rust * 3; i++) {
const x = Math.floor(Math.random() * myrianSize);
const y = Math.floor(Math.random() * myrianSize);
myrian.rust[`${x}:${y}`] = true;
}
};
// DO NOT use `Object.keys` on a Rustable because it will return way more than just the rustable stats.
const rustStats: (keyof Rustable)[] = ["moveLvl", "transferLvl", "reduceLvl", "installLvl", "maxEnergy"];
type Rustable = Pick<Bus, "moveLvl" | "transferLvl" | "reduceLvl" | "installLvl" | "maxEnergy">;
export const rustBus = (bus: Bus, rust: number) => {
const rustable = bus as Rustable;
const nonZero = rustStats.filter((stat) => rustable[stat] > 0);
const chosen = pickOne(nonZero);
rustable[chosen] = Math.max(0, rustable[chosen] - rust * 0.1);
// cap energy when maxEnergy is reduced
bus.energy = Math.min(bus.energy, bus.maxEnergy);
};
export const startRust = () => setInterval(applyRust, 30000);

View File

@@ -0,0 +1,16 @@
import { Glitch } from "@enums";
import { myrian, myrianSize } from "../Myrian";
import { findDevice } from "../Myrian";
import { NewLock } from "../NewDevices";
const applySegmentation = () => {
const segmentation = myrian.glitches[Glitch.Segmentation];
for (let i = 0; i < segmentation; i++) {
const x = Math.floor(Math.random() * myrianSize);
const y = Math.floor(Math.random() * myrianSize);
if (findDevice([x, y])) continue;
NewLock(`lock-${x}-${y}`, x, y);
}
};
export const startSegmentation = () => setInterval(applySegmentation, 30000);

1
src/Myrian/ideas.txt Normal file
View File

@@ -0,0 +1 @@
New device that makes special requests like "has red" | "at least tier 3" | "does not contain blue" that increases the reward.

View File

@@ -0,0 +1,22 @@
import React from "react";
import { Battery } from "@nsdefs";
import BatteryChargingFullIcon from "@mui/icons-material/BatteryChargingFull";
import { defaultIconStyle } from "./common";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipTier } from "./TooltipTier";
import { TooltipEnergy } from "./TooltipEnergy";
const Template = styled(BatteryChargingFullIcon)(defaultIconStyle);
const Icon = <Template />;
interface IBatteryIconProps {
battery: Battery;
}
export const BatteryIcon = ({ battery }: IBatteryIconProps): React.ReactElement => (
<DeviceTooltip device={battery} icon={Icon}>
<TooltipTier device={battery} />
<TooltipEnergy device={battery} />
</DeviceTooltip>
);

27
src/Myrian/ui/BusIcon.tsx Normal file
View File

@@ -0,0 +1,27 @@
import React from "react";
import { defaultIconStyle } from "./common";
import DirectionsBusIcon from "@mui/icons-material/DirectionsBus";
import { styled } from "@mui/styles";
import { Bus } from "@nsdefs";
import { TooltipContent } from "./TooltipContent";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipEnergy } from "./TooltipEnergy";
import { Typography } from "@mui/material";
const Template = styled(DirectionsBusIcon)(defaultIconStyle);
const Icon = <Template />;
interface IBusIconProps {
bus: Bus;
}
export const BusIcon = ({ bus }: IBusIconProps): React.ReactElement => (
<DeviceTooltip device={bus} icon={Icon}>
<Typography>moveLvl: {bus.moveLvl}</Typography>
<Typography>transferLvl: {bus.transferLvl}</Typography>
<Typography>reduceLvl: {bus.reduceLvl}</Typography>
<Typography>installLvl: {bus.installLvl}</Typography>
<TooltipEnergy device={bus} />
<TooltipContent device={bus} />
</DeviceTooltip>
);

View File

@@ -0,0 +1,20 @@
import React from "react";
import { TooltipContent } from "./TooltipContent";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { Cache } from "@nsdefs";
import { DeviceTooltip } from "./DeviceTooltip";
const Template = styled(CheckBoxOutlineBlankIcon)(defaultIconStyle);
const Icon = <Template />;
interface ICacheIconProps {
cache: Cache;
}
export const CacheIcon = ({ cache }: ICacheIconProps): React.ReactElement => (
<DeviceTooltip device={cache} icon={Icon}>
<TooltipContent device={cache} />
</DeviceTooltip>
);

View File

@@ -0,0 +1,14 @@
import React from "react";
import { Component } from "@enums";
import { getComponentColor } from "./common";
interface IComponent {
component: Component;
}
export const ComponentText = ({ component }: IComponent): React.ReactElement => (
<span style={{ color: getComponentColor(component) }}>
{component}
<br />
</span>
);

View File

@@ -0,0 +1,35 @@
import React from "react";
import { Device } from "@nsdefs";
import { DeviceType } from "@enums";
import { BusIcon } from "./BusIcon";
import { ReducerIcon } from "./Reducer";
import { LockIcon } from "./LockIcon";
import { CacheIcon } from "./CacheIcon";
import { BatteryIcon } from "./BatteryIcon";
import { OSocketIcon } from "./OSocketIcon";
import { ISocketIcon } from "./ISocketIcon";
import {
isDeviceBattery,
isDeviceBus,
isDeviceCache,
isDeviceISocket,
isDeviceLock,
isDeviceOSocket,
isDeviceReducer,
} from "../utils";
interface IDeviceIconProps {
device: Device;
}
export const DeviceIcon = ({ device }: IDeviceIconProps): React.ReactElement => {
if (isDeviceISocket(device)) return <ISocketIcon socket={device} />;
if (isDeviceOSocket(device)) return <OSocketIcon socket={device} />;
if (isDeviceBus(device)) return <BusIcon bus={device} />;
if (isDeviceReducer(device)) return <ReducerIcon reducer={device} />;
if (isDeviceLock(device)) return <LockIcon lock={device} />;
if (isDeviceCache(device)) return <CacheIcon cache={device} />;
if (isDeviceBattery(device)) return <BatteryIcon battery={device} />;
return <></>;
};

View File

@@ -0,0 +1,24 @@
import React, { ReactNode } from "react";
import { Device } from "../../ScriptEditor/NetscriptDefinitions";
import { Tooltip, Typography } from "@mui/material";
interface INewTooltipProps {
icon: React.JSX.Element;
device: Device;
children?: ReactNode;
}
export const DeviceTooltip = ({ device, icon, children }: INewTooltipProps): React.ReactElement => (
<Tooltip
title={
<>
<Typography>
{device.name} ({device.type})
</Typography>
{children}
</>
}
>
{icon}
</Tooltip>
);

88
src/Myrian/ui/Grid.tsx Normal file
View File

@@ -0,0 +1,88 @@
import React from "react";
import { cellSize } from "./common";
import { styled } from "@mui/system";
import { findDevice, myrianSize } from "../Myrian";
import { DeviceIcon } from "./DeviceIcon";
import { Typography } from "@mui/material";
const BaseCell = styled("div")({
width: cellSize,
height: cellSize,
backgroundColor: "#444",
padding: 0,
margin: "2px",
marginTop: "4px",
marginBottom: "4px",
});
const TextCell = styled(BaseCell)({
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#00000000",
});
const DeviceCell = ({ x, y }: { x: number; y: number }): React.ReactElement => {
const device = findDevice([x, y]);
return <BaseCell>{device && <DeviceIcon device={device} />}</BaseCell>;
};
const ColD = styled("div")({
padding: 0,
margin: 0,
});
interface IColProps {
x: number;
}
const DeviceCol = ({ x }: IColProps): React.ReactElement => {
return (
<ColD>
<TextCell>
<Typography>{x}</Typography>
</TextCell>
{new Array(myrianSize).fill(undefined).map((_, y) => (
<DeviceCell key={y} x={x} y={y}></DeviceCell>
))}
</ColD>
);
};
const Table = styled("div")({
border: "1px solid #fff",
borderSpacing: "2px",
overflow: "hidden",
display: "flex",
flexDirection: "row",
paddingLeft: "2px",
paddingRight: "2px",
});
const YColumn = (
<ColD>
<TextCell>
<Typography>
&nbsp;X
<br />
Y&nbsp;
</Typography>
</TextCell>
{new Array(myrianSize).fill(undefined).map((_, y) => (
<TextCell key={y}>
<Typography>{y}</Typography>
</TextCell>
))}
</ColD>
);
export const Grid = () => (
<div style={{ display: "flex" }}>
<Table>
{YColumn}
{new Array(myrianSize).fill(undefined).map((_, x) => (
<DeviceCol key={x} x={x} />
))}
</Table>
</div>
);

View File

@@ -0,0 +1,33 @@
import { Typography } from "@mui/material";
import React from "react";
import { TooltipContent } from "./TooltipContent";
import { ISocket } from "../../ScriptEditor/NetscriptDefinitions";
import { defaultIconStyle, getComponentColor } from "./common";
import OutboxIcon from "@mui/icons-material/Outbox";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { ComponentText } from "./ComponentText";
interface IIconProps {
socket: ISocket;
col: string;
}
const Icon = styled(OutboxIcon)(({ socket, col }: IIconProps) => ({
...defaultIconStyle,
color: new Date().getTime() > socket.cooldownUntil ? col : "gray",
}));
interface IIsocketIconProps {
socket: ISocket;
}
export const ISocketIcon = ({ socket }: IIsocketIconProps) => (
<DeviceTooltip device={socket} icon={<Icon socket={socket} col={getComponentColor(socket.emitting)} />}>
<Typography>
dispensing:&nbsp;
<ComponentText component={socket.emitting} />
</Typography>
<TooltipContent device={socket} />
</DeviceTooltip>
);

View File

@@ -0,0 +1,15 @@
import React from "react";
import BlockIcon from "@mui/icons-material/Block";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { Lock } from "@nsdefs";
import { DeviceTooltip } from "./DeviceTooltip";
const Template = styled(BlockIcon)(defaultIconStyle);
const Icon = <Template />;
interface ILockIconProps {
lock: Lock;
}
export const LockIcon = ({ lock }: ILockIconProps): React.ReactElement => <DeviceTooltip device={lock} icon={Icon} />;

View File

@@ -0,0 +1,32 @@
import React from "react";
import { Container, IconButton, Typography } from "@mui/material";
import { myrian } from "../Myrian";
import { useRerender } from "../../ui/React/hooks";
import { Info } from "@mui/icons-material";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { MD } from "../../ui/MD/MD";
import { tutorial } from "./tutorial";
import { Grid } from "./Grid";
const tut = <MD md={tutorial} />;
export const MyrianRoot = (): React.ReactElement => {
useRerender(50);
const onHelp = () => dialogBoxCreate(tut);
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">
Myrian OS
<IconButton onClick={onHelp}>
<Info />
</IconButton>
</Typography>
<Typography>
{myrian.vulns} vulns : {myrian.totalVulns} total vulns
</Typography>
<Grid />
</Container>
);
};

View File

@@ -0,0 +1,35 @@
import React from "react";
import { Typography } from "@mui/material";
import { OSocket } from "@nsdefs";
import { defaultIconStyle, getComponentColor } from "./common";
import MoveToInboxIcon from "@mui/icons-material/MoveToInbox";
import { styled } from "@mui/styles";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipContent } from "./TooltipContent";
import { ComponentText } from "./ComponentText";
interface IIconProps {
col: string;
}
const Icon = styled(MoveToInboxIcon)(({ col }: IIconProps) => ({
...defaultIconStyle,
color: col,
}));
interface IOSocketIconProps {
socket: OSocket;
}
export const OSocketIcon = ({ socket }: IOSocketIconProps): React.ReactElement => (
<DeviceTooltip device={socket} icon={<Icon col={getComponentColor(socket.currentRequest[0])} />}>
<Typography>
requesting:
<br />
{socket.currentRequest.map((c, i) => (
<ComponentText key={i} component={c} />
))}
</Typography>
<TooltipContent device={socket} />
</DeviceTooltip>
);

22
src/Myrian/ui/Reducer.tsx Normal file
View File

@@ -0,0 +1,22 @@
import React from "react";
import { TooltipContent } from "./TooltipContent";
import { Reducer } from "@nsdefs";
import MergeTypeIcon from "@mui/icons-material/MergeType";
import { styled } from "@mui/styles";
import { defaultIconStyle } from "./common";
import { DeviceTooltip } from "./DeviceTooltip";
import { TooltipTier } from "./TooltipTier";
const Template = styled(MergeTypeIcon)(defaultIconStyle);
const Icon = <Template />;
interface IReducerIconProps {
reducer: Reducer;
}
export const ReducerIcon = ({ reducer }: IReducerIconProps): React.ReactElement => (
<DeviceTooltip device={reducer} icon={Icon}>
<TooltipTier device={reducer} />
<TooltipContent device={reducer} />
</DeviceTooltip>
);

View File

@@ -0,0 +1,18 @@
import React from "react";
import { ContainerDevice } from "@nsdefs";
import { Typography } from "@mui/material";
import { ComponentText } from "./ComponentText";
export const TooltipContent = ({ device }: { device: ContainerDevice }): React.ReactElement => (
<>
{device.content.length !== 0 && (
<Typography>
content ({device.content.length} / {device.maxContent}):
<br />
{device.content.map((component, i) => (
<ComponentText key={i} component={component} />
))}
</Typography>
)}
</>
);

View File

@@ -0,0 +1,13 @@
import React from "react";
import { EnergyDevice } from "@nsdefs";
import { Typography } from "@mui/material";
interface ITooltipEnergyProps {
device: EnergyDevice;
}
export const TooltipEnergy = ({ device }: ITooltipEnergyProps): React.ReactElement => (
<Typography>
{device.energy} / {device.maxEnergy} energy
</Typography>
);

View File

@@ -0,0 +1,11 @@
import React from "react";
import { TieredDevice } from "@nsdefs";
import { Typography } from "@mui/material";
interface ITooltipTierProps {
device: TieredDevice;
}
export const TooltipTier = ({ device }: ITooltipTierProps): React.ReactElement => (
<Typography>Tier: {device.tier}</Typography>
);

30
src/Myrian/ui/common.ts Normal file
View File

@@ -0,0 +1,30 @@
import { Component } from "@enums";
export const cellSize = 48;
export const defaultIconStyle = {
width: cellSize + "px",
height: cellSize + "px",
color: "white",
};
const ColorR = "red";
const ColorG = "#7CFC00";
const ColorB = "#1E90FF";
const ColorY = "yellow";
const ColorC = "cyan";
const ColorM = "magenta";
const ColorW = "white";
const componentColors: Record<string, string> = {
r: ColorR,
g: ColorG,
b: ColorB,
y: ColorY,
c: ColorC,
m: ColorM,
w: ColorW,
};
export const getComponentColor = (component: Component): string =>
componentColors[component[0].toLowerCase()] ?? ColorW;

109
src/Myrian/ui/tutorial.ts Normal file
View File

@@ -0,0 +1,109 @@
export const tutorial = `# Myrian
Myrian is the name of the OS that the BitNodes run on.
By gaining access to the OS directly you can start to break it apart by generating vulnerabilities (vulns).
You do so by interracting with the various devices in the OS, represented by a grid.
## Devices
A device is a component that takes space on the Myrian, no 2 devices can be on the same tile.
Devices can only interract when directly adjacent to each other. When one or more device is
performing an action it will become busy and no other actions can be taken until the current one finishes.
Contrary to every other mechanic in the game. Async functions using myrian functions CAN run simultenaously.
### Bus
The most important device is the Bus. Here's a few of the things it can do:
- move, only 1 tile at a time and that tile must be empty. No diagonal.
- transfer content, most entity can store items called components, bus are the only device that can transfer components to and from other devices
- use other devices, e.g. Use a Reducer device to create new components.
### ISocket
These devices produce basic components that can be used for other devices, [r0, g0, b0].
They must be picked up by busses and will eventually produce another one after a certain cooldown has passed.
### OSocket
These devices request components and produce vulns in return, a bus simply needs to transfer a component into a OSocket content in order to fulfill the request
### Reducer
These devices can be used by a bus, when being used they will first check their content, if the content matches one of the recipe they will take some time to consume their content in order to produce a new, upgraded, more valuable component, e.g. r0 + r0 => r1
### Cache
These devices act as storage for components.
### Lock
These devices cannot be installed. They appear after various conditions are fulfilled in order to block certain tiles.
### Battery
These devices are only relevant when the Magnetism glitch is active. It recharges the energy of a bus.
## Installing
Bus can install new devices, when they do so a lock will appear over the tile that will eventually become the device. The cost of any device depends on the number of that type of device currently in the OS.
### Uninstalling
A bus can remove a device, there is no refund.
## Tiers
Upgrading a reducers tier allows it to reduce components of a higher tier and ONLY that higher tier.
A tier 2 reducer can only tier 2 components like r1 + r1 => r2 and loses access to r0 + r0 => r1
Upgrading a batterys tier increases the amount of energy it produces.
## Glitches
glitches are optional difficulty modifiers that make the myrian more difficult BUT increase the amount of vulns gained.
All glitches start at level 0 and must be activated when you chose. They also have a max level that differs from glitch to glitch.
### Magnetism
By default bus lose 0 energy when moving. But when this glitch is active they start losing energy, at 0 energy bus move much more slowly. Batteries must be installed and used to charge busses.
### Friction
When Friction is active busses move more slowly.
### Rust
When rust is active, hidden tiles are set on the Myrian. When a bus steps on one of these hidden tiles one of their upgrade lowers. Higher Rust level means lowers by a larger amount and more rust tiles are set.
### Isolation
When Isolation is active busses transfer and charge more slowly.
### Segmentation
When Segmentation is active random Locks will spawn on the Myrian. You have to remove these locks or the bord will be overrun with Locks and you won't be able to move.
### Virtualization
When Virtualization is active busses install and uninstall devices more slowly.
### Jamming
When Jamming is active busses use reducers more slowly.
### Roaming
When Roaming is active, isockets and osockets start to move around the map
### Encryption
Encryption is the only glitch that's always active. The level of Encryption determines the complexity of the requests made by osockets.
## Destabilization
As the number of total vulns increase the bitnode becomes unstable and it's multiplier become more favorable.
`;

125
src/Myrian/utils.ts Normal file
View File

@@ -0,0 +1,125 @@
import {
BaseDevice,
ContainerDevice,
Device,
Bus,
ISocket,
OSocket,
Reducer,
Cache,
Lock,
Battery,
TieredDevice,
EnergyDevice,
} from "@nsdefs";
import { Component, DeviceType } from "@enums";
export const pickOne = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
export const distance = (a: Device, b: Device) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
export const distanceCoord2D = (a: Device, coord: [number, number]) =>
Math.abs(a.x - coord[0]) + Math.abs(a.y - coord[1]);
export const adjacent = (a: Device, b: Device) => distance(a, b) === 1;
export const adjacentCoord2D = (a: Device, coord: [number, number]) => distanceCoord2D(a, coord) === 1;
export const makeContentMap = (content: Component[]) =>
content.reduce((acc, c) => ({ ...acc, [c]: (acc[c] ?? 0) + 1 }), {} as Record<Component, number>);
export const inventoryMatches = (a: Component[], b: Component[]) => {
const aMap = makeContentMap(a);
const bMap = makeContentMap(b);
return (
(Object.keys(aMap) as Component[]).every((k) => aMap[k] === bMap[k]) &&
Object.keys(aMap).length === Object.keys(aMap).length
);
};
const vulnsMap: Record<Component, number> = {
// tier 0
[Component.R0]: 1,
[Component.G0]: 1,
[Component.B0]: 1,
// tier 1
[Component.R1]: 4,
[Component.G1]: 4,
[Component.B1]: 4,
[Component.Y1]: 4,
[Component.C1]: 4,
[Component.M1]: 4,
// tier 2
[Component.R2]: 16,
[Component.G2]: 16,
[Component.B2]: 16,
[Component.Y2]: 16,
[Component.C2]: 16,
[Component.M2]: 16,
[Component.W2]: 16,
// tier 3
[Component.R3]: 64,
[Component.G3]: 64,
[Component.B3]: 64,
[Component.Y3]: 64,
[Component.C3]: 64,
[Component.M3]: 64,
[Component.W3]: 64,
// tier 4
[Component.R4]: 256,
[Component.G4]: 256,
[Component.B4]: 256,
[Component.Y4]: 256,
[Component.C4]: 256,
[Component.M4]: 256,
[Component.W4]: 256,
// tier 5
[Component.R5]: 1024,
[Component.G5]: 1024,
[Component.B5]: 1024,
[Component.Y5]: 1024,
[Component.C5]: 1024,
[Component.M5]: 1024,
[Component.W5]: 1024,
// tier 6
[Component.Y6]: 4096,
[Component.C6]: 4096,
[Component.M6]: 4096,
[Component.W6]: 4096,
// tier 7
[Component.W7]: 16384,
};
export const contentVulnsValue = (content: Component[]) => content.map((i) => vulnsMap[i]).reduce((a, b) => a + b, 0);
export const isDeviceContainer = (device: BaseDevice): device is ContainerDevice => "content" in device;
export const isDeviceBus = (d: Device): d is Bus => d.type === DeviceType.Bus;
export const isDeviceISocket = (d: Device): d is ISocket => d.type === DeviceType.ISocket;
export const isDeviceOSocket = (d: Device): d is OSocket => d.type === DeviceType.OSocket;
export const isDeviceReducer = (d: Device): d is Reducer => d.type === DeviceType.Reducer;
export const isDeviceCache = (d: Device): d is Cache => d.type === DeviceType.Cache;
export const isDeviceLock = (d: Device): d is Lock => d.type === DeviceType.Lock;
export const isDeviceBattery = (d: Device): d is Battery => d.type === DeviceType.Battery;
export const isDeviceTiered = (d: BaseDevice): d is TieredDevice => "tier" in d;
export const isEmittingDevice = (d: BaseDevice): d is BaseDevice & { emissionLvl: number } => "emissionLvl" in d;
export const isMovingDevice = (d: BaseDevice): d is BaseDevice & { moveLvl: number } => "moveLvl" in d;
export const isTransferingDevice = (d: BaseDevice): d is BaseDevice & { transferLvl: number } => "transferLvl" in d;
export const isReducingDevice = (d: BaseDevice): d is BaseDevice & { reduceLvl: number } => "reduceLvl" in d;
export const isInstallingDevice = (d: BaseDevice): d is BaseDevice & { installLvl: number } => "installLvl" in d;
export const isEnergyDevice = (d: BaseDevice): d is EnergyDevice => "maxEnergy" in d;

View File

@@ -1,5 +1,11 @@
import type { NetscriptContext } from "./APIWrapper";
import type { RunningScript as IRunningScript, Person as IPerson, Server as IServer, ScriptArg } from "@nsdefs";
import type {
RunningScript as IRunningScript,
Person as IPerson,
Server as IServer,
ScriptArg,
DeviceID,
} from "@nsdefs";
import React from "react";
import { killWorkerScript } from "./killWorkerScript";
@@ -74,6 +80,8 @@ export const helpers = {
gangMember,
gangTask,
log,
coord2d,
deviceID: entityID,
filePath,
scriptPath,
getRunningScript,
@@ -152,6 +160,24 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) {
return args;
}
function isCoord2D(v: unknown): v is [number, number] {
return Array.isArray(v) && v.length === 2 && typeof v[0] === "number" && typeof v[1] === "number";
}
function coord2d(ctx: NetscriptContext, argName: string, v: unknown): [number, number] {
if (!isCoord2D(v)) throw errorMessage(ctx, `${argName} should be a [number, number], was ${v}`, "TYPE");
return v;
}
function isEntityID(v: unknown): v is DeviceID {
return typeof v === "string" || isCoord2D(v);
}
function entityID(ctx: NetscriptContext, argName: string, v: unknown): DeviceID {
if (!isEntityID(v)) throw errorMessage(ctx, `${argName} should be string | [number, number], was ${v}`, "TYPE");
return v;
}
function runOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteRunOptions {
const result: CompleteRunOptions = {
threads: 1 as PositiveInteger,
@@ -302,7 +328,7 @@ function checkEnvFlags(ctx: NetscriptContext): void {
}
/** Set a timeout for performing a task, mark the script as busy in the meantime. */
function netscriptDelay(ctx: NetscriptContext, time: number): Promise<void> {
function netscriptDelay(ctx: NetscriptContext, time: number, ignoreOthers?: boolean): Promise<void> {
const ws = ctx.workerScript;
return new Promise(function (resolve, reject) {
ws.delay = window.setTimeout(() => {
@@ -313,7 +339,7 @@ function netscriptDelay(ctx: NetscriptContext, time: number): Promise<void> {
else resolve();
}, time);
ws.delayReject = reject;
ws.env.runningFn = ctx.function;
if (ignoreOthers) ws.env.runningFn = ctx.function;
});
}

View File

@@ -368,6 +368,15 @@ const stanek = {
acceptGift: RamCostConstants.StanekAcceptGift,
} as const;
const myrian: any = new Proxy(
{},
{
get() {
return 0;
},
},
);
// UI API
const ui = {
getTheme: 0,
@@ -470,6 +479,7 @@ export const RamCosts: RamCostTree<NSFull> = {
codingcontract,
sleeve,
stanek,
myrian,
ui,
grafting,

View File

@@ -108,6 +108,7 @@ import { getEnumHelper } from "./utils/EnumHelper";
import { setDeprecatedProperties, deprecationWarning } from "./utils/DeprecationHelper";
import { ServerConstants } from "./Server/data/Constants";
import { assertFunction } from "./Netscript/TypeAssertion";
import { NetscriptMyrian } from "./NetscriptFunctions/Myrian";
export const enums: NSEnums = {
CityName,
@@ -135,6 +136,7 @@ export const ns: InternalAPI<NSFull> = {
sleeve: NetscriptSleeve(),
corporation: NetscriptCorporation(),
stanek: NetscriptStanek(),
myrian: NetscriptMyrian(),
infiltration: NetscriptInfiltration(),
ui: NetscriptUserInterface(),
formulas: NetscriptFormulas(),

View File

@@ -0,0 +1,751 @@
import { InternalAPI } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers";
import {
findDevice,
inMyrianBounds,
removeDevice,
getTotalGlitchMult,
getNextOSocketRequest,
countDevices,
resetMyrian,
myrian,
myrianSize,
} from "../Myrian/Myrian";
import {
installDeviceCost,
upgradeEmissionCost,
upgradeInstallLvlCost,
upgradeMaxEnergyCost,
upgradeMoveLvlCost,
upgradeReduceLvlCost,
upgradeTierCost,
upgradeTransferLvlCost,
upgradeMaxContentCost,
} from "../Myrian/formulas/costs";
import { recipes } from "../Myrian/formulas/recipes";
import { componentTiers } from "../Myrian/formulas/components";
import {
frictionMult,
glitchMaxLvl,
glitchMult,
isolationMult,
jammingMult,
magnetismLoss,
virtualizationMult,
} from "../Myrian/formulas/glitches";
import {
adjacent,
adjacentCoord2D,
contentVulnsValue,
inventoryMatches,
isDeviceBus,
isDeviceContainer,
isDeviceISocket,
isDeviceOSocket,
isDeviceTiered,
isEmittingDevice,
isEnergyDevice,
isInstallingDevice,
isMovingDevice,
isReducingDevice,
isTransferingDevice,
pickOne,
} from "../Myrian/utils";
import { installSpeed, emissionSpeed, moveSpeed, reduceSpeed, transferSpeed } from "../Myrian/formulas/speed";
import { NewBattery, NewBus, NewCache, NewISocket, NewLock, NewOSocket, NewReducer } from "../Myrian/NewDevices";
import { rustBus } from "../Myrian/glitches/rust";
import { Bus, Myrian as IMyrian, Reducer, Battery, ISocket } from "@nsdefs";
import { DeviceType, Component, Glitch } from "@enums";
export function NetscriptMyrian(): InternalAPI<IMyrian> {
return {
DEUBG_RESET: () => resetMyrian,
DEBUG_GIVE_VULNS: (ctx) => (_amount) => {
const amount = helpers.number(ctx, "amount", _amount);
myrian.vulns += amount;
},
getDevice: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "id", _id);
const device = findDevice(id);
if (!device) return;
return JSON.parse(JSON.stringify(device));
},
getDevices: (__ctx) => () => JSON.parse(JSON.stringify(myrian.devices)),
getVulns: () => () => myrian.vulns,
renameDevice: (ctx) => (_id, _name) => {
const id = helpers.deviceID(ctx, "id", _id);
const name = helpers.string(ctx, "name", _name);
const device = findDevice(id);
if (!device) return false;
if (findDevice(name)) return false;
device.name = name;
return true;
},
moveBus:
(ctx) =>
async (_bus, _coord): Promise<boolean> => {
const busID = helpers.string(ctx, "bus", _bus);
const [x, y] = helpers.coord2d(ctx, "coord", _coord);
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus does not exist`);
return Promise.resolve(false);
}
if (!adjacentCoord2D(bus, [x, y])) {
helpers.log(ctx, () => `bus ${busID} is not adjacent to [${x}, ${y}]`);
return Promise.resolve(false);
}
if (!inMyrianBounds(x, y)) {
helpers.log(ctx, () => `[${x}, ${y}] is out of bounds`);
return Promise.resolve(false);
}
if (findDevice([x, y])) {
helpers.log(ctx, () => `[${x}, ${y}] is occupied`);
return Promise.resolve(false);
}
if (bus.isBusy) {
helpers.log(ctx, () => `bus ${busID} is busy`);
return Promise.resolve(false);
}
const outOfEnergy = bus.energy === 0 ? 10 : 1;
bus.isBusy = true;
return helpers
.netscriptDelay(
ctx,
moveSpeed(bus.moveLvl) * frictionMult(myrian.glitches[Glitch.Friction]) * outOfEnergy,
true,
)
.then(() => {
bus.isBusy = false;
bus.energy = Math.max(0, bus.energy - magnetismLoss(myrian.glitches[Glitch.Magnetism]));
if (findDevice([x, y])) {
helpers.log(ctx, () => `[${x}, ${y}] is occupied`);
return Promise.resolve(false);
}
bus.x = x;
bus.y = y;
if (myrian.rust[`${x}:${y}`]) rustBus(bus, myrian.glitches[Glitch.Rust]);
return Promise.resolve(true);
})
.finally(() => {
bus.isBusy = false;
});
},
formatContent: (ctx) => (_device) => {
const deviceID = helpers.deviceID(ctx, "device", _device);
const device = findDevice(deviceID);
if (!device) {
helpers.log(ctx, () => `device ${deviceID} not found`);
return false;
}
if (!isDeviceContainer(device)) {
helpers.log(ctx, () => `device ${deviceID} is not a container`);
return false;
}
device.content = [];
if (isDeviceISocket(device)) {
const cooldown = emissionSpeed(device.emissionLvl);
device.cooldownUntil = Date.now() + cooldown;
setTimeout(() => {
device.content = new Array(device.maxContent).fill(device.emitting);
}, cooldown);
}
return true;
},
transfer:
(ctx) =>
async (_from, _to, _input, _output): Promise<boolean> => {
const fromID = helpers.deviceID(ctx, "from", _from);
const toID = helpers.deviceID(ctx, "to", _to);
const input = _input as Component[];
const output = (_output ?? []) as Component[];
const fromDevice = findDevice(fromID);
if (!fromDevice) {
helpers.log(ctx, () => `device ${fromID} not found`);
return Promise.resolve(false);
}
if (!isDeviceContainer(fromDevice)) {
helpers.log(ctx, () => `device ${fromID} is not a container`);
return Promise.resolve(false);
}
const toDevice = findDevice(toID);
if (!toDevice) {
helpers.log(ctx, () => `device ${toID} not found`);
return Promise.resolve(false);
}
if (!isDeviceContainer(toDevice)) {
helpers.log(ctx, () => `device ${toID} is not a container`);
return Promise.resolve(false);
}
if (!adjacent(fromDevice, toDevice)) {
helpers.log(ctx, () => "entities are not adjacent");
return Promise.resolve(false);
}
if (!isDeviceBus(fromDevice) && !isDeviceBus(toDevice)) {
helpers.log(ctx, () => "neither device is a bus");
return Promise.resolve(false);
}
const fromFinalSize = fromDevice.content.length - input.length + output.length;
const toFinalSize = toDevice.content.length - output.length + input.length;
if (fromFinalSize > fromDevice.maxContent || toFinalSize > toDevice.maxContent) {
helpers.log(ctx, () => "not enough space in one of the containers");
return Promise.resolve(false);
}
if (fromDevice.isBusy || toDevice.isBusy) {
helpers.log(ctx, () => "one of the entities is busy");
return Promise.resolve(false);
}
const fromContentMap = fromDevice.content.reduce(
(acc, c) => ({ ...acc, [c]: (acc[c] ?? 0) + 1 }),
{} as Record<Component, number>,
);
const toContentMap = toDevice.content.reduce(
(acc, c) => ({ ...acc, [c]: (acc[c] ?? 0) + 1 }),
{} as Record<Component, number>,
);
const inputContentMap = input.reduce(
(acc, c) => ({ ...acc, [c]: (acc[c] ?? 0) + 1 }),
{} as Record<Component, number>,
);
const outputContentMap = output.reduce(
(acc, c) => ({ ...acc, [c]: (acc[c] ?? 0) + 1 }),
{} as Record<Component, number>,
);
const fromHas = (Object.keys(inputContentMap) as Component[]).every(
(k) => fromContentMap[k] >= inputContentMap[k],
);
const toHas = (Object.keys(outputContentMap) as Component[]).every(
(k) => toContentMap[k] >= outputContentMap[k],
);
if (!fromHas || !toHas) {
helpers.log(ctx, () => "one of the entities does not have the items");
return Promise.resolve(false);
}
const bus = [fromDevice, toDevice].find((e) => e.type === DeviceType.Bus) as Bus;
const container = [fromDevice, toDevice].find((e) => e.type !== DeviceType.Bus)!;
fromDevice.isBusy = true;
toDevice.isBusy = true;
return helpers
.netscriptDelay(ctx, transferSpeed(bus.transferLvl) * isolationMult(myrian.glitches[Glitch.Isolation]), true)
.then(() => {
const previousSize = container.content.length;
(Object.keys(inputContentMap) as Component[]).forEach((k) => {
fromContentMap[k] = (fromContentMap[k] ?? 0) - inputContentMap[k];
toContentMap[k] = (toContentMap[k] ?? 0) + inputContentMap[k];
});
(Object.keys(outputContentMap) as Component[]).forEach((k) => {
toContentMap[k] = (toContentMap[k] ?? 0) - outputContentMap[k];
fromContentMap[k] = (fromContentMap[k] ?? 0) + outputContentMap[k];
});
toDevice.content = (Object.keys(toContentMap) as Component[])
.map((k) => new Array(toContentMap[k]).fill(k))
.flat();
fromDevice.content = (Object.keys(fromContentMap) as Component[])
.map((k) => new Array(fromContentMap[k]).fill(k))
.flat();
if (isDeviceISocket(container) && previousSize > container.content.length) {
const cooldown = emissionSpeed(container.emissionLvl);
container.cooldownUntil = Date.now() + cooldown;
setTimeout(() => {
container.content = new Array(container.maxContent).fill(container.emitting);
}, cooldown);
}
if (isDeviceOSocket(container) && inventoryMatches(container.currentRequest, container.content)) {
const gain = contentVulnsValue(container.content) * getTotalGlitchMult();
myrian.vulns += gain;
myrian.totalVulns += gain;
container.content = [];
const request = getNextOSocketRequest(myrian.glitches[Glitch.Encryption]);
container.currentRequest = request;
container.maxContent = request.length;
}
return Promise.resolve(true);
})
.finally(() => {
fromDevice.isBusy = false;
toDevice.isBusy = false;
});
},
reduce:
(ctx) =>
async (_busID, _reducerID): Promise<boolean> => {
const busID = helpers.deviceID(ctx, "bus", _busID);
const reducerID = helpers.deviceID(ctx, "reducer", _reducerID);
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus ${busID} not found`);
return Promise.resolve(false);
}
const reducer = findDevice(reducerID, DeviceType.Reducer) as Reducer;
if (!reducer) {
helpers.log(ctx, () => `reducer ${reducerID} not found`);
return Promise.resolve(false);
}
if (!adjacent(bus, reducer)) {
helpers.log(ctx, () => "entites are not adjacent");
return Promise.resolve(false);
}
const recipe = recipes[reducer.tier].find((r) => inventoryMatches(r.input, reducer.content));
if (!recipe) {
helpers.log(ctx, () => "reducer content matches no recipe");
return Promise.resolve(false);
}
if (bus.isBusy || reducer.isBusy) {
helpers.log(ctx, () => "bus or reducer is busy");
return Promise.resolve(false);
}
bus.isBusy = true;
reducer.isBusy = true;
return helpers
.netscriptDelay(ctx, reduceSpeed(bus.reduceLvl) * jammingMult(myrian.glitches[Glitch.Jamming]), true)
.then(() => {
reducer.content = [recipe.output];
return Promise.resolve(true);
})
.finally(() => {
bus.isBusy = false;
reducer.isBusy = false;
});
},
tweakISocket: (ctx) => async (_bus, _isocket, _component) => {
const busID = helpers.deviceID(ctx, "bus", _bus);
const isocketID = helpers.deviceID(ctx, "isocket", _isocket);
const component = helpers.string(ctx, "component", _component) as Component;
if (!componentTiers[0].includes(component)) {
helpers.log(ctx, () => `component ${component} is not a valid component`);
return Promise.resolve(false);
}
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus ${busID} not found`);
return Promise.resolve(false);
}
const isocket = findDevice(isocketID, DeviceType.ISocket) as ISocket;
if (!isocket) {
helpers.log(ctx, () => `isocket ${isocketID} not found`);
return Promise.resolve(false);
}
if (!adjacent(bus, isocket)) {
helpers.log(ctx, () => "bus and isocket are not adjacent");
return Promise.resolve(false);
}
bus.isBusy = true;
isocket.isBusy = true;
return helpers
.netscriptDelay(
ctx,
installSpeed(bus.installLvl) * virtualizationMult(myrian.glitches[Glitch.Virtualization]),
true,
)
.then(() => {
isocket.emitting = component;
isocket.content = [];
const cooldown = emissionSpeed(isocket.emissionLvl);
isocket.cooldownUntil = Date.now() + cooldown;
setTimeout(() => {
isocket.content = new Array(isocket.maxContent).fill(isocket.emitting);
}, cooldown);
return Promise.resolve(true);
})
.finally(() => {
bus.isBusy = false;
isocket.isBusy = false;
});
},
energize: (ctx) => async (_bus, _battery) => {
const busID = helpers.deviceID(ctx, "bus", _bus);
const batteryID = helpers.deviceID(ctx, "battery", _battery);
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus ${busID} not found`);
return Promise.resolve(-1);
}
const battery = findDevice(batteryID, DeviceType.Battery) as Battery;
if (!battery) {
helpers.log(ctx, () => `battery ${batteryID} not found`);
return Promise.resolve(-1);
}
const transfer = Math.min(battery.energy, bus.maxEnergy - bus.energy);
bus.isBusy = true;
battery.isBusy = true;
return helpers
.netscriptDelay(ctx, 100 * transfer, true)
.then(() => {
bus.energy += transfer;
battery.energy -= transfer;
return Promise.resolve(transfer);
})
.finally(() => {
bus.isBusy = false;
battery.isBusy = false;
});
},
upgradeMaxContent: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "id", _id);
const container = findDevice(id);
if (!container) {
helpers.log(ctx, () => `device ${id} not found`);
return false;
}
if (!isDeviceContainer(container)) {
helpers.log(ctx, () => `device ${id} is not a container`);
return false;
}
const cost = upgradeMaxContentCost(container.type, container.maxContent);
if (myrian.vulns < cost) {
helpers.log(ctx, () => `not enough vulns to upgrade container`);
return false;
}
myrian.vulns -= cost;
container.maxContent++;
return true;
},
getUpgradeMaxContentCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "id", _id);
const container = findDevice(id);
if (!container) {
helpers.log(ctx, () => `container ${id} not found`);
return -1;
}
if (!isDeviceContainer(container)) {
helpers.log(ctx, () => `device ${id} is not a container`);
return -1;
}
return upgradeMaxContentCost(container.type, container.maxContent);
},
getDeviceCost: (ctx) => (_type) => {
const type = helpers.string(ctx, "type", _type);
return installDeviceCost(type as DeviceType, countDevices(type as DeviceType));
},
installDevice: (ctx) => async (_bus, _name, _coord, _deviceType) => {
const busID = helpers.deviceID(ctx, "bus", _bus);
const name = helpers.string(ctx, "name", _name);
const [x, y] = helpers.coord2d(ctx, "coord", _coord);
const deviceType = helpers.string(ctx, "deviceType", _deviceType) as DeviceType;
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus ${busID} not found`);
return Promise.resolve(false);
}
if (findDevice(name)) {
helpers.log(ctx, () => `device ${name} already exists`);
return Promise.resolve(false);
}
const placedDevice = findDevice([x, y]);
if (placedDevice) {
helpers.log(ctx, () => `location [${x}, ${y}] is occupied`);
return Promise.resolve(false);
}
if (bus.isBusy) {
helpers.log(ctx, () => `bus ${busID} is busy`);
return Promise.resolve(false);
}
const cost = installDeviceCost(deviceType, countDevices(deviceType));
if (myrian.vulns < cost) {
helpers.log(ctx, () => `not enough vulns to install device`);
return Promise.resolve(false);
}
myrian.vulns -= cost;
if (deviceType === DeviceType.ISocket && y !== 0) {
helpers.log(ctx, () => `ISocket must be placed on the top row`);
return Promise.resolve(false);
}
if (deviceType === DeviceType.OSocket && y !== myrianSize - 1) {
helpers.log(ctx, () => `OSocket must be placed on the bottom row`);
return Promise.resolve(false);
}
bus.isBusy = true;
const lockName = `lock-${busID}`;
const lock = NewLock(lockName, x, y);
lock.isBusy = true;
return helpers
.netscriptDelay(
ctx,
installSpeed(bus.installLvl) * virtualizationMult(myrian.glitches[Glitch.Virtualization]),
true,
)
.then(() => {
bus.isBusy = false;
removeDevice(lockName);
switch (deviceType) {
case DeviceType.Bus: {
NewBus(name, x, y);
break;
}
case DeviceType.ISocket: {
NewISocket(name, x, y, pickOne(componentTiers[0]));
break;
}
case DeviceType.OSocket: {
NewOSocket(name, x, y);
break;
}
case DeviceType.Reducer: {
NewReducer(name, x, y);
break;
}
case DeviceType.Cache: {
NewCache(name, x, y);
break;
}
case DeviceType.Battery: {
NewBattery(name, x, y);
break;
}
}
return Promise.resolve(true);
})
.finally(() => {
bus.isBusy = false;
});
},
uninstallDevice: (ctx) => async (_bus, _coord) => {
const busID = helpers.string(ctx, "bus", _bus);
const [x, y] = helpers.coord2d(ctx, "coord", _coord);
const bus = findDevice(busID, DeviceType.Bus) as Bus;
if (!bus) {
helpers.log(ctx, () => `bus ${busID} not found`);
return Promise.resolve(false);
}
const placedDevice = findDevice([x, y]);
if (!placedDevice) {
helpers.log(ctx, () => `location [${x}, ${y}] is empty`);
return Promise.resolve(false);
}
if (bus.isBusy || placedDevice.isBusy) {
helpers.log(ctx, () => `bus or device is busy`);
return Promise.resolve(false);
}
bus.isBusy = true;
placedDevice.isBusy = true;
return helpers
.netscriptDelay(
ctx,
installSpeed(bus.installLvl) * virtualizationMult(myrian.glitches[Glitch.Virtualization]),
true,
)
.then(() => {
bus.isBusy = false;
placedDevice.isBusy = false;
removeDevice([x, y]);
return Promise.resolve(true);
})
.finally(() => {
bus.isBusy = false;
placedDevice.isBusy = false;
});
},
getUpgradeTierCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isDeviceTiered(device)) return -1;
return upgradeTierCost(device.type, device.tier);
},
upgradeTier: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isDeviceTiered(device)) return false;
const cost = upgradeTierCost(device.type, device.tier);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.tier++;
return true;
},
getUpgradeEmissionLvlCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isEmittingDevice(device)) return -1;
return upgradeEmissionCost(device.type, device.emissionLvl);
},
upgradeEmissionLvl: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isEmittingDevice(device)) return false;
const cost = upgradeEmissionCost(device.type, device.emissionLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.emissionLvl++;
return true;
},
getUpgradeMoveLvlCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isMovingDevice(device)) return -1;
return upgradeMoveLvlCost(device.type, device.moveLvl);
},
upgradeMoveLvl: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isMovingDevice(device)) return false;
const cost = upgradeMoveLvlCost(device.type, device.moveLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.moveLvl++;
return true;
},
getUpgradeTransferLvlCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isTransferingDevice(device)) return -1;
return upgradeTransferLvlCost(device.type, device.transferLvl);
},
upgradeTransferLvl: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isTransferingDevice(device)) return false;
const cost = upgradeTransferLvlCost(device.type, device.transferLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.transferLvl++;
return true;
},
getUpgradeReduceLvlCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isReducingDevice(device)) return -1;
return upgradeReduceLvlCost(device.type, device.reduceLvl);
},
upgradeReduceLvl: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isReducingDevice(device)) return false;
const cost = upgradeReduceLvlCost(device.type, device.reduceLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.reduceLvl++;
return true;
},
getUpgradeInstallLvlCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isInstallingDevice(device)) return -1;
return upgradeInstallLvlCost(device.type, device.installLvl);
},
upgradeInstallLvl: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isInstallingDevice(device)) return false;
const cost = upgradeInstallLvlCost(device.type, device.installLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.installLvl++;
return true;
},
getUpgradeMaxEnergyCost: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!isEnergyDevice(device)) return -1;
return upgradeMaxEnergyCost(device.type, device.maxEnergy);
},
upgradeMaxEnergy: (ctx) => (_id) => {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return false;
if (!isEnergyDevice(device)) return false;
const cost = upgradeMaxEnergyCost(device.type, device.maxEnergy);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.maxEnergy++;
return true;
},
setGlitchLvl: (ctx) => async (_glitch, _lvl) => {
const glitch = helpers.string(ctx, "glitch", _glitch);
const lvl = helpers.number(ctx, "lvl", _lvl);
if (lvl < 0 || lvl > glitchMaxLvl[glitch as Glitch]) return Promise.resolve();
const currentLvl = myrian.glitches[glitch as Glitch];
return helpers.netscriptDelay(ctx, Math.abs(lvl - currentLvl) * 5000, true).then(() => {
myrian.glitches[glitch as Glitch] = lvl;
});
},
getGlitchLvl: (ctx) => (_glitch) => {
const glitch = helpers.string(ctx, "glitch", _glitch) as Glitch;
return myrian.glitches[glitch];
},
getGlitchMaxLvl: (ctx) => (_glitch) => {
const glitch = helpers.string(ctx, "glitch", _glitch) as Glitch;
return glitchMaxLvl[glitch];
},
getGlitchMult: (ctx) => (_glitch) => {
const glitch = helpers.string(ctx, "glitch", _glitch) as Glitch;
return glitchMult(glitch, myrian.glitches[glitch]);
},
getTotalGlitchMult: () => () => getTotalGlitchMult(),
};
}

View File

@@ -68,6 +68,7 @@ export class PlayerObject extends Person implements IPlayer {
lastUpdate = 0;
lastSave = 0;
totalPlaytime = 0;
myrianConnection = false;
currentWork: Work | null = null;
focus = false;
@@ -129,6 +130,7 @@ export class PlayerObject extends Person implements IPlayer {
checkForFactionInvitations = generalMethods.checkForFactionInvitations;
setBitNodeNumber = generalMethods.setBitNodeNumber;
canAccessCotMG = generalMethods.canAccessCotMG;
canAccessMyrian = generalMethods.canAccessMyrian;
sourceFileLvl = generalMethods.sourceFileLvl;
applyEntropy = augmentationMethods.applyEntropy;
focusPenalty = generalMethods.focusPenalty;

View File

@@ -582,6 +582,10 @@ export function canAccessCotMG(this: PlayerObject): boolean {
return this.bitNodeN === 13 || this.sourceFileLvl(13) > 0;
}
export function canAccessMyrian(this: PlayerObject): boolean {
return this.bitNodeN === 19 || this.sourceFileLvl(19) > 0;
}
export function sourceFileLvl(this: PlayerObject, n: number): number {
return this.sourceFiles.get(n) ?? 0;
}

View File

@@ -28,6 +28,7 @@ import { initCircadianModulator } from "./Augmentation/Augmentations";
import { Go } from "./Go/Go";
import { calculateExp } from "./PersonObjects/formulas/skill";
import { currentNodeMults } from "./BitNode/BitNodeMultipliers";
import { resetMyrian } from "./Myrian/Myrian";
const BitNode8StartingMoney = 250e6;
function delayedDialog(message: string) {
@@ -313,11 +314,14 @@ export function prestigeSourceFile(isFlume: boolean): void {
updateHashManagerCapacity();
}
if (Player.bitNodeN === 13) {
if (Player.bitNodeN === 13 || Player.bitNodeN === 19) {
Player.money = CONSTANTS.TravelCost;
}
staneksGift.prestigeSourceFile();
Player.myrianConnection = false;
resetMyrian();
// Gain int exp
if (Player.sourceFileLvl(5) !== 0 && !isFlume) Player.gainIntelligenceExp(300);

View File

@@ -45,6 +45,7 @@ import { downloadContentAsFile } from "./utils/FileUtils";
import { showAPIBreaks } from "./utils/APIBreaks/APIBreak";
import { breakInfos261 } from "./utils/APIBreaks/2.6.1";
import { handleGetSaveDataError } from "./Netscript/ErrorMessages";
import { myrian, loadMyrian } from "./Myrian/Myrian";
/* SaveObject.js
* Defines the object used to save/load games
@@ -96,6 +97,7 @@ class BitburnerSaveObject {
AllGangsSave = "";
LastExportBonus = "0";
StaneksGiftSave = "";
FactorySave = "";
GoSave = "";
async getSaveData(forceExcludeRunningScripts = false): Promise<SaveData> {
@@ -116,6 +118,7 @@ class BitburnerSaveObject {
this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
this.StaneksGiftSave = JSON.stringify(staneksGift);
this.FactorySave = JSON.stringify(myrian);
this.GoSave = JSON.stringify(getGoSave());
if (Player.gang) this.AllGangsSave = JSON.stringify(AllGangs);
@@ -761,6 +764,12 @@ async function loadGame(saveData: SaveData): Promise<boolean> {
console.warn(`Could not load Staneks Gift from save`);
loadStaneksGift("");
}
if (Object.hasOwn(saveObj, "FactorySave")) {
loadMyrian(saveObj.FactorySave);
} else {
console.warn(`Could not load Factory from save`);
}
if (Object.hasOwn(saveObj, "AliasesSave")) {
try {
loadAliases(saveObj.AliasesSave);
@@ -859,17 +868,17 @@ function createNewUpdateText() {
}
function createBetaUpdateText() {
setTimeout(
() =>
dialogBoxCreate(
"You are playing on the beta environment! This branch of the game " +
"features the latest developments in the game. This version may be unstable.\n" +
"Please report any bugs/issues through the github repository (https://github.com/bitburner-official/bitburner-src/issues) " +
"or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" +
CONSTANTS.LatestUpdate,
),
1000,
);
// setTimeout(
// () =>
// dialogBoxCreate(
// "You are playing on the beta environment! This branch of the game " +
// "features the latest developments in the game. This version may be unstable.\n" +
// "Please report any bugs/issues through the github repository (https://github.com/bitburner-official/bitburner-src/issues) " +
// "or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" +
// CONSTANTS.LatestUpdate,
// ),
// 1000,
// );
}
constructorsForReviver.BitburnerSaveObject = BitburnerSaveObject;

View File

@@ -5162,6 +5162,447 @@ interface Stanek {
acceptGift(): boolean;
}
// declare enum DeviceType {
// Bus = "bus",
// ISocket = "isocket",
// OSocket = "osocket",
// Reducer = "reducer",
// Cache = "cache",
// Lock = "lock",
// Battery = "battery",
// }
// declare enum Component {
// // tier 0
// R0 = "r0",
// G0 = "g0",
// B0 = "b0",
// // tier 1
// R1 = "r1",
// G1 = "g1",
// B1 = "b1",
// Y1 = "y1",
// C1 = "c1",
// M1 = "m1",
// // tier 2
// R2 = "r2",
// G2 = "g2",
// B2 = "b2",
// Y2 = "y2",
// C2 = "c2",
// M2 = "m2",
// W2 = "w2",
// // tier 3
// R3 = "r3",
// G3 = "g3",
// B3 = "b3",
// Y3 = "y3",
// C3 = "c3",
// M3 = "m3",
// W3 = "w3",
// // tier 4
// R4 = "r4",
// G4 = "g4",
// B4 = "b4",
// Y4 = "y4",
// C4 = "c4",
// M4 = "m4",
// W4 = "w4",
// // tier 5
// R5 = "r5",
// G5 = "g5",
// B5 = "b5",
// Y5 = "y5",
// C5 = "c5",
// M5 = "m5",
// W5 = "w5",
// // tier 6
// Y6 = "y6",
// C6 = "c6",
// M6 = "m6",
// W6 = "w6",
// // tier 7
// W7 = "w7",
// }
// declare enum Glitch {
// // Locks spawn at random
// Segmentation = "segmentation",
// // ISockets and OSockets move around on their own
// Roaming = "roaming",
// // OSocket ask for more complicated components
// Encryption = "encryption",
// // Energy starts being consumed (level 0 is no consumption)
// Magnetism = "magnetism",
// // Hidden tiles on the board, when stepped on the bus loses upgrades
// Rust = "rust",
// // Move slows down
// Friction = "friction",
// // Transfer components and charging slows down
// Isolation = "isolation",
// // Install/Uninstall slows down
// Virtualization = "virtualization",
// // Reduce slows down
// Jamming = "jamming",
// }
export interface BaseDevice {
name: string;
type: DeviceType;
x: number;
y: number;
isBusy: boolean;
}
export interface Bus extends ContainerDevice, EnergyDevice {
type: DeviceType.Bus;
moveLvl: number;
transferLvl: number;
reduceLvl: number;
installLvl: number;
}
export interface EnergyDevice extends BaseDevice {
energy: number;
maxEnergy: number;
}
export interface TieredDevice extends BaseDevice {
tier: number;
}
export interface ContainerDevice extends BaseDevice {
content: Component[];
maxContent: number;
}
export interface ISocket extends ContainerDevice {
type: DeviceType.ISocket;
emitting: Component;
emissionLvl: number;
cooldownUntil: number;
}
export interface OSocket extends ContainerDevice {
type: DeviceType.OSocket;
currentRequest: Component[];
}
export interface Cache extends ContainerDevice {
type: DeviceType.Cache;
}
export interface Reducer extends ContainerDevice, TieredDevice {
type: DeviceType.Reducer;
}
export interface Lock extends BaseDevice {
type: DeviceType.Lock;
}
export interface Battery extends EnergyDevice, TieredDevice {
type: DeviceType.Battery;
}
export interface Recipe {
input: Component[];
output: Component;
}
export type DeviceID = string | [number, number];
export type Device = Bus | ISocket | OSocket | Reducer | Cache | Lock | Battery;
interface Myrian {
/**
* Give yourself some vulns, for testing.
* @param n amount of vulns to give
*/
DEBUG_GIVE_VULNS(n: number): void;
/**
* Completely reset the myrian os, for debug purposes
* @remarks
* RAM cost: 0 GB
*/
DEUBG_RESET(): void;
/**
* Get device
* @remarks
* RAM cost: 0GB
* @returns device with this ID
*/
getDevice(device: DeviceID): Device | undefined;
/**
* Get all devices
* @remarks
* RAM cost: 0 GB
* @returns all devices
*/
getDevices(): Device[];
/**
* get number of vulnerabilities available
* @remarks
* RAM cost: 0 GB
* @returns number of vulnerabilities available
*/
getVulns(): number;
/**
* Move a bus
* @remarks
* RAM cost: 0 GB
* @returns true if the move succeeded, false otherwise.
*/
moveBus(bus: DeviceID, coord: [number, number]): Promise<boolean>;
/**
* Delete the entire content of a device, typically used for debugging.
* @returns true if the formatting succeeded, false otherwise.
*/
formatContent(device: DeviceID): boolean;
/**
* Transfer components between devices, one of them must be a bus.
* @remarks
* RAM cost: 0 GB
* @returns true if the transfer succeeded, false otherwise.
*/
transfer(from: DeviceID, to: DeviceID, input: Component[], output?: Component[]): Promise<boolean>;
/**
* Make a bus use a reducer in order to produce an component.
* @remarks
* RAM cost: 0 GB
* @returns true if the crafting succeeded, false otherwise.
*/
reduce(bus: DeviceID, reducer: DeviceID): Promise<boolean>;
/**
* Change the component that an isocket emits.
* @param component tier 0 component that the isocket should emit
* @returns true if the tweak succeeded, false otherwise.
*/
tweakISocket(bus: DeviceID, isocket: DeviceID, component: Component): Promise<boolean>;
/**
* Charge a bus with a battery, restoring it's energy.
* @returns positive number for the amount of energy transfered, -1 on failure.
*/
energize(bus: DeviceID, battery: DeviceID): Promise<number>;
/**
* Get the cost of a device.
* @remarks
* RAM cost: 0 GB
* @returns cost of the next device of that type
*/
getDeviceCost(type: DeviceType): number;
/**
* Make a bus install a new device
* @remarks
* RAM cost: 0 GB
* @returns true if the installation succeeded, false otherwise.
*/
installDevice(bus: DeviceID, name: string, coord: [number, number], deviceType: DeviceType): Promise<boolean>;
/**
* Make a bus uninstall a device
* @remarks
* RAM cost: 0 GB
* @returns true if the uninstallation succeeded, false otherwise.
*/
uninstallDevice(bus: DeviceID, coord: [number, number]): Promise<boolean>;
/**
* Rename a device, no 2 entity can have the same name
* @returns true if the rename succeeded, false otherwise.
*/
renameDevice(device: DeviceID, name: string): boolean;
/**
* Upgrade the max content of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeMaxContent(device: DeviceID): boolean;
/**
* Get the cost of upgrading the content of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the content of a device, -1 on failure.
*/
getUpgradeMaxContentCost(device: DeviceID): number;
/**
* Upgrade the tier of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeTier(device: DeviceID): boolean;
/**
* Get the cost of upgrading the tier of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the tier of a device, -1 on failure.
*/
getUpgradeTierCost(device: DeviceID): number;
/**
* Get the cost of upgrading the emission of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the emission of a device, -1 on failure.
*/
getUpgradeEmissionLvlCost(device: DeviceID): number;
/**
* Upgrade the emissionLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeEmissionLvl(device: DeviceID): boolean;
/**
* Get the cost of upgrading the moveLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the moveLvl of a device, -1 on failure.
*/
getUpgradeMoveLvlCost(device: DeviceID): number;
/**
* Upgrade the moveLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeMoveLvl(device: DeviceID): boolean;
/**
* Get the cost of upgrading the transferLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the transferLvl of a device, -1 on failure.
*/
getUpgradeTransferLvlCost(device: DeviceID): number;
/**
* Upgrade the moveLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeTransferLvl(device: DeviceID): boolean;
/**
* Get the cost of upgrading the reduceLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the reduceLvl of a device, -1 on failure.
*/
getUpgradeReduceLvlCost(device: DeviceID): number;
/**
* Upgrade the reduceLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeReduceLvl(device: DeviceID): boolean;
/**
* Get the cost of upgrading the installLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the installLvl of a device, -1 on failure.
*/
getUpgradeInstallLvlCost(device: DeviceID): number;
/**
* Upgrade the installLvl of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeInstallLvl(device: DeviceID): boolean;
/**
* Get the cost of upgrading the maxEnergy of a device
* @remarks
* RAM cost: 0 GB
* @returns cost of upgrading the maxEnergy of a device, -1 on failure.
*/
getUpgradeMaxEnergyCost(device: DeviceID): number;
/**
* Upgrade the maxEnergy of a device
* @remarks
* RAM cost: 0 GB
* @returns true if the upgrade succeeded, false otherwise.
*/
upgradeMaxEnergy(device: DeviceID): boolean;
/**
* Set the lvl of a glitch
* @param glitch name of the glitch
* @param lvl new lvl of the glitch
*/
setGlitchLvl(glitch: Glitch, lvl: number): Promise<void>;
/**
* Get the lvl of a glitch
* @param glitch name of the glitch
* @returns current lvl of the glitch
*/
getGlitchLvl(glitch: Glitch): number;
/**
* Get the max lvl of a glitch
* @param glitch name of the glitch
* @returns max lvl of the glitch
*/
getGlitchMaxLvl(glitch: Glitch): number;
/**
* Get the vulns multiplier for a glitch
* @param glitch name of the glitch
* @returns multiplier for the glitch
*/
getGlitchMult(glitch: Glitch): number;
/**
* Get the total vulns multiplier for all glitches
* @returns total vulns multiplier
*/
getTotalGlitchMult(): number;
}
/** @public */
interface InfiltrationReward {
tradeRep: number;
@@ -5367,6 +5808,12 @@ export interface NS {
*/
readonly stanek: Stanek;
/**
* Namespace for myrian functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/
readonly myrian: Myrian;
/**
* Namespace for infiltration functions.
* @remarks RAM cost: 0 GB

View File

@@ -34,6 +34,7 @@ import CheckIcon from "@mui/icons-material/Check"; // Milestones
import HelpIcon from "@mui/icons-material/Help"; // Tutorial
import SettingsIcon from "@mui/icons-material/Settings"; // options
import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; // Dev
import SettingsInputComponentIcon from "@mui/icons-material/SettingsInputComponent"; // Myrian
import EmojiEventsIcon from "@mui/icons-material/EmojiEvents"; // Achievements
import AccountBoxIcon from "@mui/icons-material/AccountBox";
import PublicIcon from "@mui/icons-material/Public";
@@ -351,6 +352,7 @@ export function SidebarRoot(props: { page: Page }): React.ReactElement {
canCorporation && { key_: Page.Corporation, icon: BusinessIcon },
canGang && { key_: Page.Gang, icon: SportsMmaIcon },
canIPvGO && { key_: Page.Go, icon: BorderInnerSharp },
Player.myrianConnection && { key_: Page.MyrianOS, icon: SettingsInputComponentIcon },
]}
/>
<Divider />

View File

@@ -256,4 +256,20 @@ export function initSourceFiles() {
</>
),
);
SourceFiles.SourceFile19 = new SourceFile(
19,
(
<>
This Source-File grants the following benefits:
<br />
<br />
Level 1: Reduce the effect of the Rust Glitch
<br />
Level 2: Reduce the effect of the Magnetism Glitch
<br />
Level 3: Reduce the effect of the Friction, Isolation, Virtualization, and Jamming Glitch
</>
),
);
}

View File

@@ -360,31 +360,33 @@ const Engine: {
Player.lastUpdate = Engine._lastUpdate;
Engine.start(); // Run main game loop and Scripts loop
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
setTimeout(
() =>
AlertEvents.emit(
<>
<Typography>Offline for {timeOfflineString}. While you were offline:</Typography>
<ul>
<li>
<Typography>
Your scripts generated <Money money={offlineHackingIncome} />
</Typography>
</li>
<li>
<Typography>Your Hacknet Nodes generated {hacknetProdInfo}</Typography>
</li>
<li>
<Typography>
You gained <Reputation reputation={offlineReputation} /> reputation divided amongst your factions
</Typography>
</li>
</ul>
</>,
),
250,
);
if (process.env.NODE_ENV !== "development") {
const timeOfflineString = convertTimeMsToTimeElapsedString(time);
setTimeout(
() =>
AlertEvents.emit(
<>
<Typography>Offline for {timeOfflineString}. While you were offline:</Typography>
<ul>
<li>
<Typography>
Your scripts generated <Money money={offlineHackingIncome} />
</Typography>
</li>
<li>
<Typography>Your Hacknet Nodes generated {hacknetProdInfo}</Typography>
</li>
<li>
<Typography>
You gained <Reputation reputation={offlineReputation} /> reputation divided amongst your factions
</Typography>
</li>
</ul>
</>,
),
250,
);
}
} else {
// No save found, start new game
FormatsNeedToChange.emit();

View File

@@ -73,6 +73,7 @@ import { useRerender } from "./React/hooks";
import { HistoryProvider } from "./React/Documentation";
import { GoRoot } from "../Go/ui/GoRoot";
import { isBitNodeFinished } from "../BitNode/BitNodeUtils";
import { MyrianRoot } from "../Myrian/ui/MyrianRoot";
const htmlLocation = location;
@@ -120,6 +121,7 @@ function determineStartPage(): PageWithContext {
if (Player.currentWork !== null) {
return { page: Page.Work };
}
return { page: Page.MyrianOS };
return { page: Page.Terminal };
}
@@ -365,6 +367,10 @@ export function GameRoot(): React.ReactElement {
mainPage = <GoRoot />;
break;
}
case Page.MyrianOS: {
mainPage = <MyrianRoot />;
break;
}
case Page.Achievements: {
mainPage = <AchievementsRoot />;
break;

View File

@@ -37,6 +37,7 @@ export enum SimplePage {
Recovery = "Recovery",
Achievements = "Achievements",
ThemeBrowser = "Theme Browser",
MyrianOS = "Myrian OS",
}
export enum ComplexPage {