diff --git a/doc/source/index.rst b/doc/source/index.rst index b2da5daed..9e32ebff3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -30,6 +30,7 @@ secrets that you've been searching for. Tools & Resources Changelog v1.0.0 script migration guide + v2.0.0 script migration guide 404 <404.rst> Donate diff --git a/doc/source/v2.0.0_migration.rst b/doc/source/v2.0.0_migration.rst new file mode 100644 index 000000000..868eb2663 --- /dev/null +++ b/doc/source/v2.0.0_migration.rst @@ -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. \ No newline at end of file diff --git a/src/Alias.ts b/src/Alias.ts index 59375c3e7..ee2801564 100644 --- a/src/Alias.ts +++ b/src/Alias.ts @@ -36,16 +36,18 @@ export function printAliases(): void { // Returns true if successful, false otherwise export function parseAliasDeclaration(dec: string, global = false): boolean { - const re = /^([\w|!%,@-]+)=(("(.+)")|('(.+)'))$/; + console.log(dec); + const re = /^([\w|!%,@-]+)=(.+)$/; const matches = dec.match(re); - if (matches == null || matches.length != 7) { + console.log(matches); + if (matches == null || matches.length != 3) { return false; } if (global) { - addGlobalAlias(matches[1], matches[4] || matches[6]); + addGlobalAlias(matches[1], matches[2]); } else { - addAlias(matches[1], matches[4] || matches[6]); + addAlias(matches[1], matches[2]); } return true; } diff --git a/src/GameOptions/ui/CurrentOptionsPage.tsx b/src/GameOptions/ui/CurrentOptionsPage.tsx index 7538a2df2..639ba21a8 100644 --- a/src/GameOptions/ui/CurrentOptionsPage.tsx +++ b/src/GameOptions/ui/CurrentOptionsPage.tsx @@ -107,12 +107,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => { step={25} min={0} max={500} - tooltip={ - <> - 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. - - } + tooltip={<>The maximum number of recently killed scripts the game will keep.} /> = {}; +export const GraftableAugmentations = (): Record => { + const gAugs: Record = {}; + 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 => { if (player.money < aug.cost) { @@ -56,11 +63,7 @@ export const GraftingRoot = (): React.ReactElement => { const player = use.Player(); const router = use.Router(); - for (const aug of Object.values(StaticAugmentations)) { - const name = aug.name; - const graftableAug = new GraftableAugmentation(aug); - GraftableAugmentations[name] = graftableAug; - } + const graftableAugmentations = useState(GraftableAugmentations())[0]; const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs(player)[0]); const [graftOpen, setGraftOpen] = useState(false); @@ -75,7 +78,7 @@ export const GraftingRoot = (): React.ReactElement => { const augs = getGraftingAvailableAugs(player); switch (Settings.PurchaseAugmentationsOrder) { 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: return augs; } @@ -126,7 +129,7 @@ export const GraftingRoot = (): React.ReactElement => { setSelectedAug(k)} selected={selectedAug === k}> { @@ -183,7 +186,7 @@ export const GraftingRoot = (): React.ReactElement => { Time to Graft:{" "} {convertTimeMsToTimeElapsedString( - calculateGraftingTimeWithBonus(player, GraftableAugmentations[selectedAug]), + calculateGraftingTimeWithBonus(player, graftableAugmentations[selectedAug]), )} {/* Use formula so the displayed creation time is accurate to player bonus */} diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 191a41c89..8d4e106eb 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -4471,7 +4471,7 @@ export interface NS { readonly infiltration: Infiltration; /** * Namespace for corporation functions. - * RAM cost: 0 GB + * RAM cost: 1022.4 GB */ readonly corporation: Corporation; diff --git a/src/StockMarket/BuyingAndSelling.tsx b/src/StockMarket/BuyingAndSelling.tsx index 4df530f10..4a5c053eb 100644 --- a/src/StockMarket/BuyingAndSelling.tsx +++ b/src/StockMarket/BuyingAndSelling.tsx @@ -51,7 +51,7 @@ export function buyStock( } if (stock == null || isNaN(shares)) { 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) { dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); } @@ -67,7 +67,7 @@ export function buyStock( if (Player.money < totalPrice) { if (workerScript) { workerScript.log( - "stock.buy", + "stock.buyStock", () => `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 (workerScript) { workerScript.log( - "stock.buy", + "stock.buyStock", () => `Purchasing '${shares + stock.playerShares + stock.playerShortShares}' shares would exceed ${ stock.symbol @@ -119,7 +119,7 @@ export function buyStock( } for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney( CONSTANTS.StockMarketCommission, )} in commission fees.`; - workerScript.log("stock.buy", () => resultTxt); + workerScript.log("stock.buyStock", () => resultTxt); } else if (opts.suppressDialog !== true) { dialogBoxCreate( <> @@ -149,7 +149,7 @@ export function sellStock( // Sanitize/Validate arguments if (stock == null || shares < 0 || isNaN(shares)) { 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) { dialogBoxCreate( "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 = `Sold ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol}. ` + `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) { dialogBoxCreate( <> diff --git a/src/Work/CrimeWork.ts b/src/Work/CrimeWork.ts index 7ba066151..bc05a5501 100644 --- a/src/Work/CrimeWork.ts +++ b/src/Work/CrimeWork.ts @@ -7,6 +7,7 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { CrimeType } from "../utils/WorkType"; import { Work, WorkType } from "./Work"; +import { newWorkStats, scaleWorkStats, WorkStats } from "./WorkStats"; interface CrimeWorkParams { crimeType: CrimeType; @@ -42,14 +43,22 @@ export class CrimeWork extends Work { 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 { - let crime = null; - for (const i of Object.keys(Crimes)) { - if (Crimes[i].type == this.crimeType) { - crime = Crimes[i]; - break; - } - } + const crime = this.getCrime(); if (crime == null) { dialogBoxCreate( `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(); // 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 - let hackExp = crime.hacking_exp * 2; - 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 gains = scaleWorkStats(this.earnings(), focusPenalty); let karma = crime.karma; const success = determineCrimeSuccess(player, crime.type); if (success) { - player.gainMoney(crime.money * focusPenalty, "crime"); + player.gainMoney(gains.money, "crime"); player.numPeopleKilled += crime.kills; - player.gainIntelligenceExp(crime.intelligence_exp * focusPenalty); + player.gainIntelligenceExp(gains.intExp); } else { - hackExp /= 4; - StrExp /= 4; - DefExp /= 4; - DexExp /= 4; - AgiExp /= 4; - ChaExp /= 4; + gains = scaleWorkStats(gains, 0.25); karma /= 4; } - player.gainHackingExp(hackExp * focusPenalty); - player.gainStrengthExp(StrExp * focusPenalty); - player.gainDefenseExp(DefExp * focusPenalty); - player.gainDexterityExp(DexExp * focusPenalty); - player.gainAgilityExp(AgiExp * focusPenalty); - player.gainCharismaExp(ChaExp * focusPenalty); + player.gainHackingExp(gains.hackExp); + player.gainStrengthExp(gains.strExp); + player.gainDefenseExp(gains.defExp); + player.gainDexterityExp(gains.dexExp); + player.gainAgilityExp(gains.agiExp); + player.gainCharismaExp(gains.chaExp); player.karma -= karma * focusPenalty; } diff --git a/src/Work/GraftingWork.tsx b/src/Work/GraftingWork.tsx index 0e7d29292..c1f328da2 100644 --- a/src/Work/GraftingWork.tsx +++ b/src/Work/GraftingWork.tsx @@ -27,8 +27,8 @@ export class GraftingWork extends Work { super(WorkType.GRAFTING, params?.singularity ?? true); this.unitCompleted = 0; this.augmentation = params?.augmentation ?? AugmentationNames.Targeting1; - - if (params?.player) params.player.loseMoney(GraftableAugmentations[this.augmentation].cost, "augmentations"); + const gAugs = GraftableAugmentations(); + if (params?.player) params.player.loseMoney(gAugs[this.augmentation].cost, "augmentations"); } unitNeeded(): number { diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index 4d51c2d14..70e847cc6 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -48,7 +48,7 @@ interface IWorkInfo { stopTooltip?: string | React.ReactElement; } -export function ExpRows(rate: WorkStats): React.ReactElement[] { +function ExpRows(rate: WorkStats): React.ReactElement[] { return [ rate.hackExp > 0 ? ( 0 ? ( + + ) : ( + <> + ), + rate.strExp > 0 ? ( + + ) : ( + <> + ), + rate.defExp > 0 ? ( + + ) : ( + <> + ), + rate.dexExp > 0 ? ( + + ) : ( + <> + ), + rate.agiExp > 0 ? ( + + ) : ( + <> + ), + rate.chaExp > 0 ? ( + + ) : ( + <> + ), + ]; +} + export function WorkInProgressRoot(): React.ReactElement { const setRerender = useState(false)[1]; function rerender(): void { @@ -149,7 +220,7 @@ export function WorkInProgressRoot(): React.ReactElement { if (isCrimeWork(player.currentWork)) { const crime = player.currentWork.getCrime(); const completion = (player.currentWork.unitCompleted / crime.time) * 100; - + const gains = player.currentWork.earnings(); workInfo = { buttons: { cancel: () => { @@ -163,6 +234,15 @@ export function WorkInProgressRoot(): React.ReactElement { }, title: `You are attempting to ${crime.type}`, + gains: [ + Gains (on success), + + + + + , + ...CrimeExpRows(gains), + ], progress: { remaining: crime.time - player.currentWork.unitCompleted, percentage: completion,