mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-23 08:03:48 +01:00
Merge branch 'dev' into improvement/work-in-progress-ui
This commit is contained in:
commit
bcb6176952
4
dist/main.bundle.js
vendored
4
dist/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/main.bundle.js.map
vendored
2
dist/main.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
42
dist/vendor.bundle.js
vendored
42
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vendor.bundle.js.map
vendored
2
dist/vendor.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
@ -336,12 +336,12 @@ The list contains the name of (i.e. the value returned by
|
||||
| | | |
|
||||
| | | You are given an LZ-encoded string. Decode it and output the original string. |
|
||||
| | | |
|
||||
| | | Example: decoding '5aaabc340533bca' chunk-by-chunk |
|
||||
| | | 5aaabc -> aaabc |
|
||||
| | | 5aaabc34 -> aaabcaab |
|
||||
| | | 5aaabc340 -> aaabcaab |
|
||||
| | | 5aaabc34053 -> aaabcaabaabaa |
|
||||
| | | 5aaabc340533bca -> aaabcaabaabaabca |
|
||||
| | | Example: decoding '5aaabb450723abb' chunk-by-chunk |
|
||||
| | | 5aaabb -> aaabb |
|
||||
| | | 5aaabb45 -> aaabbaaab |
|
||||
| | | 5aaabb450 -> aaabbaaab |
|
||||
| | | 5aaabb45072 -> aaabbaaababababa |
|
||||
| | | 5aaabb450723abb -> aaabbaaababababaabb |
|
||||
+-----------------------------------------+------------------------------------------------------------------------------------------+
|
||||
| Compression III: LZ Compression | | Lempel-Ziv (LZ) compression is a data compression technique which encodes data using |
|
||||
| | | references to earlier parts of the data. In this variant of LZ, data is encoded in two |
|
||||
@ -361,12 +361,12 @@ The list contains the name of (i.e. the value returned by
|
||||
| | | possible output length. |
|
||||
| | | |
|
||||
| | | Examples (some have other possible encodings of minimal length): |
|
||||
| | | abracadabra -> 7abracad47 |
|
||||
| | | mississippi -> 4miss433ppi |
|
||||
| | | aAAaAAaAaAA -> 3aAA53035 |
|
||||
| | | 2718281828 -> 627182844 |
|
||||
| | | abcdefghijk -> 9abcdefghi02jk |
|
||||
| | | aaaaaaaaaaa -> 1a911a |
|
||||
| | | aaaaaaaaaaaa -> 1a912aa |
|
||||
| | | aaaaaaaaaaaaa -> 1a91031 |
|
||||
| | | abracadabra -> 7abracad47 |
|
||||
| | | mississippi -> 4miss433ppi |
|
||||
| | | aAAaAAaAaAA -> 3aAA53035 |
|
||||
| | | 2718281828 -> 627182844 |
|
||||
| | | abcdefghijk -> 9abcdefghi02jk |
|
||||
| | | aaaaaaaaaaaa -> 3aaa91 |
|
||||
| | | aaaaaaaaaaaaa -> 1a91031 |
|
||||
| | | aaaaaaaaaaaaaa -> 1a91041 |
|
||||
+-----------------------------------------+------------------------------------------------------------------------------------------+
|
||||
|
@ -334,7 +334,7 @@
|
||||
},
|
||||
"BLADEBURNER_UNSPENT_100000": {
|
||||
"ID": "BLADEBURNER_UNSPENT_100000",
|
||||
"Name": "You should really spent those.",
|
||||
"Name": "You should really spend those.",
|
||||
"Description": "Have 100 000 unspent bladeburner skill points."
|
||||
},
|
||||
"4S": {
|
||||
|
@ -9,6 +9,18 @@ import { Money } from "../ui/React/Money";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
|
||||
import { FactionNames } from "../Faction/data/FactionNames";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AugmentationNames } from "./data/AugmentationNames";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { StaticAugmentations } from "./StaticAugmentations";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { getBaseAugmentationPriceMultiplier, getGenericAugmentationPriceMultiplier } from "./AugmentationHelpers";
|
||||
import { initSoAAugmentations } from "./data/AugmentationCreator";
|
||||
|
||||
export interface AugmentationCosts {
|
||||
moneyCost: number;
|
||||
repCost: number;
|
||||
}
|
||||
|
||||
export interface IConstructorParams {
|
||||
info: string | JSX.Element;
|
||||
@ -410,10 +422,10 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
|
||||
}
|
||||
|
||||
export class Augmentation {
|
||||
// How much money this costs to buy
|
||||
// How much money this costs to buy before multipliers
|
||||
baseCost = 0;
|
||||
|
||||
// How much faction reputation is required to unlock this
|
||||
// How much faction reputation is required to unlock this before multipliers
|
||||
baseRepRequirement = 0;
|
||||
|
||||
// Description of what this Aug is and what it does
|
||||
@ -425,9 +437,6 @@ export class Augmentation {
|
||||
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
|
||||
isSpecial = false;
|
||||
|
||||
// Augmentation level - for repeatable Augs like NeuroFlux Governor
|
||||
level = 0;
|
||||
|
||||
// Name of Augmentation
|
||||
name = "";
|
||||
|
||||
@ -438,12 +447,6 @@ export class Augmentation {
|
||||
// The Player/Person classes
|
||||
mults: IMap<number> = {};
|
||||
|
||||
// Initial cost. Doesn't change when you purchase multiple Augmentation
|
||||
startingCost = 0;
|
||||
|
||||
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
|
||||
startingRepRequirement = 0;
|
||||
|
||||
// Factions that offer this aug.
|
||||
factions: string[] = [];
|
||||
|
||||
@ -461,17 +464,15 @@ export class Augmentation {
|
||||
this.prereqs = params.prereqs ? params.prereqs : [];
|
||||
|
||||
this.baseRepRequirement = params.repCost;
|
||||
Object.freeze(this.baseRepRequirement);
|
||||
this.baseCost = params.moneyCost;
|
||||
this.startingCost = this.baseCost;
|
||||
this.startingRepRequirement = this.baseRepRequirement;
|
||||
Object.freeze(this.baseCost);
|
||||
this.factions = params.factions;
|
||||
|
||||
if (params.isSpecial) {
|
||||
this.isSpecial = true;
|
||||
}
|
||||
|
||||
this.level = 0;
|
||||
|
||||
// Set multipliers
|
||||
if (params.hacking_mult) {
|
||||
this.mults.hacking_mult = params.hacking_mult;
|
||||
@ -600,6 +601,61 @@ export class Augmentation {
|
||||
}
|
||||
}
|
||||
|
||||
getCost(player: IPlayer): AugmentationCosts {
|
||||
const augmentationReference = StaticAugmentations[this.name];
|
||||
let moneyCost = augmentationReference.baseCost;
|
||||
let repCost = augmentationReference.baseRepRequirement;
|
||||
|
||||
if (augmentationReference.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
let nextLevel = this.getLevel(player);
|
||||
--nextLevel;
|
||||
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
|
||||
repCost = augmentationReference.baseRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
|
||||
moneyCost = augmentationReference.baseCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
|
||||
|
||||
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
|
||||
moneyCost *= getBaseAugmentationPriceMultiplier();
|
||||
}
|
||||
} else if (augmentationReference.factions.includes(FactionNames.ShadowsOfAnarchy)) {
|
||||
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
|
||||
const soaMultiplier = Math.pow(
|
||||
CONSTANTS.SoACostMult,
|
||||
soaAugmentationNames.filter((augmentationName) => player.hasAugmentation(augmentationName)).length,
|
||||
);
|
||||
moneyCost = augmentationReference.baseCost * soaMultiplier;
|
||||
if (soaAugmentationNames.find((augmentationName) => augmentationName === augmentationReference.name)) {
|
||||
repCost = augmentationReference.baseRepRequirement * soaMultiplier;
|
||||
}
|
||||
} else {
|
||||
moneyCost =
|
||||
augmentationReference.baseCost *
|
||||
getGenericAugmentationPriceMultiplier() *
|
||||
BitNodeMultipliers.AugmentationMoneyCost;
|
||||
}
|
||||
return { moneyCost, repCost };
|
||||
}
|
||||
|
||||
getLevel(player: IPlayer): number {
|
||||
// Get current Neuroflux level based on Player's augmentations
|
||||
if (this.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
let currLevel = 0;
|
||||
for (let i = 0; i < player.augmentations.length; ++i) {
|
||||
if (player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
|
||||
currLevel = player.augmentations[i].level;
|
||||
}
|
||||
}
|
||||
|
||||
// Account for purchased but uninstalled Augmentations
|
||||
for (let i = 0; i < player.queuedAugmentations.length; ++i) {
|
||||
if (player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
|
||||
++currLevel;
|
||||
}
|
||||
}
|
||||
return currLevel + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Adds this Augmentation to all Factions
|
||||
addToAllFactions(): void {
|
||||
for (const fac of Object.keys(Factions)) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Augmentation } from "./Augmentation";
|
||||
import { Augmentations } from "./Augmentations";
|
||||
import { StaticAugmentations } from "./StaticAugmentations";
|
||||
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "./data/AugmentationNames";
|
||||
|
||||
@ -20,30 +20,11 @@ import {
|
||||
initNeuroFluxGovernor,
|
||||
initUnstableCircadianModulator,
|
||||
} from "./data/AugmentationCreator";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { Router } from "../ui/GameRoot";
|
||||
|
||||
export function AddToAugmentations(aug: Augmentation): void {
|
||||
export function AddToStaticAugmentations(aug: Augmentation): void {
|
||||
const name = aug.name;
|
||||
Augmentations[name] = aug;
|
||||
}
|
||||
|
||||
export function getNextNeuroFluxLevel(): number {
|
||||
// Get current Neuroflux level based on Player's augmentations
|
||||
let currLevel = 0;
|
||||
for (let i = 0; i < Player.augmentations.length; ++i) {
|
||||
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
|
||||
currLevel = Player.augmentations[i].level;
|
||||
}
|
||||
}
|
||||
|
||||
// Account for purchased but uninstalled Augmentations
|
||||
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
|
||||
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
|
||||
++currLevel;
|
||||
}
|
||||
}
|
||||
return currLevel + 1;
|
||||
StaticAugmentations[name] = aug;
|
||||
}
|
||||
|
||||
function createAugmentations(): void {
|
||||
@ -67,96 +48,37 @@ function resetFactionAugmentations(): void {
|
||||
|
||||
function initAugmentations(): void {
|
||||
resetFactionAugmentations();
|
||||
clearObject(Augmentations);
|
||||
clearObject(StaticAugmentations);
|
||||
createAugmentations();
|
||||
updateAugmentationCosts();
|
||||
Player.reapplyAllAugmentations();
|
||||
}
|
||||
|
||||
function getBaseAugmentationPriceMultiplier(): number {
|
||||
export function getBaseAugmentationPriceMultiplier(): number {
|
||||
return CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
|
||||
}
|
||||
export function getGenericAugmentationPriceMultiplier(): number {
|
||||
return Math.pow(getBaseAugmentationPriceMultiplier(), Player.queuedAugmentations.length);
|
||||
}
|
||||
|
||||
function updateNeuroFluxGovernorCosts(neuroFluxGovernorAugmentation: Augmentation): void {
|
||||
let nextLevel = getNextNeuroFluxLevel();
|
||||
--nextLevel;
|
||||
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
|
||||
neuroFluxGovernorAugmentation.baseRepRequirement =
|
||||
neuroFluxGovernorAugmentation.startingRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
|
||||
neuroFluxGovernorAugmentation.baseCost =
|
||||
neuroFluxGovernorAugmentation.startingCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
|
||||
|
||||
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
|
||||
neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSoACosts(soaAugmentation: Augmentation): void {
|
||||
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
|
||||
const soaAugCount = soaAugmentationNames.filter((augmentationName) =>
|
||||
Player.hasAugmentation(augmentationName),
|
||||
).length;
|
||||
soaAugmentation.baseCost = soaAugmentation.startingCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
|
||||
if (soaAugmentationNames.find((augmentationName) => augmentationName === soaAugmentation.name)) {
|
||||
soaAugmentation.baseRepRequirement =
|
||||
soaAugmentation.startingRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);
|
||||
}
|
||||
}
|
||||
|
||||
export function updateAugmentationCosts(): void {
|
||||
for (const name of Object.keys(Augmentations)) {
|
||||
if (Augmentations.hasOwnProperty(name)) {
|
||||
const augmentationToUpdate = Augmentations[name];
|
||||
if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
updateNeuroFluxGovernorCosts(augmentationToUpdate);
|
||||
} else if (augmentationToUpdate.factions.includes(FactionNames.ShadowsOfAnarchy)) {
|
||||
updateSoACosts(augmentationToUpdate);
|
||||
} else {
|
||||
augmentationToUpdate.baseCost =
|
||||
augmentationToUpdate.startingCost *
|
||||
getGenericAugmentationPriceMultiplier() *
|
||||
BitNodeMultipliers.AugmentationMoneyCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Resets an Augmentation during (re-initizliation)
|
||||
function resetAugmentation(aug: Augmentation): void {
|
||||
aug.addToFactions(aug.factions);
|
||||
const name = aug.name;
|
||||
if (augmentationExists(name)) {
|
||||
delete Augmentations[name];
|
||||
delete StaticAugmentations[name];
|
||||
}
|
||||
AddToAugmentations(aug);
|
||||
AddToStaticAugmentations(aug);
|
||||
}
|
||||
|
||||
function applyAugmentation(aug: IPlayerOwnedAugmentation, reapply = false): void {
|
||||
const augObj = Augmentations[aug.name];
|
||||
const staticAugmentation = StaticAugmentations[aug.name];
|
||||
|
||||
// Apply multipliers
|
||||
for (const mult of Object.keys(augObj.mults)) {
|
||||
const v = Player.getMult(mult) * augObj.mults[mult];
|
||||
for (const mult of Object.keys(staticAugmentation.mults)) {
|
||||
const v = Player.getMult(mult) * staticAugmentation.mults[mult];
|
||||
Player.setMult(mult, v);
|
||||
}
|
||||
|
||||
// Special logic for NeuroFlux Governor
|
||||
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
if (!reapply) {
|
||||
Augmentations[aug.name].level = aug.level;
|
||||
for (let i = 0; i < Player.augmentations.length; ++i) {
|
||||
if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
|
||||
Player.augmentations[i].level = aug.level;
|
||||
return;
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special logic for Congruity Implant
|
||||
if (aug.name === AugmentationNames.CongruityImplant && !reapply) {
|
||||
Player.entropy = 0;
|
||||
@ -185,7 +107,7 @@ function installAugmentations(force?: boolean): boolean {
|
||||
}
|
||||
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
|
||||
const ownedAug = Player.queuedAugmentations[i];
|
||||
const aug = Augmentations[ownedAug.name];
|
||||
const aug = StaticAugmentations[ownedAug.name];
|
||||
if (aug == null) {
|
||||
console.error(`Invalid augmentation: ${ownedAug.name}`);
|
||||
continue;
|
||||
@ -215,7 +137,7 @@ function installAugmentations(force?: boolean): boolean {
|
||||
}
|
||||
|
||||
function augmentationExists(name: string): boolean {
|
||||
return Augmentations.hasOwnProperty(name);
|
||||
return StaticAugmentations.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
export function isRepeatableAug(aug: Augmentation): boolean {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Augmentation } from "./Augmentation";
|
||||
import { IMap } from "../types";
|
||||
|
||||
export const Augmentations: IMap<Augmentation> = {};
|
||||
export const StaticAugmentations: IMap<Augmentation> = {};
|
@ -114,17 +114,6 @@ export enum AugmentationNames {
|
||||
StaneksGift2 = "Stanek's Gift - Awakening",
|
||||
StaneksGift3 = "Stanek's Gift - Serenity",
|
||||
|
||||
/*
|
||||
MightOfAres = "Might of Ares", // slash
|
||||
WisdomOfAthena = "Wisdom of Athena", // bracket
|
||||
TrickeryOfHermes = "Trickery of Hermes", // cheatcode
|
||||
BeautyOfAphrodite = "Beauty of Aphrodite", // bribe
|
||||
ChaosOfDionysus = "Chaos of Dionysus", // reverse
|
||||
FloodOfPoseidon = "Flood of Poseidon", // hex
|
||||
HuntOfArtemis = "Hunt of Artemis", // mine
|
||||
KnowledgeOfApollo = "Knowledge of Apollo", // wire
|
||||
*/
|
||||
|
||||
// Infiltrators MiniGames
|
||||
MightOfAres = "SoA - Might of Ares", // slash
|
||||
WisdomOfAthena = "SoA - Wisdom of Athena", // bracket
|
||||
@ -135,10 +124,4 @@ export enum AugmentationNames {
|
||||
HuntOfArtemis = "SoA - Hunt of Artemis", // mine
|
||||
KnowledgeOfApollo = "SoA - Knowledge of Apollo", // wire
|
||||
WKSharmonizer = "SoA - phyzical WKS harmonizer",
|
||||
|
||||
//Wasteland Augs
|
||||
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
|
||||
//PepBoyForceField Generates plasma force fields
|
||||
//PepBoyBlasts Generate high density plasma concussive blasts
|
||||
//PepBoyDataStorage STore more data on pep boy,
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { Settings } from "../../Settings/Settings";
|
||||
import { ConfirmationModal } from "../../ui/React/ConfirmationModal";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { AugmentationNames } from "../data/AugmentationNames";
|
||||
import { Augmentations } from "../Augmentations";
|
||||
import { StaticAugmentations } from "../StaticAugmentations";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { formatNumber } from "../../utils/StringHelperFunctions";
|
||||
import { Info } from "@mui/icons-material";
|
||||
@ -39,7 +39,9 @@ const NeuroFluxDisplay = ({ player }: NFGDisplayProps): React.ReactElement => {
|
||||
<Typography variant="h5" color={Settings.theme.info}>
|
||||
NeuroFlux Governor - Level {level}
|
||||
</Typography>
|
||||
<Typography color={Settings.theme.info}>{Augmentations[AugmentationNames.NeuroFluxGovernor].stats}</Typography>
|
||||
<Typography color={Settings.theme.info}>
|
||||
{StaticAugmentations[AugmentationNames.NeuroFluxGovernor].stats}
|
||||
</Typography>
|
||||
</Paper>
|
||||
) : (
|
||||
<></>
|
||||
|
@ -13,7 +13,7 @@ import React, { useState } from "react";
|
||||
import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { use } from "../../ui/Context";
|
||||
import { Augmentations } from "../Augmentations";
|
||||
import { StaticAugmentations } from "../StaticAugmentations";
|
||||
import { AugmentationNames } from "../data/AugmentationNames";
|
||||
|
||||
export function InstalledAugmentations(): React.ReactElement {
|
||||
@ -77,7 +77,7 @@ export function InstalledAugmentations(): React.ReactElement {
|
||||
</Typography>
|
||||
<Typography sx={{ maxHeight: 350, overflowY: "scroll" }}>
|
||||
{(() => {
|
||||
const aug = Augmentations[selectedAug.name];
|
||||
const aug = StaticAugmentations[selectedAug.name];
|
||||
|
||||
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
|
||||
const tooltip = (
|
||||
|
@ -8,7 +8,7 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Augmentations } from "../Augmentations";
|
||||
import { StaticAugmentations } from "../StaticAugmentations";
|
||||
|
||||
interface IAugmentedStats {
|
||||
[index: string]: number;
|
||||
@ -17,7 +17,7 @@ interface IAugmentedStats {
|
||||
function calculateAugmentedStats(): IAugmentedStats {
|
||||
const augP: IAugmentedStats = {};
|
||||
for (const aug of Player.queuedAugmentations) {
|
||||
const augObj = Augmentations[aug.name];
|
||||
const augObj = StaticAugmentations[aug.name];
|
||||
for (const mult of Object.keys(augObj.mults)) {
|
||||
const v = augP[mult] ? augP[mult] : 1;
|
||||
augP[mult] = v * augObj.mults[mult];
|
||||
|
@ -5,15 +5,14 @@
|
||||
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
|
||||
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { getNextNeuroFluxLevel } from "../AugmentationHelpers";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Augmentation } from "../Augmentation";
|
||||
import { Augmentations } from "../Augmentations";
|
||||
import { AugmentationNames } from "../data/AugmentationNames";
|
||||
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
|
||||
import { StaticAugmentations } from "../StaticAugmentations";
|
||||
|
||||
interface IPreReqsProps {
|
||||
player: IPlayer;
|
||||
@ -160,10 +159,10 @@ interface IPurchasableAugProps {
|
||||
export function PurchasableAugmentation(props: IPurchasableAugProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const aug = Augmentations[props.augName];
|
||||
|
||||
const cost = props.parent.sleeveAugs ? aug.startingCost : aug.baseCost;
|
||||
|
||||
const aug = StaticAugmentations[props.augName];
|
||||
const augCosts = aug.getCost(props.parent.player);
|
||||
const cost = props.parent.sleeveAugs ? aug.baseCost : augCosts.moneyCost;
|
||||
const repCost = augCosts.repCost;
|
||||
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
|
||||
const description = (
|
||||
<>
|
||||
@ -205,7 +204,8 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
|
||||
<>
|
||||
<Typography variant="h5">
|
||||
{props.augName}
|
||||
{props.augName === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
|
||||
{props.augName === AugmentationNames.NeuroFluxGovernor &&
|
||||
` - Level ${aug.getLevel(props.parent.player)}`}
|
||||
</Typography>
|
||||
<Typography>{description}</Typography>
|
||||
</>
|
||||
@ -222,7 +222,7 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
|
||||
}}
|
||||
>
|
||||
{aug.name}
|
||||
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${getNextNeuroFluxLevel()}`}
|
||||
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`}
|
||||
</Typography>
|
||||
{aug.factions.length === 1 && !props.parent.sleeveAugs && (
|
||||
<Exclusive player={props.parent.player} aug={aug} />
|
||||
@ -236,14 +236,14 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
|
||||
{props.owned || (
|
||||
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
|
||||
<Requirement
|
||||
fulfilled={aug.baseCost === 0 || props.parent.player.money > cost}
|
||||
fulfilled={cost === 0 || props.parent.player.money > cost}
|
||||
value={numeralWrapper.formatMoney(cost)}
|
||||
color={Settings.theme.money}
|
||||
/>
|
||||
{props.parent.rep !== undefined && (
|
||||
<Requirement
|
||||
fulfilled={props.parent.rep >= aug.baseRepRequirement}
|
||||
value={`${numeralWrapper.formatReputation(aug.baseRepRequirement)} rep`}
|
||||
fulfilled={props.parent.rep >= repCost}
|
||||
value={`${numeralWrapper.formatReputation(repCost)} rep`}
|
||||
color={Settings.theme.rep}
|
||||
/>
|
||||
)}
|
||||
|
@ -44,7 +44,7 @@ export function PurchaseAugmentationModal(props: IProps): React.ReactElement {
|
||||
<br />
|
||||
<br />
|
||||
Would you like to purchase the {props.aug.name} Augmentation for
|
||||
<Money money={props.aug.baseCost} />?
|
||||
<Money money={props.aug.getCost(player).moneyCost} />?
|
||||
<br />
|
||||
<br />
|
||||
</Typography>
|
||||
|
@ -5,7 +5,7 @@
|
||||
import { List, ListItemText, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { Player } from "../../Player";
|
||||
import { Augmentations } from "../Augmentations";
|
||||
import { StaticAugmentations } from "../StaticAugmentations";
|
||||
import { AugmentationNames } from "../data/AugmentationNames";
|
||||
|
||||
export function PurchasedAugmentations(): React.ReactElement {
|
||||
@ -23,7 +23,7 @@ export function PurchasedAugmentations(): React.ReactElement {
|
||||
let displayName = ownedAug.name;
|
||||
|
||||
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
|
||||
const aug = Augmentations[ownedAug.name];
|
||||
const aug = StaticAugmentations[ownedAug.name];
|
||||
|
||||
let level = null;
|
||||
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
|
||||
|
@ -293,7 +293,7 @@ export const CONSTANTS: {
|
||||
// BitNode/Source-File related stuff
|
||||
TotalNumBitNodes: 24,
|
||||
|
||||
Donations: 6,
|
||||
Donations: 7,
|
||||
|
||||
LatestUpdate: `
|
||||
v1.6.3 - 2022-04-01 Few stanek fixes
|
||||
|
@ -162,11 +162,7 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
|
||||
<Tooltip
|
||||
title={tutorial ? <Typography>Purchase your required materials to get production started!</Typography> : ""}
|
||||
>
|
||||
<Button
|
||||
color={tutorial ? "error" : "primary"}
|
||||
onClick={() => setPurchaseMaterialOpen(true)}
|
||||
disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}
|
||||
>
|
||||
<Button color={tutorial ? "error" : "primary"} onClick={() => setPurchaseMaterialOpen(true)}>
|
||||
{purchaseButtonText}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@ -174,6 +170,9 @@ export function MaterialElem(props: IMaterialProps): React.ReactElement {
|
||||
mat={mat}
|
||||
warehouse={warehouse}
|
||||
open={purchaseMaterialOpen}
|
||||
disablePurchaseLimit={
|
||||
props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)
|
||||
}
|
||||
onClose={() => setPurchaseMaterialOpen(false)}
|
||||
/>
|
||||
|
||||
|
@ -106,6 +106,7 @@ interface IProps {
|
||||
onClose: () => void;
|
||||
mat: Material;
|
||||
warehouse: Warehouse;
|
||||
disablePurchaseLimit: boolean;
|
||||
}
|
||||
|
||||
// Create a popup that lets the player purchase a Material
|
||||
@ -143,6 +144,7 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
|
||||
<Typography>
|
||||
Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes
|
||||
constantly.
|
||||
{props.disablePurchaseLimit ? "Note: Purchase amount is disabled as smart supply is enabled" : ""}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={buyAmt}
|
||||
@ -150,10 +152,15 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
|
||||
autoFocus={true}
|
||||
placeholder="Purchase amount"
|
||||
type="number"
|
||||
disabled={props.disablePurchaseLimit}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Button onClick={purchaseMaterial}>Confirm</Button>
|
||||
<Button onClick={clearPurchase}>Clear Purchase</Button>
|
||||
<Button disabled={props.disablePurchaseLimit} onClick={purchaseMaterial}>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button disabled={props.disablePurchaseLimit} onClick={clearPurchase}>
|
||||
Clear Purchase
|
||||
</Button>
|
||||
{division.hasResearch("Bulk Purchasing") && (
|
||||
<BulkPurchaseSection onClose={props.onClose} mat={props.mat} warehouse={props.warehouse} />
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
|
||||
import { Augmentation } from "../Augmentation/Augmentation";
|
||||
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
|
||||
@ -18,7 +18,6 @@ import {
|
||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||
import { InvitationEvent } from "./ui/InvitationModal";
|
||||
import { FactionNames } from "./data/FactionNames";
|
||||
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
|
||||
import { SFC32RNG } from "../Casino/RNG";
|
||||
|
||||
export function inviteToFaction(faction: Faction): void {
|
||||
@ -59,6 +58,7 @@ export function hasAugmentationPrereqs(aug: Augmentation): boolean {
|
||||
|
||||
export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = false): string {
|
||||
const hasPrereqs = hasAugmentationPrereqs(aug);
|
||||
const augCosts = aug.getCost(Player);
|
||||
if (!hasPrereqs) {
|
||||
const txt = `You must first purchase or install ${aug.prereqs
|
||||
.filter((req) => !Player.hasAugmentation(req))
|
||||
@ -68,28 +68,26 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
|
||||
} else {
|
||||
dialogBoxCreate(txt);
|
||||
}
|
||||
} else if (aug.baseCost !== 0 && Player.money < aug.baseCost) {
|
||||
} else if (augCosts.moneyCost !== 0 && Player.money < augCosts.moneyCost) {
|
||||
const txt = "You don't have enough money to purchase " + aug.name;
|
||||
if (sing) {
|
||||
return txt;
|
||||
}
|
||||
dialogBoxCreate(txt);
|
||||
} else if (fac.playerReputation < aug.baseRepRequirement) {
|
||||
} else if (fac.playerReputation < augCosts.repCost) {
|
||||
const txt = "You don't have enough faction reputation to purchase " + aug.name;
|
||||
if (sing) {
|
||||
return txt;
|
||||
}
|
||||
dialogBoxCreate(txt);
|
||||
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
|
||||
} else if (augCosts.moneyCost === 0 || Player.money >= augCosts.moneyCost) {
|
||||
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
|
||||
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
|
||||
queuedAugmentation.level = getNextNeuroFluxLevel();
|
||||
queuedAugmentation.level = aug.getLevel(Player);
|
||||
}
|
||||
Player.queuedAugmentations.push(queuedAugmentation);
|
||||
|
||||
Player.loseMoney(aug.baseCost, "augmentations");
|
||||
|
||||
updateAugmentationCosts();
|
||||
Player.loseMoney(augCosts.moneyCost, "augmentations");
|
||||
|
||||
if (sing) {
|
||||
return "You purchased " + aug.name;
|
||||
@ -141,14 +139,14 @@ export function processPassiveFactionRepGain(numCycles: number): void {
|
||||
export const getFactionAugmentationsFiltered = (player: IPlayer, faction: Faction): string[] => {
|
||||
// If player has a gang with this faction, return (almost) all augmentations
|
||||
if (player.hasGangWith(faction.name)) {
|
||||
let augs = Object.values(Augmentations);
|
||||
let augs = Object.values(StaticAugmentations);
|
||||
|
||||
// Remove special augs
|
||||
augs = augs.filter((a) => !a.isSpecial || a.name != AugmentationNames.CongruityImplant);
|
||||
augs = augs.filter((a) => !a.isSpecial && a.name !== AugmentationNames.CongruityImplant);
|
||||
|
||||
if (player.bitNodeN === 2) {
|
||||
// TRP is not available outside of BN2 for Gangs
|
||||
augs.push(Augmentations[AugmentationNames.TheRedPill]);
|
||||
augs.push(StaticAugmentations[AugmentationNames.TheRedPill]);
|
||||
}
|
||||
|
||||
const rng = SFC32RNG(`BN${player.bitNodeN}.${player.sourceFileLvl(player.bitNodeN)}`);
|
||||
|
@ -3,8 +3,9 @@
|
||||
*/
|
||||
import { Box, Button, Tooltip, Typography, Paper, Container } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
|
||||
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { PurchasableAugmentations } from "../../Augmentation/ui/PurchasableAugmentations";
|
||||
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
|
||||
@ -54,13 +55,13 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
function getAugsSortedByCost(): string[] {
|
||||
const augs = getAugs();
|
||||
augs.sort((augName1, augName2) => {
|
||||
const aug1 = Augmentations[augName1],
|
||||
aug2 = Augmentations[augName2];
|
||||
const aug1 = StaticAugmentations[augName1],
|
||||
aug2 = StaticAugmentations[augName2];
|
||||
if (aug1 == null || aug2 == null) {
|
||||
throw new Error("Invalid Augmentation Names");
|
||||
}
|
||||
|
||||
return aug1.baseCost - aug2.baseCost;
|
||||
return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
|
||||
});
|
||||
|
||||
return augs;
|
||||
@ -69,31 +70,32 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
function getAugsSortedByPurchasable(): string[] {
|
||||
const augs = getAugs();
|
||||
function canBuy(augName: string): boolean {
|
||||
const aug = Augmentations[augName];
|
||||
const repCost = aug.baseRepRequirement;
|
||||
const aug = StaticAugmentations[augName];
|
||||
const augCosts = aug.getCost(player);
|
||||
const repCost = augCosts.repCost;
|
||||
const hasReq = props.faction.playerReputation >= repCost;
|
||||
const hasRep = hasAugmentationPrereqs(aug);
|
||||
const hasCost = aug.baseCost !== 0 && player.money > aug.baseCost;
|
||||
const hasCost = augCosts.moneyCost !== 0 && player.money > augCosts.moneyCost;
|
||||
return hasCost && hasReq && hasRep;
|
||||
}
|
||||
const buy = augs.filter(canBuy).sort((augName1, augName2) => {
|
||||
const aug1 = Augmentations[augName1],
|
||||
aug2 = Augmentations[augName2];
|
||||
const aug1 = StaticAugmentations[augName1],
|
||||
aug2 = StaticAugmentations[augName2];
|
||||
if (aug1 == null || aug2 == null) {
|
||||
throw new Error("Invalid Augmentation Names");
|
||||
}
|
||||
|
||||
return aug1.baseCost - aug2.baseCost;
|
||||
return aug1.getCost(player).moneyCost - aug2.getCost(player).moneyCost;
|
||||
});
|
||||
const cantBuy = augs
|
||||
.filter((aug) => !canBuy(aug))
|
||||
.sort((augName1, augName2) => {
|
||||
const aug1 = Augmentations[augName1],
|
||||
aug2 = Augmentations[augName2];
|
||||
const aug1 = StaticAugmentations[augName1],
|
||||
aug2 = StaticAugmentations[augName2];
|
||||
if (aug1 == null || aug2 == null) {
|
||||
throw new Error("Invalid Augmentation Names");
|
||||
}
|
||||
return aug1.baseRepRequirement - aug2.baseRepRequirement;
|
||||
return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
|
||||
});
|
||||
|
||||
return buy.concat(cantBuy);
|
||||
@ -102,12 +104,12 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
function getAugsSortedByReputation(): string[] {
|
||||
const augs = getAugs();
|
||||
augs.sort((augName1, augName2) => {
|
||||
const aug1 = Augmentations[augName1],
|
||||
aug2 = Augmentations[augName2];
|
||||
const aug1 = StaticAugmentations[augName1],
|
||||
aug2 = StaticAugmentations[augName2];
|
||||
if (aug1 == null || aug2 == null) {
|
||||
throw new Error("Invalid Augmentation Names");
|
||||
}
|
||||
return aug1.baseRepRequirement - aug2.baseRepRequirement;
|
||||
return aug1.getCost(player).repCost - aug2.getCost(player).repCost;
|
||||
});
|
||||
|
||||
return augs;
|
||||
@ -195,10 +197,11 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
|
||||
ownedAugNames={owned}
|
||||
player={player}
|
||||
canPurchase={(player, aug) => {
|
||||
const costs = aug.getCost(player);
|
||||
return (
|
||||
hasAugmentationPrereqs(aug) &&
|
||||
props.faction.playerReputation >= aug.baseRepRequirement &&
|
||||
(aug.baseCost === 0 || player.money > aug.baseCost)
|
||||
props.faction.playerReputation >= costs.repCost &&
|
||||
(costs.moneyCost === 0 || player.money > costs.moneyCost)
|
||||
);
|
||||
}}
|
||||
purchaseAugmentation={(player, aug, showModal) => {
|
||||
|
@ -16,12 +16,10 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { Faction } from "../Faction";
|
||||
|
||||
import { use } from "../../ui/Context";
|
||||
import { CreateGangModal } from "./CreateGangModal";
|
||||
|
||||
import { Box, Paper, Typography, Button, Tooltip } from "@mui/material";
|
||||
import { Typography, Button } from "@mui/material";
|
||||
import { CovenantPurchasesRoot } from "../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot";
|
||||
import { FactionNames } from "../data/FactionNames";
|
||||
import { GangConstants } from "../../Gang/data/Constants";
|
||||
import { GangButton } from "./GangButton";
|
||||
|
||||
type IProps = {
|
||||
@ -30,7 +28,6 @@ type IProps = {
|
||||
};
|
||||
|
||||
// Info text for all options on the UI
|
||||
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and faction reputation";
|
||||
const hackingContractsInfo =
|
||||
"Complete hacking contracts for your faction. " +
|
||||
"Your effectiveness, which determines how much " +
|
||||
|
@ -189,9 +189,9 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
|
||||
title={
|
||||
<Typography>
|
||||
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game.
|
||||
After using this, save the game and then reload the page. This is different then normal kill in that
|
||||
After using this, save the game and then reload the page. This is different than normal kill in that
|
||||
normal kill will tell the script to shut down while force kill just removes the references to it (and it
|
||||
should crash on it's own). This will not remove the files on your computer. Just forcefully kill all
|
||||
should crash on its own). This will not remove the files on your computer, just forcefully kill all
|
||||
running instances of all scripts.
|
||||
</Typography>
|
||||
}
|
||||
@ -210,7 +210,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
|
||||
title={
|
||||
<Typography>
|
||||
If your save file is extremely big you can use this button to view a map of all the files on every server.
|
||||
Be careful there might be spoilers.
|
||||
Be careful: there might be spoilers.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
|
@ -60,15 +60,22 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const increase =
|
||||
const base_increase =
|
||||
calculateHashGainRate(node.level + multiplier, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult) -
|
||||
node.hashRate;
|
||||
calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
|
||||
const modded_increase = (base_increase * (node.maxRam - node.ramUsed)) / node.maxRam;
|
||||
|
||||
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);
|
||||
upgradeLevelButton = (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
+<HashRate hashes={increase} />
|
||||
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
+<HashRate hashes={base_increase} />
|
||||
</span>{" "}
|
||||
(base increase, attained when no script is running)
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
@ -109,20 +116,36 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const increase =
|
||||
const base_increase =
|
||||
calculateHashGainRate(
|
||||
node.level,
|
||||
0,
|
||||
node.maxRam * Math.pow(2, multiplier),
|
||||
node.cores,
|
||||
props.player.hacknet_node_money_mult,
|
||||
) - node.hashRate;
|
||||
) - calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
|
||||
|
||||
const modded_increase =
|
||||
calculateHashGainRate(
|
||||
node.level,
|
||||
node.ramUsed,
|
||||
node.maxRam * Math.pow(2, multiplier),
|
||||
node.cores,
|
||||
props.player.hacknet_node_money_mult,
|
||||
) -
|
||||
calculateHashGainRate(node.level, node.ramUsed, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
|
||||
|
||||
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);
|
||||
upgradeRamButton = (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
+<HashRate hashes={increase} />
|
||||
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
+<HashRate hashes={base_increase} />
|
||||
</span>{" "}
|
||||
(base increase, attained when no script is running)
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
@ -155,15 +178,22 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
multiplier = Math.min(levelsToMax, purchaseMult as number);
|
||||
}
|
||||
|
||||
const increase =
|
||||
const base_increase =
|
||||
calculateHashGainRate(node.level, 0, node.maxRam, node.cores + multiplier, props.player.hacknet_node_money_mult) -
|
||||
node.hashRate;
|
||||
calculateHashGainRate(node.level, 0, node.maxRam, node.cores, props.player.hacknet_node_money_mult);
|
||||
const modded_increase = (base_increase * (node.maxRam - node.ramUsed)) / node.maxRam;
|
||||
|
||||
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);
|
||||
upgradeCoresButton = (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
+<HashRate hashes={increase} />
|
||||
+<HashRate hashes={modded_increase} /> (effective increase, taking current RAM usage into account)
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
+<HashRate hashes={base_increase} />
|
||||
</span>{" "}
|
||||
(base increase, attained when no script is running)
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
@ -232,9 +262,31 @@ export function HacknetServerElem(props: IProps): React.ReactElement {
|
||||
<Typography>Production:</Typography>
|
||||
</TableCell>
|
||||
<TableCell colSpan={2}>
|
||||
<Typography>
|
||||
<Hashes hashes={node.totalHashesGenerated} /> (<HashRate hashes={node.hashRate} />)
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography>
|
||||
<Hashes hashes={node.totalHashesGenerated} /> hashes produced by this server since last augment
|
||||
installation.
|
||||
<br />
|
||||
<HashRate hashes={node.hashRate} /> current production rate.
|
||||
<br />
|
||||
<span style={{ opacity: 0.5 }}>
|
||||
<HashRate hashes={(node.hashRate * node.maxRam) / (node.maxRam - node.ramUsed)} />
|
||||
</span>{" "}
|
||||
max production rate. (achieved when 100% RAM is allocated to it)
|
||||
<br />
|
||||
{numeralWrapper.formatRAM(node.ramUsed)} / {numeralWrapper.formatRAM(node.maxRam)} (
|
||||
{Math.round((100 * node.ramUsed) / node.maxRam)}%) RAM allocated to script.
|
||||
<br />
|
||||
{numeralWrapper.formatRAM(node.maxRam - node.ramUsed)} / {numeralWrapper.formatRAM(node.maxRam)} (
|
||||
{Math.round((100 * (node.maxRam - node.ramUsed)) / node.maxRam)}%) RAM allocated to hash production.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
<Hashes hashes={node.totalHashesGenerated} /> (<HashRate hashes={node.hashRate} />)
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { random } from "../utils";
|
||||
import { BlinkingCursor } from "./BlinkingCursor";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { random } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { BlinkingCursor } from "./BlinkingCursor";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { Player } from "../../Player";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -48,24 +47,18 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
|
||||
<Typography variant="h4">Type it{!hasAugment ? " backward" : ""}</Typography>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)", marginLeft: hasAugment ? "50%" : "none" }}>
|
||||
{answer}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)" }}>{answer}</Typography>
|
||||
<Typography>
|
||||
{guess}
|
||||
<BlinkingCursor />
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export function BlinkingCursor(): React.ReactElement {
|
||||
const [on, setOn] = useState(true);
|
||||
@ -6,5 +6,5 @@ export function BlinkingCursor(): React.ReactElement {
|
||||
const i = setInterval(() => setOn((old) => !old), 1000);
|
||||
return () => clearInterval(i);
|
||||
});
|
||||
return <>{on ? "|" : ""}</>;
|
||||
return <>{on ? "|" : <> </>}</>;
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { random } from "../utils";
|
||||
import { BlinkingCursor } from "./BlinkingCursor";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { random } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { BlinkingCursor } from "./BlinkingCursor";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Player } from "../../Player";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -84,16 +83,16 @@ export function BracketGame(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">Close the brackets</Typography>
|
||||
<Typography style={{ fontSize: "5em" }}>
|
||||
{`${left}${right}`}
|
||||
<BlinkingCursor />
|
||||
</Typography>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { downArrowSymbol, upArrowSymbol } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -88,13 +87,11 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">Say something nice about the guard.</Typography>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">Say something nice about the guard</Typography>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h5" color={upColor}>
|
||||
{upArrowSymbol}
|
||||
</Typography>
|
||||
@ -104,8 +101,8 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
|
||||
<Typography variant="h5" color={downColor}>
|
||||
{downArrowSymbol}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -154,6 +151,7 @@ const positive = [
|
||||
"patient",
|
||||
"dynamic",
|
||||
"loyal",
|
||||
"based",
|
||||
];
|
||||
|
||||
const negative = [
|
||||
@ -177,4 +175,5 @@ const negative = [
|
||||
"picky",
|
||||
"tactless",
|
||||
"thoughtless",
|
||||
"cringe",
|
||||
];
|
||||
|
@ -1,21 +1,20 @@
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import {
|
||||
random,
|
||||
downArrowSymbol,
|
||||
getArrow,
|
||||
getInverseArrow,
|
||||
leftArrowSymbol,
|
||||
random,
|
||||
rightArrowSymbol,
|
||||
upArrowSymbol,
|
||||
downArrowSymbol,
|
||||
} from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -55,14 +54,14 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">Enter the Code!</Typography>
|
||||
<Typography variant="h4">{code[index]}</Typography>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
interface IProps {
|
||||
onFinish: () => void;
|
||||
}
|
||||
@ -13,17 +12,13 @@ export function Countdown(props: IProps): React.ReactElement {
|
||||
props.onFinish();
|
||||
return;
|
||||
}
|
||||
setTimeout(() => setX(x - 1), 200);
|
||||
setTimeout(() => setX(x - 1), 300);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">Get Ready!</Typography>
|
||||
<Typography variant="h4">{x}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
<Paper sx={{ p: 1, textAlign: "center" }}>
|
||||
<Typography variant="h4">Get Ready!</Typography>
|
||||
<Typography variant="h4">{x}</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Paper, Typography, Box } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -19,6 +18,12 @@ interface Difficulty {
|
||||
symbols: number;
|
||||
}
|
||||
|
||||
interface GridItem {
|
||||
content: string;
|
||||
color: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
const difficulties: {
|
||||
Trivial: Difficulty;
|
||||
Normal: Difficulty;
|
||||
@ -76,18 +81,33 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
}
|
||||
|
||||
const flatGrid: GridItem[] = [];
|
||||
grid.map((line, y) =>
|
||||
line.map((cell, x) => {
|
||||
const isCorrectAnswer = cell === answers[currentAnswerIndex];
|
||||
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
|
||||
|
||||
if (x === pos[0] && y === pos[1]) {
|
||||
flatGrid.push({ color: optionColor, content: cell, selected: true });
|
||||
return;
|
||||
}
|
||||
|
||||
flatGrid.push({ color: optionColor, content: cell });
|
||||
}),
|
||||
);
|
||||
|
||||
const fontSize = "2em";
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
|
||||
<Typography variant="h4">Match the symbols!</Typography>
|
||||
<Typography variant="h5" color={Settings.theme.primary}>
|
||||
Targets:{" "}
|
||||
{answers.map((a, i) => {
|
||||
if (i == currentAnswerIndex)
|
||||
return (
|
||||
<span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}>
|
||||
<span key={`${i}`} style={{ fontSize: "1em", color: Settings.theme.infolight }}>
|
||||
{a}
|
||||
</span>
|
||||
);
|
||||
@ -99,34 +119,30 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
|
||||
})}
|
||||
</Typography>
|
||||
<br />
|
||||
{grid.map((line, y) => (
|
||||
<div key={y}>
|
||||
<Typography>
|
||||
{line.map((cell, x) => {
|
||||
const isCorrectAnswer = cell === answers[currentAnswerIndex];
|
||||
|
||||
if (x == pos[0] && y == pos[1]) {
|
||||
return (
|
||||
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: "blue" }}>
|
||||
{cell}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
|
||||
return (
|
||||
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: optionColor }}>
|
||||
{cell}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${Math.round(difficulty.width)}, 1fr)`,
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{flatGrid.map((item) => (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: fontSize,
|
||||
color: item.color,
|
||||
border: item.selected ? `2px solid ${Settings.theme.infolight}` : "unset",
|
||||
lineHeight: "unset",
|
||||
p: item.selected ? "2px" : "4px",
|
||||
}}
|
||||
>
|
||||
{item.content}
|
||||
</Typography>
|
||||
<br />
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</Box>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,17 @@
|
||||
import { use } from "../../ui/Context";
|
||||
import { Button, Container, Paper, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Countdown } from "./Countdown";
|
||||
import { BracketGame } from "./BracketGame";
|
||||
import { SlashGame } from "./SlashGame";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { use } from "../../ui/Context";
|
||||
import { BackwardGame } from "./BackwardGame";
|
||||
import { BracketGame } from "./BracketGame";
|
||||
import { BribeGame } from "./BribeGame";
|
||||
import { CheatCodeGame } from "./CheatCodeGame";
|
||||
import { Countdown } from "./Countdown";
|
||||
import { Cyberpunk2077Game } from "./Cyberpunk2077Game";
|
||||
import { MinesweeperGame } from "./MinesweeperGame";
|
||||
import { WireCuttingGame } from "./WireCuttingGame";
|
||||
import { SlashGame } from "./SlashGame";
|
||||
import { Victory } from "./Victory";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { WireCuttingGame } from "./WireCuttingGame";
|
||||
|
||||
interface IProps {
|
||||
StartingDifficulty: number;
|
||||
@ -139,22 +137,20 @@ export function Game(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={3}>
|
||||
<Button onClick={cancel}>Cancel</Button>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Typography>
|
||||
Level: {level} / {props.MaxLevel}
|
||||
</Typography>
|
||||
<Progress />
|
||||
</Grid>
|
||||
<Container>
|
||||
<Paper sx={{ p: 1, mb: 1, display: "grid", justifyItems: "center", gap: 1 }}>
|
||||
{stage !== Stage.Sell && (
|
||||
<Button sx={{ width: "100%" }} onClick={cancel}>
|
||||
Cancel Infiltration
|
||||
</Button>
|
||||
)}
|
||||
<Typography variant="h5">
|
||||
Level {level} / {props.MaxLevel}
|
||||
</Typography>
|
||||
<Progress />
|
||||
</Paper>
|
||||
|
||||
<Grid item xs={12}>
|
||||
{stageComponent}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
{stageComponent}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Paper } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { use } from "../../ui/Context";
|
||||
@ -7,6 +7,7 @@ import { ProgressBar } from "../../ui/React/Progress";
|
||||
interface IProps {
|
||||
millis: number;
|
||||
onExpire: () => void;
|
||||
noPaper?: boolean;
|
||||
}
|
||||
|
||||
export function GameTimer(props: IProps): React.ReactElement {
|
||||
@ -30,9 +31,11 @@ export function GameTimer(props: IProps): React.ReactElement {
|
||||
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
|
||||
// TODO(hydroflame): there's like a bug where it triggers the end before the
|
||||
// bar physically reaches the end
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
return props.noPaper ? (
|
||||
<ProgressBar variant="determinate" value={v} color="primary" />
|
||||
) : (
|
||||
<Paper sx={{ p: 1, mb: 1 }}>
|
||||
<ProgressBar variant="determinate" value={v} color="primary" />
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import { Intro } from "./Intro";
|
||||
import { Game } from "./Game";
|
||||
import { Location } from "../../Locations/Location";
|
||||
import { use } from "../../ui/Context";
|
||||
import { calculateDifficulty, calculateReward } from "../formulas/game";
|
||||
import { Game } from "./Game";
|
||||
import { Intro } from "./Intro";
|
||||
interface IProps {
|
||||
location: Location;
|
||||
}
|
||||
@ -22,24 +22,24 @@ export function InfiltrationRoot(props: IProps): React.ReactElement {
|
||||
router.toCity();
|
||||
}
|
||||
|
||||
if (!start) {
|
||||
return (
|
||||
<Intro
|
||||
Location={props.location}
|
||||
Difficulty={difficulty}
|
||||
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
|
||||
start={() => setStart(true)}
|
||||
cancel={cancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Game
|
||||
StartingDifficulty={startingSecurityLevel}
|
||||
Difficulty={difficulty}
|
||||
Reward={reward}
|
||||
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center", height: "calc(100vh - 16px)" }}>
|
||||
{start ? (
|
||||
<Game
|
||||
StartingDifficulty={startingSecurityLevel}
|
||||
Difficulty={difficulty}
|
||||
Reward={reward}
|
||||
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
|
||||
/>
|
||||
) : (
|
||||
<Intro
|
||||
Location={props.location}
|
||||
Difficulty={difficulty}
|
||||
MaxLevel={props.location.infiltrationData.maxClearanceLevel}
|
||||
start={() => setStart(true)}
|
||||
cancel={cancel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Report } from "@mui/icons-material";
|
||||
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Location } from "../../Locations/Location";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
interface IProps {
|
||||
@ -41,9 +41,9 @@ function coloredArrow(difficulty: number): JSX.Element {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{arrowPart("white", difficulty * 13)}
|
||||
{arrowPart("orange", (difficulty - 1) * 13)}
|
||||
{arrowPart("red", (difficulty - 2) * 13)}
|
||||
{arrowPart(Settings.theme.primary, difficulty * 13)}
|
||||
{arrowPart(Settings.theme.warning, (difficulty - 1) * 13)}
|
||||
{arrowPart(Settings.theme.error, (difficulty - 2) * 13)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -51,65 +51,84 @@ function coloredArrow(difficulty: number): JSX.Element {
|
||||
|
||||
export function Intro(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h4">Infiltrating {props.Location.name}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h5" color="primary">
|
||||
Maximum level: {props.MaxLevel}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h5" color="primary">
|
||||
Difficulty: {numeralWrapper.format(props.Difficulty * 33.3333, "0")} / 100
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Container sx={{ alignItems: "center" }}>
|
||||
<Paper sx={{ p: 1, mb: 1, display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">
|
||||
Infiltrating <b>{props.Location.name}</b>
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
<b>Maximum Level: </b>
|
||||
{props.MaxLevel}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
color:
|
||||
props.Difficulty > 2
|
||||
? Settings.theme.error
|
||||
: props.Difficulty > 1
|
||||
? Settings.theme.warning
|
||||
: Settings.theme.primary,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<b>Difficulty: </b>
|
||||
{numeralWrapper.format(props.Difficulty * 33.3333, "0")} / 100
|
||||
{props.Difficulty > 1.5 && (
|
||||
<Tooltip
|
||||
title={
|
||||
<Typography color="error">
|
||||
This location is too heavily guarded for your current stats. It is recommended that you try training,
|
||||
or finding an easier location.
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Report sx={{ ml: 1 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
{props.Difficulty > 1.5 && (
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h5" color="primary">
|
||||
Warning: This location is too heavily guarded for your current stats, try training or finding an easier
|
||||
location.
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>[{coloredArrow(props.Difficulty)}]</Typography>
|
||||
<Typography
|
||||
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
|
||||
>{`▲ ▲ ▲ ▲`}</Typography>
|
||||
<Typography
|
||||
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
|
||||
>{` Trivial Normal Hard Impossible`}</Typography>
|
||||
</Paper>
|
||||
|
||||
<Grid item xs={10}>
|
||||
<Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>[{coloredArrow(props.Difficulty)}]</Typography>
|
||||
<Typography
|
||||
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
|
||||
>{` ^ ^ ^ ^`}</Typography>
|
||||
<Typography
|
||||
sx={{ lineHeight: "1em", whiteSpace: "pre" }}
|
||||
>{` Trivial Normal Hard Impossible`}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Paper sx={{ p: 1, display: "grid", justifyItems: "center" }}>
|
||||
<Typography sx={{ width: "75%", textAlign: "center" }}>
|
||||
<b>Infiltration</b> is a series of short minigames that get progressively harder. You take damage for failing
|
||||
them. Reaching the maximum level rewards you with intel that you can trade for money or reputation.
|
||||
<br />
|
||||
<br />
|
||||
<b>Gameplay:</b>
|
||||
</Typography>
|
||||
<ul>
|
||||
<Typography>
|
||||
Infiltration is a series of short minigames that get progressively harder. You take damage for failing them.
|
||||
Reaching the maximum level rewards you with intel you can trade for money or reputation.
|
||||
<li>
|
||||
The minigames you play are randomly selected.
|
||||
<br />
|
||||
It might take you a few tries to get used to them.
|
||||
</li>
|
||||
<li>No game requires use of the mouse.</li>
|
||||
<li>
|
||||
<b>Spacebar</b> is the default action/confirm button.
|
||||
</li>
|
||||
<li>
|
||||
The <b>arrow keys</b> and <b>WASD</b> can be used interchangeably.
|
||||
</li>
|
||||
<li>Sometimes the rest of the keyboard is used.</li>
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
The minigames you play are randomly selected. It might take you few tries to get used to them.
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>No game require use of the mouse.</Typography>
|
||||
<br />
|
||||
<Typography>Spacebar is the default action/confirm button.</Typography>
|
||||
<br />
|
||||
<Typography>Everything that uses arrow can also use WASD</Typography>
|
||||
<br />
|
||||
<Typography>Sometimes the rest of the keyboard is used.</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
</ul>
|
||||
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", width: "100%" }}>
|
||||
<Button onClick={props.start}>Start</Button>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button onClick={props.cancel}>Cancel</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { Close, Flag, Report } from "@mui/icons-material";
|
||||
import { Box, Paper, Typography } from "@mui/material";
|
||||
import { uniqueId } from "lodash";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -81,32 +83,77 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const flatGrid: { flagged?: boolean; current?: boolean; marked?: boolean }[] = [];
|
||||
|
||||
minefield.map((line, y) =>
|
||||
line.map((cell, x) => {
|
||||
if (memoryPhase) {
|
||||
flatGrid.push({ flagged: Boolean(minefield[y][x]) });
|
||||
return;
|
||||
} else if (x === pos[0] && y === pos[1]) {
|
||||
flatGrid.push({ current: true });
|
||||
} else if (answer[y][x]) {
|
||||
flatGrid.push({ marked: true });
|
||||
} else if (hasAugment && minefield[y][x]) {
|
||||
flatGrid.push({ flagged: true });
|
||||
} else {
|
||||
flatGrid.push({});
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
|
||||
<Typography variant="h4">{memoryPhase ? "Remember all the mines!" : "Mark all the mines!"}</Typography>
|
||||
{minefield.map((line, y) => (
|
||||
<div key={y}>
|
||||
<Typography>
|
||||
{line.map((cell, x) => {
|
||||
if (memoryPhase) {
|
||||
if (minefield[y][x]) return <span key={x}>[?] </span>;
|
||||
return <span key={x}>[ ] </span>;
|
||||
} else {
|
||||
if (x == pos[0] && y == pos[1]) return <span key={x}>[X] </span>;
|
||||
if (answer[y][x]) return <span key={x}>[.] </span>;
|
||||
if (hasAugment && minefield[y][x]) return <span key={x}>[?] </span>;
|
||||
return <span key={x}>[ ] </span>;
|
||||
}
|
||||
})}
|
||||
</Typography>
|
||||
<br />
|
||||
</div>
|
||||
))}
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${Math.round(difficulty.width)}, 1fr)`,
|
||||
gridTemplateRows: `repeat(${Math.round(difficulty.height)}, 1fr)`,
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{flatGrid.map((item) => {
|
||||
let color: string;
|
||||
let icon: React.ReactElement;
|
||||
|
||||
if (item.marked) {
|
||||
color = Settings.theme.warning;
|
||||
icon = <Flag />;
|
||||
} else if (item.current) {
|
||||
color = Settings.theme.infolight;
|
||||
icon = <Close />;
|
||||
} else if (item.flagged) {
|
||||
color = Settings.theme.error;
|
||||
icon = <Report />;
|
||||
} else {
|
||||
color = Settings.theme.primary;
|
||||
icon = <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`${item}${uniqueId()}`}
|
||||
sx={{
|
||||
color: color,
|
||||
border: `2px solid ${item.current ? Settings.theme.infolight : Settings.theme.primary}`,
|
||||
height: "32px",
|
||||
width: "32px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Box, Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { Player } from "../../Player";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -59,23 +58,25 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={5000} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center" }}>
|
||||
<Typography variant="h4">Slash when his guard is down!</Typography>
|
||||
|
||||
{hasAugment ? (
|
||||
<>
|
||||
<Typography variant="h4">Guard will drop in...</Typography>
|
||||
<GameTimer millis={timeUntilAttacking} onExpire={props.onFailure} />
|
||||
</>
|
||||
<Box sx={{ my: 1 }}>
|
||||
<Typography variant="h5">Guard will drop in...</Typography>
|
||||
<GameTimer millis={timeUntilAttacking} onExpire={() => null} noPaper />
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
|
||||
{phase === 1 && <Typography variant="h4">Preparing?</Typography>}
|
||||
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Box, Button, MenuItem, Paper, Select, SelectChangeEvent, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { FactionNames } from "../../Faction/data/FactionNames";
|
||||
import { inviteToFaction } from "../../Faction/FactionHelpers";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { use } from "../../ui/Context";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
import { use } from "../../ui/Context";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import { FactionNames } from "../../Faction/data/FactionNames";
|
||||
import { formatNumber } from "../../utils/StringHelperFunctions";
|
||||
import {
|
||||
calculateInfiltratorsRepReward,
|
||||
calculateSellInformationCashReward,
|
||||
calculateTradeInformationRepReward,
|
||||
} from "../formulas/victory";
|
||||
import { inviteToFaction } from "../../Faction/FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
StartingDifficulty: number;
|
||||
@ -66,24 +62,22 @@ export function Victory(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h4">Infiltration successful!</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Typography variant="h5" color="primary">
|
||||
You{" "}
|
||||
{isMemberOfInfiltrators ? (
|
||||
<>
|
||||
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
can trade the confidential information you found for money or reputation.
|
||||
</Typography>
|
||||
<Select value={faction} onChange={changeDropdown}>
|
||||
<Paper sx={{ p: 1, textAlign: "center", display: "flex", alignItems: "center", flexDirection: "column" }}>
|
||||
<Typography variant="h4">Infiltration successful!</Typography>
|
||||
<Typography variant="h5" color="primary" width="75%">
|
||||
You{" "}
|
||||
{isMemberOfInfiltrators ? (
|
||||
<>
|
||||
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
can trade the confidential information you found for money or reputation.
|
||||
</Typography>
|
||||
<Box sx={{ width: "fit-content" }}>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Select value={faction} onChange={changeDropdown} sx={{ mr: 1 }}>
|
||||
<MenuItem key={"none"} value={"none"}>
|
||||
{"none"}
|
||||
</MenuItem>
|
||||
@ -98,17 +92,15 @@ export function Victory(props: IProps): React.ReactElement {
|
||||
<Button onClick={trade}>
|
||||
Trade for <Reputation reputation={repGain} /> reputation
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button onClick={sell}>
|
||||
Sell for
|
||||
<Money money={moneyGain} />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button onClick={quitInfiltration}>Quit</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
</Box>
|
||||
<Button onClick={sell} sx={{ width: "100%" }}>
|
||||
Sell for
|
||||
<Money money={moneyGain} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Button onClick={quitInfiltration} sx={{ width: "100%", mt: 1 }}>
|
||||
Quit
|
||||
</Button>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Box, Paper, Typography } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { random } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { random } from "../utils";
|
||||
import { interpolate } from "./Difficulty";
|
||||
import { GameTimer } from "./GameTimer";
|
||||
import { IMinigameProps } from "./IMinigameProps";
|
||||
import { KeyHandler } from "./KeyHandler";
|
||||
|
||||
interface Difficulty {
|
||||
[key: string]: number;
|
||||
@ -102,46 +101,53 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">Cut the wires with the following properties! (keyboard 1 to 9)</Typography>
|
||||
<Paper sx={{ display: "grid", justifyItems: "center", pb: 1 }}>
|
||||
<Typography variant="h4" sx={{ width: "75%", textAlign: "center" }}>
|
||||
Cut the wires with the following properties! (keyboard 1 to 9)
|
||||
</Typography>
|
||||
{questions.map((question, i) => (
|
||||
<Typography key={i}>{question.toString()}</Typography>
|
||||
))}
|
||||
<Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: `repeat(${wires.length}, 1fr)`,
|
||||
columnGap: 3,
|
||||
justifyItems: "center",
|
||||
}}
|
||||
>
|
||||
{new Array(wires.length).fill(0).map((_, i) => {
|
||||
const isCorrectWire = checkWire(i + 1);
|
||||
const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
|
||||
return (
|
||||
<span key={i} style={{ color: color }}>
|
||||
{i + 1}
|
||||
</span>
|
||||
<Typography key={i} style={{ color: color }}>
|
||||
{i + 1}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</Typography>
|
||||
{new Array(8).fill(0).map((_, i) => (
|
||||
<div key={i}>
|
||||
<Typography>
|
||||
{new Array(8).fill(0).map((_, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{wires.map((wire, j) => {
|
||||
if ((i === 3 || i === 4) && cutWires[j]) {
|
||||
return <span key={j}> </span>;
|
||||
return <Typography key={j}></Typography>;
|
||||
}
|
||||
const isCorrectWire = checkWire(j + 1);
|
||||
const wireColor =
|
||||
hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length];
|
||||
return (
|
||||
<span key={j} style={{ color: wireColor }}>
|
||||
|{wire.tpe}|
|
||||
</span>
|
||||
<Typography key={j} style={{ color: wireColor }}>
|
||||
|{wire.tpe}|
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
<KeyHandler onKeyDown={press} onFailure={props.onFailure} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { ITutorialEvents } from "./ui/InteractiveTutorial/ITutorialEvents";
|
||||
// Ordered array of keys to Interactive Tutorial Steps
|
||||
enum iTutorialSteps {
|
||||
Start,
|
||||
NSSelection,
|
||||
GoToCharacterPage, // Click on 'Stats' page
|
||||
CharacterPage, // Introduction to 'Stats' page
|
||||
CharacterGoToTerminalPage, // Go back to Terminal
|
||||
@ -43,6 +44,7 @@ const ITutorial: {
|
||||
isRunning: boolean;
|
||||
stepIsDone: {
|
||||
[iTutorialSteps.Start]: boolean;
|
||||
[iTutorialSteps.NSSelection]: boolean;
|
||||
[iTutorialSteps.GoToCharacterPage]: boolean;
|
||||
[iTutorialSteps.CharacterPage]: boolean;
|
||||
[iTutorialSteps.CharacterGoToTerminalPage]: boolean;
|
||||
@ -80,6 +82,7 @@ const ITutorial: {
|
||||
// Keeps track of whether each step has been done
|
||||
stepIsDone: {
|
||||
[iTutorialSteps.Start]: false,
|
||||
[iTutorialSteps.NSSelection]: false,
|
||||
[iTutorialSteps.GoToCharacterPage]: false,
|
||||
[iTutorialSteps.CharacterPage]: false,
|
||||
[iTutorialSteps.CharacterGoToTerminalPage]: false,
|
||||
|
@ -156,6 +156,7 @@ const singularity: IMap<any> = {
|
||||
getUpgradeHomeCoresCost: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 2),
|
||||
workForCompany: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
|
||||
applyToCompany: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
|
||||
quitJob: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost),
|
||||
getCompanyRep: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 3),
|
||||
getCompanyFavor: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 3),
|
||||
getCompanyFavorGain: SF4Cost(RamCostConstants.ScriptSingularityFn2RamCost / 4),
|
||||
|
@ -1233,16 +1233,21 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
killall: function (_hostname: unknown = workerScript.hostname): boolean {
|
||||
killall: function (_hostname: unknown = workerScript.hostname, _safetyguard: unknown = true): boolean {
|
||||
updateDynamicRam("killall", getRamCost(Player, "killall"));
|
||||
const hostname = helper.string("killall", "hostname", _hostname);
|
||||
const safetyguard = helper.boolean(_safetyguard);
|
||||
if (hostname === undefined) {
|
||||
throw makeRuntimeErrorMsg("killall", "Takes 1 argument");
|
||||
throw makeRuntimeErrorMsg("killall", "Usage: killall(hostname, [safetyguard boolean])");
|
||||
}
|
||||
const server = safeGetServer(hostname, "killall");
|
||||
const scriptsRunning = server.runningScripts.length > 0;
|
||||
|
||||
let scriptsKilled = 0;
|
||||
|
||||
for (let i = server.runningScripts.length - 1; i >= 0; --i) {
|
||||
if (safetyguard === true && server.runningScripts[i].pid == workerScript.pid) continue;
|
||||
killWorkerScript(server.runningScripts[i], server.hostname, false);
|
||||
++scriptsKilled;
|
||||
}
|
||||
WorkerScriptStartStopEventEmitter.emit();
|
||||
workerScript.log(
|
||||
@ -1250,7 +1255,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
() => `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`,
|
||||
);
|
||||
|
||||
return scriptsRunning;
|
||||
return scriptsKilled > 0;
|
||||
},
|
||||
exit: function (): void {
|
||||
updateDynamicRam("exit", getRamCost(Player, "exit"));
|
||||
|
@ -295,6 +295,7 @@ export function NetscriptCorporation(
|
||||
if (office === 0) continue;
|
||||
cities.push(office.loc);
|
||||
}
|
||||
|
||||
return {
|
||||
name: division.name,
|
||||
type: division.type,
|
||||
@ -309,6 +310,7 @@ export function NetscriptCorporation(
|
||||
upgrades: division.upgrades.slice(),
|
||||
cities: cities,
|
||||
products: division.products === undefined ? [] : Object.keys(division.products),
|
||||
makesProducts: division.makesProducts,
|
||||
};
|
||||
}
|
||||
|
||||
@ -359,6 +361,7 @@ export function NetscriptCorporation(
|
||||
const corporation = getCorporation();
|
||||
return {
|
||||
cost: material.bCost,
|
||||
sCost: material.sCost,
|
||||
name: material.name,
|
||||
qty: material.qty,
|
||||
qlt: material.qlt,
|
||||
|
@ -342,7 +342,7 @@ export function NetscriptGang(player: IPlayer, workerScript: WorkerScript, helpe
|
||||
checkGangApiAccess("getBonusTime");
|
||||
const gang = player.gang;
|
||||
if (gang === null) throw new Error("Should not be called without Gang");
|
||||
return Math.round(gang.storedCycles / 5);
|
||||
return Math.round(gang.storedCycles / 5) * 1000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
|
||||
import { hasAugmentationPrereqs } from "../Faction/FactionHelpers";
|
||||
import { CityName } from "../Locations/data/CityNames";
|
||||
import { getRamCost } from "../Netscript/RamCostGenerator";
|
||||
@ -28,10 +28,10 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
|
||||
updateRam("getAugmentationGraftPrice");
|
||||
const augName = helper.string("getAugmentationGraftPrice", "augName", _augName);
|
||||
checkGraftingAPIAccess("getAugmentationGraftPrice");
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
|
||||
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftPrice", `Invalid aug: ${augName}`);
|
||||
}
|
||||
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
|
||||
return graftableAug.cost;
|
||||
},
|
||||
|
||||
@ -39,10 +39,10 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
|
||||
updateRam("getAugmentationGraftTime");
|
||||
const augName = helper.string("getAugmentationGraftTime", "augName", _augName);
|
||||
checkGraftingAPIAccess("getAugmentationGraftTime");
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
|
||||
throw helper.makeRuntimeErrorMsg("grafting.getAugmentationGraftTime", `Invalid aug: ${augName}`);
|
||||
}
|
||||
const graftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
const graftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
|
||||
return calculateGraftingTimeWithBonus(player, graftableAug);
|
||||
},
|
||||
|
||||
@ -64,7 +64,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
|
||||
"You must be in New Tokyo to begin grafting an Augmentation.",
|
||||
);
|
||||
}
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !Augmentations.hasOwnProperty(augName)) {
|
||||
if (!getGraftingAvailableAugs(player).includes(augName) || !StaticAugmentations.hasOwnProperty(augName)) {
|
||||
workerScript.log("grafting.graftAugmentation", () => `Invalid aug: ${augName}`);
|
||||
return false;
|
||||
}
|
||||
@ -75,7 +75,7 @@ export function NetscriptGrafting(player: IPlayer, workerScript: WorkerScript, h
|
||||
workerScript.log("graftAugmentation", () => txt);
|
||||
}
|
||||
|
||||
const craftableAug = new GraftableAugmentation(Augmentations[augName]);
|
||||
const craftableAug = new GraftableAugmentation(StaticAugmentations[augName]);
|
||||
if (player.money < craftableAug.cost) {
|
||||
workerScript.log("grafting.graftAugmentation", () => `You don't have enough money to craft ${augName}`);
|
||||
return false;
|
||||
|
@ -3,7 +3,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { purchaseAugmentation, joinFaction, getFactionAugmentationsFiltered } from "../Faction/FactionHelpers";
|
||||
import { startWorkerScript } from "../NetscriptWorker";
|
||||
import { Augmentation } from "../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
|
||||
import { augmentationExists, installAugmentations } from "../Augmentation/AugmentationHelpers";
|
||||
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
|
||||
import { killWorkerScript } from "../Netscript/killWorkerScript";
|
||||
@ -56,7 +56,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
throw _ctx.helper.makeRuntimeErrorMsg(`Invalid augmentation: '${name}'`);
|
||||
}
|
||||
|
||||
return Augmentations[name];
|
||||
return StaticAugmentations[name];
|
||||
};
|
||||
|
||||
const getFaction = function (_ctx: NetscriptContext, name: string): Faction {
|
||||
@ -122,7 +122,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
_ctx.helper.checkSingularityAccess();
|
||||
const augName = _ctx.helper.string("augName", _augName);
|
||||
const aug = getAugmentation(_ctx, augName);
|
||||
return [aug.baseRepRequirement, aug.baseCost];
|
||||
return [aug.getCost(player).moneyCost, aug.getCost(player).repCost];
|
||||
},
|
||||
getAugmentationPrereq: (_ctx: NetscriptContext) =>
|
||||
function (_augName: unknown): string[] {
|
||||
@ -136,14 +136,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
_ctx.helper.checkSingularityAccess();
|
||||
const augName = _ctx.helper.string("augName", _augName);
|
||||
const aug = getAugmentation(_ctx, augName);
|
||||
return aug.baseCost;
|
||||
return aug.getCost(player).moneyCost;
|
||||
},
|
||||
getAugmentationRepReq: (_ctx: NetscriptContext) =>
|
||||
function (_augName: unknown): number {
|
||||
_ctx.helper.checkSingularityAccess();
|
||||
const augName = _ctx.helper.string("augName", _augName);
|
||||
const aug = getAugmentation(_ctx, augName);
|
||||
return aug.baseRepRequirement;
|
||||
return aug.getCost(player).repCost;
|
||||
},
|
||||
getAugmentationStats: (_ctx: NetscriptContext) =>
|
||||
function (_augName: unknown): AugmentationStats {
|
||||
@ -183,7 +183,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
}
|
||||
}
|
||||
|
||||
if (fac.playerReputation < aug.baseRepRequirement) {
|
||||
if (fac.playerReputation < aug.getCost(player).repCost) {
|
||||
_ctx.log(() => `You do not have enough reputation with '${fac.name}'.`);
|
||||
return false;
|
||||
}
|
||||
@ -947,6 +947,12 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
}
|
||||
return res;
|
||||
},
|
||||
quitJob: (_ctx: NetscriptContext) =>
|
||||
function (_companyName: unknown): void {
|
||||
_ctx.helper.checkSingularityAccess();
|
||||
const companyName = _ctx.helper.string("companyName", _companyName);
|
||||
player.quitJob(companyName);
|
||||
},
|
||||
getCompanyRep: (_ctx: NetscriptContext) =>
|
||||
function (_companyName: unknown): number {
|
||||
_ctx.helper.checkSingularityAccess();
|
||||
@ -1079,7 +1085,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
||||
_ctx.log(() => `Invalid work type: '${type}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getFactionRep: (_ctx: NetscriptContext) =>
|
||||
function (_facName: unknown): number {
|
||||
|
@ -5,7 +5,7 @@ import { FactionWorkType } from "../Faction/FactionWorkTypeEnum";
|
||||
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
|
||||
import { CityName } from "../Locations/data/CityNames";
|
||||
import { findCrime } from "../Crime/CrimeHelpers";
|
||||
|
||||
@ -286,7 +286,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
|
||||
const aug = purchasableAugs[i];
|
||||
augs.push({
|
||||
name: aug.name,
|
||||
cost: aug.startingCost,
|
||||
cost: aug.baseCost,
|
||||
});
|
||||
}
|
||||
|
||||
@ -303,7 +303,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel
|
||||
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`);
|
||||
}
|
||||
|
||||
const aug = Augmentations[augName];
|
||||
const aug = StaticAugmentations[augName];
|
||||
if (!aug) {
|
||||
throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export class GraftableAugmentation {
|
||||
}
|
||||
|
||||
get cost(): number {
|
||||
return this.augmentation.startingCost * CONSTANTS.AugmentationGraftingCostMult;
|
||||
return this.augmentation.baseCost * CONSTANTS.AugmentationGraftingCostMult;
|
||||
}
|
||||
|
||||
get time(): number {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
|
||||
import { GraftableAugmentation } from "./GraftableAugmentation";
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
export const getGraftingAvailableAugs = (player: IPlayer): string[] => {
|
||||
const augs: string[] = [];
|
||||
|
||||
for (const [augName, aug] of Object.entries(Augmentations)) {
|
||||
for (const [augName, aug] of Object.entries(StaticAugmentations)) {
|
||||
if (aug.isSpecial) continue;
|
||||
augs.push(augName);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Construction, CheckBox, CheckBoxOutlineBlank } from "@mui/icons-materia
|
||||
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Augmentation } from "../../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
|
||||
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
|
||||
import { CONSTANTS } from "../../../Constants";
|
||||
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
|
||||
@ -54,7 +54,7 @@ export const GraftingRoot = (): React.ReactElement => {
|
||||
const player = use.Player();
|
||||
const router = use.Router();
|
||||
|
||||
for (const aug of Object.values(Augmentations)) {
|
||||
for (const aug of Object.values(StaticAugmentations)) {
|
||||
const name = aug.name;
|
||||
const graftableAug = new GraftableAugmentation(aug);
|
||||
GraftableAugmentations[name] = graftableAug;
|
||||
@ -62,6 +62,7 @@ export const GraftingRoot = (): React.ReactElement => {
|
||||
|
||||
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
|
||||
const [graftOpen, setGraftOpen] = useState(false);
|
||||
const selectedAugmentation = StaticAugmentations[selectedAug];
|
||||
|
||||
const setRerender = useState(false)[1];
|
||||
function rerender(): void {
|
||||
@ -148,22 +149,26 @@ export const GraftingRoot = (): React.ReactElement => {
|
||||
{/* Use formula so the displayed creation time is accurate to player bonus */}
|
||||
</Typography>
|
||||
|
||||
{Augmentations[selectedAug].prereqs.length > 0 && (
|
||||
<AugPreReqsChecklist player={player} aug={Augmentations[selectedAug]} />
|
||||
{selectedAugmentation.prereqs.length > 0 && (
|
||||
<AugPreReqsChecklist player={player} aug={selectedAugmentation} />
|
||||
)}
|
||||
|
||||
<br />
|
||||
|
||||
<Typography>
|
||||
{(() => {
|
||||
const aug = Augmentations[selectedAug];
|
||||
|
||||
const info = typeof aug.info === "string" ? <span>{aug.info}</span> : aug.info;
|
||||
const info =
|
||||
typeof selectedAugmentation.info === "string" ? (
|
||||
<span>{selectedAugmentation.info}</span>
|
||||
) : (
|
||||
selectedAugmentation.info
|
||||
);
|
||||
const tooltip = (
|
||||
<>
|
||||
{info}
|
||||
<br />
|
||||
<br />
|
||||
{aug.stats}
|
||||
{selectedAugmentation.stats}
|
||||
</>
|
||||
);
|
||||
return tooltip;
|
||||
|
@ -248,7 +248,7 @@ export interface IPlayer {
|
||||
queryStatFromString(str: string): number;
|
||||
getIntelligenceBonus(weight: number): number;
|
||||
getCasinoWinnings(): number;
|
||||
quitJob(company: string): void;
|
||||
quitJob(company: string, sing?: boolean): void;
|
||||
hasJob(): boolean;
|
||||
createHacknetServer(): HacknetServer;
|
||||
startCreateProgramWork(programName: string, time: number, reqLevel: number): void;
|
||||
@ -291,6 +291,6 @@ export interface IPlayer {
|
||||
sourceFileLvl(n: number): number;
|
||||
startGraftAugmentationWork(augmentationName: string, time: number): void;
|
||||
graftAugmentationWork(numCycles: number): boolean;
|
||||
finishGraftAugmentationWork(cancelled: boolean): string;
|
||||
finishGraftAugmentationWork(cancelled: boolean, singularity?: boolean): string;
|
||||
applyEntropy(stacks?: number): void;
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ export class PlayerObject implements IPlayer {
|
||||
queryStatFromString: (str: string) => number;
|
||||
getIntelligenceBonus: (weight: number) => number;
|
||||
getCasinoWinnings: () => number;
|
||||
quitJob: (company: string) => void;
|
||||
quitJob: (company: string, sing?: boolean) => void;
|
||||
hasJob: () => boolean;
|
||||
process: (router: IRouter, numCycles?: number) => void;
|
||||
createHacknetServer: () => HacknetServer;
|
||||
@ -302,7 +302,7 @@ export class PlayerObject implements IPlayer {
|
||||
sourceFileLvl: (n: number) => number;
|
||||
startGraftAugmentationWork: (augmentationName: string, time: number) => void;
|
||||
graftAugmentationWork: (numCycles: number) => boolean;
|
||||
finishGraftAugmentationWork: (cancelled: boolean) => string;
|
||||
finishGraftAugmentationWork: (cancelled: boolean, singularity?: boolean) => string;
|
||||
applyEntropy: (stacks?: number) => void;
|
||||
|
||||
constructor() {
|
||||
@ -463,12 +463,12 @@ export class PlayerObject implements IPlayer {
|
||||
this.bladeburner = null;
|
||||
this.bladeburner_max_stamina_mult = 1;
|
||||
this.bladeburner_stamina_gain_mult = 1;
|
||||
this.bladeburner_analysis_mult = 1; //Field Analysis Onl;
|
||||
this.bladeburner_analysis_mult = 1; //Field Analysis Only
|
||||
this.bladeburner_success_chance_mult = 1;
|
||||
|
||||
// Sleeves & Re-sleeving
|
||||
this.sleeves = [];
|
||||
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;
|
||||
this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant
|
||||
//bitnode
|
||||
this.bitNodeN = 1;
|
||||
|
||||
@ -483,8 +483,8 @@ export class PlayerObject implements IPlayer {
|
||||
this.playtimeSinceLastBitnode = 0;
|
||||
|
||||
// Keep track of where money comes from
|
||||
this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentatio;
|
||||
this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode ru;
|
||||
this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation
|
||||
this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run
|
||||
// Production since last Augmentation installation
|
||||
this.scriptProdSinceLastAug = 0;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { IPlayer } from "../IPlayer";
|
||||
import { PlayerObject } from "./PlayerObject";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
||||
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
@ -1364,10 +1363,10 @@ export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean
|
||||
return false;
|
||||
}
|
||||
|
||||
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean): string {
|
||||
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean, singularity = false): string {
|
||||
const augName = this.graftAugmentationName;
|
||||
if (cancelled === false) {
|
||||
applyAugmentation(Augmentations[augName]);
|
||||
applyAugmentation({ name: augName, level: 1 });
|
||||
|
||||
if (!this.hasAugmentation(AugmentationNames.CongruityImplant)) {
|
||||
this.entropy += 1;
|
||||
@ -1378,7 +1377,7 @@ export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean):
|
||||
`You've finished grafting ${augName}.<br>The augmentation has been applied to your body` +
|
||||
(this.hasAugmentation(AugmentationNames.CongruityImplant) ? "." : ", but you feel a bit off."),
|
||||
);
|
||||
} else {
|
||||
} else if (cancelled && singularity === false) {
|
||||
dialogBoxCreate(`You cancelled the grafting of ${augName}.<br>Your money was not returned to you.`);
|
||||
}
|
||||
|
||||
@ -1700,6 +1699,9 @@ export function singularityStopWork(this: IPlayer): string {
|
||||
case CONSTANTS.WorkTypeCrime:
|
||||
res = this.finishCrime(true);
|
||||
break;
|
||||
case CONSTANTS.WorkTypeGraftAugmentation:
|
||||
res = this.finishGraftAugmentationWork(true, true);
|
||||
break;
|
||||
default:
|
||||
console.error(`Unrecognized work type (${this.workType})`);
|
||||
return "";
|
||||
@ -1748,14 +1750,6 @@ export function hospitalize(this: IPlayer): number {
|
||||
//The 'sing' argument designates whether or not this is being called from
|
||||
//the applyToCompany() Netscript Singularity function
|
||||
export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing = false): boolean {
|
||||
// Get current company and job
|
||||
let currCompany = null;
|
||||
if (this.companyName !== "") {
|
||||
currCompany = Companies[this.companyName];
|
||||
}
|
||||
const currPositionName = this.jobs[this.companyName];
|
||||
|
||||
// Get company that's being applied to
|
||||
const company = Companies[this.location]; //Company being applied to
|
||||
if (!(company instanceof Company)) {
|
||||
console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`);
|
||||
@ -1765,66 +1759,44 @@ export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing =
|
||||
let pos = entryPosType;
|
||||
|
||||
if (!this.isQualified(company, pos)) {
|
||||
const reqText = getJobRequirementText(company, pos);
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Unfortunately, you do not qualify for this position<br>" + reqText);
|
||||
dialogBoxCreate("Unfortunately, you do not qualify for this position<br>" + getJobRequirementText(company, pos));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if this company has the position
|
||||
if (!company.hasPosition(pos)) {
|
||||
console.error(`Company ${company.name} does not have position ${pos}. Player.applyToCompany() failed`);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const newPos = getNextCompanyPositionHelper(pos);
|
||||
if (newPos == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
//Check if this company has this position
|
||||
if (company.hasPosition(newPos)) {
|
||||
if (!this.isQualified(company, newPos)) {
|
||||
//If player not qualified for next job, break loop so player will be given current job
|
||||
break;
|
||||
}
|
||||
pos = newPos;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
const nextPos = getNextCompanyPositionHelper(pos);
|
||||
if (nextPos == null) break;
|
||||
if (company.hasPosition(nextPos) && this.isQualified(company, nextPos)) {
|
||||
pos = nextPos;
|
||||
} else break;
|
||||
}
|
||||
|
||||
//Check if the determined job is the same as the player's current job
|
||||
if (currCompany != null) {
|
||||
if (currCompany.name == company.name && pos.name == currPositionName) {
|
||||
//Check if player already has the assigned job
|
||||
if (this.jobs[company.name] === pos.name) {
|
||||
if (!sing) {
|
||||
const nextPos = getNextCompanyPositionHelper(pos);
|
||||
if (nextPos == null) {
|
||||
if (!sing) {
|
||||
dialogBoxCreate("You are already at the highest position for your field! No promotion available");
|
||||
}
|
||||
return false;
|
||||
} else if (company.hasPosition(nextPos)) {
|
||||
if (!sing) {
|
||||
const reqText = getJobRequirementText(company, nextPos);
|
||||
dialogBoxCreate("Unfortunately, you do not qualify for a promotion<br>" + reqText);
|
||||
}
|
||||
return false;
|
||||
if (nextPos == null || !company.hasPosition(nextPos)) {
|
||||
dialogBoxCreate("You are already at the highest position for your field! No promotion available");
|
||||
} else {
|
||||
if (!sing) {
|
||||
dialogBoxCreate("You are already at the highest position for your field! No promotion available");
|
||||
}
|
||||
return false;
|
||||
const reqText = getJobRequirementText(company, nextPos);
|
||||
dialogBoxCreate("Unfortunately, you do not qualify for a promotion<br>" + reqText);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.jobs[company.name] = pos.name;
|
||||
if (!this.focus && this.isWorking && this.companyName !== this.location) this.resetWorkStatus();
|
||||
this.companyName = this.location;
|
||||
if (!this.isWorking || !this.workType.includes("Working for Company")) this.companyName = company.name;
|
||||
|
||||
if (!sing) {
|
||||
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
|
||||
dialogBoxCreate("Congratulations! You were offered a new job at " + company.name + " as a " + pos.name + "!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1869,7 +1841,7 @@ export function getNextCompanyPosition(
|
||||
return entryPosType;
|
||||
}
|
||||
|
||||
export function quitJob(this: IPlayer, company: string): void {
|
||||
export function quitJob(this: IPlayer, company: string, _sing = false): void {
|
||||
if (this.isWorking == true && this.workType.includes("Working for Company") && this.companyName == company) {
|
||||
this.finishWork(true);
|
||||
}
|
||||
|
@ -686,7 +686,7 @@ export class Sleeve extends Person {
|
||||
}
|
||||
|
||||
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
|
||||
if (!p.canAfford(aug.startingCost)) {
|
||||
if (!p.canAfford(aug.baseCost)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -695,7 +695,7 @@ export class Sleeve extends Person {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.loseMoney(aug.startingCost, "sleeves");
|
||||
p.loseMoney(aug.baseCost, "sleeves");
|
||||
this.installAugmentation(aug);
|
||||
return true;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Sleeve } from "./Sleeve";
|
||||
import { IPlayer } from "../IPlayer";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
|
||||
@ -64,13 +64,13 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
|
||||
if (p.inGang()) {
|
||||
const fac = p.getGangFaction();
|
||||
|
||||
for (const augName of Object.keys(Augmentations)) {
|
||||
const aug = Augmentations[augName];
|
||||
for (const augName of Object.keys(StaticAugmentations)) {
|
||||
const aug = StaticAugmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fac.playerReputation > aug.baseRepRequirement) {
|
||||
if (fac.playerReputation > aug.getCost(p).repCost) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
@ -89,12 +89,12 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
|
||||
}
|
||||
|
||||
for (const augName of fac.augmentations) {
|
||||
const aug: Augmentation = Augmentations[augName];
|
||||
const aug: Augmentation = StaticAugmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fac.playerReputation > aug.baseRepRequirement) {
|
||||
if (fac.playerReputation > aug.getCost(p).repCost) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export function SleeveAugmentationsModal(props: IProps): React.ReactElement {
|
||||
ownedAugNames={ownedAugNames}
|
||||
player={player}
|
||||
canPurchase={(player, aug) => {
|
||||
return player.money > aug.startingCost;
|
||||
return player.money > aug.baseCost;
|
||||
}}
|
||||
purchaseAugmentation={(player, aug, _showModal) => {
|
||||
props.sleeve.tryBuyAugmentation(player, aug);
|
||||
|
@ -77,7 +77,9 @@ function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
|
||||
}
|
||||
|
||||
return factions.filter((faction) => {
|
||||
const facInfo = Factions[faction].getInfo();
|
||||
const factionObj = Factions[faction];
|
||||
if (!factionObj) return false;
|
||||
const facInfo = factionObj.getInfo();
|
||||
return facInfo.offerHackingWork || facInfo.offerFieldWork || facInfo.offerSecurityWork;
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FactionNames } from "./Faction/data/FactionNames";
|
||||
import { CityName } from "./Locations/data/CityNames";
|
||||
import { Augmentations } from "./Augmentation/Augmentations";
|
||||
import { StaticAugmentations } from "./Augmentation/StaticAugmentations";
|
||||
import { augmentationExists, initAugmentations } from "./Augmentation/AugmentationHelpers";
|
||||
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
||||
import { initBitNodeMultipliers } from "./BitNode/BitNode";
|
||||
@ -225,9 +225,9 @@ export function prestigeSourceFile(flume: boolean): void {
|
||||
}
|
||||
|
||||
// Delete all Augmentations
|
||||
for (const name of Object.keys(Augmentations)) {
|
||||
if (Augmentations.hasOwnProperty(name)) {
|
||||
delete Augmentations[name];
|
||||
for (const name of Object.keys(StaticAugmentations)) {
|
||||
if (StaticAugmentations.hasOwnProperty(name)) {
|
||||
delete StaticAugmentations[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
42
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
42
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -1800,6 +1800,18 @@ export interface Singularity {
|
||||
*/
|
||||
workForCompany(companyName?: string, focus?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Quit jobs by company.
|
||||
* @remarks
|
||||
* RAM cost: 3 GB * 16/4/1
|
||||
*
|
||||
*
|
||||
* This function will finish work with the company provided and quit any jobs.
|
||||
*
|
||||
* @param companyName - Name of the company.
|
||||
*/
|
||||
quitJob(companyName?: string): void;
|
||||
|
||||
/**
|
||||
* Apply for a job at a company.
|
||||
* @remarks
|
||||
@ -2340,13 +2352,13 @@ export interface Singularity {
|
||||
* @example
|
||||
* ```ts
|
||||
* // NS1
|
||||
* getDarkwebProgramsAvailable();
|
||||
* getDarkwebPrograms();
|
||||
* // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
|
||||
* ```
|
||||
* @example
|
||||
* ```ts
|
||||
* // NS2
|
||||
* ns.getDarkwebProgramsAvailable();
|
||||
* ns.getDarkwebPrograms();
|
||||
* // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc]
|
||||
* ```
|
||||
* @returns - a list of programs available for purchase on the dark web, or [] if Tor has not
|
||||
@ -5311,9 +5323,10 @@ export interface NS {
|
||||
* If no host is defined, it will kill all scripts, where the script is running.
|
||||
*
|
||||
* @param host - IP or hostname of the server on which to kill all scripts.
|
||||
* @param safetyguard - Skips the script that calls this function
|
||||
* @returns True if any scripts were killed, and false otherwise.
|
||||
*/
|
||||
killall(host?: string): boolean;
|
||||
killall(host?: string, safetyguard?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Terminates the current script immediately.
|
||||
@ -6126,7 +6139,7 @@ export interface NS {
|
||||
* Returns 0 if the script does not exist.
|
||||
*
|
||||
* @param script - Filename of script. This is case-sensitive.
|
||||
* @param host - Host of target server the script is located on. This is optional, If it is not specified then the function will se the current server as the target server.
|
||||
* @param host - Host of target server the script is located on. This is optional, if it is not specified then the function will use the current server as the target server.
|
||||
* @returns Amount of RAM (in GB) required to run the specified script on the target server, and 0 if the script does not exist.
|
||||
*/
|
||||
getScriptRam(script: string, host?: string): number;
|
||||
@ -7044,22 +7057,27 @@ interface CorporationInfo {
|
||||
interface Employee {
|
||||
/** Name of the employee */
|
||||
name: string;
|
||||
/** Morale */
|
||||
/** Morale of the employee */
|
||||
mor: number;
|
||||
/** Happiness */
|
||||
/** Happiness of the employee */
|
||||
hap: number;
|
||||
/** Energy */
|
||||
/** Energy of the employee */
|
||||
ene: number;
|
||||
/** Intelligence of the employee */
|
||||
int: number;
|
||||
/** Charisma of the employee */
|
||||
cha: number;
|
||||
/** Experience of the employee */
|
||||
exp: number;
|
||||
/** Creativity of the employee */
|
||||
cre: number;
|
||||
/** Efficiency of the employee */
|
||||
eff: number;
|
||||
/** Salary */
|
||||
/** Salary of the employee */
|
||||
sal: number;
|
||||
/** City */
|
||||
/** Current Location (city) */
|
||||
loc: string;
|
||||
/** Current job */
|
||||
/** Current job position */
|
||||
pos: string;
|
||||
}
|
||||
|
||||
@ -7111,6 +7129,8 @@ interface Material {
|
||||
sell: number;
|
||||
/** cost to buy material */
|
||||
cost: number;
|
||||
/** Sell cost, can be "MP+5" */
|
||||
sCost: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -7202,6 +7222,8 @@ interface Division {
|
||||
cities: string[];
|
||||
/** Products developed by this division */
|
||||
products: string[];
|
||||
/** Whether the industry this division is in is capable of making products */
|
||||
makesProducts: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -544,11 +544,14 @@ export function Root(props: IProps): React.ReactElement {
|
||||
// this is duplicate code with saving later.
|
||||
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
|
||||
//Make sure filename + code properly follow tutorial
|
||||
if (currentScript.fileName !== "n00dles.script") {
|
||||
dialogBoxCreate("Leave the script name as 'n00dles.script'!");
|
||||
if (currentScript.fileName !== "n00dles.script" && currentScript.fileName !== "n00dles.js") {
|
||||
dialogBoxCreate("Don't change the script name for now.");
|
||||
return;
|
||||
}
|
||||
if (currentScript.code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
|
||||
const cleanCode = currentScript.code.replace(/\s/g, "");
|
||||
const ns1 = "while(true){hack('n00dles');}";
|
||||
const ns2 = `exportasyncfunctionmain(ns){while(true){awaitns.hack('n00dles');}}`;
|
||||
if (cleanCode.indexOf(ns1) == -1 && cleanCode.indexOf(ns2) == -1) {
|
||||
dialogBoxCreate("Please copy and paste the code from the tutorial!");
|
||||
return;
|
||||
}
|
||||
@ -692,7 +695,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
const serverScriptIndex = server.scripts.findIndex((script) => script.filename === closingScript.fileName);
|
||||
if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) {
|
||||
PromptEvent.emit({
|
||||
txt: "Do you want to save changes to " + closingScript.fileName + "?",
|
||||
txt: `Do you want to save changes to ${closingScript.fileName} on ${closingScript.hostname}?`,
|
||||
resolve: (result: boolean | string) => {
|
||||
if (result) {
|
||||
// Save changes
|
||||
@ -855,22 +858,31 @@ export function Root(props: IProps): React.ReactElement {
|
||||
)}
|
||||
</Tooltip>
|
||||
{filteredOpenScripts.map(({ fileName, hostname }, index) => {
|
||||
const editingCurrentScript =
|
||||
currentScript?.fileName === filteredOpenScripts[index].fileName &&
|
||||
currentScript?.hostname === filteredOpenScripts[index].hostname;
|
||||
const externalScript = hostname !== "home";
|
||||
const colorProps = editingCurrentScript
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
color: Settings.theme.primary,
|
||||
}
|
||||
: {
|
||||
background: Settings.theme.backgroundsecondary,
|
||||
borderColor: Settings.theme.backgroundsecondary,
|
||||
color: Settings.theme.secondary,
|
||||
};
|
||||
|
||||
if (externalScript) {
|
||||
colorProps.color = Settings.theme.info;
|
||||
}
|
||||
const iconButtonStyle = {
|
||||
maxWidth: `${tabIconWidth}px`,
|
||||
minWidth: `${tabIconWidth}px`,
|
||||
minHeight: "38.5px",
|
||||
maxHeight: "38.5px",
|
||||
...(currentScript?.fileName === filteredOpenScripts[index].fileName
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
color: Settings.theme.primary,
|
||||
}
|
||||
: {
|
||||
background: Settings.theme.backgroundsecondary,
|
||||
borderColor: Settings.theme.backgroundsecondary,
|
||||
color: Settings.theme.secondary,
|
||||
}),
|
||||
...colorProps,
|
||||
};
|
||||
|
||||
const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`;
|
||||
@ -905,17 +917,7 @@ export function Root(props: IProps): React.ReactElement {
|
||||
maxWidth: `${tabTextWidth}px`,
|
||||
minHeight: "38.5px",
|
||||
overflow: "hidden",
|
||||
...(currentScript?.fileName === filteredOpenScripts[index].fileName
|
||||
? {
|
||||
background: Settings.theme.button,
|
||||
borderColor: Settings.theme.button,
|
||||
color: Settings.theme.primary,
|
||||
}
|
||||
: {
|
||||
background: Settings.theme.backgroundsecondary,
|
||||
borderColor: Settings.theme.backgroundsecondary,
|
||||
color: Settings.theme.secondary,
|
||||
}),
|
||||
...colorProps,
|
||||
}}
|
||||
>
|
||||
<span style={{ overflow: "hidden", direction: "rtl", textOverflow: "ellipsis" }}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { KEYCODE } from "../../utils/helpers/keyCodes";
|
||||
import clsx from "clsx";
|
||||
import { styled, Theme, CSSObject } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
@ -275,57 +275,53 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
function handleShortcuts(this: Document, event: KeyboardEvent): any {
|
||||
if (Settings.DisableHotkeys) return;
|
||||
if ((props.player.isWorking && props.player.focus) || props.router.page() === Page.BitVerse) return;
|
||||
if (event.key === KEY.T && event.altKey) {
|
||||
if (event.code === KEYCODE.T && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickTerminal();
|
||||
} else if (event.key === KEY.C && event.altKey) {
|
||||
} else if (event.code === KEYCODE.C && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickStats();
|
||||
} else if (event.key === KEY.E && event.altKey) {
|
||||
} else if (event.code === KEYCODE.E && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickScriptEditor();
|
||||
} else if (event.key === KEY.S && event.altKey) {
|
||||
} else if (event.code === KEYCODE.S && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickActiveScripts();
|
||||
} else if (event.key === KEY.H && event.altKey) {
|
||||
} else if (event.code === KEYCODE.H && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickHacknet();
|
||||
} else if (event.key === KEY.W && event.altKey) {
|
||||
} else if (event.code === KEYCODE.W && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickCity();
|
||||
} else if (event.key === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey && canJob) {
|
||||
} else if (event.code === KEYCODE.J && event.altKey && !event.ctrlKey && !event.metaKey && canJob) {
|
||||
// ctrl/cmd + alt + j is shortcut to open Chrome dev tools
|
||||
event.preventDefault();
|
||||
clickJob();
|
||||
} else if (event.key === KEY.R && event.altKey) {
|
||||
} else if (event.code === KEYCODE.R && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickTravel();
|
||||
} else if (event.key === KEY.P && event.altKey) {
|
||||
} else if (event.code === KEYCODE.P && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickCreateProgram();
|
||||
} else if (event.key === KEY.F && event.altKey) {
|
||||
} else if (event.code === KEYCODE.F && event.altKey) {
|
||||
if (props.page == Page.Terminal && Settings.EnableBashHotkeys) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
clickFactions();
|
||||
} else if (event.key === KEY.A && event.altKey) {
|
||||
} else if (event.code === KEYCODE.A && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickAugmentations();
|
||||
} else if (event.key === KEY.U && event.altKey) {
|
||||
} else if (event.code === KEYCODE.U && event.altKey) {
|
||||
event.preventDefault();
|
||||
clickTutorial();
|
||||
} else if (event.key === KEY.B && event.altKey && props.player.bladeburner) {
|
||||
} else if (event.code === KEYCODE.B && event.altKey && props.player.bladeburner) {
|
||||
event.preventDefault();
|
||||
clickBladeburner();
|
||||
} else if (event.key === KEY.G && event.altKey && props.player.gang) {
|
||||
} else if (event.code === KEYCODE.G && event.altKey && props.player.gang) {
|
||||
event.preventDefault();
|
||||
clickGang();
|
||||
}
|
||||
// if (event.key === KEY.O && event.altKey) {
|
||||
// event.preventDefault();
|
||||
// gameOptionsBoxOpen();
|
||||
// }
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleShortcuts);
|
||||
|
@ -718,7 +718,11 @@ export class Terminal implements ITerminal {
|
||||
}
|
||||
break;
|
||||
case iTutorialSteps.TerminalCreateScript:
|
||||
if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
|
||||
if (
|
||||
commandArray.length == 2 &&
|
||||
commandArray[0] == "nano" &&
|
||||
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
|
||||
) {
|
||||
iTutorialNextStep();
|
||||
} else {
|
||||
this.error("Bad command. Please follow the tutorial");
|
||||
@ -734,7 +738,11 @@ export class Terminal implements ITerminal {
|
||||
}
|
||||
break;
|
||||
case iTutorialSteps.TerminalRunScript:
|
||||
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
|
||||
if (
|
||||
commandArray.length == 2 &&
|
||||
commandArray[0] == "run" &&
|
||||
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
|
||||
) {
|
||||
iTutorialNextStep();
|
||||
} else {
|
||||
this.error("Bad command. Please follow the tutorial");
|
||||
@ -742,7 +750,11 @@ export class Terminal implements ITerminal {
|
||||
}
|
||||
break;
|
||||
case iTutorialSteps.ActiveScriptsToTerminal:
|
||||
if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
|
||||
if (
|
||||
commandArray.length == 2 &&
|
||||
commandArray[0] == "tail" &&
|
||||
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
|
||||
) {
|
||||
iTutorialNextStep();
|
||||
} else {
|
||||
this.error("Bad command. Please follow the tutorial");
|
||||
|
@ -7,7 +7,7 @@ import Paper from "@mui/material/Paper";
|
||||
import Popper from "@mui/material/Popper";
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
import { KEY, KEYCODE } from "../../utils/helpers/keyCodes";
|
||||
import { ITerminal } from "../ITerminal";
|
||||
import { IRouter } from "../../ui/Router";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
@ -318,57 +318,57 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
|
||||
|
||||
// Extra Bash Emulation Hotkeys, must be enabled through options
|
||||
if (Settings.EnableBashHotkeys) {
|
||||
if (event.key === KEY.A && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.A && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("home");
|
||||
}
|
||||
|
||||
if (event.key === KEY.E && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.E && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("end");
|
||||
}
|
||||
|
||||
if (event.key === KEY.B && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.B && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("prevchar");
|
||||
}
|
||||
|
||||
if (event.key === KEY.B && event.altKey) {
|
||||
if (event.code === KEYCODE.B && event.altKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("prevword");
|
||||
}
|
||||
|
||||
if (event.key === KEY.F && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.F && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("nextchar");
|
||||
}
|
||||
|
||||
if (event.key === KEY.F && event.altKey) {
|
||||
if (event.code === KEYCODE.F && event.altKey) {
|
||||
event.preventDefault();
|
||||
moveTextCursor("nextword");
|
||||
}
|
||||
|
||||
if ((event.key === KEY.H || event.key === KEY.D) && event.ctrlKey) {
|
||||
if ((event.code === KEYCODE.H || event.code === KEYCODE.D) && event.ctrlKey) {
|
||||
modifyInput("backspace");
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.key === KEY.W && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.W && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("deletewordbefore");
|
||||
}
|
||||
|
||||
if (event.key === KEY.D && event.altKey) {
|
||||
if (event.code === KEYCODE.D && event.altKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("deletewordafter");
|
||||
}
|
||||
|
||||
if (event.key === KEY.U && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.U && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("clearbefore");
|
||||
}
|
||||
|
||||
if (event.key === KEY.K && event.ctrlKey) {
|
||||
if (event.code === KEYCODE.K && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
modifyInput("clearafter");
|
||||
}
|
||||
|
@ -134,20 +134,191 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
|
||||
}, []);
|
||||
|
||||
function lineClass(s: string): string {
|
||||
if (s === "error") {
|
||||
return classes.error;
|
||||
}
|
||||
if (s === "success") {
|
||||
return classes.success;
|
||||
}
|
||||
if (s === "info") {
|
||||
return classes.info;
|
||||
}
|
||||
if (s === "warn") {
|
||||
return classes.warning;
|
||||
const lineClassMap: Record<string, string> = {
|
||||
error: classes.error,
|
||||
success: classes.success,
|
||||
info: classes.info,
|
||||
warn: classes.warning,
|
||||
};
|
||||
return lineClassMap[s] || classes.primary;
|
||||
}
|
||||
|
||||
function ansiCodeStyle(code: string | null): Record<string, any> {
|
||||
// The ANSI colors actually have the dark color set as default and require extra work to get
|
||||
// bright colors. But these are rarely used or, if they are, are often re-mapped by the
|
||||
// terminal emulator to brighter colors. So for foreground colors we use the bright color set
|
||||
// and for background colors we use the dark color set. Of course, all colors are available
|
||||
// via the longer ESC[n8;5;c] sequence (n={3,4}, c=color). Ideally, these 8-bit maps could
|
||||
// be managed in the user preferences/theme.
|
||||
const COLOR_MAP_BRIGHT: Record<string | number, string> = {
|
||||
0: "#404040",
|
||||
1: "#ff0000",
|
||||
2: "#00ff00",
|
||||
3: "#ffff00",
|
||||
4: "#0000ff",
|
||||
5: "#ff00ff",
|
||||
6: "#00ffff",
|
||||
7: "#ffffff",
|
||||
};
|
||||
const COLOR_MAP_DARK: Record<string | number, string> = {
|
||||
0: "#000000",
|
||||
1: "#800000",
|
||||
2: "#008000",
|
||||
3: "#808000",
|
||||
4: "#000080",
|
||||
5: "#800080",
|
||||
6: "#008080",
|
||||
7: "#c0c0c0",
|
||||
};
|
||||
|
||||
const ansi2rgb = (code: number): string => {
|
||||
/* eslint-disable yoda */
|
||||
if (0 <= code && code < 8) {
|
||||
// x8 RGB
|
||||
return COLOR_MAP_BRIGHT[code];
|
||||
}
|
||||
if (8 <= code && code < 16) {
|
||||
// x8 RGB - "High Intensity" (but here, actually the dark set)
|
||||
return COLOR_MAP_DARK[code];
|
||||
}
|
||||
if (16 <= code && code < 232) {
|
||||
// x216 RGB
|
||||
const base = code - 16;
|
||||
const ir = Math.floor(base / 36);
|
||||
const ig = Math.floor((base % 36) / 6);
|
||||
const ib = Math.floor((base % 6) / 1);
|
||||
const r = ir <= 0 ? 0 : 55 + ir * 40;
|
||||
const g = ig <= 0 ? 0 : 55 + ig * 40;
|
||||
const b = ib <= 0 ? 0 : 55 + ib * 40;
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
if (232 <= code && code < 256) {
|
||||
// x32 greyscale
|
||||
const base = code - 232;
|
||||
const grey = base * 10 + 8;
|
||||
return `rgb(${grey}, ${grey}, ${grey})`;
|
||||
}
|
||||
// shouldn't get here (under normal circumstances), but just in case
|
||||
return "initial";
|
||||
};
|
||||
|
||||
type styleKey = "fontWeight" | "textDecoration" | "color" | "backgroundColor" | "display";
|
||||
const style: {
|
||||
fontWeight?: string;
|
||||
textDecoration?: string;
|
||||
color?: string;
|
||||
backgroundColor?: string;
|
||||
display?: string;
|
||||
} = {};
|
||||
|
||||
if (code === null || code === "0") {
|
||||
return style;
|
||||
}
|
||||
|
||||
return classes.primary;
|
||||
const codeParts = code
|
||||
.split(";")
|
||||
.map((p) => parseInt(p))
|
||||
.filter(
|
||||
(p, i, arr) =>
|
||||
// If the sequence is 38;5 (x256 foreground color) or 48;5 (x256 background color),
|
||||
// filter out the 5 so the next codePart after {38,48} is the color code.
|
||||
p != 5 || i == 0 || (arr[i - 1] != 38 && arr[i - 1] != 48),
|
||||
);
|
||||
|
||||
let nextStyleKey: styleKey | null = null;
|
||||
codeParts.forEach((codePart) => {
|
||||
/* eslint-disable yoda */
|
||||
if (nextStyleKey !== null) {
|
||||
style[nextStyleKey] = ansi2rgb(codePart);
|
||||
nextStyleKey = null;
|
||||
}
|
||||
// Decorations
|
||||
else if (codePart == 1) {
|
||||
style.fontWeight = "bold";
|
||||
} else if (codePart == 4) {
|
||||
style.textDecoration = "underline";
|
||||
}
|
||||
// Forground Color (x8)
|
||||
else if (30 <= codePart && codePart < 38) {
|
||||
if (COLOR_MAP_BRIGHT[codePart % 10]) {
|
||||
style.color = COLOR_MAP_BRIGHT[codePart % 10];
|
||||
}
|
||||
}
|
||||
// Background Color (x8)
|
||||
else if (40 <= codePart && codePart < 48) {
|
||||
if (COLOR_MAP_DARK[codePart % 10]) {
|
||||
style.backgroundColor = COLOR_MAP_DARK[codePart % 10];
|
||||
}
|
||||
}
|
||||
// Forground Color (x256)
|
||||
else if (codePart == 38) {
|
||||
nextStyleKey = "color";
|
||||
}
|
||||
// Background Color (x256)
|
||||
else if (codePart == 48) {
|
||||
nextStyleKey = "backgroundColor";
|
||||
}
|
||||
});
|
||||
// If a background color is set, render this as an inline block element (instead of inline)
|
||||
// so that the background between lines (at least those that don't wrap) is uninterrupted.
|
||||
if (style.backgroundColor !== undefined) {
|
||||
style.display = "inline-block";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function parseOutputText(item: Output): React.ReactElement {
|
||||
const parts = [];
|
||||
// Some things, oddly, can cause item.text to not be a string (e.g. expr from the CLI), which
|
||||
// causes .matchAll to throw. Ensure it's a string immediately
|
||||
if (typeof item.text === "string") {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const ANSI_ESCAPE = new RegExp("\u{001b}\\[(?<code>.*?)m", "ug");
|
||||
// Build a look-alike regex match to place at the front of the matches list
|
||||
const INITIAL = {
|
||||
0: "",
|
||||
index: 0,
|
||||
groups: { code: null },
|
||||
};
|
||||
const matches = [INITIAL, ...item.text.matchAll(ANSI_ESCAPE), null];
|
||||
if (matches.length > 2) {
|
||||
matches.slice(0, -1).forEach((m, i) => {
|
||||
const n = matches[i + 1];
|
||||
if (!m || m.index === undefined || m.groups === undefined) {
|
||||
return;
|
||||
}
|
||||
const startIndex = m.index + m[0].length;
|
||||
const stopIndex = n ? n.index : item.text.length;
|
||||
const partText = item.text.slice(startIndex, stopIndex);
|
||||
if (startIndex !== stopIndex) {
|
||||
// Don't generate "empty" spans
|
||||
parts.push({ code: m.groups.code, text: partText });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
// For example, if the string was empty or there were no escape sequence matches
|
||||
parts.push({ code: null, text: item.text });
|
||||
}
|
||||
return (
|
||||
<Typography classes={{ root: lineClass(item.color) }} paragraph={false}>
|
||||
{parts.map((part, index) => {
|
||||
const spanStyle = ansiCodeStyle(part.code);
|
||||
if (!_.isEmpty(spanStyle)) {
|
||||
// Only wrap parts with spans if the calculated spanStyle is non-empty...
|
||||
return (
|
||||
<Typography key={index} paragraph={false} component="span" sx={spanStyle}>
|
||||
{part.text}
|
||||
</Typography>
|
||||
);
|
||||
} else {
|
||||
// ... otherwise yield the unmodified, unwrapped part.
|
||||
return part.text;
|
||||
}
|
||||
})}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
const classes = useStyles();
|
||||
@ -160,7 +331,7 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
|
||||
return (
|
||||
<ListItem key={i} classes={{ root: classes.nopadding }}>
|
||||
<Typography classes={{ root: lineClass(item.color) }} paragraph={false}>
|
||||
{item.text}
|
||||
{parseOutputText(item)}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
);
|
||||
|
@ -1532,7 +1532,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
length += 2;
|
||||
}
|
||||
}
|
||||
return ans.length === length;
|
||||
|
||||
return ans.length <= length;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1555,12 +1556,12 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
"You are given the following LZ-encoded string:\n",
|
||||
` ${compressed}\n`,
|
||||
"Decode it and output the original string.\n\n",
|
||||
"Example: decoding '5aaabc340533bca' chunk-by-chunk\n",
|
||||
" 5aaabc -> aaabc\n",
|
||||
" 5aaabc34 -> aaabcaab\n",
|
||||
" 5aaabc340 -> aaabcaab\n",
|
||||
" 5aaabc34053 -> aaabcaabaabaa\n",
|
||||
" 5aaabc340533bca -> aaabcaabaabaabca",
|
||||
"Example: decoding '5aaabb450723abb' chunk-by-chunk\n",
|
||||
" 5aaabb -> aaabb\n",
|
||||
" 5aaabb45 -> aaabbaaab\n",
|
||||
" 5aaabb450 -> aaabbaaab\n",
|
||||
" 5aaabb45072 -> aaabbaaababababa\n",
|
||||
" 5aaabb450723abb -> aaabbaaababababaabb",
|
||||
].join(" ");
|
||||
},
|
||||
gen: (): string => {
|
||||
@ -1591,21 +1592,21 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
|
||||
` ${plaintext}\n`,
|
||||
"Encode it using Lempel-Ziv encoding with the minimum possible output length.\n\n",
|
||||
"Examples (some have other possible encodings of minimal length):\n",
|
||||
" abracadabra -> 7abracad47\n",
|
||||
" mississippi -> 4miss433ppi\n",
|
||||
" aAAaAAaAaAA -> 3aAA53035\n",
|
||||
" 2718281828 -> 627182844\n",
|
||||
" abcdefghijk -> 9abcdefghi02jk\n",
|
||||
" aaaaaaaaaaa -> 1a911a\n",
|
||||
" aaaaaaaaaaaa -> 1a912aa\n",
|
||||
" aaaaaaaaaaaaa -> 1a91031",
|
||||
" abracadabra -> 7abracad47\n",
|
||||
" mississippi -> 4miss433ppi\n",
|
||||
" aAAaAAaAaAA -> 3aAA53035\n",
|
||||
" 2718281828 -> 627182844\n",
|
||||
" abcdefghijk -> 9abcdefghi02jk\n",
|
||||
" aaaaaaaaaaaa -> 3aaa91\n",
|
||||
" aaaaaaaaaaaaa -> 1a91031\n",
|
||||
" aaaaaaaaaaaaaa -> 1a91041",
|
||||
].join(" ");
|
||||
},
|
||||
gen: (): string => {
|
||||
return comprLZGenerate();
|
||||
},
|
||||
solver: (plain: string, ans: string): boolean => {
|
||||
return comprLZDecode(ans) === plain && ans.length === comprLZEncode(plain).length;
|
||||
return comprLZDecode(ans) === plain && ans.length <= comprLZEncode(plain).length;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -4,7 +4,6 @@ import ReactDOM from "react-dom";
|
||||
import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme";
|
||||
import { LoadingScreen } from "./ui/LoadingScreen";
|
||||
import { initElectron } from "./Electron";
|
||||
import { AlertEvents } from "./ui/React/AlertManager";
|
||||
initElectron();
|
||||
globalThis["React"] = React;
|
||||
globalThis["ReactDOM"] = ReactDOM;
|
||||
|
@ -9,6 +9,7 @@ import ArrowBackIos from "@mui/icons-material/ArrowBackIos";
|
||||
import { ITutorialEvents } from "./ITutorialEvents";
|
||||
import { CopyableText } from "../React/CopyableText";
|
||||
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import EqualizerIcon from "@mui/icons-material/Equalizer";
|
||||
import LastPageIcon from "@mui/icons-material/LastPage";
|
||||
@ -27,6 +28,7 @@ import {
|
||||
iTutorialSteps,
|
||||
iTutorialEnd,
|
||||
} from "../../InteractiveTutorial";
|
||||
import { NSSelection } from "./NSSelection";
|
||||
|
||||
interface IContent {
|
||||
content: React.ReactElement;
|
||||
@ -45,9 +47,23 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
}),
|
||||
);
|
||||
|
||||
enum Language {
|
||||
None,
|
||||
NS1,
|
||||
NS2,
|
||||
}
|
||||
|
||||
export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
const [nsSelectionOpen, setNSSelectionOpen] = useState(false);
|
||||
const [language, setLanguage] = useState(Language.None);
|
||||
const classes = useStyles();
|
||||
|
||||
const tutorialScriptName = {
|
||||
[Language.None]: "n00dles.script",
|
||||
[Language.NS1]: "n00dles.script",
|
||||
[Language.NS2]: "n00dles.js",
|
||||
}[language];
|
||||
|
||||
const contents: { [number: string]: IContent | undefined } = {
|
||||
[iTutorialSteps.Start as number]: {
|
||||
content: (
|
||||
@ -66,6 +82,47 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
),
|
||||
canNext: true,
|
||||
},
|
||||
[iTutorialSteps.NSSelection as number]: {
|
||||
content: (
|
||||
<>
|
||||
<Typography>The tutorial will adjust to your programming ability.</Typography>
|
||||
<Typography>Bitburner has 2 types of scripts:</Typography>
|
||||
<List>
|
||||
<ListItem>
|
||||
<Typography>NS1: Javascript from 2009, very simple. Recommended for beginners to programming.</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>
|
||||
NS2: Native, modern Javascript. Recommended if you know any programming language or are serious about
|
||||
learning programming.
|
||||
</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Typography>
|
||||
Both are available at all time and interchangeably. This choice is only for the tutorial.
|
||||
</Typography>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLanguage(Language.NS1);
|
||||
iTutorialNextStep();
|
||||
}}
|
||||
>
|
||||
Use NS1
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLanguage(Language.NS2);
|
||||
iTutorialNextStep();
|
||||
}}
|
||||
>
|
||||
Use NS2
|
||||
</Button>
|
||||
<Button onClick={() => setNSSelectionOpen(true)}>More info</Button>
|
||||
<br />
|
||||
</>
|
||||
),
|
||||
canNext: false,
|
||||
},
|
||||
[iTutorialSteps.GoToCharacterPage as number]: {
|
||||
content: (
|
||||
<>
|
||||
@ -321,7 +378,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> nano"}</Typography>
|
||||
|
||||
<Typography>Scripts must end with the .script extension. Let's make a script now by entering </Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> nano n00dles.script"}</Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> nano ${tutorialScriptName}`}</Typography>
|
||||
|
||||
<Typography>
|
||||
after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)
|
||||
@ -334,16 +391,28 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
content: (
|
||||
<>
|
||||
<Typography>
|
||||
This is the script editor. You can use it to program your scripts. Scripts are written in a simplified
|
||||
version of javascript. Copy and paste the following code into the script editor: <br />
|
||||
This is the script editor. You can use it to program your scripts.{" "}
|
||||
{language !== Language.NS2 && <>Scripts are written in a simplified version of javascript.</>} Copy and
|
||||
paste the following code into the script editor: <br />
|
||||
</Typography>
|
||||
|
||||
<Typography classes={{ root: classes.code }}>
|
||||
<CopyableText
|
||||
value={`while(true) {
|
||||
{language !== Language.NS2 && (
|
||||
<CopyableText
|
||||
value={`while(true) {
|
||||
hack('n00dles');
|
||||
}`}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
{language === Language.NS2 && (
|
||||
<CopyableText
|
||||
value={`export async function main(ns) {
|
||||
while(true) {
|
||||
await ns.hack('n00dles');
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
For anyone with basic programming experience, this code should be straightforward. This script will
|
||||
@ -378,7 +447,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
<Typography>
|
||||
We have 8GB of free RAM on this machine, which is enough to run our script. Let's run our script using
|
||||
</Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> run n00dles.script"}</Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> run ${tutorialScriptName}`}</Typography>
|
||||
</>
|
||||
),
|
||||
canNext: false,
|
||||
@ -425,7 +494,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
One last thing about scripts, each active script contains logs that detail what it's doing. We can check
|
||||
these logs using the tail command. Do that now for the script we just ran by typing{" "}
|
||||
</Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> tail n00dles.script"}</Typography>
|
||||
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> tail ${tutorialScriptName}`}</Typography>
|
||||
</>
|
||||
),
|
||||
canNext: false,
|
||||
@ -447,14 +516,6 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
in the main navigation menu to look at the documentation.
|
||||
<br />
|
||||
<br />
|
||||
If you know even a little bit of programming it is highly recommended you use NS2 instead. You will enjoy
|
||||
the game much more. NS1 files end with .script and are a subset of javascript. NS2 files end with .js and
|
||||
are full speed native javascript.
|
||||
<br />
|
||||
<br />
|
||||
You can learn more about the difference between them later in the documentation.
|
||||
<br />
|
||||
<br />
|
||||
For now, let's move on to something else!
|
||||
</Typography>
|
||||
</>
|
||||
@ -549,25 +610,30 @@ export function InteractiveTutorialRoot(): React.ReactElement {
|
||||
const content = contents[step];
|
||||
if (content === undefined) throw new Error("error in the tutorial");
|
||||
return (
|
||||
<Paper square sx={{ maxWidth: "70vw", p: 2 }}>
|
||||
{content.content}
|
||||
{step !== iTutorialSteps.TutorialPageInfo && (
|
||||
<>
|
||||
<IconButton onClick={iTutorialPrevStep} aria-label="previous">
|
||||
<ArrowBackIos />
|
||||
</IconButton>
|
||||
{content.canNext && (
|
||||
<IconButton onClick={iTutorialNextStep} aria-label="next">
|
||||
<ArrowForwardIos />
|
||||
</IconButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={iTutorialEnd}>
|
||||
{step !== iTutorialSteps.TutorialPageInfo ? "SKIP TUTORIAL" : "FINISH TUTORIAL"}
|
||||
</Button>
|
||||
</Paper>
|
||||
<>
|
||||
<NSSelection open={nsSelectionOpen} onClose={() => setNSSelectionOpen(false)} />
|
||||
<Paper square sx={{ maxWidth: "70vw", p: 2 }}>
|
||||
{content.content}
|
||||
{step !== iTutorialSteps.TutorialPageInfo && (
|
||||
<>
|
||||
{step !== iTutorialSteps.Start && (
|
||||
<IconButton onClick={iTutorialPrevStep} aria-label="previous">
|
||||
<ArrowBackIos />
|
||||
</IconButton>
|
||||
)}
|
||||
{content.canNext && (
|
||||
<IconButton onClick={iTutorialNextStep} aria-label="next">
|
||||
<ArrowForwardIos />
|
||||
</IconButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Button onClick={iTutorialEnd}>
|
||||
{step !== iTutorialSteps.TutorialPageInfo ? "SKIP TUTORIAL" : "FINISH TUTORIAL"}
|
||||
</Button>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
82
src/ui/InteractiveTutorial/NSSelection.tsx
Normal file
82
src/ui/InteractiveTutorial/NSSelection.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { Tab, Tabs, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
import { Modal } from "../React/Modal";
|
||||
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ns1Example = `while(true) {
|
||||
hack('n00dles');
|
||||
}`;
|
||||
const ns2Example = `/** @param {NS} ns */
|
||||
export async function main(ns) {
|
||||
while(true) {
|
||||
await ns.hack('n00dles');
|
||||
}
|
||||
}`;
|
||||
|
||||
export function NSSelection(props: IProps): React.ReactElement {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
function handleChange(event: React.SyntheticEvent, tab: number): void {
|
||||
setValue(tab);
|
||||
}
|
||||
|
||||
function onMount(editor: IStandaloneCodeEditor): void {
|
||||
editor.updateOptions({ readOnly: true });
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose}>
|
||||
<Tabs variant="fullWidth" value={value} onChange={handleChange}>
|
||||
<Tab label="NS1" />
|
||||
<Tab label="NS2" />
|
||||
</Tabs>
|
||||
|
||||
{value === 0 && (
|
||||
<>
|
||||
<Typography>
|
||||
These scripts end with '.script'. Using a very old interpreted version of javascript. It is perfect for
|
||||
beginner to programming.
|
||||
</Typography>
|
||||
<Typography>Example script using NS1:</Typography>
|
||||
<Editor
|
||||
loading={<></>}
|
||||
defaultLanguage="javascript"
|
||||
defaultValue={ns1Example}
|
||||
height={`${300}px`}
|
||||
theme={"vs-dark"}
|
||||
onMount={onMount}
|
||||
options={{ fontSize: 30 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{value === 1 && (
|
||||
<>
|
||||
<Typography>
|
||||
These scripts end with '.js'. Scripts using ns2 are running natively along the game. If you know any
|
||||
programming language you should be using this. However if you're unfamiliar with javascript Promises you
|
||||
might want to read up on them a little bit before diving in.
|
||||
</Typography>
|
||||
<Typography>Example script using NS2:</Typography>
|
||||
<Editor
|
||||
loading={<></>}
|
||||
defaultLanguage="javascript"
|
||||
defaultValue={ns2Example}
|
||||
height={`${300}px`}
|
||||
theme={"vs-dark"}
|
||||
options={{ fontSize: 30 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
zIndex: 999999,
|
||||
},
|
||||
paper: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
|
@ -105,6 +105,13 @@ export function comprLZEncode(plain: string): string {
|
||||
|
||||
// start new literal
|
||||
set(new_state, 0, 1, string + length + offset);
|
||||
|
||||
// end current backreference and start new backreference
|
||||
for (let new_offset = 1; new_offset <= Math.min(9, i); ++new_offset) {
|
||||
if (plain[i - new_offset] === c) {
|
||||
set(new_state, new_offset, 1, string + length + offset + "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,6 @@ export function exceptionAlert(e: IError | string): void {
|
||||
"This is a bug, please report to game developer with this " +
|
||||
"message as well as details about how to reproduce the bug.<br><br>" +
|
||||
"If you want to be safe, I suggest refreshing the game WITHOUT saving so that your " +
|
||||
"safe doesn't get corrupted",
|
||||
"save doesn't get corrupted",
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Keyboard key codes
|
||||
* Keyboard key codes as returned by event.key
|
||||
*/
|
||||
export enum KEY {
|
||||
//SHIFT: 16, // Check by `&& event.shiftKey`
|
||||
@ -70,3 +70,69 @@ export enum KEY {
|
||||
Y = "y",
|
||||
Z = "z",
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyboard key codes as returned by event.code
|
||||
*/
|
||||
export enum KEYCODE {
|
||||
//SHIFT: 16, // Check by `&& event.shiftKey`
|
||||
//CTRL: 17, // Check by `&& event.ctrlKey`
|
||||
//ALT: 18, // Check by `&& event.altKey`
|
||||
ENTER = "Enter",
|
||||
ESC = "Escape",
|
||||
TAB = "Tab",
|
||||
SPACE = "Space",
|
||||
BACKSPACE = "Backspace",
|
||||
UP_ARROW = "ArrowUp",
|
||||
DOWN_ARROW = "ArrowDown",
|
||||
LEFT_ARROW = "ArrowLeft",
|
||||
RIGHT_ARROW = "ArrowRight",
|
||||
|
||||
BACKWARD_SLASH = "Backslash",
|
||||
BACKQUOTE = "Backquote",
|
||||
COMMA = "Comma",
|
||||
DOT = "Period",
|
||||
EQUAL = "Equal",
|
||||
FORWARD_SLASH = "Slash",
|
||||
HYPHEN = "Minus",
|
||||
SEMICOLON = "Semicolon",
|
||||
QUOTE = "Quote",
|
||||
|
||||
k0 = "Digit0",
|
||||
k1 = "Digit1",
|
||||
k2 = "Digit2",
|
||||
k3 = "Digit3",
|
||||
k4 = "Digit4",
|
||||
k5 = "Digit5",
|
||||
k6 = "Digit6",
|
||||
k7 = "Digit7",
|
||||
k8 = "Digit8",
|
||||
k9 = "Digit9",
|
||||
|
||||
A = "KeyA",
|
||||
B = "KeyB",
|
||||
C = "KeyC",
|
||||
D = "KeyD",
|
||||
E = "KeyE",
|
||||
F = "KeyF",
|
||||
G = "KeyG",
|
||||
H = "KeyH",
|
||||
I = "KeyI",
|
||||
J = "KeyJ",
|
||||
K = "KeyK",
|
||||
L = "KeyL",
|
||||
M = "KeyM",
|
||||
N = "KeyN",
|
||||
O = "KeyO",
|
||||
P = "KeyP",
|
||||
Q = "KeyQ",
|
||||
R = "KeyR",
|
||||
S = "KeyS",
|
||||
T = "KeyT",
|
||||
U = "KeyU",
|
||||
V = "KeyV",
|
||||
W = "KeyW",
|
||||
X = "KeyX",
|
||||
Y = "KeyY",
|
||||
Z = "KeyZ",
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user