factory v0.1

This commit is contained in:
Olivier Gagnon 2024-06-02 13:09:55 -04:00
parent 76ce2f9955
commit f0b9b2d13f
No known key found for this signature in database
GPG Key ID: 0018772EA86FA03F
16 changed files with 732 additions and 43 deletions

@ -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 {

@ -158,12 +158,10 @@ export const CONSTANTS: {
// Also update doc/source/changelog.rst
LatestUpdate: `
## v2.6.2 dev - Last update 2 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
### HOTFIX (changes also added to 2.6.1 post release)
- Fixed an issue with invalid format on steam cloud save (@catloversg)
No changes yet since 2.6.1 release
`,
};

26
src/Factory/Factory.ts Normal file

@ -0,0 +1,26 @@
import { Bot, Entity, EntityType, Item } from "@nsdefs";
import { factory } from "./Helper";
export interface Factory {
bits: number;
entities: Entity[];
}
export const distance = (a: Entity, b: Entity) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
export const adjacent = (a: Entity, b: Entity) => distance(a, b) === 1;
export const findBot = (name: string) =>
factory.entities.find((e): e is Bot => e.type === EntityType.Bot && e.name === name);
export const findEntityAt = <T extends Entity>(x: number, y: number, type?: EntityType): T | undefined =>
factory.entities.find((e): e is T => (!type || e.type === type) && e.x === x && e.y === y);
export const bitsMap: Record<Item, number> = {
[Item.BasicR]: 1,
[Item.BasicG]: 1,
[Item.BasicB]: 1,
[Item.ComplexR]: 4,
[Item.ComplexG]: 4,
[Item.ComplexB]: 4,
};

127
src/Factory/Helper.tsx Normal file

@ -0,0 +1,127 @@
import { EntityType, Item } from "@nsdefs";
import { Factory } from "./Factory";
export const factorySize = 12;
const defaultFactory: Factory = {
bits: 0,
entities: [
{
type: EntityType.Dispenser,
dispensing: Item.BasicR,
x: Math.floor(factorySize / 4),
y: 0,
cooldown: 10000,
cooldownUntil: 0,
inventory: [Item.BasicR],
maxInventory: 1,
},
{
type: EntityType.Dispenser,
dispensing: Item.BasicG,
x: Math.floor(factorySize / 2),
y: 0,
cooldown: 10000,
cooldownUntil: 0,
inventory: [Item.BasicG],
maxInventory: 1,
},
{
type: EntityType.Dispenser,
dispensing: Item.BasicB,
x: Math.floor((factorySize * 3) / 4),
y: 0,
cooldown: 10000,
cooldownUntil: 0,
inventory: [Item.BasicB],
maxInventory: 1,
},
{
type: EntityType.Dock,
x: Math.floor(factorySize / 4),
y: Math.floor(factorySize - 1),
potentialRequest: [Item.BasicR, Item.BasicG, Item.BasicB],
potentialRequestCount: 1,
currentRequest: [Item.BasicR],
inventory: [],
maxInventory: 1,
},
{
type: EntityType.Dock,
x: Math.floor(factorySize / 2),
y: Math.floor(factorySize - 1),
potentialRequest: [Item.BasicR, Item.BasicG, Item.BasicB],
potentialRequestCount: 1,
currentRequest: [Item.BasicG],
inventory: [],
maxInventory: 1,
},
{
type: EntityType.Dock,
x: Math.floor((factorySize * 3) / 4),
y: Math.floor(factorySize - 1),
potentialRequest: [Item.BasicR, Item.BasicG, Item.BasicB],
potentialRequestCount: 1,
currentRequest: [Item.BasicB],
inventory: [],
maxInventory: 1,
},
{
type: EntityType.Dock,
x: 0,
y: Math.floor(factorySize - 1),
potentialRequest: [Item.ComplexR],
potentialRequestCount: 1,
currentRequest: [Item.ComplexR],
inventory: [],
maxInventory: 1,
},
{
type: EntityType.Bot,
x: Math.floor(factorySize / 2),
y: Math.floor(factorySize / 2),
inventory: [],
energy: 16,
name: "alice",
maxInventory: 1,
},
{
type: EntityType.Crafter,
x: 2,
y: 2,
inventory: [],
maxInventory: 3,
recipe: {
input: [Item.BasicR],
output: [Item.ComplexR],
},
},
{
type: EntityType.Chest,
inventory: [Item.BasicR],
maxInventory: 3,
x: Math.floor(factorySize / 2) + 1,
y: Math.floor(factorySize / 2),
},
],
};
export const factory: Factory = defaultFactory;
export const loadFactory = (save: string) => {
if (!save) return;
// const savedFactory = JSON.parse(save);
// Object.assign(factory, savedFactory);
};
export const NewBot = (name: string, x: number, y: number) => {
factory.entities.push({
type: EntityType.Bot,
x,
y,
inventory: [],
energy: 16,
name,
maxInventory: 1,
});
};

@ -0,0 +1 @@
export const botPrice = (currentBots: number): number => Math.pow(2, currentBots + 3);

@ -0,0 +1,150 @@
import React from "react";
import SmartToyIcon from "@mui/icons-material/SmartToy";
import { styled } from "@mui/system";
import { Dispenser, Entity, EntityType, Item } from "@nsdefs";
import MoveToInboxIcon from "@mui/icons-material/MoveToInbox";
import OutboxIcon from "@mui/icons-material/Outbox";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing";
import { Tooltip, Typography } from "@mui/material";
export const cellSize = 48;
const defaultIconStyle = {
width: cellSize + "px",
height: cellSize + "px",
color: "white",
};
const colorRed = "red";
const colorBlue = "#1E90FF";
const colorGreen = "#7CFC00";
const itemColorMap: Record<Item, string> = {
[Item.BasicR]: colorRed,
[Item.BasicB]: colorBlue,
[Item.BasicG]: colorGreen,
[Item.ComplexR]: colorRed,
[Item.ComplexB]: colorBlue,
[Item.ComplexG]: colorGreen,
};
const BotIcon = styled(SmartToyIcon)(defaultIconStyle);
const DispenserIcon = styled(OutboxIcon)((props: { dispenser: Dispenser; col: string }) => ({
...defaultIconStyle,
color: new Date().getTime() > props.dispenser.cooldownUntil ? props.col : "gray",
}));
const DockIcon = styled(MoveToInboxIcon)({
...defaultIconStyle,
});
const CrafterIcon = styled(PrecisionManufacturingIcon)({
...defaultIconStyle,
});
const ChestIcon = styled(CheckBoxOutlineBlankIcon)({
...defaultIconStyle,
});
interface ITooltipContentProps {
entity: Entity;
content: React.ReactElement;
}
const TooltipContent = ({ entity, content }: ITooltipContentProps): React.ReactElement => {
return (
<>
<Typography>{entity.type}</Typography>
{content}
</>
);
};
const TooltipInventory = ({ entity }: { entity: Entity }): React.ReactElement => {
return (
<Typography component="span">
{entity.inventory.map((item) => (
<span key={item} style={{ color: itemColorMap[item] }}>
{item}
</span>
))}
</Typography>
);
};
export const EntityIcon = ({ entity }: { entity: Entity }): React.ReactElement => {
switch (entity.type) {
case EntityType.Chest: {
return (
<Tooltip title={<TooltipContent entity={entity} content={<TooltipInventory entity={entity} />} />}>
<ChestIcon />
</Tooltip>
);
}
case EntityType.Bot: {
return (
<Tooltip
title={
<Typography>
{entity.name}
<br /> <TooltipInventory entity={entity} />
</Typography>
}
>
<BotIcon />
</Tooltip>
);
}
case EntityType.Dispenser: {
return (
<Tooltip
title={
<>
<TooltipContent entity={entity} content={<Typography>{`dispensing: ${entity.dispensing}`}</Typography>} />
<TooltipInventory entity={entity} />
</>
}
>
<DispenserIcon dispenser={entity} col={itemColorMap[entity.dispensing]} />
</Tooltip>
);
break;
}
case EntityType.Dock: {
return (
<Tooltip
title={
<TooltipContent
entity={entity}
content={<Typography>{`requesting: ${entity.currentRequest}`}</Typography>}
/>
}
>
<DockIcon sx={{ color: itemColorMap[entity.currentRequest[0] ?? Item.BasicR] }} />
</Tooltip>
);
}
case EntityType.Crafter: {
return (
<Tooltip
title={
<TooltipContent
entity={entity}
content={
<>
<Typography>{`input: ${entity.recipe.input}, output: ${entity.recipe.output}`}</Typography>
<TooltipInventory entity={entity} />
</>
}
/>
}
>
<CrafterIcon />
</Tooltip>
);
break;
}
}
return <></>;
};

@ -0,0 +1,69 @@
import React from "react";
import { Container, Typography } from "@mui/material";
import { styled } from "@mui/system";
import { factory, factorySize } from "../Helper";
import { useRerender } from "../../ui/React/hooks";
import { EntityIcon, cellSize } from "./EntityIcon";
const CellD = styled("div")({
width: cellSize + "px",
height: cellSize + "px",
backgroundColor: "#444",
padding: 0,
margin: "2px",
marginTop: "4px",
marginBottom: "4px",
});
const Cell = ({ x, y }: { x: number; y: number }): React.ReactElement => {
const entity = factory.entities.find((e) => e.x === x && e.y === y);
return <CellD>{entity && <EntityIcon entity={entity} />}</CellD>;
};
const RowD = styled("div")({
padding: 0,
margin: 0,
});
interface IColProps {
x: number;
}
const Col = ({ x }: IColProps): React.ReactElement => {
return (
<RowD>
{new Array(factorySize).fill(null).map((_, y) => (
<Cell key={y} x={x} y={y}></Cell>
))}
</RowD>
);
};
const Table = styled("div")({
border: "1px solid #fff",
borderSpacing: "2px",
overflow: "hidden",
display: "flex",
flexDirection: "row",
paddingLeft: "2px",
paddingRight: "2px",
});
interface IProps {}
export const FactoryRoot = (__props: IProps): React.ReactElement => {
useRerender(200);
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">Factory</Typography>
<div style={{ display: "flex" }}>
<Table>
{new Array(factorySize).fill(null).map((_, index) => (
<Col key={index} x={index} />
))}
</Table>
</div>
</Container>
);
};

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

