Clean up Myrian code

This commit is contained in:
Olivier Gagnon
2024-06-11 17:49:35 -04:00
parent 3f95518891
commit e83f91de6e
33 changed files with 998 additions and 1154 deletions

View File

@ -1,250 +0,0 @@
import { DeviceType, Component, Lock, Glitch } from "@nsdefs";
import { Myrian, findDevice, inMyrianBounds } from "./Myrian";
import { getNextISocketRequest } from "./formulas/formulas";
import { decodeBase64 } from "bcryptjs";
import { roamingTime } from "./formulas/glitches";
export const myrianSize = 12;
const defaultGlitches = {
[Glitch.Segmentation]: 0,
[Glitch.Roaming]: 0,
[Glitch.Encryption]: 0,
[Glitch.Magnetism]: 0,
[Glitch.Rust]: 0,
[Glitch.Friction]: 0,
[Glitch.Isolation]: 0,
[Glitch.Jamming]: 0,
[Glitch.Virtualization]: 0,
};
const defaultMyrian: Myrian = {
vulns: 0,
totalVulns: 0,
devices: [],
glitches: { ...defaultGlitches },
rust: {},
};
export const myrian: Myrian = defaultMyrian;
export const NewBus = (name: string, x: number, y: number) => {
myrian.devices.push({
name,
type: DeviceType.Bus,
isBusy: false,
x,
y,
content: [],
maxContent: 1,
moveLvl: 0,
transferLvl: 0,
reduceLvl: 0,
installLvl: 0,
energy: 16,
maxEnergy: 16,
});
};
export const NewCache = (name: string, x: number, y: number) => {
myrian.devices.push({
name,
type: DeviceType.Cache,
isBusy: false,
content: [],
maxContent: 1,
x,
y,
});
};
export const NewReducer = (name: string, x: number, y: number) => {
myrian.devices.push({
name,
type: DeviceType.Reducer,
isBusy: false,
x,
y,
content: [],
maxContent: 2,
tier: 1,
});
};
export const NewISocket = (name: string, x: number, y: number, dispensing: Component) => {
myrian.devices.push({
name,
type: DeviceType.ISocket,
isBusy: false,
x,
y,
emitting: dispensing,
emissionLvl: 0,
cooldownUntil: 0,
content: [dispensing],
maxContent: 1,
});
};
export const NewOSocket = (name: string, x: number, y: number) => {
myrian.devices.push({
name,
type: DeviceType.OSocket,
isBusy: false,
x,
y,
currentRequest: getNextISocketRequest(0),
content: [],
maxContent: 1,
});
};
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) => {
myrian.devices.push({
name,
type: DeviceType.Battery,
isBusy: false,
x,
y,
tier: 0,
energy: 64,
maxEnergy: 64,
});
};
export const loadMyrian = (save: string) => {
if (!save) return;
// const savedFactory = JSON.parse(save);
// Object.assign(factory, savedFactory);
};
export const resetMyrian = () => {
myrian.vulns = 0;
myrian.totalVulns = 0;
myrian.devices = [];
myrian.glitches = { ...defaultGlitches };
myrian.rust = {};
Object.assign(myrian, defaultMyrian);
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));
};
setInterval(() => {
myrian.devices.forEach((device) => {
if (device.type !== DeviceType.Battery) return;
const up = Math.pow(2, device.tier + 1);
device.energy = Math.min(device.energy + up, device.maxEnergy);
});
}, 1000);
setInterval(() => {
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);
}
}, 30000);
setInterval(() => {
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;
}
}, 30000);
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;
});
};
setTimeout(applyRoaming, 0);
resetMyrian();

View File

