Merge pull request #3746 from danielyxie/dev

Update infinite loop safety net mechanism
This commit is contained in:
hydroflame 2022-05-24 17:10:00 -04:00 committed by GitHub
commit 16b0970b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 119 additions and 75 deletions

2
dist/main.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -129,7 +129,7 @@
"electron:packager-win": "electron-packager .package bitburner --platform win32 --arch x64 --out .build --overwrite --icon .package/icon.png",
"electron:packager-mac": "electron-packager .package bitburner --platform darwin --arch x64 --out .build --overwrite --icon .package/icon.png",
"electron:packager-linux": "electron-packager .package bitburner --platform linux --arch x64 --out .build --overwrite --icon .package/icon.png",
"allbuild": "npm run build && npm run electron && git add --all && git commit -m \"allbuild commit $(git rev-parse --short HEAD)\" && git push -f -u origin dev",
"allbuild": "npm run build && npm run electron && git add --all && git commit -m \"allbuild commit $(git rev-parse --short HEAD)\" && git push -u origin dev",
"preversion": "npm install && npm run test",
"version": "sh ./tools/build-release.sh && git add --all",
"postversion": "git push -u origin dev && git push --tags",

@ -105,7 +105,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
"injects *Γ-based cells that provides general enhancement to the body.",
stats: (
<>
This augmentation makes many aspect of infiltration easier and more productive. Such as increased timer,
This augmentation makes many aspects of infiltration easier and more productive. Such as increased timer,
rewards, reduced damage taken, etc.
</>
),
@ -117,10 +117,10 @@ export const initSoAAugmentations = (): Augmentation[] => [
repCost: 1e4,
moneyCost: 1e6,
info:
"Extra-occular neurons taken from old martial art master. Injecting the user the ability to " +
"predict enemy attack before they even know it themself.",
"Extra-occular neurons taken from old martial art master. Injecting them gives the user the ability to " +
"predict the enemy's attack before they even know it themself.",
stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
<>This augmentation makes the Slash minigame easier by showing you via an indicator when the slash in coming.</>
),
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
@ -129,7 +129,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
name: AugmentationNames.WisdomOfAthena,
repCost: 1e4,
moneyCost: 1e6,
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
info: "A connective brain implant to SASHA that focuses on pattern recognition and predictive templating.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
@ -138,7 +138,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
name: AugmentationNames.ChaosOfDionysus,
repCost: 1e4,
moneyCost: 1e6,
info: "Opto-occipito implant to process visual signal before brain interpretation.",
info: "Opto-occipito implant to process visual signals before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
@ -176,7 +176,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
name: AugmentationNames.HuntOfArtemis,
repCost: 1e4,
moneyCost: 1e6,
info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the users electro-magnetic sensitivity.",
info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the user's electro-magnetic sensitivity.",
stats: (
<>
This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their
@ -190,7 +190,7 @@ export const initSoAAugmentations = (): Augmentation[] => [
name: AugmentationNames.KnowledgeOfApollo,
repCost: 1e4,
moneyCost: 1e6,
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantee's delta wave.",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
isSpecial: true,
factions: [FactionNames.ShadowsOfAnarchy],
@ -515,7 +515,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info:
"TITN is a series of viruses that targets and alters the sequences of human DNA in genes that " +
"control personality. The TITN-41 strain alters these genes so that the subject becomes more " +
"outgoing and socialable.",
"outgoing and sociable.",
charisma_mult: 1.15,
charisma_exp_mult: 1.15,
factions: [FactionNames.Silhouette],
@ -526,9 +526,9 @@ export const initGeneralAugmentations = (): Augmentation[] => [
moneyCost: 1.375e9,
info:
"A cranial implant that greatly assists in the user's ability to analyze social situations " +
"and interactions. The system uses a wide variety of factors such as facial expressions, body " +
"language, and the voice tone, and inflection to determine the best course of action during social" +
"situations. The implant also uses deep learning software to continuously learn new behavior" +
"and interactions. The system uses a wide variety of factors such as facial expression, body " +
"language, voice tone, and inflection to determine the best course of action during social " +
"situations. The implant also uses deep learning software to continuously learn new behavior " +
"patterns and how to best respond.",
charisma_mult: 1.6,
charisma_exp_mult: 1.6,
@ -988,7 +988,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info:
"This is an additional installation that upgrades the functionality of the " +
"PC Direct-Neural Interface augmentation. When connected to a computer, " +
"The Neural Network upgrade allows the user to use their own brain's " +
"the Neural Network upgrade allows the user to use their own brain's " +
"processing power to aid the computer in computational tasks.",
prereqs: [AugmentationNames.PCDNI],
company_rep_mult: 2,
@ -1172,7 +1172,7 @@ export const initGeneralAugmentations = (): Augmentation[] => [
info:
"A skin implant that reinforces the skin with highly-advanced synthetic cells. These " +
"cells, when powered, have a negative refractive index. As a result, they bend light " +
"around the skin, making the user much harder to see to the naked eye.",
"around the skin, making the user much harder to see with the naked eye.",
agility_mult: 1.05,
crime_money_mult: 1.1,
factions: [FactionNames.SlumSnakes, FactionNames.Tetrads],
@ -1734,7 +1734,7 @@ export const initBladeburnerAugmentations = (): Augmentation[] => [
info:
"An improved version of Vangelis, a synthetic symbiotic virus that is " +
"injected into human brain tissue. On top of the benefits of the original " +
"virus, this also grants an accelerated healing factor and enhanced " +
"virus, this also grants accelerated healing and enhanced " +
"reflexes.",
prereqs: [AugmentationNames.VangelisVirus],
defense_exp_mult: 1.1,
@ -1930,7 +1930,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
info:
"The next evolution is near, a coming together of man and machine. A synthesis greater than the birth of the human " +
"organism. Time spent with the gift has allowed for acclimatization of the invasive augment and the toll it takes upon " +
"your frame granting lesser penalty of 5% to all stats.",
"your frame granting a 5% reduced penalty to all stats.",
prereqs: [AugmentationNames.StaneksGift1],
isSpecial: true,
hacking_chance_mult: 0.95 / 0.9,
@ -1969,7 +1969,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [
info:
"The synthesis of human and machine is nothing to fear. It is our destiny. " +
"You will become greater than the sum of our parts. As One. Embrace your gift " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility in the form " +
"of no longer suffering a stat penalty. ",
prereqs: [AugmentationNames.StaneksGift2, AugmentationNames.StaneksGift1],
isSpecial: true,

@ -2,7 +2,7 @@
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, Info, NewReleases, Report } from "@mui/icons-material";
import { CheckBox, CheckBoxOutlineBlank, CheckCircle, NewReleases, Report } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react";
import { Faction } from "../../Faction/Faction";
@ -11,8 +11,8 @@ import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentation } from "../Augmentation";
import { AugmentationNames } from "../data/AugmentationNames";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { StaticAugmentations } from "../StaticAugmentations";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
interface IPreReqsProps {
player: IPlayer;
@ -42,11 +42,13 @@ const PreReqs = (props: IPreReqsProps): React.ReactElement => {
}
>
<Typography
variant="body2"
sx={{
ml: 1,
fontSize: "0.9rem",
display: "flex",
alignItems: "center",
color: hasPreReqs ? Settings.theme.successlight : Settings.theme.error,
gridArea: "prereqs",
}}
>
{hasPreReqs ? (
@ -100,7 +102,10 @@ const Exclusive = (props: IExclusiveProps): React.ReactElement => {
</>
}
>
<NewReleases sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)" }} />
<NewReleases
fontSize="small"
sx={{ ml: 1, color: Settings.theme.money, transform: "rotate(180deg)", gridArea: "exclusive" }}
/>
</Tooltip>
);
};
@ -113,7 +118,9 @@ interface IReqProps {
const Requirement = (props: IReqProps): React.ReactElement => {
return (
<Typography sx={{ display: "flex", alignItems: "center", color: props.color }}>
<Typography
sx={{ display: "flex", alignItems: "center", color: props.fulfilled ? props.color : Settings.theme.error }}
>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
@ -138,7 +145,7 @@ export const PurchasableAugmentations = (props: IPurchasableAugsProps): React.Re
<Container
maxWidth="lg"
disableGutters
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 1 }}
sx={{ mx: 0, display: "grid", gridTemplateColumns: "repeat(1, 1fr)", gap: 0.75 }}
>
{props.augNames.map((augName: string) => (
<PurchasableAugmentation key={augName} parent={props} augName={augName} owned={false} />
@ -176,11 +183,12 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
return (
<Paper
sx={{
p: 1,
p: 0.5,
display: "grid",
gridTemplateColumns: "minmax(0, 4fr) 1fr",
gridTemplateColumns: "minmax(0, 4fr) 1.4fr",
gap: 1,
opacity: props.owned ? 0.75 : 1,
minWidth: "1100px",
}}
>
<>
@ -192,13 +200,13 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
})
}
disabled={!props.parent.canPurchase(props.parent.player, aug) || props.owned}
sx={{ width: "48px", height: "48px", float: "left", clear: "none", mr: 1 }}
sx={{ width: "48px", height: "36px", float: "left", clear: "none", mr: 1 }}
>
{props.owned ? "Owned" : "Buy"}
</Button>
<Box sx={{ maxWidth: props.owned ? "100%" : "85%" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ display: "grid", alignItems: "center", gridTemplateAreas: `"title exclusive prereqs"` }}>
<Tooltip
title={
<>
@ -211,34 +219,33 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac
</>
}
>
<Info sx={{ mr: 1 }} color="info" />
<Typography
sx={{
gridArea: "title",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
color:
props.owned || !props.parent.canPurchase(props.parent.player, aug)
? Settings.theme.disabled
: Settings.theme.primary,
}}
>
{aug.name}
{aug.name === AugmentationNames.NeuroFluxGovernor && ` - Level ${aug.getLevel(props.parent.player)}`}
</Typography>
</Tooltip>
<Typography
variant="h6"
sx={{
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
color:
props.owned || !props.parent.canPurchase(props.parent.player, aug)
? Settings.theme.disabled
: Settings.theme.primary,
}}
>
{aug.name}
{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} />
)}
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
{aug.prereqs.length > 0 && !props.parent.sleeveAugs && <PreReqs player={props.parent.player} aug={aug} />}
</Box>
</Box>
{props.owned || (
<Box sx={{ display: "grid", alignItems: "center", justifyItems: "left" }}>
<Box sx={{ display: "grid", alignItems: "center", gridTemplateColumns: "1fr 1fr" }}>
<Requirement
fulfilled={cost === 0 || props.parent.player.money > cost}
value={numeralWrapper.formatMoney(cost)}

@ -287,6 +287,8 @@ export class Bladeburner implements IBladeburner {
resetAction(): void {
this.action = new ActionIdentifier({ type: ActionTypes.Idle });
this.actionTimeCurrent = 0;
this.actionTimeToComplete = 0;
}
clearConsole(): void {

@ -155,7 +155,7 @@ function getRandomReward(): ICodingContractReward {
}
function getRandomServer(): BaseServer {
const servers = GetAllServers();
const servers = GetAllServers().filter((server: BaseServer) => server.serversOnNetwork.length !== 0);
let randIndex = getRandomInt(0, servers.length - 1);
let randServer = servers[randIndex];

@ -227,7 +227,7 @@ export const CONSTANTS: {
// BitNode/Source-File related stuff
TotalNumBitNodes: 24,
InfiniteLoopLimit: 1000,
InfiniteLoopLimit: 2000,
Donations: 7,

@ -60,6 +60,7 @@ export function Overview({ rerender }: IProps): React.ReactElement {
["Total Funds:", <Money money={corp.funds} />],
["Total Revenue:", <MoneyRate money={corp.revenue} />],
["Total Expenses:", <MoneyRate money={corp.expenses} />],
["Total Profit:", <MoneyRate money={corp.revenue - corp.expenses} />],
["Publicly Traded:", corp.public ? "Yes" : "No"],
["Owned Stock Shares:", numeralWrapper.format(corp.numShares, "0.000a")],
["Stock Price:", corp.public ? <Money money={corp.sharePrice} /> : "N/A"],

@ -44,8 +44,8 @@ export function Victory(props: IProps): React.ReactElement {
}
function trade(): void {
handleInfiltrators();
if (faction === "none") return;
handleInfiltrators();
Factions[faction].playerReputation += repGain;
quitInfiltration();
}

@ -96,11 +96,15 @@ function wrapFunction(
const safetyEnabled = Settings.InfinityLoopSafety;
function wrappedFunction(...args: unknown[]): unknown {
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
if (safetyEnabled) workerScript.infiniteLoopSafetyCounter++;
if (workerScript.infiniteLoopSafetyCounter > CONSTANTS.InfiniteLoopLimit)
throw new Error(
`Infinite loop without sleep detected. ${CONSTANTS.InfiniteLoopLimit} ns functions were called without 'sleep'. This will cause your UI to hang. Are you using 'asleep' by mistake?`,
);
if (safetyEnabled) {
const now = performance.now();
if (now - workerScript.infiniteLoopSafety > CONSTANTS.InfiniteLoopLimit) {
throw new Error(
`Potential infinite loop without sleep detected. The game spent ${CONSTANTS.InfiniteLoopLimit}ms stuck in this script. (Are you using 'asleep' by mistake?)`,
);
}
}
return func(ctx)(...args);
}
const parent = getNestedProperty(wrappedAPI, ...tree);

@ -226,6 +226,7 @@ const bladeburner: IMap<any> = {
stopBladeburnerAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2,
getCurrentAction: RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4,
getActionTime: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCurrentTime: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionEstimatedSuccessChance: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionRepGain: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
getActionCountRemaining: RamCostConstants.ScriptBladeburnerApiBaseRamCost,
@ -356,6 +357,7 @@ export const RamCosts: IMap<any> = {
print: 0,
printf: 0,
tprint: 0,
tprintf: 0,
clearLog: 0,
disableLog: 0,
enableLog: 0,

@ -114,7 +114,7 @@ export class WorkerScript {
/**
* Once this counter reaches it's limit the script crashes. It is reset when a promise completes.
*/
infiniteLoopSafetyCounter = 0;
infiniteLoopSafety = performance.now();
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) {
this.name = runningScriptObj.filename;

@ -14,7 +14,7 @@ export function netscriptDelay(time: number, workerScript: WorkerScript): Promis
workerScript.delay = null;
workerScript.delayReject = undefined;
workerScript.infiniteLoopSafetyCounter = 0;
workerScript.infiniteLoopSafety = performance.now();
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);

@ -137,6 +137,19 @@ export function NetscriptBladeburner(player: IPlayer, workerScript: WorkerScript
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionCurrentTime: (ctx: NetscriptContext) => (): number => {
checkBladeburnerAccess(ctx);
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
const timecomputed =
Math.min(bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow, bladeburner.actionTimeToComplete) *
1000;
return timecomputed;
} catch (e: any) {
throw ctx.makeRuntimeErrorMsg(e);
}
},
getActionEstimatedSuccessChance:
(ctx: NetscriptContext) =>
(_type: unknown, _name: unknown): [number, number] => {

@ -190,6 +190,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
className: sl.className,
};
},
getInformation:

@ -1110,6 +1110,8 @@ export interface SleeveTask {
gymStatType: string;
/** Faction work type being performed, if any */
factionWorkType: string;
/** Class being taken at university, if any */
className: string;
}
/**
@ -2884,7 +2886,7 @@ export interface Bladeburner {
* @remarks
* RAM cost: 4 GB
*
* Returns the number of seconds it takes to complete the specified action
* Returns the number of milliseconds it takes to complete the specified action
*
* @param type - Type of action.
* @param name - Name of action. Must be an exact match.
@ -2892,6 +2894,17 @@ export interface Bladeburner {
*/
getActionTime(type: string, name: string): number;
/**
* Get the time elapsed on current action.
* @remarks
* RAM cost: 4 GB
*
* Returns the number of milliseconds already spent on the current action.
*
* @returns Number of milliseconds already spent on the current action.
*/
getActionCurrentTime(): number;
/**
* Get estimate success chance of an action.
* @remarks

@ -73,6 +73,7 @@ import { weaken } from "./commands/weaken";
import { wget } from "./commands/wget";
import { hash } from "../hash/hash";
import { apr1 } from "./commands/apr1";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
export class Terminal implements ITerminal {
// Flags to determine whether the player is currently running a hack or an analyze
@ -204,7 +205,7 @@ export class Terminal implements ITerminal {
router.toBitVerse(false, false);
return;
}
let moneyGained = calculatePercentMoneyHacked(server, player);
let moneyGained = calculatePercentMoneyHacked(server, player) * BitNodeMultipliers.ManualHackMoney;
moneyGained = Math.floor(server.moneyAvailable * moneyGained);
if (moneyGained <= 0) {

@ -272,20 +272,19 @@ const Engine: {
const numCyclesOffline = Math.floor(timeOffline / CONSTANTS._idleSpeed);
// Generate coding contracts
// let numContracts = 0;
// if (numCyclesOffline < 3000 * 100) {
// // if we have less than 100 rolls, just roll them exactly.
// for (let i = 0; i < numCyclesOffline / 3000; i++) {
// if (Math.random() < 0.25) numContracts++;
// }
// } else {
// // just average it.
// numContracts = (numCyclesOffline / 3000) * 0.25;
// }
// console.log(`${numCyclesOffline} ${numContracts}`);
// for (let i = 0; i < numContracts; i++) {
// generateRandomContract();
// }
let numContracts = 0;
if (numCyclesOffline < 3000 * 100) {
// if we have less than 100 rolls, just roll them exactly.
for (let i = 0; i < numCyclesOffline / 3000; i++) {
if (Math.random() < 0.25) numContracts++;
}
} else {
// just average it.
numContracts = (numCyclesOffline / 3000) * 0.25;
}
for (let i = 0; i < numContracts; i++) {
generateRandomContract();
}
let offlineReputation = 0;
const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * 0.75;

@ -103,6 +103,7 @@ const useStyles = makeStyles((_theme: Theme) =>
scrollbarWidth: "auto",
flexDirection: "column-reverse",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
},
titleButton: {
padding: "1px 0",