@ -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 { NetscriptFactory } from "./NetscriptFunctions/Factory";
export const enums: NSEnums = {
CityName,
@ -135,6 +136,7 @@ export const ns: InternalAPI<NSFull> = {
sleeve: NetscriptSleeve(),
corporation: NetscriptCorporation(),
stanek: NetscriptStanek(),
factory: NetscriptFactory(),
infiltration: NetscriptInfiltration(),
ui: NetscriptUserInterface(),
formulas: NetscriptFormulas(),

@ -0,0 +1,184 @@
import { Bot, Factory as IFactory, EntityType, Item, Crafter } from "@nsdefs";
import { InternalAPI } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers";
import { NewBot, factory, factorySize } from "../Factory/Helper";
import { adjacent, bitsMap, findBot, findEntityAt } from "../Factory/Factory";
import { botPrice } from "../Factory/formulas/formulas";
export function NetscriptFactory(): InternalAPI<IFactory> {
return {
getBits: () => () => factory.bits,
getBotPrice: () => () => {
const botCount = factory.entities.reduce((acc, e) => (e.type === EntityType.Bot ? acc + 1 : acc), 0);
return botPrice(botCount);
},
purchaseBot: (ctx) => (_name, _x, _y) => {
const name = helpers.string(ctx, "name", _name);
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
const another = factory.entities.find((e): e is Bot => e.type === EntityType.Bot && e.name === name);
if (another) {
helpers.log(ctx, () => `bot "${name}" already exists`);
return false;
}
const location = factory.entities.find((e) => e.x === x && e.y === y);
if (location) {
helpers.log(ctx, () => `location [${x}, ${y}] is occupied`);
return false;
}
const botCount = factory.entities.reduce((acc, e) => (e.type === EntityType.Bot ? acc + 1 : acc), 0);
const price = botPrice(botCount);
if (factory.bits < price) {
helpers.log(ctx, () => `not enough bits to purchase bot`);
return false;
}
factory.bits -= price;
NewBot(name, x, y);
return false;
},
moveBot:
(ctx) =>
async (_name, _x, _y): Promise<boolean> => {
const name = helpers.string(ctx, "name", _name);
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
const bot = factory.entities.find((e) => e.type === EntityType.Bot && e.name === name);
if (!bot) return Promise.resolve(false);
const dist = Math.abs(bot.x - x) + Math.abs(bot.y - y);
if (dist !== 1 || x < 0 || x > factorySize || y < 0 || y > factorySize) return Promise.resolve(false);
if (factory.entities.find((e) => e.x === x && e.y === y)) return Promise.resolve(false);
return helpers.netscriptDelay(ctx, 50).then(function () {
bot.x = x;
bot.y = y;
return Promise.resolve(true);
});
},
getBot:
(ctx) =>
(_name): Bot | undefined => {
const name = helpers.string(ctx, "name", _name);
const bot = factory.entities.find((e): e is Bot => e.type === EntityType.Bot && e.name === name);
if (!bot) return;
return JSON.parse(JSON.stringify(bot));
},
entityAt: (ctx) => (_x, _y) => {
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
return factory.entities.find((e) => e.x === x && e.y === y);
},
entities: (__ctx) => () => JSON.parse(JSON.stringify(factory.entities)),
transfer:
(ctx) =>
async (_name, _x, _y, _pickup, _drop): Promise<boolean> => {
const name = helpers.string(ctx, "name", _name);
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
const pickup = _pickup as Item[];
const drop = _drop as Item[];
const bot = findBot(name);
if (!bot) {
helpers.log(ctx, () => `bot "${name}" not found`);
return Promise.resolve(false);
}
const container = findEntityAt(x, y);
if (!container) {
helpers.log(ctx, () => `container not found at [${x}, ${y}]`);
return Promise.resolve(false);
}
if (!adjacent(bot, container)) {
helpers.log(ctx, () => "bot is not adjacent to container");
return Promise.resolve(false);
}
const botFinalSize = bot.inventory.length + pickup.length - drop.length;
const containerFinalSize = container.inventory.length + drop.length - pickup.length;
if (botFinalSize > bot.maxInventory || containerFinalSize > container.maxInventory) {
helpers.log(ctx, () => "not enough space in bot or container");
return Promise.resolve(false);
}
const containerHasItems = pickup.every((item) => container.inventory.includes(item));
const botHasItems = drop.every((item) => bot.inventory.includes(item));
if (!containerHasItems || !botHasItems) {
helpers.log(ctx, () => "container or bot does not have items");
return Promise.resolve(false);
}
container.inventory = container.inventory.filter((item) => !pickup.includes(item));
container.inventory.push(...drop);
bot.inventory = bot.inventory.filter((item) => !drop.includes(item));
bot.inventory.push(...pickup);
switch (container.type) {
case EntityType.Dispenser: {
container.cooldownUntil = Date.now() + container.cooldown;
setTimeout(() => {
if (container.inventory.length < container.maxInventory) {
container.inventory.push(container.dispensing);
}
}, container.cooldown);
break;
}
case EntityType.Dock: {
if (
container.inventory.every((item) => container.currentRequest.includes(item)) &&
container.currentRequest.every((item) => container.inventory.includes(item))
) {
factory.bits += container.inventory.map((i) => bitsMap[i]).reduce((a, b) => a + b, 0);
container.inventory = [];
const newRequest = new Array(container.potentialRequestCount)
.fill(null)
.map(() => container.potentialRequest[Math.floor(Math.random() * container.potentialRequest.length)]);
container.currentRequest = newRequest;
}
break;
}
}
return Promise.resolve(true);
},
craft:
(ctx) =>
async (_name, _x, _y): Promise<boolean> => {
const name = helpers.string(ctx, "name", _name);
const x = helpers.number(ctx, "x", _x);
const y = helpers.number(ctx, "y", _y);
const bot = findBot(name);
if (!bot) {
helpers.log(ctx, () => `bot "${name}" not found`);
return Promise.resolve(false);
}
const crafter = findEntityAt<Crafter>(x, y, EntityType.Crafter);
if (!crafter) {
helpers.log(ctx, () => `crafter not found at [${x}, ${y}]`);
return Promise.resolve(false);
}
if (!adjacent(bot, crafter)) {
helpers.log(ctx, () => "bot is not adjacent to crafter");
return Promise.resolve(false);
}
if (
!crafter.recipe.input.every((item) => crafter.inventory.includes(item)) &&
crafter.inventory.every((item) => crafter.recipe.input.includes(item))
) {
helpers.log(ctx, () => "crafter is missing ingredients");
return Promise.resolve(false);
}
return helpers.netscriptDelay(ctx, 5000).then(function () {
crafter.inventory = [...crafter.recipe.output];
return Promise.resolve(true);
});
},
};
}

@ -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 { factory, loadFactory } from "./Factory/Helper";
/* 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(factory);
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")) {
loadFactory(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;

@ -5143,6 +5143,105 @@ interface Stanek {
acceptGift(): boolean;
}
export enum Tile {
Empty = 0,
}
export enum EntityType {
Bot = "bot",
Dispenser = "dispenser",
Dock = "dock",
Crafter = "crafter",
Chest = "chest",
}
export enum Item {
BasicR = "basicr",
BasicG = "basicg",
BasicB = "basicb",
ComplexR = "complexr",
ComplexG = "complexg",
ComplexB = "complexb",
}
export interface BaseEntity {
type: EntityType;
x: number;
y: number;
}
export interface Bot extends InventoryEntity {
type: EntityType.Bot;
name: string;
energy: number;
}
export interface InventoryEntity extends BaseEntity {
inventory: Item[];
maxInventory: number;
}
export interface Dispenser extends InventoryEntity {
type: EntityType.Dispenser;
dispensing: Item;
cooldown: number;
cooldownUntil: number;
}
export interface Dock extends InventoryEntity {
type: EntityType.Dock;
potentialRequest: Item[];
potentialRequestCount: number;
currentRequest: Item[];
}
export interface Chest extends InventoryEntity {
type: EntityType.Chest;
}
export interface Crafter extends InventoryEntity {
type: EntityType.Crafter;
recipe: Recipe;
}
export interface Recipe {
input: Item[];
output: Item[];
}
export type Entity = Bot | Dispenser | Dock | Crafter | Chest;
interface Factory {
/**
* Move a bot
* @remarks
* RAM cost: 0 GB
* @returns true if the move succeeded, false otherwise.
*/
moveBot(name: string, x: number, y: number): Promise<boolean>;
/**
* Get information about a bot
* @remarks
* RAM cost: 0 GB
* @returns bot information
*/
getBot(name: string): Bot | undefined;
entityAt(x: number, y: number): Entity | undefined;
entities(): Entity[];
transfer(name: string, x: number, y: number, pickup: Item[], drop: Item[]): Promise<boolean>;
craft(name: string, x: number, y: number): Promise<boolean>;
getBits(): number;
getBotPrice(): number;
purchaseBot(name: string, x: number, y: number): boolean;
}
/** @public */
interface InfiltrationReward {
tradeRep: number;
@ -5348,6 +5447,12 @@ export interface NS {
*/
readonly stanek: Stanek;
/**
* Namespace for factory functions. Contains spoilers.
* @remarks RAM cost: 0 GB
*/
readonly factory: Factory;
/**
* Namespace for infiltration functions.
* @remarks RAM cost: 0 GB

@ -354,6 +354,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 },
{ key_: Page.Factory, icon: SportsMmaIcon },
]}
/>
<Divider />

@ -347,31 +347,31 @@ 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,
);
// 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();

@ -72,6 +72,7 @@ import { MathJaxContext } from "better-react-mathjax";
import { useRerender } from "./React/hooks";
import { HistoryProvider } from "./React/Documentation";
import { GoRoot } from "../Go/ui/GoRoot";
import { FactoryRoot } from "../Factory/ui/FactoryRoot";
const htmlLocation = location;
@ -115,6 +116,7 @@ export let Router: IRouter = {
function determineStartPage() {
if (RecoveryMode) return Page.Recovery;
if (Player.currentWork !== null) return Page.Work;
return Page.Factory;
return Page.Terminal;
}
@ -360,6 +362,10 @@ export function GameRoot(): React.ReactElement {
mainPage = <GoRoot />;
break;
}
case Page.Factory: {
mainPage = <FactoryRoot />;
break;
}
case Page.Achievements: {
mainPage = <AchievementsRoot />;
break;

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