@ -1,22 +1,11 @@
import {
Bus,
Device,
DeviceType,
ContainerDevice,
Component,
ISocket,
OSocket,
Reducer,
Cache,
Lock,
BaseDevice,
DeviceID,
Glitch,
Battery,
} from "@nsdefs";
import { myrian, myrianSize } from "./Helper";
import { Device, DeviceType, Component, DeviceID, Glitch } from "@nsdefs";
import { glitchMult } from "./formulas/glitches";
import { pickOne } from "./utils";
import { componentTiers } from "./formulas/components";
import { NewBattery, NewBus, NewCache, NewISocket, NewLock, NewOSocket, NewReducer } from "./NewDevices";
import { startRoaming } from "./glitches/roaming";
import { startRust } from "./glitches/rust";
import { startSegmentation } from "./glitches/segmentation";
export interface Myrian {
vulns: number;
@ -26,100 +15,30 @@ export interface Myrian {
rust: Record<string, boolean>;
}
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 myrianSize = 12;
export const adjacent = (a: Device, b: Device) => distance(a, b) === 1;
export const adjacentCoord2D = (a: Device, coord: [number, number]) => distanceCoord2D(a, coord) === 1;
const defaultGlitches = Object.values(Glitch).reduce((acc, g) => ({ ...acc, [g]: 0 }), {}) as Record<Glitch, number>;
export const inventoryMatches = (a: Component[], b: Component[]) => {
if (a.length != b.length) return false;
return a.every((i) => b.includes(i));
export const myrian: Myrian = {
vulns: 0,
totalVulns: 0,
devices: [],
glitches: { ...defaultGlitches },
rust: {},
};
export const loadMyrian = (save: string) => {
resetMyrian();
startRoaming();
startRust();
startSegmentation();
if (!save) return;
// const savedMyrian = JSON.parse(save);
// Object.assign(myrian, savedMyrian);
};
export const inMyrianBounds = (x: number, y: number) => x >= 0 && x < myrianSize && y >= 0 && y < myrianSize;
export 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 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 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),
@ -136,16 +55,28 @@ export const getTotalGlitchMult = () =>
return acc * glitchMult(glitch as Glitch, lvl);
}, 1);
// 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 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));
};

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

@ -0,0 +1,109 @@
import { Battery, Bus, Cache, Component, DeviceType, ISocket, Lock, OSocket, Reducer } from "@nsdefs";
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,67 @@
import { DeviceType } from "@nsdefs";
// 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]: [Infinity, 1, -1, 4095],
[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]: [2, 1, 3, 0],
[DeviceType.Battery]: [1.1, 1, 3, 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]: [1.5, 1, 2, 0],
[DeviceType.Cache]: [1.2, 10, 0, 63],
[DeviceType.Battery]: [1.2, 10, 0, 63],
});

View File

@ -1,126 +0,0 @@
import { DeviceType } from "@nsdefs";
import { myrian } from "../Helper";
import { componentTiers } from "./components";
import { pickOne } from "../utils";
type FactoryFormulaParams = [number, number, number, number];
const maxContentScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [8, 0.5, 2, 0],
[DeviceType.ISocket]: [4, 1, 5, 0],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, 1, -1, 4095],
[DeviceType.Cache]: [1.2, 10, 0, 63],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
// a^(b*X+c)+d
const exp = (p: FactoryFormulaParams, x: number): number => Math.pow(p[0], p[1] * x + p[2]) + p[3];
export const upgradeMaxContentCost = (type: DeviceType, currentMaxContent: number): number =>
exp(maxContentScale[type], currentMaxContent);
const countDevices = (type: DeviceType) => myrian.devices.reduce((acc, d) => (d.type === type ? acc + 1 : acc), 0);
const deviceScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [4, 0.5, 2, 0],
[DeviceType.ISocket]: [2, 1, 4, 0],
[DeviceType.OSocket]: [4, 1, 3, 0],
[DeviceType.Reducer]: [1.5, 1, 2, 0],
[DeviceType.Cache]: [1.2, 10, 0, 63],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [1.2, 10, 0, 63],
};
export const deviceCost = (type: DeviceType, count?: number) =>
exp(deviceScale[type], count === undefined ? countDevices(type) : count);
export const getNextISocketRequest = (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));
};
const tierScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [1.5, 1, 2, 0],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [2, 1, 3, 0],
};
export const tierCost = (type: DeviceType, tier: number) => exp(tierScale[type], tier);
const emissionScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.ISocket]: [2, 1, 3, 0],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
export const emissionCost = (type: DeviceType, emissionLvl: number) => exp(emissionScale[type], emissionLvl);
const moveLvlScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [2, 1, 3, 0],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
export const moveLvlCost = (type: DeviceType, moveLvl: number) => exp(moveLvlScale[type], moveLvl);
const transferLvlScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [2, 1, 3, 0],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
export const transferLvlCost = (type: DeviceType, transferLvl: number) => exp(transferLvlScale[type], transferLvl);
const reduceLvlScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [2, 1, 3, 0],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
export const reduceLvlCost = (type: DeviceType, reduceLvl: number) => exp(reduceLvlScale[type], reduceLvl);
const installLvlScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [2, 1, 3, 0],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [Infinity, Infinity, Infinity, Infinity],
};
export const installLvlCost = (type: DeviceType, installLvl: number) => exp(installLvlScale[type], installLvl);
const maxEnergyScale: Record<DeviceType, FactoryFormulaParams> = {
[DeviceType.Bus]: [2, 1, 3, 0],
[DeviceType.ISocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.OSocket]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Reducer]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Cache]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Lock]: [Infinity, Infinity, Infinity, Infinity],
[DeviceType.Battery]: [1.1, 1, 3, 8],
};
export const maxEnergyCost = (type: DeviceType, maxEnergy: number) => exp(maxEnergyScale[type], maxEnergy);

