fix a bunch of small v2 related bugs

This commit is contained in:
Olivier Gagnon 2022-07-26 08:08:51 -04:00
parent 5d2b81053d
commit faa3e212f3
11 changed files with 234 additions and 61 deletions

@ -30,6 +30,7 @@ secrets that you've been searching for.
Tools & Resources <toolsandresources> Tools & Resources <toolsandresources>
Changelog <changelog> Changelog <changelog>
v1.0.0 script migration guide <v1.0.0_migration.rst> v1.0.0 script migration guide <v1.0.0_migration.rst>
v2.0.0 script migration guide <v2.0.0_migration.rst>
404 <404.rst> 404 <404.rst>
Donate <https://paypal.me/danielyxie> Donate <https://paypal.me/danielyxie>

@ -0,0 +1,93 @@
v2.0.0 Migration Guide
======================
In v2.0.0 a few more API have been broken.
Working
-------
Working has been rebuilt from the grounds up. The motivation for that change is that all
different types of work all required different cached variables on the main Player object.
This caused a lot of bugs and crashes. It's been reworked in such a way as to prevent bugs
and make it nearly trivial to add new kinds of work.
All work type give their reward immediately. No need to stop work to bank rewards like reputation.
Faction and Company work no longer have a time limit.
Company work no longer reduces rep gain by half for quitting early.
Company faction require 400k rep to join (from 200k)
Backdooring company server reduces faction requirement to 300k.
All types of work generally no longer keep track of cumulative gains like exp and reputation since it's applied instantly.
commitCrime
-----------
crime now loops, meaning after finishing one shoplift you start the next one with no input. While the signature
has not changed its behavior has. It also has a new 'focus' parameters.
getPlayer
---------
The following work-related fields are not longer included:
* workChaExpGained
* currentWorkFactionName
* workDexExpGained
* workHackExpGained
* createProgramReqLvl
* workStrExpGained
* companyName
* crimeType
* workRepGained
* workChaExpGainRate
* workType
* workStrExpGainRate
* isWorking
* workRepGainRate
* workDefExpGained
* currentWorkFactionDescription
* workHackExpGainRate
* workAgiExpGainRate
* workDexExpGainRate
* workMoneyGained
* workMoneyLossRate
* workMoneyGainRate
* createProgramName
* workDefExpGainRate
* workAgiExpGained
* className
The reason for that is that these fields are all, in one way or another, included in the new work field 'currentWork'.
Some of these values are also irrelevant.
'currentWork' will be one of many different kind of value. For example when creating a program it will have a programName field.
One field that all kinds of work have in common is 'type' which denotes the current kind of work.
All fields ending in _mult have been moved to the 'mults' struct.
For example: getPlayer().hacking_skill_mult is now getPlayer().mults.hacking_skill
workForCompany
--------------
The argument 'companyName' is now not-optional.
getScriptIncome & getScriptExpGain
----------------------------------
Those 2 functions used to have a call where no arguments would return the total for all scripts. This caused weird signature.
If you want to get the total income/exp for all scripts used the new getTotalScriptIncome / getTotalScriptExpGain instead.
scp
---
scp has it's 2 last argument reversed, the signature is now scp(files, destination, optional_source)
Singularity
-----------
A while ago top level singularity function were deprecated in favor of the singularity namespace.
This means calls like 'ns.connect' need to be changed to 'ns.singularity.connect'
stock.buy and stock.sell
------------------------
These 2 functions were renamed to stock.buyStock and stock.sellStock because 'buy' and 'sell'
are very common tokens that would trick the ram calculation.

