diff --git a/src/DarkNet/effects/cacheFiles.ts b/src/DarkNet/effects/cacheFiles.ts index 21ee04990..ab2f0f2bc 100644 --- a/src/DarkNet/effects/cacheFiles.ts +++ b/src/DarkNet/effects/cacheFiles.ts @@ -12,6 +12,7 @@ import type { DarknetServer } from "../../Server/DarknetServer"; import { resolveCacheFilePath } from "../../Paths/CacheFilePath"; import type { CacheResult } from "@nsdefs"; import { addClue, cctCooldownReached } from "./effects"; +import { getBitNodeMultipliers } from "../../BitNode/BitNode"; export const generateCacheFilename = (isPhishingCache: boolean, prefix?: string) => { const filenamePrefix = prefix ?? cachePrefixes[Math.floor(Math.random() * cachePrefixes.length)]; @@ -41,11 +42,15 @@ export const getRewardFromCache = (server: DarknetServer, cacheName: string, sup }; } - const rewards = [getMoneyReward, getProgramAndStockMarketRelatedRewards, getStockReward, getDataFileReward]; + const rewards = [getProgramAndStockMarketRelatedRewards, getStockReward, getDataFileReward]; if (cacheName.endsWith(".d.cache")) { // only include ccts from caches generated from phishing attacks rewards.push(getCCTReward); } + if (getBitNodeMultipliers(Player.bitNodeN, 1).DarknetMoneyMultiplier) { + // only include money reward if it is not disabled by the bn mults + rewards.push(getMoneyReward); + } const reward = rewards[Math.floor(Math.random() * rewards.length)]; const result = reward(difficulty, server); @@ -98,10 +103,14 @@ export const getStockReward = (difficulty: number): string => { initStockMarket(); } const stockSymbols = Object.keys(StockSymbol); - const randomStock = stockSymbols[Math.floor(Math.random() * stockSymbols.length)]; - const shares = Math.floor(1 + difficulty * 5 + Math.random() * 10); - StockMarket[randomStock].playerShares += shares; - return `You have discovered a stock option cache containing ${shares} shares of ${randomStock}!`; + const stock = StockMarket[stockSymbols[Math.floor(Math.random() * stockSymbols.length)]]; + const maxNewShares = stock.maxShares - stock.playerShares - stock.playerShortShares; + if (maxNewShares <= 0) { + return getMoneyReward(difficulty); + } + const shares = Math.min(Math.floor(1 + difficulty * 5 + Math.random() * 10), maxNewShares); + stock.playerShares += shares; + return `You have discovered a stock option cache containing ${shares} shares of ${stock.symbol}!`; }; export const getDataFileReward = (difficulty: number, server: DarknetServer): string => { diff --git a/test/jest/Darknet/Darknet.test.ts b/test/jest/Darknet/Darknet.test.ts index 40d29a8cf..19f58f2c5 100644 --- a/test/jest/Darknet/Darknet.test.ts +++ b/test/jest/Darknet/Darknet.test.ts @@ -61,9 +61,11 @@ import { DarknetServer } from "../../../src/Server/DarknetServer"; import { isDirectoryPath } from "../../../src/Paths/Directory"; import { isFilePath } from "../../../src/Paths/FilePath"; import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth"; -import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles"; +import { generateCacheFilename, getStockReward } from "../../../src/DarkNet/effects/cacheFiles"; import { getAllDarknetServers } from "../../../src/DarkNet/utils/darknetNetworkUtils"; import { prestigeAugmentation } from "../../../src/Prestige"; +import { initStockMarket, StockMarket } from "../../../src/StockMarket/StockMarket"; +import { StockSymbol } from "@enums"; beforeAll(() => { initGameEnvironment(); @@ -839,3 +841,48 @@ describe("Clue filename generator", () => { } }); }); + +describe("Stock cache reward", () => { + test("stock reward does not exceed maxShares and falls back to money reward", () => { + initStockMarket(); + + const remaining = 3; + // Fill every stock to near capacity so no matter which one is randomly picked, it triggers clamping + for (const stockName of Object.keys(StockSymbol)) { + const stock = StockMarket[stockName]; + stock.playerShares = stock.maxShares - remaining; + stock.playerShortShares = 0; + } + + // Use high difficulty to ensure the unclamped share count would exceed remaining + const difficulty = 100; + const result = getStockReward(difficulty); + + // Should have awarded at most `remaining` shares + expect(result).toContain(`${remaining} shares`); + + // Verify the chosen stock was clamped to exactly maxShares + for (const stockName of Object.keys(StockSymbol)) { + const stock = StockMarket[stockName]; + expect(stock.playerShares).toBeLessThanOrEqual(stock.maxShares); + } + }); + + test("stock reward falls back to money when stock is fully owned", () => { + initStockMarket(); + + // Fill every stock to max capacity + for (const stockName of Object.keys(StockSymbol)) { + const stock = StockMarket[stockName]; + stock.playerShares = stock.maxShares; + stock.playerShortShares = 0; + } + + const moneyBefore = Player.money; + const result = getStockReward(5); + + // Should have fallen back to a money reward + expect(result).toContain("discovered a cache with"); + expect(Player.money).toBeGreaterThan(moneyBefore); + }); +});