View File

@ -14,8 +14,8 @@ export const glitchMaxLvl: Record<Glitch, number> = {
export const giltchMultCoefficients: Record<Glitch, number> = {
[Glitch.Segmentation]: 1,
[Glitch.Roaming]: 0, // 1,
[Glitch.Encryption]: 0, // 0.1,
[Glitch.Roaming]: 1,
[Glitch.Encryption]: 0.1,
[Glitch.Magnetism]: 0.2,
[Glitch.Rust]: 1,
[Glitch.Friction]: 0.2,
@ -24,6 +24,7 @@ export const giltchMultCoefficients: Record<Glitch, number> = {
[Glitch.Jamming]: 0.2,
};
// vulns mult by glitch lvl
export const glitchMult = (glitch: Glitch, lvl: number) => 1 + lvl * giltchMultCoefficients[glitch];
// move hinderance
@ -41,4 +42,5 @@ export const jammingMult = (lvl: number) => Math.pow(1.3, 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

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

View File

@ -1,5 +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) => 1000 / (level + 10);
// speed to install / uninstall devices and tweak ISockets
export const installSpeed = (level: number) => 100000 / (level + 10);
export const isocketSpeed = (level: number) => 100000 / (level + 10);
// time until ISocket refreshes
export const emissionSpeed = (level: number) => 100000 / (level + 10);

View File

@ -0,0 +1,12 @@
import { DeviceType } from "@nsdefs";
import { myrian } from "../Myrian";
const applyBattery = () => {
myrian.devices.forEach((device) => {
if (device.type !== DeviceType.Battery) 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 "@nsdefs";
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,29 @@
import { Bus, Glitch } from "@nsdefs";
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 "@nsdefs";
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);

View File

@ -1,101 +0,0 @@
# Myrian
Myrian is the name of the OS that the BitNodes run on. Eventually you'll unlock the mechanic by going to the glitch in Ishima and "breaking partially free" of the BN.
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
### 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, certain devices can be used.
Performing any action makes all devices involved "busy". If a Bus is transfering content between itself and a cache (chest), they both become busy until the operation finishes.
Contrary to every other mechanic in the game. Async functions using myrian functions CAN run simultenaously.
### ISocket
These devices produce basic components that can be used for other devices, [r0, y0, 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
Currently 2 devices have tiers, reducers and OSockets.
Upgrading a reducer 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 OSocket allows it to request higher tier components (as well as more components at a time).
## 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.

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 "@nsdefs";
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

@ -1,239 +1,33 @@
import React from "react";
import DirectionsBusIcon from "@mui/icons-material/DirectionsBus";
import { styled } from "@mui/system";
import { ISocket, Device, DeviceType, Component } from "@nsdefs";
import { Device, DeviceType } from "@nsdefs";
import MoveToInboxIcon from "@mui/icons-material/MoveToInbox";
import OutboxIcon from "@mui/icons-material/Outbox";
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
import MergeTypeIcon from "@mui/icons-material/MergeType";
import BatteryChargingFullIcon from "@mui/icons-material/BatteryChargingFull";
import BlockIcon from "@mui/icons-material/Block";
import { Tooltip, Typography } from "@mui/material";
import { isDeviceContainer } from "../Myrian";
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";
export const cellSize = 48;
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 = "whte";
const itemColorMap: Record<Component, string> = {
// tier 0
[Component.R0]: ColorR,
[Component.G0]: ColorG,
[Component.B0]: ColorB,
// tier 1
[Component.R1]: ColorR,
[Component.G1]: ColorG,
[Component.B1]: ColorB,
[Component.Y1]: ColorY,
[Component.C1]: ColorC,
[Component.M1]: ColorM,
// tier 2
[Component.R2]: ColorR,
[Component.G2]: ColorG,
[Component.B2]: ColorB,
[Component.Y2]: ColorY,
[Component.C2]: ColorC,
[Component.M2]: ColorM,
[Component.W2]: ColorW,
// tier 3
[Component.R3]: ColorR,
[Component.G3]: ColorG,
[Component.B3]: ColorB,
[Component.Y3]: ColorY,
[Component.C3]: ColorC,
[Component.M3]: ColorM,
[Component.W3]: ColorW,
// tier 4
[Component.R4]: ColorR,
[Component.G4]: ColorG,
[Component.B4]: ColorB,
[Component.Y4]: ColorY,
[Component.C4]: ColorC,
[Component.M4]: ColorM,
[Component.W4]: ColorW,
// tier 5
[Component.R5]: ColorR,
[Component.G5]: ColorG,
[Component.B5]: ColorB,
[Component.Y5]: ColorY,
[Component.C5]: ColorC,
[Component.M5]: ColorM,
[Component.W5]: ColorW,
// tier 6
[Component.Y6]: ColorY,
[Component.C6]: ColorC,
[Component.M6]: ColorM,
[Component.W6]: ColorW,
// tier 7
[Component.W7]: ColorW,
};
const LockIcon = styled(BlockIcon)(defaultIconStyle);
const BusIcon = styled(DirectionsBusIcon)(defaultIconStyle);
const ISocketIcon = styled(OutboxIcon)((props: { dispenser: ISocket; col: string }) => ({
...defaultIconStyle,
color: new Date().getTime() > props.dispenser.cooldownUntil ? props.col : "gray",
}));
const OSocketIcon = styled(MoveToInboxIcon)(defaultIconStyle);
const ReducerIcon = styled(MergeTypeIcon)(defaultIconStyle);
const CacheIcon = styled(CheckBoxOutlineBlankIcon)(defaultIconStyle);
const BatteryIcon = styled(BatteryChargingFullIcon)(defaultIconStyle);
interface ITooltipContentProps {
interface IDeviceIconProps {
device: Device;
content: React.ReactElement;
}
const TooltipContent = ({ device, content }: ITooltipContentProps): React.ReactElement => {
return (
<>
<Typography>
{device.name} ({device.type})
</Typography>
{content}
</>
);
};
const TooltipInventory = ({ device }: { device: Device }): React.ReactElement => {
if (!isDeviceContainer(device)) return <></>;
return (
<Typography component="span">
{device.content.map((item) => (
<span key={item} style={{ color: itemColorMap[item] }}>
{item}
</span>
))}
</Typography>
);
};
export const DeviceIcon = ({ device }: { device: Device }): React.ReactElement => {
export const DeviceIcon = ({ device }: IDeviceIconProps): React.ReactElement => {
switch (device.type) {
case DeviceType.Lock: {
return <LockIcon />;
}
case DeviceType.Battery: {
return (
<Tooltip
title={
<TooltipContent
device={device}
content={
<Typography>
{device.energy} / {device.maxEnergy}
</Typography>
}
/>
}
>
<BatteryIcon />
</Tooltip>
);
}
case DeviceType.Cache: {
return (
<Tooltip title={<TooltipContent device={device} content={<TooltipInventory device={device} />} />}>
<CacheIcon />
</Tooltip>
);
}
case DeviceType.Bus: {
return (
<Tooltip
title={
<Typography>
{device.name}
<br /> <TooltipInventory device={device} />
</Typography>
}
>
<BusIcon />
</Tooltip>
);
}
case DeviceType.ISocket: {
return (
<Tooltip
title={
<>
<TooltipContent device={device} content={<Typography>{`dispensing: ${device.emitting}`}</Typography>} />
<TooltipInventory device={device} />
</>
}
>
<ISocketIcon dispenser={device} col={itemColorMap[device.emitting]} />
</Tooltip>
);
break;
}
case DeviceType.OSocket: {
return (
<Tooltip
title={
<TooltipContent
device={device}
content={<Typography>{`requesting: ${device.currentRequest}`}</Typography>}
/>
}
>
<OSocketIcon sx={{ color: itemColorMap[device.currentRequest[0] ?? Component.R0] }} />
</Tooltip>
);
}
case DeviceType.Reducer: {
return (
<Tooltip
title={
<TooltipContent
device={device}
content={
<>
<Typography>Tier: {device.tier}</Typography>
<TooltipInventory device={device} />
</>
}
/>
}
>
<ReducerIcon />
</Tooltip>
);
break;
}
case DeviceType.ISocket:
return <ISocketIcon socket={device} />;
case DeviceType.OSocket:
return <OSocketIcon socket={device} />;
case DeviceType.Bus:
return <BusIcon bus={device} />;
case DeviceType.Reducer:
return <ReducerIcon reducer={device} />;
case DeviceType.Lock:
return <LockIcon lock={device} />;
case DeviceType.Cache:
return <CacheIcon cache={device} />;
case DeviceType.Battery:
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

@ -1,93 +1,20 @@
import React from "react";
import { Container, IconButton, Typography } from "@mui/material";
import { styled } from "@mui/system";
import { myrian, myrianSize } from "../Helper";
import { myrian } from "../Myrian";
import { useRerender } from "../../ui/React/hooks";
import { DeviceIcon, cellSize } from "./DeviceIcon";
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 CellD = styled("div")({
width: cellSize + "px",
height: cellSize + "px",
backgroundColor: "#444",
padding: 0,
margin: "2px",
marginTop: "4px",
marginBottom: "4px",
});
const tut = <MD md={tutorial} />;
const Cell = ({ x, y }: { x: number; y: number }): React.ReactElement => {
const device = myrian.devices.find((e) => e.x === x && e.y === y);
return <CellD>{device && <DeviceIcon device={device} />}</CellD>;
};
const RowD = styled("div")({
padding: 0,
margin: 0,
});
interface IColProps {
x: number;
}
const Col = ({ x }: IColProps): React.ReactElement => {
return (
<RowD>
{new Array(myrianSize + 1).fill(null).map((_, y) => {
if (y === 0)
return (
<CellD
sx={{ display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#00000000" }}
key={y}
>
<Typography>{x - 1}</Typography>
</CellD>
);
return <Cell key={y} x={x - 1} y={y - 1}></Cell>;
})}
</RowD>
);
};
const Table = styled("div")({
border: "1px solid #fff",
borderSpacing: "2px",
overflow: "hidden",
display: "flex",
flexDirection: "row",
paddingLeft: "2px",
paddingRight: "2px",
});
const YHeader = () => {
return (
<RowD>
{new Array(myrianSize + 1).fill(null).map((_, y) => {
return (
<CellD
sx={{ display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "#00000000" }}
key={y}
>
<Typography>{y === 0 ? "Y\\X" : y - 1}</Typography>
</CellD>
);
})}
</RowD>
);
};
interface IProps {}
export const MyrianRoot = (__props: IProps): React.ReactElement => {
export const MyrianRoot = (): React.ReactElement => {
useRerender(50);
const onHelp = () => {
dialogBoxCreate(<MD md={tutorial} />);
};
const onHelp = () => dialogBoxCreate(tut);
return (
<Container maxWidth="lg" disableGutters sx={{ mx: 0 }}>
<Typography variant="h4">
@ -99,14 +26,7 @@ export const MyrianRoot = (__props: IProps): React.ReactElement => {
<Typography>
{myrian.vulns} vulns : {myrian.totalVulns} total vulns
</Typography>
<div style={{ display: "flex" }}>
<Table>
{new Array(myrianSize + 1).fill(null).map((_, x) => {
if (x === 0) return <YHeader key={x} />;
return <Col key={x} x={x} />;
})}
</Table>
</div>
<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 "@nsdefs";
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;

View File

@ -1 +1,118 @@
import {
Component,
BaseDevice,
ContainerDevice,
Device,
Bus,
DeviceType,
ISocket,
OSocket,
Reducer,
Cache,
Lock,
Battery,
TieredDevice,
EnergyDevice,
} from "@nsdefs";
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 inventoryMatches = (a: Component[], b: Component[]) => {
if (a.length != b.length) return false;
return a.every((i) => b.includes(i));
};
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

@ -2,42 +2,27 @@ import { Bus, Myrian as IMyrian, DeviceType, Component, Reducer, Glitch, Battery
import { InternalAPI } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers";
import {
NewBattery,
NewBus,
NewCache,
NewISocket,
NewLock,
NewOSocket,
NewReducer,
myrian as myrian,
myrianSize,
resetMyrian,
} from "../Myrian/Helper";
import {
adjacent,
adjacentCoord2D,
vulnsMap,
findDevice,
inMyrianBounds,
inventoryMatches,
isDeviceContainer,
isDeviceBus,
removeDevice,
getTotalGlitchMult,
rustBus,
getNextOSocketRequest,
countDevices,
resetMyrian,
myrian,
myrianSize,
} from "../Myrian/Myrian";
import {
deviceCost,
emissionCost,
getNextISocketRequest,
installLvlCost,
maxEnergyCost,
moveLvlCost,
reduceLvlCost,
tierCost,
transferLvlCost,
installDeviceCost,
upgradeEmissionCost,
upgradeInstallLvlCost,
upgradeMaxEnergyCost,
upgradeMoveLvlCost,
upgradeReduceLvlCost,
upgradeTierCost,
upgradeTransferLvlCost,
upgradeMaxContentCost,
} from "../Myrian/formulas/formulas";
} from "../Myrian/formulas/costs";
import { recipes } from "../Myrian/formulas/recipes";
import { componentTiers } from "../Myrian/formulas/components";
import {
@ -49,8 +34,25 @@ import {
magnetismLoss,
virtualizationMult,
} from "../Myrian/formulas/glitches";
import { pickOne } from "../Myrian/utils";
import { installSpeed, isocketSpeed, moveSpeed, reduceSpeed, transferSpeed } from "../Myrian/formulas/speed";
import {
adjacent,
adjacentCoord2D,
contentVulnsValue,
inventoryMatches,
isDeviceBus,
isDeviceContainer,
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";
export function NetscriptMyrian(): InternalAPI<IMyrian> {
return {
@ -209,7 +211,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
switch (container.type) {
case DeviceType.ISocket: {
if (previousSize <= container.content.length) break;
const cooldown = isocketSpeed(container.emissionLvl);
const cooldown = emissionSpeed(container.emissionLvl);
container.cooldownUntil = Date.now() + cooldown;
setTimeout(() => {
container.content = new Array(container.maxContent).fill(container.emitting);
@ -219,12 +221,11 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
case DeviceType.OSocket: {
if (!inventoryMatches(container.currentRequest, container.content)) break;
const gain =
container.content.map((i) => vulnsMap[i]).reduce((a, b) => a + b, 0) * getTotalGlitchMult();
const gain = contentVulnsValue(container.content) * getTotalGlitchMult();
myrian.vulns += gain;
myrian.totalVulns += gain;
container.content = [];
const request = getNextISocketRequest(myrian.glitches[Glitch.Encryption]);
const request = getNextOSocketRequest(myrian.glitches[Glitch.Encryption]);
container.currentRequest = request;
container.maxContent = request.length;
break;
@ -323,7 +324,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
.then(() => {
isocket.emitting = component;
isocket.content = [];
const cooldown = isocketSpeed(isocket.emissionLvl);
const cooldown = emissionSpeed(isocket.emissionLvl);
isocket.cooldownUntil = Date.now() + cooldown;
setTimeout(() => {
isocket.content = new Array(isocket.maxContent).fill(isocket.emitting);
@ -410,7 +411,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
getDeviceCost: (ctx) => (_type) => {
const type = helpers.string(ctx, "type", _type);
return deviceCost(type as DeviceType);
return installDeviceCost(type as DeviceType, countDevices(type as DeviceType));
},
installDevice: (ctx) => async (_bus, _name, _coord, _deviceType) => {
@ -441,7 +442,7 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
return Promise.resolve(false);
}
const cost = deviceCost(deviceType);
const cost = installDeviceCost(deviceType, countDevices(deviceType));
if (myrian.vulns < cost) {
helpers.log(ctx, () => `not enough vulns to install device`);
return Promise.resolve(false);
@ -548,15 +549,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("tier" in device)) return -1;
return tierCost(device.type, device.tier);
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 (!("tier" in device)) return false;
const cost = tierCost(device.type, device.tier);
if (!isDeviceTiered(device)) return false;
const cost = upgradeTierCost(device.type, device.tier);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.tier++;
@ -566,15 +567,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("emissionLvl" in device)) return -1;
return emissionCost(device.type, device.emissionLvl);
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 (!("emissionLvl" in device)) return false;
const cost = emissionCost(device.type, device.emissionLvl);
if (!isEmittingDevice(device)) return false;
const cost = upgradeEmissionCost(device.type, device.emissionLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.emissionLvl++;
@ -584,15 +585,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("moveLvl" in device)) return -1;
return moveLvlCost(device.type, device.moveLvl);
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 (!("moveLvl" in device)) return false;
const cost = moveLvlCost(device.type, device.moveLvl);
if (!isMovingDevice(device)) return false;
const cost = upgradeMoveLvlCost(device.type, device.moveLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.moveLvl++;
@ -602,15 +603,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("transferLvl" in device)) return -1;
return transferLvlCost(device.type, device.transferLvl);
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 (!("transferLvl" in device)) return false;
const cost = transferLvlCost(device.type, device.transferLvl);
if (!isTransferingDevice(device)) return false;
const cost = upgradeTransferLvlCost(device.type, device.transferLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.transferLvl++;
@ -620,15 +621,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("reduceLvl" in device)) return -1;
return reduceLvlCost(device.type, device.reduceLvl);
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 (!("reduceLvl" in device)) return false;
const cost = reduceLvlCost(device.type, device.reduceLvl);
if (!isReducingDevice(device)) return false;
const cost = upgradeReduceLvlCost(device.type, device.reduceLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.reduceLvl++;
@ -638,15 +639,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("installLvl" in device)) return -1;
return installLvlCost(device.type, device.installLvl);
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 (!("installLvl" in device)) return false;
const cost = installLvlCost(device.type, device.installLvl);
if (!isInstallingDevice(device)) return false;
const cost = upgradeInstallLvlCost(device.type, device.installLvl);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.installLvl++;
@ -656,15 +657,15 @@ export function NetscriptMyrian(): InternalAPI<IMyrian> {
const id = helpers.deviceID(ctx, "device", _id);
const device = findDevice(id);
if (!device) return -1;
if (!("maxEnergy" in device)) return -1;
return maxEnergyCost(device.type, device.maxEnergy);
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 (!("maxEnergy" in device)) return false;
const cost = maxEnergyCost(device.type, device.maxEnergy);
if (!isEnergyDevice(device)) return false;
const cost = upgradeMaxEnergyCost(device.type, device.maxEnergy);
if (myrian.vulns < cost) return false;
myrian.vulns -= cost;
device.maxEnergy++;

View File

@ -45,7 +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/Helper";
import { myrian, loadMyrian } from "./Myrian/Myrian";
/* SaveObject.js
* Defines the object used to save/load games