@ -36,16 +36,18 @@ export function printAliases(): void {
// Returns true if successful, false otherwise // Returns true if successful, false otherwise
export function parseAliasDeclaration(dec: string, global = false): boolean { export function parseAliasDeclaration(dec: string, global = false): boolean {
const re = /^([\w|!%,@-]+)=(("(.+)")|('(.+)'))$/; console.log(dec);
const re = /^([\w|!%,@-]+)=(.+)$/;
const matches = dec.match(re); const matches = dec.match(re);
if (matches == null || matches.length != 7) { console.log(matches);
if (matches == null || matches.length != 3) {
return false; return false;
} }
if (global) { if (global) {
addGlobalAlias(matches[1], matches[4] || matches[6]); addGlobalAlias(matches[1], matches[2]);
} else { } else {
addAlias(matches[1], matches[4] || matches[6]); addAlias(matches[1], matches[2]);
} }
return true; return true;
} }

@ -107,12 +107,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
step={25} step={25}
min={0} min={0}
max={500} max={500}
tooltip={ tooltip={<>The maximum number of recently killed scripts the game will keep.</>}
<>
The maximum number of lines a script's logs can hold. Setting this too high can cause the game to use a
lot of memory if you have many scripts running.
</>
}
/> />
<OptionsSlider <OptionsSlider
label="Netscript log size" label="Netscript log size"

@ -84,7 +84,6 @@ import {
Infiltration as IInfiltration, Infiltration as IInfiltration,
RunningScript as IRunningScript, RunningScript as IRunningScript,
RecentScript as IRecentScript, RecentScript as IRecentScript,
SourceFileLvl,
BasicHGWOptions, BasicHGWOptions,
ProcessInfo, ProcessInfo,
HackingMultipliers, HackingMultipliers,
@ -2470,6 +2469,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
inBladeburner: Player.inBladeburner(), inBladeburner: Player.inBladeburner(),
hasCorporation: Player.hasCorporation(), hasCorporation: Player.hasCorporation(),
entropy: Player.entropy, entropy: Player.entropy,
currentWork: Player.currentWork,
}; };
Object.assign(data.jobs, Player.jobs); Object.assign(data.jobs, Player.jobs);
return data; return data;

