From b7157b63e0dca58f6c62a1cf0994a7449af63900 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Thu, 21 Feb 2019 18:26:28 -0800 Subject: [PATCH] Implemented a money tracker that keeps record where all of the player's money comes from. Players can see a breakdown in the 'Stats' page --- src/Bladeburner.js | 1 + src/Corporation/Corporation.js | 5 +- .../{CrimeHelpers.js => CrimeHelpers.ts} | 33 ++-- src/Gang.js | 1 + src/HacknetNode.js | 29 ++-- src/NetscriptFunctions.js | 4 +- src/PersonObjects/IPlayer.ts | 35 ++++ src/Player.js | 35 ++-- src/SaveObject.js | 2 - src/Script.js | 1 + src/StockMarket/StockMarket.js | 2 + src/Terminal.js | 1 + src/engine.js | 101 +----------- src/ui/displayCharacterInfo.ts | 156 ++++++++++++++++++ src/utils/MoneySourceTracker.ts | 56 +++++++ utils/InfiltrationBox.js | 1 + 16 files changed, 321 insertions(+), 142 deletions(-) rename src/Crime/{CrimeHelpers.js => CrimeHelpers.ts} (69%) create mode 100644 src/ui/displayCharacterInfo.ts create mode 100644 src/utils/MoneySourceTracker.ts diff --git a/src/Bladeburner.js b/src/Bladeburner.js index b7b1ead82..fd14be272 100644 --- a/src/Bladeburner.js +++ b/src/Bladeburner.js @@ -1209,6 +1209,7 @@ Bladeburner.prototype.completeAction = function() { if (!isOperation) { moneyGain = ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; Player.gainMoney(moneyGain); + Player.recordMoneySource(moneyGain, "bladeburner"); } if (isOperation) { diff --git a/src/Corporation/Corporation.js b/src/Corporation/Corporation.js index 3bc5227af..69708e7d3 100644 --- a/src/Corporation/Corporation.js +++ b/src/Corporation/Corporation.js @@ -2868,7 +2868,9 @@ Corporation.prototype.process = function() { const totalDividends = (this.dividendPercentage / 100) * cycleProfit; const retainedEarnings = cycleProfit - totalDividends; const dividendsPerShare = totalDividends / this.totalShares; - Player.gainMoney(this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100))); + const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100)); + Player.gainMoney(profit); + Player.recordMoneySource(profit, "corporation"); this.funds = this.funds.plus(retainedEarnings); } } else { @@ -3533,6 +3535,7 @@ Corporation.prototype.displayCorporationOverviewContent = function() { this.shareSalesUntilPriceUpdate = newSharesUntilUpdate; this.shareSaleCooldown = SellSharesCooldown; Player.gainMoney(profit); + Player.recordMoneySource(profit, "corporation"); removeElementById(popupId); dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` + `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` + diff --git a/src/Crime/CrimeHelpers.js b/src/Crime/CrimeHelpers.ts similarity index 69% rename from src/Crime/CrimeHelpers.js rename to src/Crime/CrimeHelpers.ts index a40b2e96a..27b2fd233 100644 --- a/src/Crime/CrimeHelpers.js +++ b/src/Crime/CrimeHelpers.ts @@ -1,30 +1,27 @@ import { Crimes } from "./Crimes"; - -import { Player } from "../Player"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { dialogBoxCreate } from "../../utils/DialogBox"; -export function determineCrimeSuccess(type, moneyGained) { - var chance = 0; - var found = false; - for(const i in Crimes) { - const crime = Crimes[i]; - if(crime.type == type) { - chance = crime.successRate(Player); - found = true; - break; - } +export function determineCrimeSuccess(p: IPlayer, type: string) { + let chance: number = 0; + let found: boolean = false; + for (const i in Crimes) { + let crime = Crimes[i]; + if (crime.type == type) { + chance = crime.successRate(p); + found = true; + break; + } } - if(!found) { - console.log(crime); - dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer"); - return; + if (!found) { + dialogBoxCreate(`ERR: Unrecognized crime type: ${type} This is probably a bug please contact the developer`, false); + return; } if (Math.random() <= chance) { //Success - Player.gainMoney(moneyGained); return true; } else { //Failure @@ -32,7 +29,7 @@ export function determineCrimeSuccess(type, moneyGained) { } } -export function findCrime(roughName) { +export function findCrime(roughName: string) { if (roughName.includes("shoplift")) { return Crimes.Shoplift; } else if (roughName.includes("rob") && roughName.includes("store")) { diff --git a/src/Gang.js b/src/Gang.js index 6b6c2de23..0d858648f 100644 --- a/src/Gang.js +++ b/src/Gang.js @@ -247,6 +247,7 @@ Gang.prototype.processGains = function(numCycles=1, player) { } if (typeof moneyGains === "number") { player.gainMoney(moneyGains * numCycles); + player.recordMoneySource(moneyGains * numCycles, "gang"); } else { console.warn("ERROR: respectGains is NaN"); } diff --git a/src/HacknetNode.js b/src/HacknetNode.js index cf6433300..3ca569288 100644 --- a/src/HacknetNode.js +++ b/src/HacknetNode.js @@ -4,15 +4,19 @@ import { Engine } from "./engine"; import {iTutorialSteps, iTutorialNextStep, ITutorial} from "./InteractiveTutorial"; import {Player} from "./Player"; +import {Page, routing} from "./ui/navigationTracking"; +import { numeralWrapper } from "./ui/numeralFormat"; + import {dialogBoxCreate} from "../utils/DialogBox"; import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners"; import {Reviver, Generic_toJSON, Generic_fromJSON} from "../utils/JSONReviver"; import {createElement} from "../utils/uiHelpers/createElement"; -import {Page, routing} from "./ui/navigationTracking"; -import {formatNumber} from "../utils/StringHelperFunctions"; import {getElementById} from "../utils/uiHelpers/getElementById"; +// Stores total money gain rate from all of the player's Hacknet Nodes +let TotalHacknetNodeProduction = 0; + /** * Overwrites the inner text of the specified HTML element if it is different from what currently exists. * @param {string} elementId The HTML ID to find the first instance of. @@ -299,7 +303,7 @@ function updateTotalHacknetProduction() { for (var i = 0; i < Player.hacknetNodes.length; ++i) { total += Player.hacknetNodes[i].moneyGainRatePerSecond; } - Player.totalHacknetNodeProduction = total; + TotalHacknetNodeProduction = total; } function getCostOfNextHacknetNode() { @@ -446,9 +450,9 @@ function updateHacknetNodesContent() { //Set purchase button to inactive if not enough money, and update its price display var cost = getCostOfNextHacknetNode(); var purchaseButton = getElementById("hacknet-nodes-purchase-button"); - var formattedCost = formatNumber(cost, 2); + var formattedCost = numeralWrapper.formatMoney(cost); - updateText("hacknet-nodes-purchase-button", "Purchase Hacknet Node - $" + formattedCost); + updateText("hacknet-nodes-purchase-button", `Purchase Hacknet Node - ${formattedCost}`); if (Player.money.lt(cost)) { purchaseButton.setAttribute("class", "a-link-button-inactive"); @@ -457,8 +461,8 @@ function updateHacknetNodesContent() { } //Update player's money - updateText("hacknet-nodes-player-money", "$" + formatNumber(Player.money.toNumber(), 2)); - updateText("hacknet-nodes-total-production", "$" + formatNumber(Player.totalHacknetNodeProduction, 2) + " / sec"); + updateText("hacknet-nodes-player-money", numeralWrapper.formatMoney(Player.money.toNumber())); + updateText("hacknet-nodes-total-production", numeralWrapper.formatMoney(TotalHacknetNodeProduction) + " / sec"); //Update information in each owned hacknet node for (var i = 0; i < Player.hacknetNodes.length; ++i) { @@ -559,8 +563,8 @@ function updateHacknetNodeDomElement(nodeObj) { var nodeName = nodeObj.name; updateText("hacknet-node-name-" + nodeName, nodeName); - updateText("hacknet-node-total-production-" + nodeName, "$" + formatNumber(nodeObj.totalMoneyGenerated, 2)); - updateText("hacknet-node-production-rate-" + nodeName, "($" + formatNumber(nodeObj.moneyGainRatePerSecond, 2) + " / sec)"); + updateText("hacknet-node-total-production-" + nodeName, numeralWrapper.formatMoney(nodeObj.totalMoneyGenerated)); + updateText("hacknet-node-production-rate-" + nodeName, "(" + numeralWrapper.formatMoney(nodeObj.moneyGainRatePerSecond) + " / sec)"); updateText("hacknet-node-level-" + nodeName, nodeObj.level); updateText("hacknet-node-ram-" + nodeName, nodeObj.ram + "GB"); updateText("hacknet-node-cores-" + nodeName, nodeObj.cores); @@ -582,7 +586,7 @@ function updateHacknetNodeDomElement(nodeObj) { } var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(multiplier); - updateText("hacknet-node-upgrade-level-" + nodeName, "Upgrade x" + multiplier + " - $" + formatNumber(upgradeLevelCost, 2)) + updateText("hacknet-node-upgrade-level-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeLevelCost)) if (Player.money.lt(upgradeLevelCost)) { upgradeLevelButton.setAttribute("class", "a-link-button-inactive"); } else { @@ -606,7 +610,7 @@ function updateHacknetNodeDomElement(nodeObj) { } var upgradeRamCost = nodeObj.calculateRamUpgradeCost(multiplier); - updateText("hacknet-node-upgrade-ram-" + nodeName, "Upgrade x" + multiplier + " - $" + formatNumber(upgradeRamCost, 2)); + updateText("hacknet-node-upgrade-ram-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeRamCost)); if (Player.money.lt(upgradeRamCost)) { upgradeRamButton.setAttribute("class", "a-link-button-inactive"); } else { @@ -629,7 +633,7 @@ function updateHacknetNodeDomElement(nodeObj) { multiplier = Math.min(levelsToMax, hacknetNodePurchaseMultiplier); } var upgradeCoreCost = nodeObj.calculateCoreUpgradeCost(multiplier); - updateText("hacknet-node-upgrade-core-" + nodeName, "Upgrade x" + multiplier + " - $" + formatNumber(upgradeCoreCost, 2)); + updateText("hacknet-node-upgrade-core-" + nodeName, "Upgrade x" + multiplier + " - " + numeralWrapper.formatMoney(upgradeCoreCost)); if (Player.money.lt(upgradeCoreCost)) { upgradeCoreButton.setAttribute("class", "a-link-button-inactive"); } else { @@ -660,6 +664,7 @@ function processSingleHacknetNodeEarnings(numCycles, nodeObj) { nodeObj.totalMoneyGenerated += totalEarnings; nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000)); Player.gainMoney(totalEarnings); + Player.recordMoneySource(totalEarnings, "hacknetnode"); return totalEarnings; } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 2e67db7f6..439eebfa5 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -8,7 +8,7 @@ import { augmentationExists, installAugmentations } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { determineCrimeSuccess, findCrime } from "./Crime/CrimeHelpers"; +import { findCrime } from "./Crime/CrimeHelpers"; import {Bladeburner} from "./Bladeburner"; import {Company} from "./Company/Company"; import {Companies, companyExists} from "./Company/Companies"; @@ -381,6 +381,7 @@ function NetscriptFunctions(workerScript) { Player.gainMoney(moneyGained); workerScript.scriptRef.onlineMoneyMade += moneyGained; Player.scriptProdSinceLastAug += moneyGained; + Player.recordMoneySource(moneyGained, "hacking"); workerScript.scriptRef.recordHack(server.ip, moneyGained, threads); Player.gainHackingExp(expGainedOnSuccess); workerScript.scriptRef.onlineExpGained += expGainedOnSuccess; @@ -1621,6 +1622,7 @@ function NetscriptFunctions(workerScript) { if (isNaN(netProfit)) {netProfit = 0;} workerScript.scriptRef.onlineMoneyMade += netProfit; Player.scriptProdSinceLastAug += netProfit; + Player.recordMoneySource(netProfit, "stock"); stock.playerShares -= shares; if (stock.playerShares == 0) { diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 3325bd274..5c3ad149e 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -10,23 +10,32 @@ import { IMap } from "../types"; import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile"; +import { MoneySourceTracker } from "../utils/MoneySourceTracker"; export interface IPlayer { // Class members augmentations: IPlayerOwnedAugmentation[]; bladeburner: any; bitNodeN: number; + city: string; companyName: string; corporation: any; factions: string[]; + hacknetNodes: any[]; hasWseAccount: boolean; jobs: IMap; money: any; + moneySourceA: MoneySourceTracker; + moneySourceB: MoneySourceTracker; + playtimeSinceLastAug: number; + playtimeSinceLastBitnode: number; + purchasedServers: any[]; queuedAugmentations: IPlayerOwnedAugmentation[]; resleeves: Resleeve[]; sleeves: Sleeve[]; sleevesFromCovenant: number; sourceFiles: IPlayerOwnedSourceFile[]; + totalPlaytime: number; // Stats hacking_skill: number; @@ -46,7 +55,32 @@ export interface IPlayer { charisma_exp: number; // Multipliers + hacking_chance_mult: number; + hacking_speed_mult: number; + hacking_money_mult: number; + hacking_grow_mult: number; + hacking_mult: number; + hacking_exp_mult: number; + strength_mult: number; + strength_exp_mult: number; + defense_mult: number; + defense_exp_mult: number; + dexterity_mult: number; + dexterity_exp_mult: number; + agility_mult: number; + agility_exp_mult: number; + charisma_mult: number; + charisma_exp_mult: number; + hacknet_node_money_mult: number; + hacknet_node_purchase_cost_mult: number; + hacknet_node_ram_cost_mult: number; + hacknet_node_core_cost_mult: number; + hacknet_node_level_cost_mult: number; + company_rep_mult: number; + faction_rep_mult: number; + work_money_mult: number; crime_success_mult: number; + crime_money_mult: number; // Methods canAfford(cost: number): boolean; @@ -63,6 +97,7 @@ export interface IPlayer { loseMoney(money: number): void; reapplyAllAugmentations(resetMultipliers: boolean): void; reapplyAllSourceFiles(): void; + recordMoneySource(amt: number, source: string): void; startCrime(crimeType: string, hackExp: number, strExp: number, diff --git a/src/Player.js b/src/Player.js index 497c921f0..231b369cb 100644 --- a/src/Player.js +++ b/src/Player.js @@ -31,6 +31,7 @@ import {SourceFiles, applySourceFile} from "./SourceFile"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import Decimal from "decimal.js"; import {numeralWrapper} from "./ui/numeralFormat"; +import { MoneySourceTracker } from "./utils/MoneySourceTracker"; import {dialogBoxCreate} from "../utils/DialogBox"; import {clearEventListeners} from "../utils/uiHelpers/clearEventListeners"; import {createRandomIp} from "../utils/IPAddress"; @@ -92,8 +93,6 @@ function PlayerObject() { //Money this.money = new Decimal(1000); - this.total_money = new Decimal(0); //Total money ever earned in this "simulation" - this.lifetime_money = new Decimal(0); //Total money ever earned //IP Address of Starting (home) computer this.homeComputer = ""; @@ -114,7 +113,6 @@ function PlayerObject() { this.currentServer = ""; //IP address of Server currently being accessed through terminal this.purchasedServers = []; //IP Addresses of purchased servers this.hacknetNodes = []; - this.totalHacknetNodeProduction = 0; //Factions this.factions = []; //Names of all factions player has joined @@ -218,11 +216,12 @@ function PlayerObject() { this.playtimeSinceLastAug = 0; this.playtimeSinceLastBitnode = 0; - //Production since last Augmentation installation + // Keep track of where money comes from + 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; - this.stockProdSinceLastAug = 0; - this.crimeProdSinceLastAug = 0; - this.jobProdSinceLastAug = 0; }; PlayerObject.prototype.init = function() { @@ -315,11 +314,12 @@ PlayerObject.prototype.prestigeAugmentation = function() { this.lastUpdate = new Date().getTime(); + // Statistics Trackers this.playtimeSinceLastAug = 0; this.scriptProdSinceLastAug = 0; + this.moneySourceA.reset(); this.hacknetNodes.length = 0; - this.totalHacknetNodeProduction = 0; //Re-calculate skills and reset HP this.updateSkillLevels(); @@ -405,7 +405,6 @@ PlayerObject.prototype.prestigeSourceFile = function() { this.lastUpdate = new Date().getTime(); this.hacknetNodes.length = 0; - this.totalHacknetNodeProduction = 0; //Gang this.gang = null; @@ -420,9 +419,12 @@ PlayerObject.prototype.prestigeSourceFile = function() { //BitNode 3: Corporatocracy this.corporation = 0; + // Statistics trackers this.playtimeSinceLastAug = 0; this.playtimeSinceLastBitnode = 0; this.scriptProdSinceLastAug = 0; + this.moneySourceA.reset(); + this.moneySourceB.reset(); this.updateSkillLevels(); this.hp = this.max_hp; @@ -542,8 +544,6 @@ PlayerObject.prototype.gainMoney = function(money) { console.log("ERR: NaN passed into Player.gainMoney()"); return; } this.money = this.money.plus(money); - this.total_money = this.total_money.plus(money); - this.lifetime_money = this.lifetime_money.plus(money); } PlayerObject.prototype.loseMoney = function(money) { @@ -561,6 +561,11 @@ PlayerObject.prototype.canAfford = function(cost) { return this.money.gte(cost); } +PlayerObject.prototype.recordMoneySource = function(amt, source) { + this.moneySourceA.record(amt, source); + this.moneySourceB.record(amt, source); +} + PlayerObject.prototype.gainHackingExp = function(exp) { if (isNaN(exp)) { console.log("ERR: NaN passed into Player.gainHackingExp()"); return; @@ -690,6 +695,7 @@ PlayerObject.prototype.processWorkEarnings = function(numCycles=1) { this.gainAgilityExp(agiExpGain); this.gainCharismaExp(chaExpGain); this.gainMoney(moneyGain); + this.recordMoneySource(moneyGain, "work"); this.workHackExpGained += hackExpGain; this.workStrExpGained += strExpGain; this.workDefExpGained += defExpGain; @@ -1561,7 +1567,7 @@ PlayerObject.prototype.finishCrime = function(cancelled) { //Determine crime success/failure if (!cancelled) { var statusText = ""; //TODO, unique message for each crime when you succeed - if (determineCrimeSuccess(this.crimeType, this.workMoneyGained)) { + if (determineCrimeSuccess(Player, this.crimeType)) { //Handle Karma and crime statistics let crime = null; for(const i in Crimes) { @@ -1574,6 +1580,8 @@ PlayerObject.prototype.finishCrime = function(cancelled) { console.log(this.crimeType); dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer"); } + Player.gainMoney(this.workMoneyGained); + Player.recordMoneySource(this.workMoneyGained, "crime"); this.karma -= crime.karma; this.numPeopleKilled += crime.kills; if(crime.intelligence_exp > 0) { @@ -2429,6 +2437,7 @@ PlayerObject.prototype.gainCodingContractReward = function(reward, difficulty=1) default: var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney; this.gainMoney(moneyGain); + this.recordMoneySource(moneyGain, "codingcontract"); return `Gained ${numeralWrapper.format(moneyGain, '$0.000a')}`; break; } @@ -2441,8 +2450,6 @@ function loadPlayer(saveString) { //Parse Decimal.js objects Player.money = new Decimal(Player.money); - Player.total_money = new Decimal(Player.total_money); - Player.lifetime_money = new Decimal(Player.lifetime_money); if (Player.corporation instanceof Corporation) { Player.corporation.funds = new Decimal(Player.corporation.funds); diff --git a/src/SaveObject.js b/src/SaveObject.js index 69492e264..5667b1efb 100755 --- a/src/SaveObject.js +++ b/src/SaveObject.js @@ -293,8 +293,6 @@ function loadImportedGame(saveObj, saveString) { //Parse Decimal.js objects tempPlayer.money = new Decimal(tempPlayer.money); - tempPlayer.total_money = new Decimal(tempPlayer.total_money); - tempPlayer.lifetime_money = new Decimal(tempPlayer.lifetime_money); tempAllServers = JSON.parse(tempSaveObj.AllServersSave, Reviver); tempCompanies = JSON.parse(tempSaveObj.CompaniesSave, Reviver); diff --git a/src/Script.js b/src/Script.js index fe511aad8..5733ab404 100755 --- a/src/Script.js +++ b/src/Script.js @@ -854,6 +854,7 @@ function scriptCalculateOfflineProduction(runningScriptObj) { } totalOfflineProduction += production; Player.gainMoney(production); + Player.recordMoneySource(production, "hacking"); console.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname); runningScriptObj.log(runningScriptObj.filename + " generated $" + production + " while offline by hacking " + serv.hostname); serv.moneyAvailable -= production; diff --git a/src/StockMarket/StockMarket.js b/src/StockMarket/StockMarket.js index 8a3080a77..c6f0f4d51 100644 --- a/src/StockMarket/StockMarket.js +++ b/src/StockMarket/StockMarket.js @@ -454,6 +454,7 @@ function sellStock(stock, shares) { if (shares === 0) {return false;} var gains = stock.price * shares - CONSTANTS.StockMarketCommission; Player.gainMoney(gains); + Player.recordMoneySource(profit, "stock"); stock.playerShares = Math.round(stock.playerShares - shares); if (stock.playerShares == 0) { stock.playerAvgPx = 0; @@ -550,6 +551,7 @@ function sellShort(stock, shares, workerScript=null) { var profit = ((stock.playerAvgShortPx - stock.price) * shares) - CONSTANTS.StockMarketCommission; if (isNaN(profit)) {profit = 0;} Player.gainMoney(origCost + profit); + Player.recordMoneySource(profit, "stock"); if (tixApi) { workerScript.scriptRef.onlineMoneyMade += profit; Player.scriptProdSinceLastAug += profit; diff --git a/src/Terminal.js b/src/Terminal.js index 1f8c8994f..926318047 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -729,6 +729,7 @@ let Terminal = { server.moneyAvailable -= moneyGained; Player.gainMoney(moneyGained); + Player.recordMoneySource(moneyGained, "hacking"); Player.gainHackingExp(expGainedOnSuccess) Player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain); diff --git a/src/engine.js b/src/engine.js index 6c3ca4388..1122e4469 100644 --- a/src/engine.js +++ b/src/engine.js @@ -76,6 +76,7 @@ import { clearResleevesPage, createResleevesPage } from "./PersonObjects/Resleeving/ResleevingUI"; import { createStatusText } from "./ui/createStatusText"; +import { displayCharacterInfo } from "./ui/displayCharacterInfo"; import {Page, routing} from "./ui/navigationTracking"; import {numeralWrapper} from "./ui/numeralFormat"; import {setSettingsLabels} from "./ui/setSettingsLabels"; @@ -271,7 +272,7 @@ const Engine = { loadCharacterContent: function() { Engine.hideAllContent(); Engine.Display.characterContent.style.display = "block"; - Engine.displayCharacterInfo(); + Engine.updateCharacterInfo(); routing.navigateTo(Page.CharacterInfo); MainMenuLinks.Stats.classList.add("active"); }, @@ -593,96 +594,8 @@ const Engine = { }, /* Display character info */ - displayCharacterInfo: function() { - removeChildrenFromElement(Engine.Display.characterInfo); - - let companyPosition = ""; - if (Player.companyName !== "") { - companyPosition = Player.jobs[Player.companyName]; - } - - var intText = ""; - if (Player.intelligence > 0) { - intText = 'Intelligence: ' + (Player.intelligence).toLocaleString() + '
'; - } - - let bitNodeTimeText = ""; - if(Player.sourceFiles.length > 0) { - bitNodeTimeText = 'Time played since last Bitnode destroyed: ' + convertTimeMsToTimeElapsedString(Player.playtimeSinceLastBitnode) + '
'; - } - - Engine.Display.characterInfo.appendChild(createElement("pre", { - innerHTML: - 'General

' + - 'Current City: ' + Player.city + '

' + - `Employer at which you last worked: ${Player.companyName}
` + - `Job you last worked: ${companyPosition}
` + - `All Employers: ${Object.keys(Player.jobs).join(", ")}

` + - 'Money: $' + formatNumber(Player.money.toNumber(), 2) + '


' + - 'Stats

' + - 'Hacking Level: ' + (Player.hacking_skill).toLocaleString() + - ' (' + numeralWrapper.format(Player.hacking_exp, '(0.000a)') + ' experience)
' + - 'Strength: ' + (Player.strength).toLocaleString() + - ' (' + numeralWrapper.format(Player.strength_exp, '(0.000a)') + ' experience)
' + - 'Defense: ' + (Player.defense).toLocaleString() + - ' (' + numeralWrapper.format(Player.defense_exp, '(0.000a)') + ' experience)
' + - 'Dexterity: ' + (Player.dexterity).toLocaleString() + - ' (' + numeralWrapper.format(Player.dexterity_exp, '(0.000a)') + ' experience)
' + - 'Agility: ' + (Player.agility).toLocaleString() + - ' (' + numeralWrapper.format(Player.agility_exp, '(0.000a)') + ' experience)
' + - 'Charisma: ' + (Player.charisma).toLocaleString() + - ' (' + numeralWrapper.format(Player.charisma_exp, '(0.000a)') + ' experience)
' + - intText + '

' + - 'Multipliers

' + - 'Hacking Chance multiplier: ' + formatNumber(Player.hacking_chance_mult * 100, 2) + '%
' + - 'Hacking Speed multiplier: ' + formatNumber(Player.hacking_speed_mult * 100, 2) + '%
' + - 'Hacking Money multiplier: ' + formatNumber(Player.hacking_money_mult * 100, 2) + '%
' + - 'Hacking Growth multiplier: ' + formatNumber(Player.hacking_grow_mult * 100, 2) + '%

' + - 'Hacking Level multiplier: ' + formatNumber(Player.hacking_mult * 100, 2) + '%
' + - 'Hacking Experience multiplier: ' + formatNumber(Player.hacking_exp_mult * 100, 2) + '%

' + - 'Strength Level multiplier: ' + formatNumber(Player.strength_mult * 100, 2) + '%
' + - 'Strength Experience multiplier: ' + formatNumber(Player.strength_exp_mult * 100, 2) + '%

' + - 'Defense Level multiplier: ' + formatNumber(Player.defense_mult * 100, 2) + '%
' + - 'Defense Experience multiplier: ' + formatNumber(Player.defense_exp_mult * 100, 2) + '%

' + - 'Dexterity Level multiplier: ' + formatNumber(Player.dexterity_mult * 100, 2) + '%
' + - 'Dexterity Experience multiplier: ' + formatNumber(Player.dexterity_exp_mult * 100, 2) + '%

' + - 'Agility Level multiplier: ' + formatNumber(Player.agility_mult * 100, 2) + '%
' + - 'Agility Experience multiplier: ' + formatNumber(Player.agility_exp_mult * 100, 2) + '%

' + - 'Charisma Level multiplier: ' + formatNumber(Player.charisma_mult * 100, 2) + '%
' + - 'Charisma Experience multiplier: ' + formatNumber(Player.charisma_exp_mult * 100, 2) + '%

' + - 'Hacknet Node production multiplier: ' + formatNumber(Player.hacknet_node_money_mult * 100, 2) + '%
' + - 'Hacknet Node purchase cost multiplier: ' + formatNumber(Player.hacknet_node_purchase_cost_mult * 100, 2) + '%
' + - 'Hacknet Node RAM upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_ram_cost_mult * 100, 2) + '%
' + - 'Hacknet Node Core purchase cost multiplier: ' + formatNumber(Player.hacknet_node_core_cost_mult * 100, 2) + '%
' + - 'Hacknet Node level upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_level_cost_mult * 100, 2) + '%

' + - 'Company reputation gain multiplier: ' + formatNumber(Player.company_rep_mult * 100, 2) + '%
' + - 'Faction reputation gain multiplier: ' + formatNumber(Player.faction_rep_mult * 100, 2) + '%
' + - 'Salary multiplier: ' + formatNumber(Player.work_money_mult * 100, 2) + '%
' + - 'Crime success multiplier: ' + formatNumber(Player.crime_success_mult * 100, 2) + '%
' + - 'Crime money multiplier: ' + formatNumber(Player.crime_money_mult * 100, 2) + '%


' + - 'Misc

' + - 'Servers owned: ' + Player.purchasedServers.length + '
' + - 'Hacknet Nodes owned: ' + Player.hacknetNodes.length + '
' + - 'Augmentations installed: ' + Player.augmentations.length + '
' + - 'Time played since last Augmentation: ' + convertTimeMsToTimeElapsedString(Player.playtimeSinceLastAug) + '
' + - bitNodeTimeText + - 'Time played: ' + convertTimeMsToTimeElapsedString(Player.totalPlaytime), - })); - - if (Player.sourceFiles.length !== 0) { - var index = "BitNode" + Player.bitNodeN; - - Engine.Display.characterInfo.appendChild(createElement("p", { - width:"60%", - innerHTML: - "
Current BitNode: " + Player.bitNodeN + " (" + BitNodes[index].name + ")

", - })); - - Engine.Display.characterInfo.appendChild(createElement("p", { - width:"60%", fontSize: "13px", marginLeft:"4%", - innerHTML:BitNodes[index].info, - })) - } + updateCharacterInfo: function() { + displayCharacterInfo(Engine.Display.characterInfo, Player); }, /* Display locations in the world*/ @@ -1060,9 +973,7 @@ const Engine = { if (Engine.Counters.updateDisplays <= 0) { Engine.displayCharacterOverviewInfo(); - if (routing.isOn(Page.CharacterInfo)) { - Engine.displayCharacterInfo(); - } else if (routing.isOn(Page.HacknetNodes)) { + if (routing.isOn(Page.HacknetNodes)) { updateHacknetNodesContent(); } else if (routing.isOn(Page.CreateProgram)) { displayCreateProgramContent(); @@ -1080,6 +991,8 @@ const Engine = { if (Engine.Counters.updateDisplaysMed <= 0) { if (routing.isOn(Page.Corporation)) { Player.corporation.updateUIContent(); + } else if (routing.isOn(Page.CharacterInfo)) { + Engine.updateCharacterInfo(); } Engine.Counters.updateDisplaysMed = 9; } diff --git a/src/ui/displayCharacterInfo.ts b/src/ui/displayCharacterInfo.ts new file mode 100644 index 000000000..4328329c0 --- /dev/null +++ b/src/ui/displayCharacterInfo.ts @@ -0,0 +1,156 @@ +// Displays character info on a given element. This is used to create & update +// the 'Stats' page from the main menu +import { BitNodes } from "../BitNode/BitNode"; +import { IPlayer } from "../PersonObjects/IPlayer"; + +import { numeralWrapper } from "../ui/numeralFormat"; +import { MoneySourceTracker } from "../utils/MoneySourceTracker"; + +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; + +import { createElement } from "../../utils/uiHelpers/createElement"; +import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; + +export function displayCharacterInfo(elem: HTMLElement, p: IPlayer) { + removeChildrenFromElement(elem); + + let companyPosition = ""; + if (p.companyName !== "") { + companyPosition = p.jobs[p.companyName]; + } + + var intText = ""; + if (p.intelligence > 0) { + intText = 'Intelligence: ' + (p.intelligence).toLocaleString() + '
'; + } + + let bitNodeTimeText = ""; + if(p.sourceFiles.length > 0) { + bitNodeTimeText = 'Time played since last Bitnode destroyed: ' + convertTimeMsToTimeElapsedString(p.playtimeSinceLastBitnode) + '
'; + } + + const unlockedBitnodes: boolean = p.sourceFiles.length !== 0; + + // General info + elem.appendChild(createElement("pre", { + display: "block", + innerHTML: + 'General

' + + 'Current City: ' + p.city + '

' + + `Employer at which you last worked: ${p.companyName}
` + + `Job you last worked: ${companyPosition}
` + + `All Employers: ${Object.keys(p.jobs).join(", ")}

` + })); + + // Money, and a button to show money breakdown + elem.appendChild(createElement("pre", { + display: "inline-block", + innerHTML: 'Money: ' + numeralWrapper.formatMoney(p.money.toNumber()) + '


', + margin: "6px", + })); + + function convertMoneySourceTrackerToString(src: MoneySourceTracker): string { + let parts: string[] = [`Total: ${numeralWrapper.formatMoney(src.total)}`]; + if (src.bladeburner) { parts.push(`Bladeburner: ${numeralWrapper.formatMoney(src.bladeburner)}`) }; + if (src.codingcontract) { parts.push(`Coding Contracts: ${numeralWrapper.formatMoney(src.codingcontract)}`) }; + if (src.work) { parts.push(`Company Work: ${numeralWrapper.formatMoney(src.work)}`) }; + if (src.corporation) { parts.push(`Corporation: ${numeralWrapper.formatMoney(src.corporation)}}`) }; + if (src.crime) { parts.push(`Crimes: ${numeralWrapper.formatMoney(src.crime)}`) }; + if (src.gang) { parts.push(`Gang: ${numeralWrapper.formatMoney(src.gang)}`) }; + if (src.hacking) { parts.push(`Hacking: ${numeralWrapper.formatMoney(src.hacking)}`) }; + if (src.hacknetnode) { parts.push(`Hacknet Nodes: ${numeralWrapper.formatMoney(src.hacknetnode)}`) }; + if (src.infiltration) { parts.push(`Infiltration: ${numeralWrapper.formatMoney(src.infiltration)}`) }; + if (src.stock) { parts.push(`Stock Market: ${numeralWrapper.formatMoney(src.stock)}`) }; + + return parts.join("
"); + } + + elem.appendChild(createElement("button", { + class: "popup-box-button", + display: "inline-block", + float: "none", + innerText: "Money Statistics & Breakdown", + clickListener: () => { + let txt: string = "Money earned since you last installed Augmentations:
" + + convertMoneySourceTrackerToString(p.moneySourceA); + if (unlockedBitnodes) { + txt += "

Money earned in this BitNode:
" + + convertMoneySourceTrackerToString(p.moneySourceB); + } + + dialogBoxCreate(txt, false); + } + })); + + // Stats and multiplier + elem.appendChild(createElement("pre", { + display: "block", + innerHTML: + 'Stats

' + + 'Hacking Level: ' + (p.hacking_skill).toLocaleString() + + ' (' + numeralWrapper.format(p.hacking_exp, '(0.000a)') + ' experience)
' + + 'Strength: ' + (p.strength).toLocaleString() + + ' (' + numeralWrapper.format(p.strength_exp, '(0.000a)') + ' experience)
' + + 'Defense: ' + (p.defense).toLocaleString() + + ' (' + numeralWrapper.format(p.defense_exp, '(0.000a)') + ' experience)
' + + 'Dexterity: ' + (p.dexterity).toLocaleString() + + ' (' + numeralWrapper.format(p.dexterity_exp, '(0.000a)') + ' experience)
' + + 'Agility: ' + (p.agility).toLocaleString() + + ' (' + numeralWrapper.format(p.agility_exp, '(0.000a)') + ' experience)
' + + 'Charisma: ' + (p.charisma).toLocaleString() + + ' (' + numeralWrapper.format(p.charisma_exp, '(0.000a)') + ' experience)
' + + intText + '

' + + 'Multipliers

' + + 'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(p.hacking_chance_mult) + '
' + + 'Hacking Speed multiplier: ' + numeralWrapper.formatPercentage(p.hacking_speed_mult) + '
' + + 'Hacking Money multiplier: ' + numeralWrapper.formatPercentage(p.hacking_money_mult) + '
' + + 'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(p.hacking_grow_mult) + '

' + + 'Hacking Level multiplier: ' + numeralWrapper.formatPercentage(p.hacking_mult) + '
' + + 'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(p.hacking_exp_mult) + '

' + + 'Strength Level multiplier: ' + numeralWrapper.formatPercentage(p.strength_mult) + '
' + + 'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(p.strength_exp_mult) + '

' + + 'Defense Level multiplier: ' + numeralWrapper.formatPercentage(p.defense_mult) + '
' + + 'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(p.defense_exp_mult) + '

' + + 'Dexterity Level multiplier: ' + numeralWrapper.formatPercentage(p.dexterity_mult) + '
' + + 'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(p.dexterity_exp_mult) + '

' + + 'Agility Level multiplier: ' + numeralWrapper.formatPercentage(p.agility_mult) + '
' + + 'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(p.agility_exp_mult) + '

' + + 'Charisma Level multiplier: ' + numeralWrapper.formatPercentage(p.charisma_mult) + '
' + + 'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(p.charisma_exp_mult) + '

' + + 'Hacknet Node production multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_money_mult) + '
' + + 'Hacknet Node purchase cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_purchase_cost_mult) + '
' + + 'Hacknet Node RAM upgrade cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_ram_cost_mult) + '
' + + 'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_core_cost_mult) + '
' + + 'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_level_cost_mult) + '

' + + 'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(p.company_rep_mult) + '
' + + 'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(p.faction_rep_mult) + '
' + + 'Salary multiplier: ' + numeralWrapper.formatPercentage(p.work_money_mult) + '
' + + 'Crime success multiplier: ' + numeralWrapper.formatPercentage(p.crime_success_mult) + '
' + + 'Crime money multiplier: ' + numeralWrapper.formatPercentage(p.crime_money_mult) + '


' + + 'Misc

' + + 'Servers owned: ' + p.purchasedServers.length + '
' + + 'Hacknet Nodes owned: ' + p.hacknetNodes.length + '
' + + 'Augmentations installed: ' + p.augmentations.length + '
' + + 'Time played since last Augmentation: ' + convertTimeMsToTimeElapsedString(p.playtimeSinceLastAug) + '
' + + bitNodeTimeText + + 'Time played: ' + convertTimeMsToTimeElapsedString(p.totalPlaytime), + })); + + // BitNode information, if player has gotten that far + if (unlockedBitnodes) { + var index = "BitNode" + p.bitNodeN; + + elem.appendChild(createElement("p", { + width:"60%", + innerHTML: + "
Current BitNode: " + p.bitNodeN + " (" + BitNodes[index].name + ")

", + })); + + elem.appendChild(createElement("p", { + width:"60%", fontSize: "13px", marginLeft:"4%", + innerHTML:BitNodes[index].info, + })) + } + +} diff --git a/src/utils/MoneySourceTracker.ts b/src/utils/MoneySourceTracker.ts new file mode 100644 index 000000000..4ed0a09c2 --- /dev/null +++ b/src/utils/MoneySourceTracker.ts @@ -0,0 +1,56 @@ +/** + * This is an object that is used to keep track of where all of the player's + * money is coming from + */ +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +export class MoneySourceTracker { + // Initiatizes a MoneySourceTracker object from a JSON save state. + static fromJSON(value: any): MoneySourceTracker { + return Generic_fromJSON(MoneySourceTracker, value.data); + } + + bladeburner: number = 0; + codingcontract: number = 0; + corporation: number = 0; + crime: number = 0; + gang: number = 0; + hacking: number = 0; + hacknetnode: number = 0; + infiltration: number = 0; + stock: number = 0; + total: number = 0; + work: number = 0; + + [key: string]: number | Function; + + constructor() {} + + // Record money earned + record(amt: number, source: string): void { + const sanitizedSource = source.toLowerCase(); + if (typeof this[sanitizedSource] !== "number") { + console.warn(`MoneySourceTracker.record() called with invalid source: ${source}`); + return; + } + + ( this[sanitizedSource]) += amt; + this.total += amt; + } + + // Reset the money tracker by setting all stats to 0 + reset(): void { + for (const prop in this) { + if (typeof this[prop] === "number") { + this[prop] = 0; + } + } + } + + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("MoneySourceTracker", this); + } +} + +Reviver.constructors.MoneySourceTracker = MoneySourceTracker; diff --git a/utils/InfiltrationBox.js b/utils/InfiltrationBox.js index af83bd0f4..fb922fc63 100644 --- a/utils/InfiltrationBox.js +++ b/utils/InfiltrationBox.js @@ -88,6 +88,7 @@ function infiltrationBoxCreate(inst) { sellButton.addEventListener("click", function(e) { if (!e.isTrusted) {return false;} Player.gainMoney(moneyValue); + Player.recordMoneySource(moneyValue, "infiltration"); dialogBoxCreate("You sold the classified information you stole from " + inst.companyName + " for $" + formatNumber(moneyValue, 2) + " on the black market!

" + expGainText);