From f0b9b2d13f3d332acc78f1a5a56d7e9f77a859db Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Sun, 2 Jun 2024 13:09:55 -0400 Subject: [PATCH] factory v0.1 --- electron/storage.js | 6 +- src/Constants.ts | 6 +- src/Factory/Factory.ts | 26 +++ src/Factory/Helper.tsx | 127 ++++++++++++++ src/Factory/formulas/formulas.ts | 1 + src/Factory/ui/EntityIcon.tsx | 150 +++++++++++++++++ src/Factory/ui/FactoryRoot.tsx | 69 ++++++++ src/Netscript/RamCostGenerator.ts | 10 ++ src/NetscriptFunctions.ts | 2 + src/NetscriptFunctions/Factory.ts | 184 +++++++++++++++++++++ src/SaveObject.ts | 31 ++-- src/ScriptEditor/NetscriptDefinitions.d.ts | 105 ++++++++++++ src/Sidebar/ui/SidebarRoot.tsx | 1 + src/engine.tsx | 50 +++--- src/ui/GameRoot.tsx | 6 + src/ui/Router.ts | 1 + 16 files changed, 732 insertions(+), 43 deletions(-) create mode 100644 src/Factory/Factory.ts create mode 100644 src/Factory/Helper.tsx create mode 100644 src/Factory/formulas/formulas.ts create mode 100644 src/Factory/ui/EntityIcon.tsx create mode 100644 src/Factory/ui/FactoryRoot.tsx create mode 100644 src/NetscriptFunctions/Factory.ts diff --git a/electron/storage.js b/electron/storage.js index a4380408c..6d97ba2d4 100644 --- a/electron/storage.js +++ b/electron/storage.js @@ -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 { diff --git a/src/Constants.ts b/src/Constants.ts index c19f82bd2..3a904f0eb 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -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 `, }; diff --git a/src/Factory/Factory.ts b/src/Factory/Factory.ts new file mode 100644 index 000000000..58a70cc32 --- /dev/null +++ b/src/Factory/Factory.ts @@ -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 = (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.BasicR]: 1, + [Item.BasicG]: 1, + [Item.BasicB]: 1, + [Item.ComplexR]: 4, + [Item.ComplexG]: 4, + [Item.ComplexB]: 4, +}; diff --git a/src/Factory/Helper.tsx b/src/Factory/Helper.tsx new file mode 100644 index 000000000..076ced2e7 --- /dev/null +++ b/src/Factory/Helper.tsx @@ -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, + }); +}; diff --git a/src/Factory/formulas/formulas.ts b/src/Factory/formulas/formulas.ts new file mode 100644 index 000000000..92490083d --- /dev/null +++ b/src/Factory/formulas/formulas.ts @@ -0,0 +1 @@ +export const botPrice = (currentBots: number): number => Math.pow(2, currentBots + 3); diff --git a/src/Factory/ui/EntityIcon.tsx b/src/Factory/ui/EntityIcon.tsx new file mode 100644 index 000000000..16482134c --- /dev/null +++ b/src/Factory/ui/EntityIcon.tsx @@ -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.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 ( + <> + {entity.type} + {content} + + ); +}; + +const TooltipInventory = ({ entity }: { entity: Entity }): React.ReactElement => { + return ( + + {entity.inventory.map((item) => ( + + {item} + + ))} + + ); +}; + +export const EntityIcon = ({ entity }: { entity: Entity }): React.ReactElement => { + switch (entity.type) { + case EntityType.Chest: { + return ( + } />}> + + + ); + } + case EntityType.Bot: { + return ( + + {entity.name} +
+ + } + > + +
+ ); + } + case EntityType.Dispenser: { + return ( + + {`dispensing: ${entity.dispensing}`}} /> + + + } + > + + + ); + break; + } + case EntityType.Dock: { + return ( + {`requesting: ${entity.currentRequest}`}} + /> + } + > + + + ); + } + case EntityType.Crafter: { + return ( + + {`input: ${entity.recipe.input}, output: ${entity.recipe.output}`} + + + } + /> + } + > + + + ); + break; + } + } + return <>; +}; diff --git a/src/Factory/ui/FactoryRoot.tsx b/src/Factory/ui/FactoryRoot.tsx new file mode 100644 index 000000000..6675110a5 --- /dev/null +++ b/src/Factory/ui/FactoryRoot.tsx @@ -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 {entity && }; +}; + +const RowD = styled("div")({ + padding: 0, + margin: 0, +}); + +interface IColProps { + x: number; +} + +const Col = ({ x }: IColProps): React.ReactElement => { + return ( + + {new Array(factorySize).fill(null).map((_, y) => ( + + ))} + + ); +}; + +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 ( + + Factory +
+ + {new Array(factorySize).fill(null).map((_, index) => ( + + ))} +
+
+
+ ); +}; diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index e466bc767..455b8a447 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -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 = { codingcontract, sleeve, stanek, + factory, ui, grafting, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index a9e63df53..9d3bdc17c 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -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 = { sleeve: NetscriptSleeve(), corporation: NetscriptCorporation(), stanek: NetscriptStanek(), + factory: NetscriptFactory(), infiltration: NetscriptInfiltration(), ui: NetscriptUserInterface(), formulas: NetscriptFormulas(), diff --git a/src/NetscriptFunctions/Factory.ts b/src/NetscriptFunctions/Factory.ts new file mode 100644 index 000000000..bef59d470 --- /dev/null +++ b/src/NetscriptFunctions/Factory.ts @@ -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 { + 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 => { + 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 => { + 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 => { + 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(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); + }); + }, + }; +} diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 41257697b..6ee5d99b1 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -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 { @@ -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 { 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; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index bf9263925..d0c469d80 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -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; + + /** + * 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; + + craft(name: string, x: number, y: number): Promise; + + 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 diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 2e31d25b3..34332e797 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -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 }, ]} /> diff --git a/src/engine.tsx b/src/engine.tsx index 554cf3c43..0c934b058 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -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( - <> - Offline for {timeOfflineString}. While you were offline: -
    -
  • - - Your scripts generated - -
  • -
  • - Your Hacknet Nodes generated {hacknetProdInfo} -
  • -
  • - - You gained reputation divided amongst your factions - -
  • -
- , - ), - 250, - ); + // const timeOfflineString = convertTimeMsToTimeElapsedString(time); + // setTimeout( + // () => + // AlertEvents.emit( + // <> + // Offline for {timeOfflineString}. While you were offline: + //
    + //
  • + // + // Your scripts generated + // + //
  • + //
  • + // Your Hacknet Nodes generated {hacknetProdInfo} + //
  • + //
  • + // + // You gained reputation divided amongst your factions + // + //
  • + //
+ // , + // ), + // 250, + // ); } else { // No save found, start new game FormatsNeedToChange.emit(); diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 8b7ddfc13..3d4baf17e 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -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 = ; break; } + case Page.Factory: { + mainPage = ; + break; + } case Page.Achievements: { mainPage = ; break; diff --git a/src/ui/Router.ts b/src/ui/Router.ts index b40e4bf5e..a461f8e95 100644 --- a/src/ui/Router.ts +++ b/src/ui/Router.ts @@ -37,6 +37,7 @@ export enum SimplePage { Recovery = "Recovery", Achievements = "Achievements", ThemeBrowser = "Theme Browser", + Factory = "Factory", } export enum ComplexPage {