@ -11,7 +11,6 @@ import { LocationName } from "../../../Locations/data/LocationNames";
import { Locations } from "../../../Locations/Locations"; import { Locations } from "../../../Locations/Locations";
import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums"; import { PurchaseAugmentationsOrderSetting } from "../../../Settings/SettingEnums";
import { Settings } from "../../../Settings/Settings"; import { Settings } from "../../../Settings/Settings";
import { IMap } from "../../../types";
import { use } from "../../../ui/Context"; import { use } from "../../../ui/Context";
import { ConfirmationModal } from "../../../ui/React/ConfirmationModal"; import { ConfirmationModal } from "../../../ui/React/ConfirmationModal";
import { Money } from "../../../ui/React/Money"; import { Money } from "../../../ui/React/Money";
@ -20,7 +19,15 @@ import { IPlayer } from "../../IPlayer";
import { GraftableAugmentation } from "../GraftableAugmentation"; import { GraftableAugmentation } from "../GraftableAugmentation";
import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers"; import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers";
export const GraftableAugmentations: IMap<GraftableAugmentation> = {}; export const GraftableAugmentations = (): Record<string, GraftableAugmentation> => {
const gAugs: Record<string, GraftableAugmentation> = {};
for (const aug of Object.values(StaticAugmentations)) {
const name = aug.name;
const graftableAug = new GraftableAugmentation(aug);
gAugs[name] = graftableAug;
}
return gAugs;
};
const canGraft = (player: IPlayer, aug: GraftableAugmentation): boolean => { const canGraft = (player: IPlayer, aug: GraftableAugmentation): boolean => {
if (player.money < aug.cost) { if (player.money < aug.cost) {
@ -56,11 +63,7 @@ export const GraftingRoot = (): React.ReactElement => {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
for (const aug of Object.values(StaticAugmentations)) { const graftableAugmentations = useState(GraftableAugmentations())[0];
const name = aug.name;
const graftableAug = new GraftableAugmentation(aug);
GraftableAugmentations[name] = graftableAug;
}
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]); const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]);
const [graftOpen, setGraftOpen] = useState(false); const [graftOpen, setGraftOpen] = useState(false);
@ -75,7 +78,7 @@ export const GraftingRoot = (): React.ReactElement => {
const augs = getGraftingAvailableAugs(player); const augs = getGraftingAvailableAugs(player);
switch (Settings.PurchaseAugmentationsOrder) { switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: case PurchaseAugmentationsOrderSetting.Cost:
return augs.sort((a, b) => GraftableAugmentations[a].cost - GraftableAugmentations[b].cost); return augs.sort((a, b) => graftableAugmentations[a].cost - graftableAugmentations[b].cost);
default: default:
return augs; return augs;
} }
@ -126,7 +129,7 @@ export const GraftingRoot = (): React.ReactElement => {
<ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}> <ListItemButton key={i + 1} onClick={() => setSelectedAug(k)} selected={selectedAug === k}>
<Typography <Typography
sx={{ sx={{
color: canGraft(player, GraftableAugmentations[k]) color: canGraft(player, graftableAugmentations[k])
? Settings.theme.primary ? Settings.theme.primary
: Settings.theme.disabled, : Settings.theme.disabled,
}} }}
@ -143,11 +146,11 @@ export const GraftingRoot = (): React.ReactElement => {
<Button <Button
onClick={() => setGraftOpen(true)} onClick={() => setGraftOpen(true)}
sx={{ width: "100%" }} sx={{ width: "100%" }}
disabled={!canGraft(player, GraftableAugmentations[selectedAug])} disabled={!canGraft(player, graftableAugmentations[selectedAug])}
> >
Graft Augmentation ( Graft Augmentation (
<Typography> <Typography>
<Money money={GraftableAugmentations[selectedAug].cost} player={player} /> <Money money={graftableAugmentations[selectedAug].cost} player={player} />
</Typography> </Typography>
) )
</Button> </Button>
@ -183,7 +186,7 @@ export const GraftingRoot = (): React.ReactElement => {
<Typography color={Settings.theme.info}> <Typography color={Settings.theme.info}>
<b>Time to Graft:</b>{" "} <b>Time to Graft:</b>{" "}
{convertTimeMsToTimeElapsedString( {convertTimeMsToTimeElapsedString(
calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]), calculateGraftingTimeWithBonus(player, graftableAugmentations[selectedAug]),
)} )}
{/* Use formula so the displayed creation time is accurate to player bonus */} {/* Use formula so the displayed creation time is accurate to player bonus */}
</Typography> </Typography>

@ -4471,7 +4471,7 @@ export interface NS {
readonly infiltration: Infiltration; readonly infiltration: Infiltration;
/** /**
* Namespace for corporation functions. * Namespace for corporation functions.
* RAM cost: 0 GB * RAM cost: 1022.4 GB
*/ */
readonly corporation: Corporation; readonly corporation: Corporation;

@ -51,7 +51,7 @@ export function buyStock(
} }
if (stock == null || isNaN(shares)) { if (stock == null || isNaN(shares)) {
if (workerScript) { if (workerScript) {
workerScript.log("stock.buy", () => `Invalid arguments: stock='${stock}' shares='${shares}'`); workerScript.log("stock.buyStock", () => `Invalid arguments: stock='${stock}' shares='${shares}'`);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
} }
@ -67,7 +67,7 @@ export function buyStock(
if (Player.money < totalPrice) { if (Player.money < totalPrice) {
if (workerScript) { if (workerScript) {
workerScript.log( workerScript.log(
"stock.buy", "stock.buyStock",
() => () =>
`You do not have enough money to purchase this position. You need ${numeralWrapper.formatMoney(totalPrice)}.`, `You do not have enough money to purchase this position. You need ${numeralWrapper.formatMoney(totalPrice)}.`,
); );
@ -86,7 +86,7 @@ export function buyStock(
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (workerScript) { if (workerScript) {
workerScript.log( workerScript.log(
"stock.buy", "stock.buyStock",
() => () =>
`Purchasing '${shares + stock.playerShares + stock.playerShortShares}' shares would exceed ${ `Purchasing '${shares + stock.playerShares + stock.playerShortShares}' shares would exceed ${
stock.symbol stock.symbol
@ -119,7 +119,7 @@ export function buyStock(
} for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney( } for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(
CONSTANTS.StockMarketCommission, CONSTANTS.StockMarketCommission,
)} in commission fees.`; )} in commission fees.`;
workerScript.log("stock.buy", () => resultTxt); workerScript.log("stock.buyStock", () => resultTxt);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(
<> <>
@ -149,7 +149,7 @@ export function sellStock(
// Sanitize/Validate arguments // Sanitize/Validate arguments
if (stock == null || shares < 0 || isNaN(shares)) { if (stock == null || shares < 0 || isNaN(shares)) {
if (workerScript) { if (workerScript) {
workerScript.log("stock.sell", () => `Invalid arguments: stock='${stock}' shares='${shares}'`); workerScript.log("stock.sellStock", () => `Invalid arguments: stock='${stock}' shares='${shares}'`);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(
"Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer", "Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer",
@ -195,7 +195,7 @@ export function sellStock(
const resultTxt = const resultTxt =
`Sold ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol}. ` + `Sold ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol}. ` +
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`; `After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
workerScript.log("stock.sell", () => resultTxt); workerScript.log("stock.sellStock", () => resultTxt);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(
<> <>

@ -7,6 +7,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { CrimeType } from "../utils/WorkType"; import { CrimeType } from "../utils/WorkType";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { newWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
interface CrimeWorkParams { interface CrimeWorkParams {
crimeType: CrimeType; crimeType: CrimeType;
@ -42,14 +43,22 @@ export class CrimeWork extends Work {
return false; return false;
} }
earnings(): WorkStats {
const crime = this.getCrime();
return newWorkStats({
money: crime.money,
hackExp: crime.hacking_exp * 2,
strExp: crime.strength_exp * 2,
defExp: crime.defense_exp * 2,
dexExp: crime.dexterity_exp * 2,
agiExp: crime.agility_exp * 2,
chaExp: crime.charisma_exp * 2,
intExp: crime.intelligence_exp * 2,
});
}
commit(player: IPlayer): void { commit(player: IPlayer): void {
let crime = null; const crime = this.getCrime();
for (const i of Object.keys(Crimes)) {
if (Crimes[i].type == this.crimeType) {
crime = Crimes[i];
break;
}
}
if (crime == null) { if (crime == null) {
dialogBoxCreate( dialogBoxCreate(
`ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`, `ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`,
@ -59,33 +68,23 @@ export class CrimeWork extends Work {
const focusPenalty = player.focusPenalty(); const focusPenalty = player.focusPenalty();
// exp times 2 because were trying to maintain the same numbers as before the conversion // exp times 2 because were trying to maintain the same numbers as before the conversion
// Technically the definition of Crimes should have the success numbers and failure should divide by 4 // Technically the definition of Crimes should have the success numbers and failure should divide by 4
let hackExp = crime.hacking_exp * 2; let gains = scaleWorkStats(this.earnings(), focusPenalty);
let StrExp = crime.strength_exp * 2;
let DefExp = crime.defense_exp * 2;
let DexExp = crime.dexterity_exp * 2;
let AgiExp = crime.agility_exp * 2;
let ChaExp = crime.charisma_exp * 2;
let karma = crime.karma; let karma = crime.karma;
const success = determineCrimeSuccess(player, crime.type); const success = determineCrimeSuccess(player, crime.type);
if (success) { if (success) {
player.gainMoney(crime.money * focusPenalty, "crime"); player.gainMoney(gains.money, "crime");
player.numPeopleKilled += crime.kills; player.numPeopleKilled += crime.kills;
player.gainIntelligenceExp(crime.intelligence_exp * focusPenalty); player.gainIntelligenceExp(gains.intExp);
} else { } else {
hackExp /= 4; gains = scaleWorkStats(gains, 0.25);
StrExp /= 4;
DefExp /= 4;
DexExp /= 4;
AgiExp /= 4;
ChaExp /= 4;
karma /= 4; karma /= 4;
} }
player.gainHackingExp(hackExp * focusPenalty); player.gainHackingExp(gains.hackExp);
player.gainStrengthExp(StrExp * focusPenalty); player.gainStrengthExp(gains.strExp);
player.gainDefenseExp(DefExp * focusPenalty); player.gainDefenseExp(gains.defExp);
player.gainDexterityExp(DexExp * focusPenalty); player.gainDexterityExp(gains.dexExp);
player.gainAgilityExp(AgiExp * focusPenalty); player.gainAgilityExp(gains.agiExp);
player.gainCharismaExp(ChaExp * focusPenalty); player.gainCharismaExp(gains.chaExp);
player.karma -= karma * focusPenalty; player.karma -= karma * focusPenalty;
} }

@ -27,8 +27,8 @@ export class GraftingWork extends Work {
super(WorkType.GRAFTING, params?.singularity ?? true); super(WorkType.GRAFTING, params?.singularity ?? true);
this.unitCompleted = 0; this.unitCompleted = 0;
this.augmentation = params?.augmentation ?? AugmentationNames.Targeting1; this.augmentation = params?.augmentation ?? AugmentationNames.Targeting1;
const gAugs = GraftableAugmentations();
if (params?.player) params.player.loseMoney(GraftableAugmentations[this.augmentation].cost, "augmentations"); if (params?.player) params.player.loseMoney(gAugs[this.augmentation].cost, "augmentations");
} }
unitNeeded(): number { unitNeeded(): number {

@ -48,7 +48,7 @@ interface IWorkInfo {
stopTooltip?: string | React.ReactElement; stopTooltip?: string | React.ReactElement;
} }
export function ExpRows(rate: WorkStats): React.ReactElement[] { function ExpRows(rate: WorkStats): React.ReactElement[] {
return [ return [
rate.hackExp > 0 ? ( rate.hackExp > 0 ? (
<StatsRow <StatsRow
@ -119,6 +119,77 @@ export function ExpRows(rate: WorkStats): React.ReactElement[] {
]; ];
} }
function CrimeExpRows(rate: WorkStats): React.ReactElement[] {
return [
rate.hackExp > 0 ? (
<StatsRow
name="Hacking Exp"
color={Settings.theme.hack}
data={{
content: `${numeralWrapper.formatExp(rate.hackExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
rate.strExp > 0 ? (
<StatsRow
name="Strength Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(rate.strExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
rate.defExp > 0 ? (
<StatsRow
name="Defense Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(rate.defExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
rate.dexExp > 0 ? (
<StatsRow
name="Dexterity Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(rate.dexExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
rate.agiExp > 0 ? (
<StatsRow
name="Agility Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(rate.agiExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
rate.chaExp > 0 ? (
<StatsRow
name="Charisma Exp"
color={Settings.theme.cha}
data={{
content: `${numeralWrapper.formatExp(rate.chaExp * CYCLES_PER_SEC)}`,
}}
/>
) : (
<></>
),
];
}
export function WorkInProgressRoot(): React.ReactElement { export function WorkInProgressRoot(): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
@ -149,7 +220,7 @@ export function WorkInProgressRoot(): React.ReactElement {
if (isCrimeWork(player.currentWork)) { if (isCrimeWork(player.currentWork)) {
const crime = player.currentWork.getCrime(); const crime = player.currentWork.getCrime();
const completion = (player.currentWork.unitCompleted / crime.time) * 100; const completion = (player.currentWork.unitCompleted / crime.time) * 100;
const gains = player.currentWork.earnings();
workInfo = { workInfo = {
buttons: { buttons: {
cancel: () => { cancel: () => {
@ -163,6 +234,15 @@ export function WorkInProgressRoot(): React.ReactElement {
}, },
title: `You are attempting to ${crime.type}`, title: `You are attempting to ${crime.type}`,
gains: [
<Typography>Gains (on success)</Typography>,
<StatsRow name="Money:" color={Settings.theme.money}>
<Typography>
<Money money={gains.money} />
</Typography>
</StatsRow>,
...CrimeExpRows(gains),
],
progress: { progress: {
remaining: crime.time - player.currentWork.unitCompleted, remaining: crime.time - player.currentWork.unitCompleted,
percentage: completion, percentage: completion,