Merge pull request #514 from danielyxie/dev

v0.41.2
This commit is contained in:
danielyxie 2018-11-23 17:40:08 -08:00 committed by GitHub
commit c1c0297c92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2460 additions and 1563 deletions

@ -39,6 +39,19 @@
padding: 16px;
}
.mainmenu.compact > li a,
.mainmenu.compact > li button {
display: block;
color: #e6e6e6;
background-color: #555;
text-decoration: none;
cursor: pointer;
width: 100%;
text-align: left;
padding: 4px;
}
/* Hovering makes them lighter */
.mainmenu > li a:hover,
.mainmenu > li a:hover:not(.active),
@ -73,7 +86,8 @@
}
/* Accordion Outline */
.mainmenu-accordion-header {
.mainmenu-accordion-header,
.mainmenu-accordion-header-compact {
outline: 2px solid #fff !important;
}
@ -83,7 +97,8 @@
}
/* Plus and minus signs */
.mainmenu-accordion-header:after {
.mainmenu-accordion-header:after,
.mainmenu-accordion-header-compact:after {
content: '\02795';
float: right;
font-size: $defaultFontSize * 0.8125;
@ -103,8 +118,9 @@
}
.mainmenu-accordion-header.opened,
.mainmenu-accordion-header-classic.opened {
background-color: #222;
.mainmenu-accordion-header-classic.opened,
.mainmenu-accordion-header-compact.opened {
background-color: #222 !important;
&:after {
content: "\2796";

File diff suppressed because one or more lines are too long

25
dist/engine.css vendored

@ -521,6 +521,17 @@ button {
.mainmenu.classic > li button {
padding: 16px; }
.mainmenu.compact > li a,
.mainmenu.compact > li button {
display: block;
color: #e6e6e6;
background-color: #555;
text-decoration: none;
cursor: pointer;
width: 100%;
text-align: left;
padding: 4px; }
/* Hovering makes them lighter */
.mainmenu > li a:hover,
.mainmenu > li a:hover:not(.active),
@ -550,7 +561,8 @@ button {
position: relative; }
/* Accordion Outline */
.mainmenu-accordion-header {
.mainmenu-accordion-header,
.mainmenu-accordion-header-compact {
outline: 2px solid #fff !important; }
.mainmenu-accordion-header-classic {
@ -558,7 +570,8 @@ button {
padding: 16px !important; }
/* Plus and minus signs */
.mainmenu-accordion-header:after {
.mainmenu-accordion-header:after,
.mainmenu-accordion-header-compact:after {
content: '\2795';
float: right;
font-size: 13px;
@ -576,10 +589,12 @@ button {
margin-left: 5px; }
.mainmenu-accordion-header.opened,
.mainmenu-accordion-header-classic.opened {
background-color: #222; }
.mainmenu-accordion-header-classic.opened,
.mainmenu-accordion-header-compact.opened {
background-color: #222 !important; }
.mainmenu-accordion-header.opened:after,
.mainmenu-accordion-header-classic.opened:after {
.mainmenu-accordion-header-classic.opened:after,
.mainmenu-accordion-header-compact.opened:after {
content: "\2796"; }
/* Slide down transition */

116
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,33 @@
Changelog
=========
v0.41.2 - 11/23/2018
--------------------
* IMPORTANT - Netscript Changes:
* rm() now takes an optional parameter that lets you specify on which server to delete the file
* Added growthAnalyze() Netscript function
* Gang Changes:
* UI now displays your chance to win a clash with other gangs
* Added getChanceToWinClash() function to the Gang API
* Added getEquipmentType() function to the Gang API
* Added several new hacking-based equipment and Augmentations
* Rebalanced several equipment/upgrades to give less defense
* Wanted level gain rate is now be slightly higher for all tasks
* Rebalanced parameters for "hacking" tasks
* Added new Main Menu configuration in .fconf: "compact"
* Added the terminal command 'expr', which can be used to evaluate simple mathematical expressions
* Bug Fix: Can no longer purchase duplicate equipment/Augmentations through gang.purchaseEquipment()
* Bug Fix: scp() should no longer throw errors when used with 2-arguments and an array of files
* Bug Fix: Coding Contracts no longer give money in BitNode-8
* Bug Fix: In Bladeburner, you can no longer start a BlackOp through the Netscript API if it has already been completed
* Bug Fix: In Bladeburner, fixed a bug which caused the configured 'automate' actions to occasionally be switched to other actions
* Bug Fix: 'Return to World' button at locations no longer accumulates event listeners
* Bug Fix: Working & taking classes now continuously add/subtract money during the action, instead of doing it at completion
* Bug Fix: Top-right overview panel now displays negative money using '-' instead of '()'
* Bug Fix: Stock Market UI should no longer show 'NaN' profit immediately after buying a stock
v0.41.1 - 11/5/2018
-------------------
* IMPORTANT - Netscript Changes:

@ -56,7 +56,7 @@ weaken
.. js:function:: weaken(hostname/ip)
:param string hostname.ip: IP or hostname of the target server to weaken
:param string hostname/ip: IP or hostname of the target server to weaken
:returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied
by the number of script threads
:RAM cost: 0.15 GB
@ -72,6 +72,31 @@ weaken
weaken("foodnstuff");
growthAnalyze
^^^^^^^^^^^^^
.. js:function:: growthAnalyze(hostname/ip, growthAmount)
:param string hostname/ip: IP or hostname of server to analyze
:param number growthAmount: Multiplicative factor by which the server is grown. Decimal form.
:returns: The amount of grow() calls needed to grow the specified server by the specified amount
:RAM cost: 1 GB
This function returns the number of "growths" needed in order to increase the amount
of money available on the specified server by the specified amount.
The specified amount is multiplicative and is in decimal form, not percentage.
For example, if you want to determine how many `grow()` calls you need
to double the amount of money on `foodnstuff`, you would use::
growthAnalyze("foodnstuff", 2);
If this returns 100, then this means you need to call `grow()` 100 times
in order to double the money (or once with 100 threads).
**Warning**: The value returned by this function isn't necessarily a whole number.
sleep
^^^^^
@ -951,9 +976,10 @@ getPortHandle
rm
^^
.. js:function:: rm(fn)
.. js:function:: rm(fn[, hostname/ip=current server])
:param string fn: Filename of file to remove. Must include the extension
:param string hostname/ip: Hostname or IP Address of the server on which to delete the file. Optional. Defaults to current server
:returns: True if it successfully deletes the file, and false otherwise
:RAM cost: 1 GB
@ -1011,7 +1037,7 @@ getScriptRam
.. js:function:: getScriptRam(scriptname[, hostname/ip])
:param string scriptname: Filename of script. This is case-sensitive.
:param string hostname/ip: Hostname or IP of target server the script is located on. This is optional, If it is not specified then the function will se the current server as the target server.
:param string hostname/ip: Hostname or IP of target server the script is located on. This is optional, If it is not specified then the function will set the current server as the target server.
:RAM cost: 0.1 GB
Returns the amount of RAM required to run the specified script on the target server. Returns

@ -187,6 +187,23 @@ getEquipmentCost
:returns: Cost to purchase the specified Equipment/Augmentation (number). Infinity
for invalid arguments
getEquipmentType
----------------
.. js:function:: getEquipmentType(equipName)
:param string equipName: Name of equipment
Get the specified equipment type, which can be one of the following:
* Weapon
* Armor
* Vehicle
* Rootkit
* Augmentation
:returns: A string stating the type of the equipment
purchaseEquipment
-----------------
@ -234,6 +251,17 @@ setTerritoryWarfare
Set whether or not the gang should engage in territory warfare
getChanceToWinClash
-------------------
.. js:function:: getChanceToWinClash(gangName)
:param string gangName: Target gang
Returns the chance you have to win a clash with the specified gang. The chance
is returned in decimal form, not percentage
getBonusTime
------------

@ -59,7 +59,8 @@ var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*";
let NetscriptFunctions =
"hack|sleep|grow|weaken|print|tprint|scan|nuke|brutessh|ftpcrack|" + //Netscript functions
"hack|sleep|grow|weaken|growthAnalyze|print|tprint|scan|nuke|brutessh|" +
"ftpcrack|" +
"clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|" +
"relaysmtp|httpworm|sqlinject|run|exec|spawn|kill|killall|exit|" +
"scp|ls|ps|hasRootAccess|" +
@ -106,8 +107,9 @@ let NetscriptFunctions =
"gang|" +
"getMemberNames|getGangInformation|getMemberInformation|canRecruitMember|" +
"recruitMember|getTaskNames|setMemberTask|getEquipmentNames|" +
"getEquipmentCost|purchaseEquipment|ascendMember|setTerritoryWarfare|" +
"getBonusTime|" +
"getEquipmentCost|getEquipmentType|purchaseEquipment|ascendMember|" +
"setTerritoryWarfare|" +
"getChanceToWinClash|getBonusTime|" +
// Bladeburner API
"bladeburner|getContractNames|getOperationNames|getBlackOpNames|" +

@ -304,6 +304,7 @@ function initBitNodeMultipliers() {
BitNodeMultipliers.InfiltrationMoney = 0;
BitNodeMultipliers.RepToDonateToFaction = 0;
BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0;
break;
case 11: //The Big Crash
BitNodeMultipliers.ServerMaxMoney = 0.1;

@ -29,6 +29,11 @@ interface IBitNodeMultipliers {
*/
ClassGymExpGain: number;
/**
* Influences the amount of money gained from completing Coding Contracts
**/
CodingContractMoney: number;
/**
* Influences the experience gained for each ability when the player completes working their job.
*/
@ -154,6 +159,7 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
HacknetNodeMoney: 1,
ManualHackMoney: 1,
ScriptHackMoney: 1,
CodingContractMoney: 1,
ClassGymExpGain: 1,
CompanyWorkExpGain: 1,

@ -918,14 +918,15 @@ Bladeburner.prototype.process = function() {
//Automation
if (this.automateEnabled) {
// Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference
if (this.stamina <= this.automateThreshLow) {
if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) {
this.action = this.automateActionLow;
this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name});
this.startAction(this.action);
}
} else if (this.stamina >= this.automateThreshHigh) {
if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) {
this.action = this.automateActionHigh;
this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name});
this.startAction(this.action);
}
}
@ -1119,6 +1120,13 @@ Bladeburner.prototype.startAction = function(actionId) {
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]:
try {
// Safety measure - don't repeat BlackOps that are already done
if (this.blackops[actionId.name] != null) {
this.resetAction();
this.log("Error: Tried to start a Black Operation that had already been completed");
break;
}
var action = this.getActionObject(actionId);
if (action == null) {
throw new Error("Failed to get BlackOperation object for: " + actionId.name);
@ -2096,6 +2104,8 @@ Bladeburner.prototype.createBlackOpsContent = function() {
"Black Operations (Black Ops) are special, one-time covert operations. " +
"Each Black Op must be unlocked successively by completing " +
"the one before it.<br><br>" +
"<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete " +
"all of the Black Ops.</b><br><br>" +
"Like normal operations, you may use a team for Black Ops. Failing " +
"a black op will incur heavy HP and rank losses.";
@ -3373,6 +3383,12 @@ Bladeburner.prototype.startActionNetscriptFn = function(type, name, workerScript
return false;
}
// Can't start a BlackOp if its already been done
if (this.blackops[actionId.name] != null) {
workerScript.log(`Failed to start Black Op ${actionId.name} because its already been completed`);
return false;
}
// Can't start a BlackOp if you haven't done the one before it
var blackops = [];
for (const nm in BlackOperations) {

@ -34,7 +34,7 @@ CharacterOverview.prototype.update = function() {
let changed = false;
changed = replaceAndChanged(this.hp, Player.hp + " / " + Player.max_hp) || changed;
changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '($0.000a)')) || changed;
changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '$0.000a')) || changed;
changed = replaceAndChanged(this.hack, (Player.hacking_skill).toLocaleString()) || changed;
changed = replaceAndChanged(this.str, (Player.strength).toLocaleString()) || changed;
changed = replaceAndChanged(this.def, (Player.defense).toLocaleString()) || changed;

File diff suppressed because it is too large Load Diff

50
src/Company/Companies.ts Normal file

@ -0,0 +1,50 @@
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts
import { companiesMetadata } from "./data/CompaniesMetadata";
import { Company, IConstructorParams } from "./Company";
import { IMap } from "../types";
import { Reviver } from "../../utils/JSONReviver";
export let Companies: IMap<Company> = {};
function addCompany(params: IConstructorParams) {
if (Companies[params.name] != null) {
console.warn(`Duplicate Company Position being defined: ${params.name}`);
}
Companies[params.name] = new Company(params);
}
// Used to initialize new Company objects for the Companies map
// Called when creating new game or after a prestige/reset
export function initCompanies() {
// Save Old Company data for 'favor'
const oldCompanies = Companies;
// Re-construct all Companies
Companies = {};
companiesMetadata.forEach((e) => {
addCompany(e);
});
// Reset data
for (const companyName in Companies) {
const company = Companies[companyName];
const oldCompany = oldCompanies[companyName];
if (!(oldCompany instanceof Company)) {
// New game, so no OldCompanies data
company.favor = 0;
} else {
company.favor = oldCompanies[companyName].favor;
if (isNaN(company.favor)) { company.favor = 0; }
}
}
}
// Used to load Companies map from a save
export function loadCompanies(saveString: string) {
Companies = JSON.parse(saveString, Reviver);
}
// Utility function to check if a string is valid company name
export function companyExists(name: string) {
return Companies.hasOwnProperty(name);
}

135
src/Company/Company.ts Normal file

@ -0,0 +1,135 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { CompanyPosition } from "./CompanyPosition";
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
export interface IConstructorParams {
name: string;
info: string;
companyPositions: IMap<boolean>;
expMultiplier: number;
salaryMultiplier: number;
jobStatReqOffset: number;
}
const DefaultConstructorParams: IConstructorParams = {
name: "",
info: "",
companyPositions: {},
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
}
export class Company {
/**
* Initiatizes a Company from a JSON save state.
*/
static fromJSON(value: any): Company {
return Generic_fromJSON(Company, value.data);
}
/**
* Company name
*/
name: string;
/**
* Description and general information about company
*/
info: string;
/**
* Object that holds all available positions in this Company.
* Position names are held in keys.
* The values for the keys don't matter, but we'll make them booleans
*
* Must match names of Company Positions, defined in data/companypositionnames.ts
*/
companyPositions: IMap<boolean>;
/**
* Company-specific multiplier for earnings
*/
expMultiplier: number;
salaryMultiplier: number;
/**
* The additional levels of stats you need to quality for a job
* in this company.
*
* For example, the base stat requirement for an intern position is 1.
* But if a company has a offset of 200, then you would need stat(s) of 201
*/
jobStatReqOffset: number;
/**
* Properties to track the player's progress in this company
*/
isPlayerEmployed: boolean;
playerReputation: number;
favor: number;
rolloverRep: number;
constructor(p: IConstructorParams = DefaultConstructorParams) {
this.name = p.name;
this.info = p.info;
this.companyPositions = p.companyPositions;
this.expMultiplier = p.expMultiplier;
this.salaryMultiplier = p.salaryMultiplier;
this.jobStatReqOffset = p.jobStatReqOffset;
this.isPlayerEmployed = false;
this.playerReputation = 1;
this.favor = 0;
this.rolloverRep = 0;
}
hasPosition(pos: CompanyPosition | string): boolean {
if (pos instanceof CompanyPosition) {
return (this.companyPositions[pos.name] != null);
} else {
return (this.companyPositions[pos] != null);
}
}
gainFavor(): void {
if (this.favor == null) { this.favor = 0; }
if (this.rolloverRep == null) { this.rolloverRep = 0; }
var res = this.getFavorGain();
if (res.length != 2) {
console.error("Invalid result from getFavorGain() function");
return;
}
this.favor += res[0];
this.rolloverRep = res[1];
}
getFavorGain(): number[] {
if (this.favor == null) { this.favor = 0; }
if (this.rolloverRep == null) { this.rolloverRep = 0; }
let favorGain = 0, rep = this.playerReputation + this.rolloverRep;
let reqdRep = CONSTANTS.CompanyReputationToFavorBase *
Math.pow(CONSTANTS.CompanyReputationToFavorMult, this.favor);
while(rep > 0) {
if (rep >= reqdRep) {
++favorGain;
rep -= reqdRep;
} else {
break;
}
reqdRep *= CONSTANTS.FactionReputationToFavorMult;
}
return [favorGain, rep];
}
/**
* Serialize the current file to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Company", this);
}
}
Reviver.constructors.Company = Company;

@ -0,0 +1,182 @@
import { CONSTANTS } from "../Constants";
import * as names from "./data/CompanyPositionNames";
/* tslint:disable:completed-docs */
export interface IConstructorParams {
name: string;
nextPosition: string | null;
baseSalary: number;
repMultiplier: number;
reqdHacking?: number;
reqdStrength?: number;
reqdDefense?: number;
reqdDexterity?: number;
reqdAgility?: number;
reqdCharisma?: number;
reqdReputation?: number;
hackingEffectiveness?: number;
strengthEffectiveness?: number;
defenseEffectiveness?: number;
dexterityEffectiveness?: number;
agilityEffectiveness?: number;
charismaEffectiveness?: number;
hackingExpGain?: number;
strengthExpGain?: number;
defenseExpGain?: number;
dexterityExpGain?: number;
agilityExpGain?: number;
charismaExpGain?: number;
}
export class CompanyPosition {
/**
* Position title
*/
name: string;
/**
* Title of next position to be promoted to
*/
nextPosition: string | null;
/**
* Base salary for this position ($ per 200ms game cycle)
* Will be multiplier by a company-specific multiplier for final salary
*/
baseSalary: number;
/**
* Reputation multiplier
*/
repMultiplier: number;
/**
* Required stats to earn this position
*/
requiredAgility: number;
requiredCharisma: number;
requiredDefense: number;
requiredDexterity: number;
requiredHacking: number;
requiredStrength: number;
/**
* Required company reputation to earn this position
*/
requiredReputation: number;
/**
* Effectiveness of each stat time for job performance
*/
hackingEffectiveness: number;
strengthEffectiveness: number;
defenseEffectiveness: number;
dexterityEffectiveness: number;
agilityEffectiveness: number;
charismaEffectiveness: number;
/**
* Experience gain for performing job (per 200ms game cycle)
*/
hackingExpGain: number;
strengthExpGain: number;
defenseExpGain: number;
dexterityExpGain: number;
agilityExpGain: number;
charismaExpGain: number;
constructor(p: IConstructorParams) {
this.name = p.name;
this.nextPosition = p.nextPosition;
this.baseSalary = p.baseSalary;
this.repMultiplier = p.repMultiplier;
this.requiredHacking = (p.reqdHacking != null) ? p.reqdHacking : 0;
this.requiredStrength = (p.reqdStrength != null) ? p.reqdStrength : 0;
this.requiredDefense = (p.reqdDefense != null) ? p.reqdDefense : 0;
this.requiredDexterity = (p.reqdDexterity != null) ? p.reqdDexterity : 0;
this.requiredAgility = (p.reqdAgility != null) ? p.reqdAgility : 0;
this.requiredCharisma = (p.reqdCharisma != null) ? p.reqdCharisma : 0;
this.requiredReputation = (p.reqdReputation != null) ? p.reqdReputation : 0;
this.hackingEffectiveness = (p.hackingEffectiveness != null) ? p.hackingEffectiveness : 0;
this.strengthEffectiveness = (p.strengthEffectiveness != null) ? p.strengthEffectiveness : 0;
this.defenseEffectiveness = (p.defenseEffectiveness != null) ? p.defenseEffectiveness : 0;
this.dexterityEffectiveness = (p.dexterityEffectiveness != null) ? p.dexterityEffectiveness : 0;
this.agilityEffectiveness = (p.agilityEffectiveness != null) ? p.agilityEffectiveness : 0;
this.charismaEffectiveness = (p.charismaEffectiveness != null) ? p.charismaEffectiveness : 0;
if (Math.round(this.hackingEffectiveness + this.strengthEffectiveness + this.defenseEffectiveness +
this.dexterityEffectiveness + this.agilityEffectiveness + this.charismaEffectiveness) !== 100) {
console.error(`CompanyPosition ${this.name} parameters do not sum to 100`);
}
this.hackingExpGain = (p.hackingExpGain != null) ? p.hackingExpGain : 0;
this.strengthExpGain = (p.strengthExpGain != null) ? p.strengthExpGain : 0;
this.defenseExpGain = (p.defenseExpGain != null) ? p.defenseExpGain : 0;
this.dexterityExpGain = (p.dexterityExpGain != null) ? p.dexterityExpGain : 0;
this.agilityExpGain = (p.agilityExpGain != null) ? p.agilityExpGain : 0;
this.charismaExpGain = (p.charismaExpGain != null) ? p.charismaExpGain : 0;
}
calculateJobPerformance(hack: number, str: number, def: number, dex: number, agi: number, cha: number): number {
const hackRatio: number = this.hackingEffectiveness * hack / CONSTANTS.MaxSkillLevel;
const strRatio: number = this.strengthEffectiveness * str / CONSTANTS.MaxSkillLevel;
const defRatio: number = this.defenseEffectiveness * def / CONSTANTS.MaxSkillLevel;
const dexRatio: number = this.dexterityEffectiveness * dex / CONSTANTS.MaxSkillLevel;
const agiRatio: number = this.agilityEffectiveness * agi / CONSTANTS.MaxSkillLevel;
const chaRatio: number = this.charismaEffectiveness * cha / CONSTANTS.MaxSkillLevel;
let reputationGain: number = this.repMultiplier * (hackRatio + strRatio + defRatio + dexRatio + agiRatio + chaRatio) / 100;
if (isNaN(reputationGain)) {
console.error("Company reputation gain calculated to be NaN");
reputationGain = 0;
}
return reputationGain;
}
isSoftwareJob(): boolean {
return names.SoftwareCompanyPositions.includes(this.name);
}
isITJob(): boolean {
return names.ITCompanyPositions.includes(this.name);
}
isSecurityEngineerJob(): boolean {
return names.SecurityEngineerCompanyPositions.includes(this.name);
}
isNetworkEngineerJob(): boolean {
return names.NetworkEngineerCompanyPositions.includes(this.name);
}
isBusinessJob(): boolean {
return names.BusinessCompanyPositions.includes(this.name);
}
isSecurityJob(): boolean {
return names.SecurityCompanyPositions.includes(this.name);
}
isAgentJob(): boolean {
return names.AgentCompanyPositions.includes(this.name);
}
isSoftwareConsultantJob(): boolean {
return names.SoftwareConsultantCompanyPositions.includes(this.name);
}
isBusinessConsultantJob(): boolean {
return names.BusinessConsultantCompanyPositions.includes(this.name);
}
isPartTimeJob(): boolean {
return names.PartTimeCompanyPositions.includes(this.name);
}
}

@ -0,0 +1,17 @@
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts
import { companyPositionMetadata } from "./data/CompanyPositionsMetadata";
import { CompanyPosition, IConstructorParams } from "./CompanyPosition";
import { IMap } from "../types";
export const CompanyPositions: IMap<CompanyPosition> = {};
function addCompanyPosition(params: IConstructorParams) {
if (CompanyPositions[params.name] != null) {
console.warn(`Duplicate Company Position being defined: ${params.name}`);
}
CompanyPositions[params.name] = new CompanyPosition(params);
}
companyPositionMetadata.forEach((e) => {
addCompanyPosition(e);
});

@ -0,0 +1,40 @@
import { Company } from "./Company";
import { CompanyPosition } from "./CompanyPosition";
/**
* Returns a string with the given CompanyPosition's stat requirements
*/
export function getJobRequirementText(company: Company, pos: CompanyPosition, tooltiptext: boolean = false): string {
let reqText: string = "";
const offset: number = company.jobStatReqOffset;
const reqHacking: number = pos.requiredHacking > 0 ? pos.requiredHacking+offset : 0;
const reqStrength: number = pos.requiredStrength > 0 ? pos.requiredStrength+offset : 0;
const reqDefense: number = pos.requiredDefense > 0 ? pos.requiredDefense+offset : 0;
const reqDexterity: number = pos.requiredDexterity > 0 ? pos.requiredDexterity+offset : 0;
const reqAgility: number = pos.requiredDexterity > 0 ? pos.requiredDexterity+offset : 0;
const reqCharisma: number = pos.requiredCharisma > 0 ? pos.requiredCharisma+offset : 0;
const reqRep: number = pos.requiredReputation;
if (tooltiptext) {
reqText = "Requires:<br>";
reqText += (reqHacking.toString() + " hacking<br>");
reqText += (reqStrength.toString() + " strength<br>");
reqText += (reqDefense.toString() + " defense<br>");
reqText += (reqDexterity.toString() + " dexterity<br>");
reqText += (reqAgility.toString() + " agility<br>");
reqText += (reqCharisma.toString() + " charisma<br>");
reqText += (reqRep.toString() + " reputation");
} else {
reqText = "(Requires ";
if (reqHacking > 0) {reqText += (reqHacking + " hacking, ");}
if (reqStrength > 0) {reqText += (reqStrength + " strength, ");}
if (reqDefense > 0) {reqText += (reqDefense + " defense, ");}
if (reqDexterity > 0) {reqText += (reqDexterity + " dexterity, ");}
if (reqAgility > 0) {reqText += (reqAgility + " agility, ");}
if (reqCharisma > 0) {reqText += (reqCharisma + " charisma, ");}
if (reqRep > 1) {reqText += (reqRep + " reputation, ");}
reqText = reqText.substring(0, reqText.length - 2);
reqText += ")";
}
return reqText;
}

@ -0,0 +1,13 @@
// Function that returns the next Company Position in the "ladder"
// i.e. the next position to get promoted to
import { CompanyPosition } from "./CompanyPosition";
import { CompanyPositions } from "./CompanyPositions";
export function getNextCompanyPosition(currPos: CompanyPosition | null): CompanyPosition | null {
if (currPos == null) { return null; }
const nextPosName: string | null = currPos.nextPosition;
if (nextPosName == null) { return null; }
return CompanyPositions[nextPosName];
}

1
src/Company/README.md Normal file

@ -0,0 +1 @@
Implementation of Company and Job related mechanics

@ -0,0 +1,544 @@
import { IConstructorParams } from "../Company";
import { Locations } from "../../Locations";
import * as posNames from "./CompanyPositionNames";
import { IMap } from "../../types";
// Create Objects containing Company Positions by category
// Will help in metadata construction later
const AllSoftwarePositions: IMap<boolean> = {};
const AllITPositions: IMap<boolean> = {};
const AllNetworkEngineerPositions: IMap<boolean> = {};
const AllTechnologyPositions: IMap<boolean> = {};
const AllBusinessPositions: IMap<boolean> = {};
const AllAgentPositions: IMap<boolean> = {};
const AllSecurityPositions: IMap<boolean> = {};
const AllSoftwareConsultantPositions: IMap<boolean> = {};
const AllBusinessConsultantPositions: IMap<boolean> = {};
const SoftwarePositionsUpToHeadOfEngineering: IMap<boolean> = {};
const SoftwarePositionsUpToLeadDeveloper: IMap<boolean> = {};
const BusinessPositionsUpToOperationsManager: IMap<boolean> = {};
const WaiterOnly: IMap<boolean> = {};
const EmployeeOnly: IMap<boolean> = {};
const PartTimeWaiterOnly: IMap<boolean> = {};
const PartTimeEmployeeOnly: IMap<boolean> = {};
const OperationsManagerOnly: IMap<boolean> = {};
const CEOOnly: IMap<boolean> = {};
posNames.SoftwareCompanyPositions.forEach((e) => {
AllSoftwarePositions[e] = true;
AllTechnologyPositions[e] = true;
});
posNames.ITCompanyPositions.forEach((e) => {
AllITPositions[e] = true;
AllTechnologyPositions[e] = true;
});
posNames.NetworkEngineerCompanyPositions.forEach((e) => {
AllNetworkEngineerPositions[e] = true;
AllTechnologyPositions[e] = true;
});
AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;
posNames.BusinessCompanyPositions.forEach((e) => {
AllBusinessPositions[e] = true;
});
posNames.SecurityCompanyPositions.forEach((e) => {
AllSecurityPositions[e] = true;
});
posNames.AgentCompanyPositions.forEach((e) => {
AllAgentPositions[e] = true;
});
posNames.SoftwareConsultantCompanyPositions.forEach((e) => {
AllSoftwareConsultantPositions[e] = true;
});
posNames.BusinessConsultantCompanyPositions.forEach((e) => {
AllBusinessConsultantPositions[e] = true;
});
for (let i = 0; i < posNames.SoftwareCompanyPositions.length; ++i) {
const e = posNames.SoftwareCompanyPositions[i];
if (i <= 5) {
SoftwarePositionsUpToHeadOfEngineering[e] = true;
}
if (i <= 3) {
SoftwarePositionsUpToLeadDeveloper[e] = true;
}
}
for (let i = 0; i < posNames.BusinessCompanyPositions.length; ++i) {
const e = posNames.BusinessCompanyPositions[i];
if (i <= 3) {
BusinessPositionsUpToOperationsManager[e] = true;
}
}
WaiterOnly[posNames.MiscCompanyPositions[0]] = true;
EmployeeOnly[posNames.MiscCompanyPositions[1]] = true;
PartTimeWaiterOnly[posNames.PartTimeCompanyPositions[0]] = true;
PartTimeEmployeeOnly[posNames.PartTimeCompanyPositions[1]] = true;
OperationsManagerOnly[posNames.BusinessCompanyPositions[3]] = true;
CEOOnly[posNames.BusinessCompanyPositions[5]] = true;
// Metadata
export const companiesMetadata: IConstructorParams[] = [
{
name: Locations.AevumECorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
{
name: Locations.Sector12MegaCorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
{
name: Locations.AevumBachmanAndAssociates,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.6,
salaryMultiplier: 2.6,
jobStatReqOffset: 224,
},
{
name: Locations.Sector12BladeIndustries,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 224,
},
{
name: Locations.VolhavenNWO,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 249,
},
{
name: Locations.AevumClarkeIncorporated,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
{
name: Locations.VolhavenOmniTekIncorporated,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
{
name: Locations.Sector12FourSigma,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.5,
salaryMultiplier: 2.5,
jobStatReqOffset: 224,
},
{
name: Locations.ChongqingKuaiGongInternational,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 2.2,
salaryMultiplier: 2.2,
jobStatReqOffset: 224,
},
{
name: Locations.AevumFulcrumTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 224,
},
{
name: Locations.IshimaStormTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllSoftwareConsultantPositions,
AllBusinessPositions
),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoDefComm,
info: "",
companyPositions: Object.assign({},
CEOOnly,
AllTechnologyPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
{
name: Locations.VolhavenHeliosLabs,
info: "",
companyPositions: Object.assign({},
CEOOnly,
AllTechnologyPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoVitaLife,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: Locations.Sector12IcarusMicrosystems,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
{
name: Locations.Sector12UniversalEnergy,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 199,
},
{
name: Locations.AevumGalacticCybersystems,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
{
name: Locations.AevumAeroCorp,
info: "",
companyPositions: Object.assign({},
CEOOnly,
OperationsManagerOnly,
AllTechnologyPositions,
AllSecurityPositions
),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: Locations.VolhavenOmniaCybersystems,
info: "",
companyPositions: Object.assign({},
CEOOnly,
OperationsManagerOnly,
AllTechnologyPositions,
AllSecurityPositions,
),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: Locations.ChongqingSolarisSpaceSystems,
info: "",
companyPositions: Object.assign({},
CEOOnly,
OperationsManagerOnly,
AllTechnologyPositions,
AllSecurityPositions,
),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: Locations.Sector12DeltaOne,
info: "",
companyPositions: Object.assign({},
CEOOnly,
OperationsManagerOnly,
AllTechnologyPositions,
AllSecurityPositions,
),
expMultiplier: 1.6,
salaryMultiplier: 1.6,
jobStatReqOffset: 199,
},
{
name: Locations.NewTokyoGlobalPharmaceuticals,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions,
AllSecurityPositions
),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 224,
},
{
name: Locations.IshimaNovaMedical,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions,
AllSecurityPositions
),
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
{
name: Locations.Sector12CIA,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
{
name: Locations.Sector12NSA,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
{
name: Locations.AevumWatchdogSecurity,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions,
AllSoftwareConsultantPositions
),
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 124,
},
{
name: Locations.VolhavenLexoCorp,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllSoftwareConsultantPositions,
AllBusinessPositions,
AllSecurityPositions
),
expMultiplier: 1.4,
salaryMultiplier: 1.4,
jobStatReqOffset: 99,
},
{
name: Locations.AevumRhoConstruction,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToLeadDeveloper,
BusinessPositionsUpToOperationsManager
),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 49,
},
{
name: Locations.Sector12AlphaEnterprises,
info: "",
companyPositions: Object.assign({},
SoftwarePositionsUpToLeadDeveloper,
BusinessPositionsUpToOperationsManager,
AllSoftwareConsultantPositions
),
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 99,
},
{
name: Locations.AevumPolice,
info: "",
companyPositions: Object.assign({},
AllSecurityPositions,
SoftwarePositionsUpToLeadDeveloper
),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 99,
},
{
name: Locations.VolhavenSysCoreSecurities,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 124,
},
{
name: Locations.VolhavenCompuTek,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
{
name: Locations.AevumNetLinkTechnologies,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions
),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 99,
},
{
name: Locations.Sector12CarmichaelSecurity,
info: "",
companyPositions: Object.assign({},
AllTechnologyPositions,
AllAgentPositions,
AllSecurityPositions
),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
{
name: Locations.Sector12FoodNStuff,
info: "",
companyPositions: Object.assign({},
EmployeeOnly, PartTimeEmployeeOnly
),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
{
name: Locations.Sector12JoesGuns,
info: "",
companyPositions: Object.assign({},
EmployeeOnly, PartTimeEmployeeOnly
),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
{
name: Locations.IshimaOmegaSoftware,
info: "",
companyPositions: Object.assign({},
AllSoftwarePositions,
AllSoftwareConsultantPositions,
AllITPositions
),
expMultiplier: 1.1,
salaryMultiplier: 1.1,
jobStatReqOffset: 49,
},
{
name: Locations.NewTokyoNoodleBar,
info: "",
companyPositions: Object.assign({},
WaiterOnly, PartTimeWaiterOnly
),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
]

@ -0,0 +1,602 @@
// Metadata used for constructing Company Positions
import { IConstructorParams } from "../CompanyPosition";
import * as posNames from "./CompanyPositionNames";
export const companyPositionMetadata: IConstructorParams[] = [
{
name: posNames.SoftwareCompanyPositions[0], // Software Enginering Intern
nextPosition: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer
baseSalary: 33,
charismaEffectiveness: 15,
charismaExpGain: 0.02,
hackingEffectiveness: 85,
hackingExpGain: 0.05,
reqdHacking: 1,
repMultiplier: 0.9,
},
{
name: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer
nextPosition: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer
baseSalary: 80,
charismaEffectiveness: 15,
charismaExpGain: 0.05,
hackingEffectiveness: 85,
hackingExpGain: 0.1,
reqdHacking: 51,
reqdReputation: 8e3,
repMultiplier: 1.1,
},
{
name: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer
nextPosition: posNames.SoftwareCompanyPositions[3], // Lead Software Developer
baseSalary: 165,
charismaEffectiveness: 20,
charismaExpGain: 0.08,
hackingEffectiveness: 80,
hackingExpGain: 0.4,
reqdCharisma: 51,
reqdHacking: 251,
reqdReputation: 40e3,
repMultiplier: 1.3,
},
{
name: posNames.SoftwareCompanyPositions[3], // Lead Software Developer
nextPosition: posNames.SoftwareCompanyPositions[4], // Head of Software
baseSalary: 500,
charismaEffectiveness: 25,
charismaExpGain: 0.1,
hackingEffectiveness: 75,
hackingExpGain: 0.8,
reqdCharisma: 151,
reqdHacking: 401,
reqdReputation: 200e3,
repMultiplier: 1.5,
},
{
name: posNames.SoftwareCompanyPositions[4], // Head of Software
nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering
baseSalary: 800,
charismaEffectiveness: 25,
charismaExpGain: 0.5,
hackingEffectiveness: 75,
hackingExpGain: 1,
reqdCharisma: 251,
reqdHacking: 501,
reqdReputation: 400e3,
repMultiplier: 1.6,
},
{
name: posNames.SoftwareCompanyPositions[5], // Head of Engineering
nextPosition: posNames.SoftwareCompanyPositions[6], // Vice President of Technology
baseSalary: 1650,
charismaEffectiveness: 25,
charismaExpGain: 0.5,
hackingEffectiveness: 75,
hackingExpGain: 1.1,
reqdCharisma: 251,
reqdHacking: 501,
reqdReputation: 800e3,
repMultiplier: 1.6,
},
{
name: posNames.SoftwareCompanyPositions[6], // Vice President of Technology
nextPosition: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer
baseSalary: 2310,
charismaEffectiveness: 30,
charismaExpGain: 0.6,
hackingEffectiveness: 70,
hackingExpGain: 1.2,
reqdCharisma: 401,
reqdHacking: 601,
reqdReputation: 1.6e6,
repMultiplier: 1.75,
},
{
name: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer
nextPosition: null,
baseSalary: 2640,
charismaEffectiveness: 35,
charismaExpGain: 1,
hackingEffectiveness: 65,
hackingExpGain: 1.5,
reqdCharisma: 501,
reqdHacking: 751,
reqdReputation: 3.2e6,
repMultiplier: 2,
},
{
name: posNames.ITCompanyPositions[0], // IT Intern
nextPosition: posNames.ITCompanyPositions[1], // IT Analyst
baseSalary: 26,
charismaEffectiveness: 10,
charismaExpGain: 0.01,
hackingEffectiveness: 90,
hackingExpGain: 0.04,
reqdHacking: 1,
repMultiplier: 0.9,
},
{
name: posNames.ITCompanyPositions[1], // IT Analyst
nextPosition: posNames.ITCompanyPositions[2], // IT Manager
baseSalary: 66,
charismaEffectiveness: 15,
charismaExpGain: 0.02,
hackingEffectiveness: 85,
hackingExpGain: 0.08,
reqdHacking: 26,
reqdReputation: 7e3,
repMultiplier: 1.1,
},
{
name: posNames.ITCompanyPositions[2], // IT Manager
nextPosition: posNames.ITCompanyPositions[3], // Systems Administrator
baseSalary: 132,
charismaEffectiveness: 20,
charismaExpGain: 0.1,
hackingEffectiveness: 80,
hackingExpGain: 0.3,
reqdCharisma: 51,
reqdHacking: 151,
reqdReputation: 35e3,
repMultiplier: 1.3,
},
{
name: posNames.ITCompanyPositions[3], // Systems Administrator
nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering
baseSalary: 410,
charismaEffectiveness: 20,
charismaExpGain: 0.2,
hackingEffectiveness: 80,
hackingExpGain: 0.5,
reqdCharisma: 76,
reqdHacking: 251,
reqdReputation: 175e3,
repMultiplier: 1.4,
},
{
name: posNames.SecurityEngineerCompanyPositions[0], // Security Engineer
nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering
baseSalary: 121,
charismaEffectiveness: 15,
charismaExpGain: 0.05,
hackingEffectiveness: 85,
hackingExpGain: 0.4,
reqdCharisma: 26,
reqdHacking: 151,
reqdReputation: 35e3,
repMultiplier: 1.2,
},
{
name: posNames.NetworkEngineerCompanyPositions[0], // Network Engineer
nextPosition: posNames.NetworkEngineerCompanyPositions[1], // Network Adminsitrator
baseSalary: 121,
charismaEffectiveness: 15,
charismaExpGain: 0.05,
hackingEffectiveness: 85,
hackingExpGain: 0.4,
reqdCharisma: 26,
reqdHacking: 151,
reqdReputation: 35e3,
repMultiplier: 1.2,
},
{
name: posNames.NetworkEngineerCompanyPositions[1], // Network Administrator
nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering
baseSalary: 410,
charismaEffectiveness: 20,
charismaExpGain: 0.1,
hackingEffectiveness: 80,
hackingExpGain: 0.5,
reqdCharisma: 76,
reqdHacking: 251,
reqdReputation: 175e3,
repMultiplier: 1.3,
},
{
name: posNames.BusinessCompanyPositions[0], // Business Intern
nextPosition: posNames.BusinessCompanyPositions[1], // Business Analyst
baseSalary: 46,
charismaEffectiveness: 90,
charismaExpGain: 0.08,
hackingEffectiveness: 10,
hackingExpGain: 0.01,
reqdCharisma: 1,
reqdHacking: 1,
repMultiplier: 0.9,
},
{
name: posNames.BusinessCompanyPositions[1], // Business Analyst
nextPosition: posNames.BusinessCompanyPositions[2], // Business Manager
baseSalary: 100,
charismaEffectiveness: 85,
charismaExpGain: 0.15,
hackingEffectiveness: 15,
hackingExpGain: 0.02,
reqdCharisma: 51,
reqdHacking: 6,
reqdReputation: 8e3,
repMultiplier: 1.1,
},
{
name: posNames.BusinessCompanyPositions[2], // Business Manager
nextPosition: posNames.BusinessCompanyPositions[3], // Operations Manager
baseSalary: 200,
charismaEffectiveness: 85,
charismaExpGain: 0.3,
hackingEffectiveness: 15,
hackingExpGain: 0.02,
reqdCharisma: 101,
reqdHacking: 51,
reqdReputation: 40e3,
repMultiplier: 1.3,
},
{
name: posNames.BusinessCompanyPositions[3], // Operations Manager
nextPosition: posNames.BusinessCompanyPositions[4], // Chief Financial Officer
baseSalary: 660,
charismaEffectiveness: 85,
charismaExpGain: 0.4,
hackingEffectiveness: 15,
hackingExpGain: 0.02,
reqdCharisma: 226,
reqdHacking: 51,
reqdReputation: 200e3,
repMultiplier: 1.5,
},
{
name: posNames.BusinessCompanyPositions[4], // Chief Financial Officer
nextPosition: posNames.BusinessCompanyPositions[5], // Chief Executive Officer
baseSalary: 1950,
charismaEffectiveness: 90,
charismaExpGain: 1,
hackingEffectiveness: 10,
hackingExpGain: 0.05,
reqdCharisma: 501,
reqdHacking: 76,
reqdReputation: 800e3,
repMultiplier: 1.6,
},
{
name: posNames.BusinessCompanyPositions[5], // Chief Executive Officer
nextPosition: null,
baseSalary: 3900,
charismaEffectiveness: 90,
charismaExpGain: 1.5,
hackingEffectiveness: 10,
hackingExpGain: 0.05,
reqdCharisma: 751,
reqdHacking: 101,
reqdReputation: 3.2e6,
repMultiplier: 1.75,
},
{
name: posNames.SecurityCompanyPositions[0], // Police Officer
nextPosition: posNames.SecurityCompanyPositions[1], // Police Chief
baseSalary: 82,
hackingEffectiveness: 5,
strengthEffectiveness: 20,
defenseEffectiveness: 20,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 15,
hackingExpGain: 0.02,
strengthExpGain: 0.08,
defenseExpGain: 0.08,
dexterityExpGain: 0.08,
agilityExpGain: 0.08,
charismaExpGain: 0.04,
reqdHacking: 11,
reqdStrength: 101,
reqdDefense: 101,
reqdDexterity: 101,
reqdAgility: 101,
reqdCharisma: 51,
reqdReputation: 8e3,
repMultiplier: 1,
},
{
name: posNames.SecurityCompanyPositions[1], // Police Chief
nextPosition: null,
baseSalary: 460,
hackingEffectiveness: 5,
strengthEffectiveness: 20,
defenseEffectiveness: 20,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 15,
hackingExpGain: 0.02,
strengthExpGain: 0.1,
defenseExpGain: 0.1,
dexterityExpGain: 0.1,
agilityExpGain: 0.1,
charismaExpGain: 0.1,
reqdHacking: 101,
reqdStrength: 301,
reqdDefense: 301,
reqdDexterity: 301,
reqdAgility: 301,
reqdCharisma: 151,
reqdReputation: 36e3,
repMultiplier: 1.25,
},
{
name: posNames.SecurityCompanyPositions[2], // Security Guard
nextPosition: posNames.SecurityCompanyPositions[3], // Security Officer
baseSalary: 50,
hackingEffectiveness: 5,
strengthEffectiveness: 20,
defenseEffectiveness: 20,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 15,
hackingExpGain: 0.01,
strengthExpGain: 0.04,
defenseExpGain: 0.04,
dexterityExpGain: 0.04,
agilityExpGain: 0.04,
charismaExpGain: 0.02,
reqdStrength: 51,
reqdDefense: 51,
reqdDexterity: 51,
reqdAgility: 51,
reqdCharisma: 1,
repMultiplier: 1,
},
{
name: posNames.SecurityCompanyPositions[3], // Security Officer
nextPosition: posNames.SecurityCompanyPositions[4], // Security Supervisor
baseSalary: 195,
hackingEffectiveness: 10,
strengthEffectiveness: 20,
defenseEffectiveness: 20,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 10,
hackingExpGain: 0.02,
strengthExpGain: 0.1,
defenseExpGain: 0.1,
dexterityExpGain: 0.1,
agilityExpGain: 0.1,
charismaExpGain: 0.05,
reqdHacking: 26,
reqdStrength: 151,
reqdDefense: 151,
reqdDexterity: 151,
reqdAgility: 151,
reqdCharisma: 51,
reqdReputation: 8e3,
repMultiplier: 1.1,
},
{
name: posNames.SecurityCompanyPositions[4], // Security Supervisor
nextPosition: posNames.SecurityCompanyPositions[5], // Head of Security
baseSalary: 660,
hackingEffectiveness: 10,
strengthEffectiveness: 15,
defenseEffectiveness: 15,
dexterityEffectiveness: 15,
agilityEffectiveness: 15,
charismaEffectiveness: 30,
hackingExpGain: 0.02,
strengthExpGain: 0.12,
defenseExpGain: 0.12,
dexterityExpGain: 0.12,
agilityExpGain: 0.12,
charismaExpGain: 0.1,
reqdHacking: 26,
reqdStrength: 251,
reqdDefense: 251,
reqdDexterity: 251,
reqdAgility: 251,
reqdCharisma: 101,
reqdReputation: 36e3,
repMultiplier: 1.25,
},
{
name: posNames.SecurityCompanyPositions[5], // Head of Security
nextPosition: null,
baseSalary: 1320,
hackingEffectiveness: 10,
strengthEffectiveness: 15,
defenseEffectiveness: 15,
dexterityEffectiveness: 15,
agilityEffectiveness: 15,
charismaEffectiveness: 30,
hackingExpGain: 0.05,
strengthExpGain: 0.15,
defenseExpGain: 0.15,
dexterityExpGain: 0.15,
agilityExpGain: 0.15,
charismaExpGain: 0.15,
reqdHacking: 51,
reqdStrength: 501,
reqdDefense: 501,
reqdDexterity: 501,
reqdAgility: 501,
reqdCharisma: 151,
reqdReputation: 144e3,
repMultiplier: 1.4,
},
{
name: posNames.AgentCompanyPositions[0], // Field Agent
nextPosition: posNames.AgentCompanyPositions[1], // Secret Agent
baseSalary: 330,
hackingEffectiveness: 10,
strengthEffectiveness: 15,
defenseEffectiveness: 15,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 20,
hackingExpGain: 0.04,
strengthExpGain: 0.08,
defenseExpGain: 0.08,
dexterityExpGain: 0.08,
agilityExpGain: 0.08,
charismaExpGain: 0.05,
reqdHacking: 101,
reqdStrength: 101,
reqdDefense: 101,
reqdDexterity: 101,
reqdAgility: 101,
reqdCharisma: 101,
reqdReputation: 8e3,
repMultiplier: 1,
},
{
name: posNames.AgentCompanyPositions[1], // Secret Agent
nextPosition: posNames.AgentCompanyPositions[2], // Special Operative
baseSalary: 990,
hackingEffectiveness: 15,
strengthEffectiveness: 15,
defenseEffectiveness: 15,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 15,
hackingExpGain: 0.1,
strengthExpGain: 0.15,
defenseExpGain: 0.15,
dexterityExpGain: 0.15,
agilityExpGain: 0.15,
charismaExpGain: 0.1,
reqdHacking: 201,
reqdStrength: 251,
reqdDefense: 251,
reqdDexterity: 251,
reqdAgility: 251,
reqdCharisma: 201,
reqdReputation: 32e3,
repMultiplier: 1.25,
},
{
name: posNames.AgentCompanyPositions[2], // Special Operative
nextPosition: null,
baseSalary: 2000,
hackingEffectiveness: 15,
strengthEffectiveness: 15,
defenseEffectiveness: 15,
dexterityEffectiveness: 20,
agilityEffectiveness: 20,
charismaEffectiveness: 15,
hackingExpGain: 0.15,
strengthExpGain: 0.2,
defenseExpGain: 0.2,
dexterityExpGain: 0.2,
agilityExpGain: 0.2,
charismaExpGain: 0.15,
reqdHacking: 251,
reqdStrength: 501,
reqdDefense: 501,
reqdDexterity: 501,
reqdAgility: 501,
reqdCharisma: 251,
reqdReputation: 162e3,
repMultiplier: 1.5,
},
{
name: posNames.MiscCompanyPositions[0], // Waiter
nextPosition: null,
baseSalary: 22,
strengthEffectiveness: 10,
dexterityEffectiveness: 10,
agilityEffectiveness: 10,
charismaEffectiveness: 70,
strengthExpGain: 0.02,
defenseExpGain: 0.02,
dexterityExpGain: 0.02,
agilityExpGain: 0.02,
charismaExpGain: 0.05,
repMultiplier: 1,
},
{
name: posNames.MiscCompanyPositions[1], // Employee
nextPosition: null,
baseSalary: 22,
strengthEffectiveness: 10,
dexterityEffectiveness: 10,
agilityEffectiveness: 10,
charismaEffectiveness: 70,
strengthExpGain: 0.02,
defenseExpGain: 0.02,
dexterityExpGain: 0.02,
agilityExpGain: 0.02,
charismaExpGain: 0.04,
repMultiplier: 1,
},
{
name: posNames.SoftwareConsultantCompanyPositions[0], // Software Consultant
nextPosition: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant
baseSalary: 66,
hackingEffectiveness: 80,
charismaEffectiveness: 20,
hackingExpGain: 0.08,
charismaExpGain: 0.03,
reqdHacking: 51,
repMultiplier: 1,
},
{
name: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant
nextPosition: null,
baseSalary: 132,
hackingEffectiveness: 75,
charismaEffectiveness: 25,
hackingExpGain: 0.25,
charismaExpGain: 0.06,
reqdHacking: 251,
reqdCharisma: 51,
repMultiplier: 1.2,
},
{
name: posNames.BusinessConsultantCompanyPositions[0], // Business Consultant
nextPosition: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant
baseSalary: 66,
hackingEffectiveness: 20,
charismaEffectiveness: 80,
hackingExpGain: 0.015,
charismaExpGain: 0.15,
reqdHacking: 6,
reqdCharisma: 51,
repMultiplier: 1,
},
{
name: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant
nextPosition: null,
baseSalary: 525,
hackingEffectiveness: 15,
charismaEffectiveness: 85,
hackingExpGain: 0.015,
charismaExpGain: 0.3,
reqdHacking: 51,
reqdCharisma: 226,
repMultiplier: 1.2,
},
{
name: posNames.PartTimeCompanyPositions[0], // Part-time waiter
nextPosition: null,
baseSalary: 20,
strengthEffectiveness: 10,
dexterityEffectiveness: 10,
agilityEffectiveness: 10,
charismaEffectiveness: 70,
strengthExpGain: 0.0075,
defenseExpGain: 0.0075,
dexterityExpGain: 0.0075,
agilityExpGain: 0.0075,
charismaExpGain: 0.04,
repMultiplier: 1,
},
{
name: posNames.PartTimeCompanyPositions[1], // Part-time employee
nextPosition: null,
baseSalary: 20,
strengthEffectiveness: 10,
dexterityEffectiveness: 10,
agilityEffectiveness: 10,
charismaEffectiveness: 70,
strengthExpGain: 0.0075,
defenseExpGain: 0.0075,
dexterityExpGain: 0.0075,
agilityExpGain: 0.0075,
charismaExpGain: 0.03,
repMultiplier: 1,
},
]

@ -0,0 +1,72 @@
// Defs for job titles, stored in arrays and categorized by job "type"
export const SoftwareCompanyPositions: string[] = [
"Software Engineering Intern",
"Junior Software Engineer",
"Senior Software Engineer",
"Lead Software Developer",
"Head of Software",
"Head of Engineering",
"Vice President of Technology",
"Chief Technology Officer"
];
export const ITCompanyPositions: string[] = [
"IT Intern",
"IT Analyst",
"IT Manager",
"Systems Administrator"
];
export const SecurityEngineerCompanyPositions: string[] = [
"Security Engineer"
];
export const NetworkEngineerCompanyPositions: string[] = [
"Network Engineer",
"Network Administrator"
];
export const BusinessCompanyPositions: string[] = [
"Business Intern",
"Business Analyst",
"Business Manager",
"Operations Manager",
"Chief Financial Officer",
"Chief Executive Officer"
];
export const SecurityCompanyPositions: string[] = [
"Police Officer",
"Police Chief",
"Security Guard",
"Security Officer",
"Security Supervisor",
"Head of Security"
];
export const AgentCompanyPositions: string[] = [
"Field Agent",
"Secret Agent",
"Special Operative"
];
export const MiscCompanyPositions: string[] = [
"Waiter",
"Employee"
];
export const SoftwareConsultantCompanyPositions: string[] = [
"Software Consultant",
"Senior Software Consultant"
];
export const BusinessConsultantCompanyPositions: string[] = [
"Business Consultant",
"Senior Business Consultant"
];
export const PartTimeCompanyPositions: string[] = [
"Part-time Waiter",
"Part-time Employee"
];

@ -1,5 +1,7 @@
let CONSTANTS = {
Version: "0.41.1",
import {IMap} from "./types";
export let CONSTANTS: IMap<any> = {
Version: "0.41.2",
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -54,6 +56,7 @@ let CONSTANTS = {
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
@ -502,33 +505,31 @@ let CONSTANTS = {
LatestUpdate:
`
v0.41.1
v0.41.2
* IMPORTANT - Netscript Changes:
** purchaseTor() now returns true if you already have a TOR router (it used to return false)
** getPurchasedServerCost() now returns Infinity if the specified RAM is an invalid amount or is greater than the max amount of RAM (2 ^ 20 GB)
** Added purchase4SMarketData() and purchase4SMarketDataTixApi() functions
** getScriptLogs() now takes in optional arguments that let you get the logs of another script
** rm() now takes an optional parameter that lets you specify on which server to delete the file
** Added growthAnalyze() Netscript function
* Stock Market changes:
** Stocks now have "maximum prices". These are hidden from the player
** If a stock reaches its "maximum price", it will most likely drop in value (although it might still rise)
** Each stock has its own, unique maximum price
** Maximum price for each stock are randomly generated and change during each 'reset'
** Stock Market cycles are now accumulated/stored, much like it is for Gangs and Bladeburners
** Accumulated/stored cycles cause stock prices to update up to 50% faster (from every 6 seconds to 4 seconds)
*** This means that after coming back from being offline, stock prices will update faster to make up for offline time
* Gang Changes:
** UI now displays your chance to win a clash with other gangs
** Added getChanceToWinClash() function to the Gang API
** Added getEquipmentType() function to the Gang API
** Added several new hacking-based equipment and Augmentations
** Rebalanced several equipment/upgrades to give less defense
** Wanted level gain rate is now be slightly higher for all tasks
** Rebalanced parameters for "hacking" tasks
* Decreased the Hacking Level multiplier for BitNodes 6 and 7 to 0.4 (from 0.5)
* Bladeburner console history is now saved and persists when switching screens or closing/reopening the game
* In Bladeburner, if your stamina reaches 0 your current action will be cancelled
* b1t_flum3.exe is no longer removed from your home computer upon reset
* Added main menu link for the Stock Market (once you've purchased an account)
* Job main menu link only appears if you actually have a job
* Bug Fix: Netscript Gang API functions purchaseEquipment() and ascendMember() should now work properly
* Bug Fix: After installing Augs, the "Portfolio Mode" button on the Stock Market page should be properly reset
* Bug Fix: bladeburner.getActionCountRemaining()'s return value is now rounded down (by Kline-)
* Added new Main Menu configuration in .fconf: "compact"
* Added the terminal command 'expr', which can be used to evaluate simple mathematical expressions
* Bug Fix: Can no longer purchase duplicate equipment/Augmentations through gang.purchaseEquipment()
* Bug Fix: scp() should no longer throw errors when used with 2-arguments and an array of files
* Bug Fix: Coding Contracts no longer give money in BitNode-8
* Bug Fix: In Bladeburner, you can no longer start a BlackOp through the Netscript API if it has already been completed
* Bug Fix: In Bladeburner, fixed a bug which caused the configured 'automate' actions to occasionally be switched to other actions
* Bug Fix: 'Return to World' button at locations no longer accumulates event listeners
* Bug Fix: Working & taking classes now continuously add/subtract money during the action, instead of doing it at completion
* Bug Fix: Top-right overview panel now displays negative money using '-' instead of '()'
* Bug Fix: Stock Market UI should no longer show 'NaN' profit immediately after buying a stock
`
}
export {CONSTANTS};

@ -345,6 +345,26 @@ export function createDevMenu() {
innerText: "Gain Bladeburner Rank",
});
const bladeburnerStoredCyclesInput = createElement("input", {
class: "text-input",
margin: "5px",
placeholder: "# Cycles to Add",
type: "number",
});
const bladeburnerStoredCyclesButton = createElement("button", {
class: "std-button",
clickListener: () => {
try {
const cycles = parseInt(bladeburnerStoredCyclesInput.value);
Player.bladeburner.storedCycles += cycles;
} catch(e) {
exceptionAlert(`Failed to add cycles to Bladeburner in dev menu: ${e}`);
}
},
innerText: "Add Cycles to Bladeburner mechanic",
});
// Gang
const gangHeader = createElement("h2", {innerText: "Gang"});
@ -501,6 +521,9 @@ export function createDevMenu() {
devMenuContainer.appendChild(bladeburnerGainRankInput);
devMenuContainer.appendChild(bladeburnerGainRankButton);
devMenuContainer.appendChild(createElement("br"));
devMenuContainer.appendChild(bladeburnerStoredCyclesInput);
devMenuContainer.appendChild(bladeburnerStoredCyclesButton);
devMenuContainer.appendChild(createElement("br"));
devMenuContainer.appendChild(gangHeader);
devMenuContainer.appendChild(gangStoredCyclesInput);
devMenuContainer.appendChild(gangAddStoredCycles);

@ -387,12 +387,15 @@ function displayFactionContent(factionName) {
class:"a-link-button", innerText:"Manage Gang",
clickListener: () => {
if (!Player.inGang()) {
// Determine whether this is a hacking gang
let hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") { hacking = true; }
// Configure Yes/No buttons for the pop-up
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Create Gang";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", () => {
var hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") {hacking = true;}
Player.startGang(factionName, hacking);
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
@ -402,10 +405,24 @@ function displayFactionContent(factionName) {
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
yesNoBoxCreate("Would you like to create a new Gang with " + factionName + "?<br><br>" +
// Pop-up text
let gangTypeText = "";
if (hacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${factionName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed. There are NO differences between the Factions you can " +
"create a Gang with and each of these Factions have all Augmentations available");
"this BitNode is destroyed.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
} else {
Engine.loadGangContent();
}

@ -23,7 +23,7 @@ var FconfComments = {
ENABLE_TIMESTAMPS: "Terminal commands and log entries will be timestamped. The timestamp\n" +
"will have the format: M/D h:m",
MAIN_MENU_STYLE: "Customize the main navigation menu on the left-hand side. Current options:\n\n" +
"default, classic",
"default, classic, compact",
THEME_BACKGROUND_COLOR: "Sets the background color for not only the Terminal, but also for\n" +
"most of the game's UI.\n\n" +
"The color must be specified as a pound sign (#) followed by a \n" +
@ -46,7 +46,7 @@ var FconfComments = {
"before its effect takes place.",
}
const MainMenuStyleOptions = ["default", "classic"];
const MainMenuStyleOptions = ["default", "classic", "compact"];
//Parse Fconf settings from the config text
//Throws an exception if parsing fails
@ -231,12 +231,18 @@ function setMainMenuStyle() {
if (FconfSettings["MAIN_MENU_STYLE"] === "default") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("classic");
mainMenu.classList.remove("compact");
addClassToAllAccordionHeaders("mainmenu-accordion-header");
} else if (FconfSettings["MAIN_MENU_STYLE"] === "classic") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("compact");
mainMenu.classList.add("classic");
addClassToAllAccordionHeaders("mainmenu-accordion-header-classic");
} else if (FconfSettings["MAIN_MENU_STYLE"] === "compact") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("classic");
mainMenu.classList.add("compact");
addClassToAllAccordionHeaders("mainmenu-accordion-header-compact");
} else {
return;
}

@ -272,11 +272,11 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
if (gainRoll < 0.5) {
// Multiplicative gain (50% chance)
// This is capped per cycle, to prevent it from getting out of control
const multiplicativeGain = AllGangs[name].power * 0.008;
AllGangs[name].power += Math.min(0.9, multiplicativeGain);
const multiplicativeGain = AllGangs[name].power * 0.005;
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
} else {
// Additive gain (50% chance)
const additiveGain = 0.5 * gainRoll * AllGangs[name].territory;
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
AllGangs[name].power += (additiveGain);
}
}
@ -533,6 +533,27 @@ Gang.prototype.getUpgradeCost = function(upgName) {
return GangMemberUpgrades[upgName].getCost(this);
}
// Returns a player-friendly string stating the type of the specified upgrade
Gang.prototype.getUpgradeType = function(upgName) {
const upg = GangMemberUpgrades[upgName];
if (upg == null) { return ""; }
switch (upg.type) {
case "w":
return "Weapon";
case "a":
return "Armor";
case "v":
return "Vehicle";
case "r":
return "Rootkit";
case "g":
return "Augmentation";
default:
return "";
}
}
Gang.prototype.toJSON = function() {
return Generic_toJSON("Gang", this);
}
@ -660,7 +681,7 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) {
if (task.baseWanted < 0) {
return 0.5 * task.baseWanted * statWeight * territoryMult;
} else {
return 7 * task.baseWanted / (3 * statWeight * territoryMult);
return 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
}
}
@ -788,6 +809,11 @@ GangMember.prototype.buyUpgrade = function(upg, player, gang) {
if (!(upg instanceof GangMemberUpgrade)) {
return false;
}
// Prevent purchasing of already-owned upgrades
if (this.augmentations.includes(upg.name) || this.upgrades.includes(upg.name)) {
return false;
}
if (player.money.lt(upg.getCost(gang))) { return false; }
player.loseMoney(upg.getCost(gang));
if (upg.type === "g") {
@ -1521,9 +1547,10 @@ Gang.prototype.updateGangContent = function() {
// Update territory information
UIElems.gangTerritoryInfoText.innerHTML = "";
for (var gangname in AllGangs) {
const playerPower = AllGangs[this.facName].power;
for (const gangname in AllGangs) {
if (AllGangs.hasOwnProperty(gangname)) {
var gangTerritoryInfo = AllGangs[gangname];
const gangTerritoryInfo = AllGangs[gangname];
let territory = gangTerritoryInfo.territory * 100;
//Fix some rounding issues graphically
@ -1536,12 +1563,16 @@ Gang.prototype.updateGangContent = function() {
displayNumber = formatNumber(territory, 2);
}
if (gangname == this.facName) {
UIElems.gangTerritoryInfoText.innerHTML += ("<b>" + gangname + "</b><br>(Power: " + formatNumber(gangTerritoryInfo.power, 6) + "): " +
displayNumber + "%<br><br>");
if (gangname === this.facName) {
let newHTML = `<b><u>${gangname}</u></b><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br><br>`;
UIElems.gangTerritoryInfoText.innerHTML += newHTML;
} else {
UIElems.gangTerritoryInfoText.innerHTML += (gangname + "<br>(Power: " + formatNumber(gangTerritoryInfo.power, 6) + "): " +
displayNumber + "%<br><br>");
const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower);
let newHTML = `<u>${gangname}</u><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br>`;
newHTML += `Chance to win clash with this gang: ${numeralWrapper.format(clashVictoryChance, "0.000%")}<br><br>`;
UIElems.gangTerritoryInfoText.innerHTML += newHTML;
}
}
}
@ -1853,6 +1884,25 @@ Gang.prototype.updateGangMemberDisplayElement = function(memberObj) {
`Wanted Level: ${formatNumber(5*memberObj.calculateWantedLevelGain(this), 6)} / sec`,
`Total Respect Earned: ${formatNumber(memberObj.earnedRespect, 6)}`].join("<br>");
}
// Update selector to have the correct task
const taskSelector = document.getElementById(name + "gang-member-task-selector");
if (taskSelector) {
let tasks = this.getAllTaskNames();
tasks.unshift("---");
if (GangMemberTasks.hasOwnProperty(memberObj.task)) {
const taskName = memberObj.task;
let taskIndex = 0;
for (let i = 0; i < tasks.length; ++i) {
if (taskName === tasks[i]) {
taskIndex = i;
break;
}
}
taskSelector.selectedIndex = taskIndex;
}
}
}
Gang.prototype.setGangMemberTaskDescription = function(memberObj, taskName) {

@ -10,6 +10,7 @@ export const TerminalHelpText: string =
"cls See 'clear' command <br>" +
"connect [ip/hostname] Connects to a remote server<br>" +
"download [script/text file] Downloads scripts or text files to your computer<br>" +
"expr [math expression] Evaluate a mathematical expression<br>" +
"free Check the machine's memory (RAM) usage<br>" +
"hack Hack the current machine<br>" +
"help [command] Display this help text, or the help text for a command<br>" +
@ -96,6 +97,12 @@ export const HelpTexts: IMap<string> = {
"Download all scripts and text files: download *<br>" +
"Download all scripts: download *.script<br>" +
"Download all text files: download *.txt<br>",
expr: "expr [mathematical expression]<br>" +
"Evaluate a simple mathematical expression. Supports native JavaScript operators:<br>" +
"+, -, /, *, **, %<br>" +
"Example:<br>" +
"expr 25 * 2 ** 10<br>" +
"Note that letters (non-digits) are not allowed and will be removed from the input.",
free: "free<br>" +
"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as " +
"how much of it is being used.",

@ -1,6 +1,8 @@
import {Bladeburner} from "./Bladeburner";
import {CompanyPositions, initCompanies,
Companies, getJobRequirementText} from "./Company";
import {CompanyPositions} from "./Company/CompanyPositions";
import {Companies} from "./Company/Companies";
import {getJobRequirementText} from "./Company/GetJobRequirementText";
import * as posNames from "./Company/data/CompanyPositionNames";
import {Corporation} from "./CompanyManagement";
import {CONSTANTS} from "./Constants";
import {Crimes} from "./Crimes";
@ -28,7 +30,7 @@ import {yesNoBoxCreate, yesNoTxtInpBoxCreate,
yesNoTxtInpBoxClose} from "../utils/YesNoBox";
function displayLocationContent() {
var returnToWorld = document.getElementById("location-return-to-world-button");
var returnToWorld = clearEventListeners("location-return-to-world-button");
var locationName = document.getElementById("location-name");
@ -111,7 +113,7 @@ function displayLocationContent() {
var cityHallCreateCorporation = document.getElementById("location-cityhall-create-corporation");
var nsaBladeburner = document.getElementById("location-nsa-bladeburner");
var nsaBladeburner = clearEventListeners("location-nsa-bladeburner");
var loc = Player.location;
@ -233,7 +235,7 @@ function displayLocationContent() {
//Check if the player is employed at this Location. If he is, display the "Work" button,
//update the job title, etc.
if (loc != "" && loc === Player.companyName) {
var company = Companies[loc];
let company = Companies[loc];
jobTitle.style.display = "block";
jobReputation.style.display = "inline";
@ -241,8 +243,8 @@ function displayLocationContent() {
locationTxtDiv1.style.display = "block";
locationTxtDiv2.style.display = "block";
locationTxtDiv3.style.display = "block";
jobTitle.innerHTML = "Job Title: " + Player.companyPosition.positionName;
var repGain = company.getFavorGain();
jobTitle.innerHTML = "Job Title: " + Player.companyPosition;
let repGain = company.getFavorGain();
if (repGain.length != 2) {repGain = 0;}
repGain = repGain[0];
jobReputation.innerHTML = "Company reputation: " + formatNumber(company.playerReputation, 4) +
@ -256,10 +258,13 @@ function displayLocationContent() {
"favor you gain depends on how much reputation you have with the company</span>";
work.style.display = "block";
var currPos = Player.companyPosition;
let currPos = CompanyPositions[Player.companyPosition];
if (currPos == null) {
throw new Error("Player's companyPosition property has an invalid value");
}
work.addEventListener("click", function() {
if (currPos.isPartTimeJob()) {
if (currPos.isPartTimeJob() || currPos.isSoftwareConsultantJob() || currPos.isBusinessConsultantJob()) {
Player.startWorkPartTime();
} else {
Player.startWork();
@ -301,19 +306,19 @@ function displayLocationContent() {
var hospitalTreatmentCost = (Player.max_hp - Player.hp) * CONSTANTS.HospitalCostPerHp;
//Set tooltip for job requirements
setJobRequirementTooltip(loc, CompanyPositions.SoftwareIntern, softwareJob);
setJobRequirementTooltip(loc, CompanyPositions.SoftwareConsultant, softwareConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions.ITIntern, itJob);
setJobRequirementTooltip(loc, CompanyPositions.SecurityEngineer, securityEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions.NetworkEngineer, networkEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions.BusinessIntern, businessJob);
setJobRequirementTooltip(loc, CompanyPositions.BusinessConsultant, businessConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions.SecurityGuard, securityJob);
setJobRequirementTooltip(loc, CompanyPositions.FieldAgent, agentJob);
setJobRequirementTooltip(loc, CompanyPositions.Employee, employeeJob);
setJobRequirementTooltip(loc, CompanyPositions.PartTimeEmployee, employeePartTimeJob);
setJobRequirementTooltip(loc, CompanyPositions.Waiter, waiterJob);
setJobRequirementTooltip(loc, CompanyPositions.PartTimeWaiter, waiterPartTimeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SoftwareCompanyPositions[0]], softwareJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], softwareConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.ITCompanyPositions[0]], itJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], securityEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], networkEngineerJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessCompanyPositions[0]], businessJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], businessConsultantJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.SecurityCompanyPositions[0]], securityJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.AgentCompanyPositions[0]], agentJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[1]], employeeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[1]], employeePartTimeJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.MiscCompanyPositions[0]], waiterJob);
setJobRequirementTooltip(loc, CompanyPositions[posNames.PartTimeCompanyPositions[0]], waiterPartTimeJob);
switch (loc) {
case Locations.AevumTravelAgency:
@ -1035,13 +1040,13 @@ function displayLocationContent() {
if (loc == Player.companyName) {
var currPos = Player.companyPosition;
if (currPos.positionName == CompanyPositions.Employee.positionName) {
if (currPos == "Employee") {
employeeJob.style.display = "none";
} else if (currPos.positionName == CompanyPositions.Waiter.positionName) {
} else if (currPos == "Waiter") {
waiterJob.style.display = "none";
} else if (currPos.positionName == CompanyPositions.PartTimeEmployee.positionName) {
} else if (currPos == "Part-time Employee") {
employeePartTimeJob.style.display = "none";
} else if (currPos.positionName == CompanyPositions.PartTimeWaiter.positionName) {
} else if (currPos == "Part-time Waiter") {
waiterPartTimeJob.style.display = "none";
}
}

@ -8,8 +8,10 @@ import {Augmentations, Augmentation,
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {determineCrimeSuccess, findCrime} from "./Crimes";
import {Bladeburner} from "./Bladeburner";
import {Companies, Company, CompanyPosition,
CompanyPositions, companyExists} from "./Company";
import {Company} from "./Company/Company";
import {Companies, companyExists} from "./Company/Companies";
import {CompanyPosition} from "./Company/CompanyPosition";
import {CompanyPositions} from "./Company/CompanyPositions";
import {CONSTANTS} from "./Constants";
import {Programs} from "./CreateProgram";
import {DarkWebItems} from "./DarkWeb";
@ -31,7 +33,7 @@ import {Script, findRunningScript, RunningScript,
isScriptFilename} from "./Script";
import {Server, getServer, AddToAllServers,
AllServers, processSingleServerGrowth,
GetServerByHostname} from "./Server";
GetServerByHostname, numCycleForGrowth} from "./Server";
import {Settings} from "./Settings";
import {SpecialServerIps} from "./SpecialServerIps";
import {Stock} from "./Stock";
@ -461,6 +463,20 @@ function NetscriptFunctions(workerScript) {
return Promise.resolve(moneyAfter/moneyBefore);
});
},
growthAnalyze : function(ip, growth) {
if (workerScript.checkingRam) {
return updateStaticRam("growthAnalyze", CONSTANTS.ScriptGrowthAnalyzeRamCost);
}
updateDynamicRam("growthAnalyze", CONSTANTS.ScriptGrowthAnalyzeRamCost);
// Check argument validity
const server = safeGetServer(ip, 'growthAnalyze');
if (isNaN(growth)) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`);
}
return numCycleForGrowth(server, Number(growth));
},
weaken : function(ip) {
if (workerScript.checkingRam) {
return updateStaticRam("weaken", CONSTANTS.ScriptWeakenRamCost);
@ -919,7 +935,7 @@ function NetscriptFunctions(workerScript) {
var destServer, currServ;
if (arguments.length === 3) { //scriptname, source, destination
if (ip2 != null) { // 3 Argument version: scriptname, source, destination
if (scriptname === undefined || ip1 === undefined || ip2 === undefined) {
throw makeRuntimeRejectMsg(workerScript, "ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");
}
@ -932,7 +948,7 @@ function NetscriptFunctions(workerScript) {
if (currServ == null) {
throw makeRuntimeRejectMsg(workerScript, `ERROR: Invalid hostname/ip passed into scp() command: ${ip1}`);
}
} else if (arguments.length === 2) { //scriptname, destination
} else if (ip1 != null) { // 2 Argument version: scriptname, destination
if (scriptname === undefined || ip1 === undefined) {
throw makeRuntimeRejectMsg(workerScript, "ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");
}
@ -945,6 +961,8 @@ function NetscriptFunctions(workerScript) {
if (currServ == null) {
throw makeRuntimeRejectMsg(workerScript, "Could not find server ip for this script. This is a bug please contact game developer");
}
} else {
throw makeRuntimeRejectMsg(workerScript, "ERROR: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");
}
//Scp for lit files
@ -2138,14 +2156,18 @@ function NetscriptFunctions(workerScript) {
}
return port;
},
rm : function(fn) {
rm : function(fn, ip) {
if (workerScript.checkingRam) {
return updateStaticRam("rm", CONSTANTS.ScriptReadWriteRamCost);
}
updateDynamicRam("rm", CONSTANTS.ScriptReadWriteRamCost);
var s = getServer(workerScript.serverIp);
if (ip == null || ip === "") {
ip = workerScript.serverIp;
}
var s = getServer(ip);
if (s == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in clear(). This is a bug please contact game dev");
throw makeRuntimeRejectMsg(workerScript, `Invalid server specified for rm(): ${ip}`);
}
if (fn.includes(".exe")) {
@ -2779,8 +2801,8 @@ function NetscriptFunctions(workerScript) {
}
var companyPositionTitle = "";
if (Player.companyPosition instanceof CompanyPosition) {
companyPositionTitle = Player.companyPosition.positionName;
if (CompanyPositions[Player.companyPosition] instanceof CompanyPosition) {
companyPositionTitle = Player.companyPosition;
}
return {
bitnode: Player.bitNodeN,
@ -2921,7 +2943,8 @@ function NetscriptFunctions(workerScript) {
return;
}
if (Player.companyPosition == "" || !(Player.companyPosition instanceof CompanyPosition)) {
const companyPosition = CompanyPositions[Player.companyPosition];
if (Player.companyPosition === "" || !(companyPosition instanceof CompanyPosition)) {
workerScript.scriptRef.log("ERROR: workForCompany() failed because you do not have a job");
return false;
}
@ -2933,13 +2956,13 @@ function NetscriptFunctions(workerScript) {
}
}
if (Player.companyPosition.isPartTimeJob()) {
if (companyPosition.isPartTimeJob()) {
Player.startWorkPartTime();
} else {
Player.startWork();
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.workForCompany == null) {
workerScript.scriptRef.log("Began working at " + Player.companyName + " as a " + Player.companyPosition.positionName);
workerScript.log(`Began working at ${Player.companyName} as a ${Player.companyPosition}`);
}
return true;
},
@ -3015,11 +3038,11 @@ function NetscriptFunctions(workerScript) {
}
if (res) {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.applyToCompany == null) {
workerScript.scriptRef.log("You were offered a new job at " + companyName + " as a " + Player.companyPosition.positionName);
workerScript.log(`You were offered a new job at ${companyName} as a ${Player.companyPosition}`);
}
} else {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.applyToCompany == null) {
workerScript.scriptRef.log("You failed to get a new job/promotion at " + companyName + " in the " + field + " field.");
workerScript.log(`You failed to get a new job/promotion at ${companyName} in the ${field} field.`);
}
}
return res;
@ -3860,6 +3883,19 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getEquipmentCost", e));
}
},
getEquipmentType : function(equipName) {
if (workerScript.checkingRam) {
return updateStaticRam("getEquipmentType", CONSTANTS.ScriptGangApiBaseRamCost / 2);
}
updateDynamicRam("getEquipmentType", CONSTANTS.ScriptGangApiBaseRamCost / 2);
nsGang.checkGangApiAccess(workerScript, "getEquipmentType");
try {
return Player.gang.getUpgradeType(equipName);
} catch(e) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getEquipmentType", e));
}
},
purchaseEquipment : function(memberName, equipName) {
if (workerScript.checkingRam) {
return updateStaticRam("purchaseEquipment", CONSTANTS.ScriptGangApiBaseRamCost);
@ -3932,6 +3968,27 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("setTerritoryWarfare", e));
}
},
getChanceToWinClash : function(otherGang) {
if (workerScript.checkingRam) {
return updateStaticRam("getChanceToWinClash", CONSTANTS.ScriptGangApiBaseRamCost);
}
updateDynamicRam("getChanceToWinClash", CONSTANTS.ScriptGangApiBaseRamCost);
nsGang.checkGangApiAccess(workerScript, "getChanceToWinClash");
try {
if (AllGangs[otherGang] == null) {
workerScript.log(`Invalid gang specified in gang.getChanceToWinClash() : ${otherGang}`);
return 0;
}
const playerPower = AllGangs[Player.gang.facName].power;
const otherPower = AllGangs[otherGang].power;
return playerPower / (otherPower + playerPower);
} catch(e) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getChanceToWinClash", e));
}
},
getBonusTime : function() {
if (workerScript.checkingRam) { return 0; }
nsGang.checkGangApiAccess(workerScript, "getBonusTime");

@ -3,9 +3,12 @@ import {Augmentations, applyAugmentation,
PlayerOwnedAugmentation} from "./Augmentations";
import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {CodingContractRewardType} from "./CodingContracts";
import {Company, Companies, getNextCompanyPosition,
getJobRequirementText, CompanyPosition,
CompanyPositions} from "./Company";
import {Company} from "./Company/Company";
import {Companies} from "./Company/Companies";
import {getNextCompanyPosition} from "./Company/GetNextCompanyPosition";
import {getJobRequirementText} from "./Company/GetJobRequirementText";
import {CompanyPositions} from "./Company/CompanyPositions";
import * as posNames from "./Company/data/CompanyPositionNames";
import {CONSTANTS} from "./Constants";
import {Corporation} from "./CompanyManagement";
import {Programs} from "./CreateProgram";
@ -94,8 +97,8 @@ function PlayerObject() {
this.location = "";
//Company Information
this.companyName = ""; //Name of Company, equivalent to an object from Locations
this.companyPosition = ""; //CompanyPosition object
this.companyName = ""; // Name of Company. Must match a key value in Companies map
this.companyPosition = ""; // Name of Company Position. Must match a key value in CompanyPositions map
//Servers
this.currentServer = ""; //IP address of Server currently being accessed through terminal
@ -636,12 +639,13 @@ PlayerObject.prototype.resetWorkStatus = function() {
}
PlayerObject.prototype.processWorkEarnings = function(numCycles=1) {
var hackExpGain = this.workHackExpGainRate * numCycles;
var strExpGain = this.workStrExpGainRate * numCycles;
var defExpGain = this.workDefExpGainRate * numCycles;
var dexExpGain = this.workDexExpGainRate * numCycles;
var agiExpGain = this.workAgiExpGainRate * numCycles;
var chaExpGain = this.workChaExpGainRate * numCycles;
const hackExpGain = this.workHackExpGainRate * numCycles;
const strExpGain = this.workStrExpGainRate * numCycles;
const defExpGain = this.workDefExpGainRate * numCycles;
const dexExpGain = this.workDexExpGainRate * numCycles;
const agiExpGain = this.workAgiExpGainRate * numCycles;
const chaExpGain = this.workChaExpGainRate * numCycles;
const moneyGain = (this.workMoneyGainRate - this.workMoneyLossRate) * numCycles;
this.gainHackingExp(hackExpGain);
this.gainStrengthExp(strExpGain);
@ -649,6 +653,7 @@ PlayerObject.prototype.processWorkEarnings = function(numCycles=1) {
this.gainDexterityExp(dexExpGain);
this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain);
this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain;
@ -709,13 +714,13 @@ PlayerObject.prototype.work = function(numCycles) {
var comp = Companies[this.companyName], companyRep = "0";
if (comp == null || !(comp instanceof Company)) {
console.log("ERROR: Could not find Company: " + this.companyName);
console.error(`Could not find Company: ${this.companyName}`);
} else {
companyRep = comp.playerReputation;
}
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML = "You are currently working as a " + this.companyPosition.positionName +
txt.innerHTML = "You are currently working as a " + this.companyPosition +
" at " + this.companyName + " (Current Company Reputation: " +
numeralWrapper.format(companyRep, '0,0') + ")<br><br>" +
"You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "<br><br>" +
@ -741,8 +746,6 @@ PlayerObject.prototype.finishWork = function(cancelled, sing=false) {
var company = Companies[this.companyName];
company.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You earned a total of: <br>" +
@ -837,7 +840,7 @@ PlayerObject.prototype.workPartTime = function(numCycles) {
}
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML = "You are currently working as a " + this.companyPosition.positionName +
txt.innerHTML = "You are currently working as a " + this.companyPosition +
" at " + Player.companyName + " (Current Company Reputation: " +
numeralWrapper.format(companyRep, '0,0') + ")<br><br>" +
"You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "<br><br>" +
@ -859,8 +862,6 @@ PlayerObject.prototype.finishWorkPartTime = function(sing=false) {
var company = Companies[this.companyName];
company.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You earned a total of: <br>" +
@ -1028,8 +1029,6 @@ PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) {
var faction = Factions[this.currentWorkFactionName];
faction.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + " <br><br> " +
@ -1069,61 +1068,117 @@ PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) {
//Money gained per game cycle
PlayerObject.prototype.getWorkMoneyGain = function() {
var bn11Mult = 1;
var company = Companies[this.companyName];
if (hasBn11SF) {
bn11Mult = 1 + (company.favor / 100);
// If player has SF-11, calculate salary multiplier from favor
let bn11Mult = 1;
const company = Companies[this.companyName];
if (hasBn11SF) { bn11Mult = 1 + (company.favor / 100); }
// Get base salary
const companyPosition = CompanyPositions[this.companyPosition];
if (companyPosition == null) {
console.error(`Could not find CompanyPosition object for ${this.companyPosition}. Work salary will be 0`);
return 0;
}
return this.companyPosition.baseSalary * company.salaryMultiplier *
this.work_money_mult * BitNodeMultipliers.CompanyWorkMoney * bn11Mult;
return companyPosition.baseSalary * company.salaryMultiplier * this.work_money_mult * BitNodeMultipliers.CompanyWorkMoney * bn11Mult;
}
//Hack exp gained per game cycle
PlayerObject.prototype.getWorkHackExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.hackingExpGain * company.expMultiplier *
this.hacking_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work hack exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.hackingExpGain * company.expMultiplier * this.hacking_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Str exp gained per game cycle
PlayerObject.prototype.getWorkStrExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.strengthExpGain * company.expMultiplier *
this.strength_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work str exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.strengthExpGain * company.expMultiplier * this.strength_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Def exp gained per game cycle
PlayerObject.prototype.getWorkDefExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.defenseExpGain * company.expMultiplier *
this.defense_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work def exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.defenseExpGain * company.expMultiplier * this.defense_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Dex exp gained per game cycle
PlayerObject.prototype.getWorkDexExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.dexterityExpGain * company.expMultiplier *
this.dexterity_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work dex exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.dexterityExpGain * company.expMultiplier * this.dexterity_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Agi exp gained per game cycle
PlayerObject.prototype.getWorkAgiExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.agilityExpGain * company.expMultiplier *
this.agility_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work agi exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.agilityExpGain * company.expMultiplier * this.agility_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Charisma exp gained per game cycle
PlayerObject.prototype.getWorkChaExpGain = function() {
var company = Companies[this.companyName];
return this.companyPosition.charismaExpGain * company.expMultiplier *
this.charisma_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work cha exp gain will be 0`].join(" "));
return 0;
}
return companyPosition.charismaExpGain * company.expMultiplier * this.charisma_exp_mult * BitNodeMultipliers.CompanyWorkExpGain;
}
//Reputation gained per game cycle
PlayerObject.prototype.getWorkRepGain = function() {
var company = Companies[this.companyName];
var jobPerformance = this.companyPosition.calculateJobPerformance(this.hacking_skill, this.strength,
const company = Companies[this.companyName];
const companyPosition = CompanyPositions[this.companyPosition];
if (company == null || companyPosition == null) {
console.error([`Could not find Company object for ${this.companyName}`,
`or CompanyPosition object for ${this.companyPosition}.`,
`Work rep gain will be 0`].join(" "));
return 0;
}
var jobPerformance = companyPosition.calculateJobPerformance(this.hacking_skill, this.strength,
this.defense, this.dexterity,
this.agility, this.charisma);
@ -1365,7 +1420,6 @@ PlayerObject.prototype.finishClass = function(sing=false) {
if (this.workMoneyGained > 0) {
throw new Error("ERR: Somehow gained money while taking class");
}
this.loseMoney(this.workMoneyGained * -1);
this.updateSkillLevels();
var txt = "After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ", <br>" +
@ -1611,20 +1665,25 @@ PlayerObject.prototype.hospitalize = function() {
//The 'sing' argument designates whether or not this is being called from
//the applyToCompany() Netscript Singularity function
PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
var currCompany = "";
if (this.companyName != "") {
// Get current company and job
let currCompany = null;
if (this.companyName !== "") {
currCompany = Companies[this.companyName];
}
var currPositionName = "";
if (this.companyPosition != "") {
currPositionName = this.companyPosition.positionName;
}
var company = Companies[this.location]; //Company being applied to
if (sing && !(company instanceof Company)) {
const currPositionName = this.companyPosition;
// Get company that's being applied to
const company = Companies[this.location]; //Company being applied to
if (!(company instanceof Company)) {
if (sing) {
return "ERROR: Invalid company name: " + this.location + ". applyToCompany() failed";
} else {
console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`);
return;
}
}
var pos = entryPosType;
let pos = entryPosType;
if (!this.isQualified(company, pos)) {
var reqText = getJobRequirementText(company, pos);
@ -1634,8 +1693,7 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
}
while (true) {
if (Engine.Debug) {console.log("Determining qualification for next Company Position");}
var newPos = getNextCompanyPosition(pos);
let newPos = getNextCompanyPosition(pos);
if (newPos == null) {break;}
//Check if this company has this position
@ -1651,9 +1709,8 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
}
//Check if the determined job is the same as the player's current job
if (currCompany != "") {
if (currCompany.companyName == company.companyName &&
pos.positionName == currPositionName) {
if (currCompany != null) {
if (currCompany.name == company.name && pos.name == currPositionName) {
var nextPos = getNextCompanyPosition(pos);
if (nextPos == null) {
if (sing) {return false;}
@ -1672,31 +1729,30 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
//Lose reputation from a Company if you are leaving it for another job
var leaveCompany = false;
var oldCompanyName = "";
if (currCompany != "") {
if (currCompany.companyName != company.companyName) {
let leaveCompany = false;
let oldCompanyName = "";
if (currCompany != null) {
if (currCompany.name != company.name) {
leaveCompany = true;
oldCompanyName = currCompany.companyName;
company.playerReputation -= 1000;
if (company.playerReputation < 0) {company.playerReputation = 0;}
oldCompanyName = currCompany.name;
currCompany.playerReputation -= 1000;
if (currCompany.playerReputation < 0) { currCompany.playerReputation = 0; }
}
}
this.companyName = company.companyName;
this.companyPosition = pos;
this.companyName = company.name;
this.companyPosition = pos.name;
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (leaveCompany) {
if (sing) { return true; }
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " +
pos.positionName + "!<br>" +
"You lost 1000 reputation at your old company " + oldCompanyName + " because you left.");
dialogBoxCreate([`Congratulations! You were offered a new job at ${this.companyName} as a ${pos.name}!`,
`You lost 1000 reputation at your old company ${oldCompanyName} because you left.`].join("<br>"));
} else {
if (sing) { return true; }
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.positionName + "!");
dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
}
Engine.loadLocationContent();
@ -1705,51 +1761,51 @@ PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) {
//Returns your next position at a company given the field (software, business, etc.)
PlayerObject.prototype.getNextCompanyPosition = function(company, entryPosType) {
var currCompany = null;
if (this.companyName != "") {
if (this.companyName !== "") {
currCompany = Companies[this.companyName];
}
//Not employed at this company, so return the entry position
if (currCompany == null || (currCompany.companyName != company.companyName)) {
if (currCompany == null || (currCompany.name != company.name)) {
return entryPosType;
}
//If the entry pos type and the player's current position have the same type,
//return the player's "nextCompanyPosition". Otherwise return the entryposType
//Employed at this company, so just return the next position if it exists.
if ((this.companyPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) ||
(this.companyPosition.isITJob() && entryPosType.isITJob()) ||
(this.companyPosition.isBusinessJob() && entryPosType.isBusinessJob()) ||
(this.companyPosition.isSecurityEngineerJob() && entryPosType.isSecurityEngineerJob()) ||
(this.companyPosition.isNetworkEngineerJob() && entryPosType.isNetworkEngineerJob()) ||
(this.companyPosition.isSecurityJob() && entryPosType.isSecurityJob()) ||
(this.companyPosition.isAgentJob() && entryPosType.isAgentJob()) ||
(this.companyPosition.isSoftwareConsultantJob() && entryPosType.isSoftwareConsultantJob()) ||
(this.companyPosition.isBusinessConsultantJob() && entryPosType.isBusinessConsultantJob()) ||
(this.companyPosition.isPartTimeJob() && entryPosType.isPartTimeJob())) {
return getNextCompanyPosition(this.companyPosition);
const currentPosition = CompanyPositions[this.companyPosition];
if ((currentPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) ||
(currentPosition.isITJob() && entryPosType.isITJob()) ||
(currentPosition.isBusinessJob() && entryPosType.isBusinessJob()) ||
(currentPosition.isSecurityEngineerJob() && entryPosType.isSecurityEngineerJob()) ||
(currentPosition.isNetworkEngineerJob() && entryPosType.isNetworkEngineerJob()) ||
(currentPosition.isSecurityJob() && entryPosType.isSecurityJob()) ||
(currentPosition.isAgentJob() && entryPosType.isAgentJob()) ||
(currentPosition.isSoftwareConsultantJob() && entryPosType.isSoftwareConsultantJob()) ||
(currentPosition.isBusinessConsultantJob() && entryPosType.isBusinessConsultantJob()) ||
(currentPosition.isPartTimeJob() && entryPosType.isPartTimeJob())) {
return getNextCompanyPosition(currentPosition);
}
return entryPosType;
}
PlayerObject.prototype.applyForSoftwareJob = function(sing=false) {
return this.applyForJob(CompanyPositions.SoftwareIntern, sing);
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
}
PlayerObject.prototype.applyForSoftwareConsultantJob = function(sing=false) {
return this.applyForJob(CompanyPositions.SoftwareConsultant, sing);
return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing);
}
PlayerObject.prototype.applyForItJob = function(sing=false) {
return this.applyForJob(CompanyPositions.ITIntern, sing);
return this.applyForJob(CompanyPositions[posNames.ITCompanyPositions[0]], sing);
}
PlayerObject.prototype.applyForSecurityEngineerJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.SecurityEngineer)) {
return this.applyForJob(CompanyPositions.SecurityEngineer, sing);
if (this.isQualified(company, CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]])) {
return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1758,8 +1814,8 @@ PlayerObject.prototype.applyForSecurityEngineerJob = function(sing=false) {
PlayerObject.prototype.applyForNetworkEngineerJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.NetworkEngineer)) {
return this.applyForJob(CompanyPositions.NetworkEngineer, sing);
if (this.isQualified(company, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]])) {
return this.applyForJob(CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], sing);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1767,22 +1823,23 @@ PlayerObject.prototype.applyForNetworkEngineerJob = function(sing=false) {
}
PlayerObject.prototype.applyForBusinessJob = function(sing=false) {
return this.applyForJob(CompanyPositions.BusinessIntern, sing);
return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing);
}
PlayerObject.prototype.applyForBusinessConsultantJob = function(sing=false) {
return this.applyForJob(CompanyPositions.BusinessConsultant, sing);
return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing);
}
PlayerObject.prototype.applyForSecurityJob = function(sing=false) {
//TODO If case for POlice departments
return this.applyForJob(CompanyPositions.SecurityGuard, sing);
// TODO Police Jobs
// Indexing starts at 2 because 0 is for police officer
return this.applyForJob(CompanyPositions[posNames.SecurityCompanyPositions[2]], sing);
}
PlayerObject.prototype.applyForAgentJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.FieldAgent)) {
return this.applyForJob(CompanyPositions.FieldAgent, sing);
if (this.isQualified(company, CompanyPositions[posNames.AgentCompanyPositions[0]])) {
return this.applyForJob(CompanyPositions[posNames.AgentCompanyPositions[0]], sing);
} else {
if (sing) {return false;}
dialogBoxCreate("Unforunately, you do not qualify for this position");
@ -1791,9 +1848,9 @@ PlayerObject.prototype.applyForAgentJob = function(sing=false) {
PlayerObject.prototype.applyForEmployeeJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.Employee)) {
this.companyName = company.companyName;
this.companyPosition = CompanyPositions.Employee;
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) {
this.companyName = company.name;
this.companyPosition = posNames.MiscCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1807,9 +1864,9 @@ PlayerObject.prototype.applyForEmployeeJob = function(sing=false) {
PlayerObject.prototype.applyForPartTimeEmployeeJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.PartTimeEmployee)) {
this.companyName = company.companyName;
this.companyPosition = CompanyPositions.PartTimeEmployee;
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
this.companyName = company.name;
this.companyPosition = posNames.PartTimeCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1823,9 +1880,9 @@ PlayerObject.prototype.applyForPartTimeEmployeeJob = function(sing=false) {
PlayerObject.prototype.applyForWaiterJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.Waiter)) {
this.companyName = company.companyName;
this.companyPosition = CompanyPositions.Waiter;
if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) {
this.companyName = company.name;
this.companyPosition = posNames.MiscCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -1839,9 +1896,9 @@ PlayerObject.prototype.applyForWaiterJob = function(sing=false) {
PlayerObject.prototype.applyForPartTimeWaiterJob = function(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions.PartTimeWaiter)) {
this.companyName = company.companyName;
this.companyPosition = CompanyPositions.PartTimeWaiter;
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) {
this.companyName = company.name;
this.companyPosition = posNames.PartTimeCompanyPositions[0];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
if (sing) {return true;}
@ -2156,10 +2213,12 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
//Silhouette
var silhouetteFac = Factions["Silhouette"];
const companyPosition = CompanyPositions[this.companyPosition];
if (!silhouetteFac.isBanned && !silhouetteFac.isMember && !silhouetteFac.alreadyInvited &&
(this.companyPosition.positionName == CompanyPositions.CTO.positionName ||
this.companyPosition.positionName == CompanyPositions.CFO.positionName ||
this.companyPosition.positionName == CompanyPositions.CEO.positionName) &&
companyPosition != null &&
(companyPosition.name == "Chief Technology Officer" ||
companyPosition.name == "Chief Financial Officer" ||
companyPosition.name == "Chief Executive Officer") &&
this.money.gte(15000000) && this.karma <= -22) {
invitedFactions.push(silhouetteFac);
}
@ -2307,7 +2366,7 @@ PlayerObject.prototype.gainCodingContractReward = function(reward, difficulty=1)
break;
case CodingContractRewardType.Money:
default:
var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty;
var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney;
this.gainMoney(moneyGain);
return `Gained ${numeralWrapper.format(moneyGain, '$0.000a')}`;
break;

@ -4,7 +4,7 @@ import {Augmentations, augmentationExists,
import {initBitNodeMultipliers} from "./BitNode";
import {Bladeburner} from "./Bladeburner";
import {writeCinematicText} from "./CinematicText";
import {Companies, Company, initCompanies} from "./Company";
import {Companies, initCompanies} from "./Company/Companies";
import {Programs} from "./CreateProgram";
import {Engine} from "./engine";
import {Factions, Faction, initFactions,

@ -1,7 +1,7 @@
import {loadAliases, loadGlobalAliases,
Aliases, GlobalAliases} from "./Alias";
import {loadCompanies, Companies,
CompanyPositions} from "./Company";
import {Companies, loadCompanies} from "./Company/Companies";
import {CompanyPosition} from "./Company/CompanyPosition";
import {CONSTANTS} from "./Constants";
import {Engine} from "./engine";
import {loadFactions, Factions,
@ -110,6 +110,41 @@ BitburnerSaveObject.prototype.saveGame = function(db) {
createStatusText("Game saved!");
}
// Makes necessary changes to the loaded/imported data to ensure
// the game stills works with new versions
function evaluateVersionCompatibility(ver) {
// This version refactored the Company/job-related code
if (ver <= "0.41.2") {
// Player's company position is now a string
if (Player.companyPosition != null && typeof Player.companyPosition !== "string") {
console.log("Changed Player.companyPosition value to be compatible with v0.41.2");
Player.companyPosition = Player.companyPosition.data.positionName;
if (Player.companyPosition == null) {
Player.companyPosition = "";
}
}
// The "companyName" property of all Companies is renamed to "name"
for (var companyName in Companies) {
const company = Companies[companyName];
if ((company.name == null || company.name === 0 || company.name === "") && company.companyName != null) {
console.log("Changed company name property to be compatible with v0.41.2");
company.name = company.companyName;
}
if (company.companyPositions instanceof Array) {
console.log("Changed company companyPositions property to be compatible with v0.41.2");
const pos = {};
for (let i = 0; i < company.companyPositions.length; ++i) {
pos[company.companyPositions[i]] = true;
}
company.companyPositions = pos;
}
}
}
}
function loadGame(saveString) {
if (saveString === "" || saveString == null || saveString === undefined) {
if (!window.localStorage.getItem("bitburnerSave")) {
@ -187,28 +222,8 @@ function loadGame(saveString) {
if (saveObj.hasOwnProperty("VersionSave")) {
try {
var ver = JSON.parse(saveObj.VersionSave, Reviver);
if (Player.bitNodeN == null || Player.bitNodeN === 0) {
Player.setBitNodeNumber(1);
}
if (ver.startsWith("0.27.") || ver.startsWith("0.28.")) {
console.log("Evaluating changes needed for version compatibility");
if (Player.augmentations.length > 0 || Player.queuedAugmentations.length > 0 ||
Player.sourceFiles.length > 0) {
//If you have already purchased an Aug...you are far enough in the game
//that everything should be available
Player.firstFacInvRecvd = true;
Player.firstAugPurchased = true;
Player.firstTimeTraveled = true;
Player.firstProgramAvailable = true;
} else {
if (Player.factions.length > 0 || Player.factionInvitations.length > 0) {
Player.firstFacInvRecvd = true;
}
if (Player.hacking_skill >= 25) {
Player.firstScriptAvailable = true;
}
}
}
evaluateVersionCompatibility(ver);
if (window.location.href.toLowerCase().includes("bitburner-beta")) {
//Beta branch, always show changes
createBetaUpdateText();
@ -306,23 +321,11 @@ function loadImportedGame(saveObj, saveString) {
if (tempSaveObj.hasOwnProperty("VersionSave")) {
try {
var ver = JSON.parse(tempSaveObj.VersionSave, Reviver);
if (ver.startsWith("0.27.") || ver.startsWith("0.28.")) {
if (tempPlayer.bitNodeN == null || tempPlayer.bitNodeN == 0) {
tempPlayer.bitNodeN = 1;
}
if (tempPlayer.sourceFiles == null) {
tempPlayer.sourceFiles = [];
}
}
if (ver != CONSTANTS.Version) {
//createNewUpdateText();
}
evaluateVersionCompatibility(ver);
} catch(e) {
console.log("Parsing Version save failed: " + e);
//createNewUpdateText();
console.error("Parsing Version save failed: " + e);
}
} else {
//createNewUpdateText();
}
if (tempPlayer.bitNodeN == 2 && tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) {
try {
@ -401,29 +404,8 @@ function loadImportedGame(saveObj, saveString) {
if (saveObj.hasOwnProperty("VersionSave")) {
try {
var ver = JSON.parse(saveObj.VersionSave, Reviver);
if (Player.bitNodeN == null || Player.bitNodeN == 0) {
Player.setBitNodeNumber(1);
evaluateVersionCompatibility(ver);
}
if (ver.startsWith("0.27.") || ver.startsWith("0.28.")) {
console.log("Evaluating changes needed for version compatibility");
if (Player.augmentations.length > 0 || Player.queuedAugmentations.length > 0 ||
Player.sourceFiles.length > 0) {
//If you have already purchased an Aug...you are far enough in the game
//that everything should be available
Player.firstFacInvRecvd = true;
Player.firstAugPurchased = true;
Player.firstTimeTraveled = true;
Player.firstProgramAvailable = true;
} else {
if (Player.factions.length > 0 || Player.factionInvitations.length > 0) {
Player.firstFacInvRecvd = true;
}
if (Player.hacking_skill >= 25) {
Player.firstScriptAvailable = true;
}
}
}
if (ver != CONSTANTS.Version) {
createNewUpdateText();
}
@ -459,7 +441,6 @@ function loadImportedGame(saveObj, saveString) {
console.log("Importing game");
Engine.setDisplayElements(); //Sets variables for important DOM elements
Engine.init(); //Initialize buttons, work, etc.
CompanyPositions.init();
//Calculate the number of cycles have elapsed while offline
Engine._lastUpdate = new Date().getTime();

@ -466,7 +466,7 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
const nextModule = parseQueue.shift();
let code;
if (nextModule.startsWith("https://")) {
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) {
try {
const module = await eval('import(nextModule)');
code = "";

@ -206,7 +206,7 @@ Server.fromJSON = function(value) {
Reviver.constructors.Server = Server;
function initForeignServers() {
export function initForeignServers() {
/* Create a randomized network for all the foreign servers */
//Groupings for creating a randomized network
const networkLayers = [];
@ -287,7 +287,9 @@ function initForeignServers() {
}
}
function numCycleForGrowth(server, growth) {
// Returns the number of cycles needed to grow the specified server by the
// specified amount. 'growth' parameter is in decimal form, not percentage
export function numCycleForGrowth(server, growth) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
@ -300,7 +302,7 @@ function numCycleForGrowth(server, growth) {
}
//Applied server growth for a single server. Returns the percentage growth
function processSingleServerGrowth(server, numCycles) {
export function processSingleServerGrowth(server, numCycles) {
//Server growth processed once every 450 game cycles
const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0);
@ -343,7 +345,7 @@ function processSingleServerGrowth(server, numCycles) {
return server.moneyAvailable / oldMoneyAvailable;
}
function prestigeHomeComputer(homeComp) {
export function prestigeHomeComputer(homeComp) {
const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name);
homeComp.programs.length = 0; //Remove programs
@ -366,14 +368,14 @@ function prestigeHomeComputer(homeComp) {
//List of all servers that exist in the game, indexed by their ip
let AllServers = {};
function prestigeAllServers() {
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
function loadAllServers(saveString) {
export function loadAllServers(saveString) {
AllServers = JSON.parse(saveString, Reviver);
}
@ -386,7 +388,7 @@ function SizeOfAllServers() {
}
//Add a server onto the map of all servers in the game
function AddToAllServers(server) {
export function AddToAllServers(server) {
var serverIp = server.ip;
if (ipExists(serverIp)) {
console.log("IP of server that's being added: " + serverIp);
@ -400,7 +402,7 @@ function AddToAllServers(server) {
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
function GetServerByHostname(hostname) {
export function GetServerByHostname(hostname) {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) {
@ -412,7 +414,7 @@ function GetServerByHostname(hostname) {
}
//Get server by IP or hostname. Returns null if invalid
function getServer(s) {
export function getServer(s) {
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
@ -459,6 +461,4 @@ Directory.prototype.getPath = function(name) {
return res.join("");
}
export {Server, AllServers, getServer, GetServerByHostname, loadAllServers,
AddToAllServers, processSingleServerGrowth, initForeignServers,
prestigeAllServers, prestigeHomeComputer};
export {Server, AllServers};

@ -403,12 +403,13 @@ function stockMarketCycle() {
//Returns true if successful, false otherwise
function buyStock(stock, shares) {
if (stock == null || shares < 0 || isNaN(shares)) {
// Validate arguments
shares = Math.round(shares);
if (shares == 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
return false;
}
shares = Math.round(shares);
if (shares == 0) {return false;}
var totalPrice = stock.price * shares;
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
@ -420,7 +421,7 @@ function buyStock(stock, shares) {
var origTotal = stock.playerShares * stock.playerAvgPx;
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
var newTotal = origTotal + totalPrice;
stock.playerShares += shares;
stock.playerShares = Math.round(stock.playerShares + shares);
stock.playerAvgPx = newTotal / stock.playerShares;
updateStockPlayerPosition(stock);
dialogBoxCreate("Bought " + numeralWrapper.format(shares, '0,0') + " shares of " + stock.symbol + " at " +
@ -441,7 +442,7 @@ function sellStock(stock, shares) {
if (shares === 0) {return false;}
var gains = stock.price * shares - CONSTANTS.StockMarketCommission;
Player.gainMoney(gains);
stock.playerShares -= shares;
stock.playerShares = Math.round(stock.playerShares - shares);
if (stock.playerShares == 0) {
stock.playerAvgPx = 0;
}
@ -455,7 +456,11 @@ function sellStock(stock, shares) {
//Returns true if successful and false otherwise
function shortStock(stock, shares, workerScript=null) {
var tixApi = (workerScript instanceof WorkerScript);
if (stock == null || isNaN(shares) || shares < 0) {
// Validate arguments
shares = Math.round(shares);
if (shares === 0 || shares < 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
workerScript.scriptRef.log("ERROR: shortStock() failed because of invalid arguments.");
} else {
@ -464,8 +469,6 @@ function shortStock(stock, shares, workerScript=null) {
}
return false;
}
shares = Math.round(shares);
if (shares === 0) {return false;}
var totalPrice = stock.price * shares;
if (Player.money.lt(totalPrice + CONSTANTS.StockMarketCommission)) {
@ -484,7 +487,7 @@ function shortStock(stock, shares, workerScript=null) {
var origTotal = stock.playerShortShares * stock.playerAvgShortPx;
Player.loseMoney(totalPrice + CONSTANTS.StockMarketCommission);
var newTotal = origTotal + totalPrice;
stock.playerShortShares += shares;
stock.playerShortShares = Math.round(stock.playerShortShares + shares);
stock.playerAvgShortPx = newTotal / stock.playerShortShares;
updateStockPlayerPosition(stock);
if (tixApi) {
@ -526,7 +529,7 @@ function sellShort(stock, shares, workerScript=null) {
Player.scriptProdSinceLastAug += profit;
}
stock.playerShortShares -= shares;
stock.playerShortShares = Math.round(stock.playerShortShares - shares);
if (stock.playerShortShares === 0) {
stock.playerAvgShortPx = 0;
}
@ -1370,14 +1373,14 @@ function updateStockPlayerPosition(stock) {
}
//Calculate returns
var totalCost = stock.playerShares * stock.playerAvgPx,
gains = (stock.price - stock.playerAvgPx) * stock.playerShares,
percentageGains = gains / totalCost;
const totalCost = stock.playerShares * stock.playerAvgPx;
let gains = (stock.price - stock.playerAvgPx) * stock.playerShares;
let percentageGains = gains / totalCost;
if (isNaN(percentageGains)) { percentageGains = 0; }
var shortTotalCost = stock.playerShortShares * stock.playerAvgShortPx,
shortGains = (stock.playerAvgShortPx - stock.price) * stock.playerShortShares,
shortPercentageGains = shortGains/ shortTotalCost;
const shortTotalCost = stock.playerShortShares * stock.playerAvgShortPx;
let shortGains = (stock.playerAvgShortPx - stock.price) * stock.playerShortShares;
let shortPercentageGains = shortGains/ shortTotalCost;
if (isNaN(shortPercentageGains)) { shortPercentageGains = 0; }
stock.posTxtEl.innerHTML =

@ -1090,6 +1090,24 @@ let Terminal = {
}
}
post("Error: " + fn + " does not exist");
break;
case "expr":
if (commandArray.length <= 1) {
post("Incorrect usage of expr command. Usage: expr [math expression]");
return;
}
const expr = commandArray.slice(1).join("");
// Sanitize the math expression
const sanitizedExpr = expr.replace(/s+/g, '').replace(/[^-()\d/*+.]/g, '');
let result;
try {
result = eval(sanitizedExpr);
} catch(e) {
post(`Could not evaluate expression: ${sanitizedExpr}`);
return;
}
post(result);
break;
case "free":
Terminal.executeFreeCommand(commandArray);

@ -49,56 +49,56 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isCombat: false,
isHacking: true,
name: "Ransomware",
params: {baseRespect: 0.00005, baseWanted: 0.00001, baseMoney: 1, hackWeight: 100, difficulty: 1},
params: {baseRespect: 0.00005, baseWanted: 0.0001, baseMoney: 1, hackWeight: 100, difficulty: 1},
},
{
desc: "Assign this gang member to attempt phishing scams and attacks<br><br>Earns money - Slightly increases respect - Slightly increases wanted level",
isCombat: false,
isHacking: true,
name: "Phishing",
params: {baseRespect: 0.00008, baseWanted: 0.001, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5},
params: {baseRespect: 0.00008, baseWanted: 0.003, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5},
},
{
desc: "Assign this gang member to attempt identity theft<br><br>Earns money - Increases respect - Increases wanted level",
isCombat: false,
isHacking: true,
name: "Identity Theft",
params: {baseRespect: 0.0001, baseWanted: 0.01, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5},
params: {baseRespect: 0.0001, baseWanted: 0.075, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5},
},
{
desc: "Assign this gang member to carry out DDoS attacks<br><br>Increases respect - Increases wanted level",
isCombat: false,
isHacking: true,
name: "DDoS Attacks",
params: {baseRespect: 0.0004, baseWanted: 0.05, hackWeight: 100, difficulty: 8},
params: {baseRespect: 0.0004, baseWanted: 0.2, hackWeight: 100, difficulty: 8},
},
{
desc: "Assign this gang member to create and distribute malicious viruses<br><br>Increases respect - Increases wanted level",
isCombat: false,
isHacking: true,
name: "Plant Virus",
params: {baseRespect: 0.0006, baseWanted: 0.05, hackWeight: 100, difficulty: 12},
params: {baseRespect: 0.0006, baseWanted: 0.4, hackWeight: 100, difficulty: 12},
},
{
desc: "Assign this gang member to commit financial fraud and digital counterfeiting<br><br>Earns money - Slightly increases respect - Slightly increases wanted level",
isCombat: false,
isHacking: true,
name: "Fraud & Counterfeiting",
params: {baseRespect: 0.0005, baseWanted: 0.1, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20},
params: {baseRespect: 0.0004, baseWanted: 0.3, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20},
},
{
desc: "Assign this gang member to launder money<br><br>Earns money - Increases respect - Increases wanted level",
isCombat: false,
isHacking: true,
name: "Money Laundering",
params: {baseRespect: 0.0006, baseWanted: 0.2, baseMoney: 40, hackWeight: 75, chaWeight: 25, difficulty: 25},
params: {baseRespect: 0.001, baseWanted: 1.25, baseMoney: 120, hackWeight: 75, chaWeight: 25, difficulty: 25},
},
{
desc: "Assign this gang member to commit acts of cyberterrorism<br><br>Greatly increases respect - Greatly increases wanted level",
isCombat: false,
isHacking: true,
name: "Cyberterrorism",
params: {baseRespect: 0.001, baseWanted: 0.5, hackWeight: 80, chaWeight: 20, difficulty: 36},
params: {baseRespect: 0.01, baseWanted: 6, hackWeight: 80, chaWeight: 20, difficulty: 36},
},
{
desc: "Assign this gang member to be an ethical hacker for corporations<br><br>Earns money - Lowers wanted level",
@ -140,7 +140,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Strongarm Civilians",
params: {
baseRespect: 0.00004, baseWanted: 0.0035, baseMoney: 2.5,
baseRespect: 0.00004, baseWanted: 0.02, baseMoney: 2.5,
hackWeight: 10, strWeight: 25, defWeight: 25, dexWeight: 20, agiWeight: 10, chaWeight: 10,
difficulty: 5,
territory: {
@ -156,7 +156,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Run a Con",
params: {
baseRespect: 0.00012, baseWanted: 0.04, baseMoney: 15,
baseRespect: 0.00012, baseWanted: 0.05, baseMoney: 15,
strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40,
difficulty: 14,
},
@ -167,7 +167,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Armed Robbery",
params: {
baseRespect: 0.00014, baseWanted: 0.08, baseMoney: 38,
baseRespect: 0.00014, baseWanted: 0.1, baseMoney: 38,
hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20,
difficulty: 20,
},
@ -178,7 +178,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Traffick Illegal Arms",
params: {
baseRespect: 0.0002, baseWanted: 0.18, baseMoney: 58,
baseRespect: 0.0002, baseWanted: 0.24, baseMoney: 58,
hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 25,
difficulty: 32,
territory: {
@ -194,7 +194,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Threaten & Blackmail",
params: {
baseRespect: 0.0002, baseWanted: 0.1, baseMoney: 24,
baseRespect: 0.0002, baseWanted: 0.125, baseMoney: 24,
hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25,
difficulty: 28,
},
@ -205,7 +205,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Human Trafficking",
params: {
baseRespect: 0.004, baseWanted: 1, baseMoney: 120,
baseRespect: 0.004, baseWanted: 1.25, baseMoney: 120,
hackWeight: 30, strWeight: 5, defWeight: 5, dexWeight: 30, chaWeight: 30,
difficulty: 36,
territory: {
@ -221,7 +221,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
isHacking: false,
name: "Terrorism",
params: {
baseRespect: 0.01, baseWanted: 5,
baseRespect: 0.01, baseWanted: 6,
hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20,
difficulty: 36,
territory: {

@ -34,31 +34,31 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
},
{
cost: 50e6,
mults: {str: 1.12, def: 1.12, agi: 1.1},
mults: {str: 1.12, def: 1.1, agi: 1.1},
name: "P90C",
upgType: "w",
},
{
cost: 60e6,
mults: {str: 1.2, def: 1.2},
mults: {str: 1.2, def: 1.15},
name: "Steyr AUG",
upgType: "w",
},
{
cost: 100e6,
mults: {str: 1.25, def: 1.25},
mults: {str: 1.25, def: 1.2},
name: "AK-47",
upgType: "w",
},
{
cost: 150e6,
mults: {str: 1.3, def: 1.3},
mults: {str: 1.3, def: 1.25},
name: "M15A10 Assault Rifle",
upgType: "w",
},
{
cost: 225e6,
mults: {str: 1.3, dex: 1.3, agi: 1.3},
mults: {str: 1.3, dex: 1.25, agi: 1.3},
name: "AWM Sniper Rifle",
upgType: "w",
},
@ -117,17 +117,29 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
upgType: "r",
},
{
cost: 15e6,
cost: 25e6,
mults: {hack: 1.1},
name: "Soulstealer Rootkit",
upgType: "r",
},
{
cost: 50e6,
cost: 75e6,
mults: {hack: 1.15},
name: "Demon Rootkit",
upgType: "r",
},
{
cost: 40e6,
mults: {hack: 1.12},
name: "Hmap Node",
upgType: "r",
},
{
cost: 75e6,
mults: {hack: 1.15},
name: "Jack the Ripper",
upgType: "r",
},
{
cost: 10e9,
mults: {str: 1.3, dex: 1.3},
@ -182,6 +194,12 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
name: "Neuralstimulator",
upgType: "g",
},
{
cost: 7.5e9,
mults: {hack: 1.1},
name: "DataJack",
upgType: "g",
},
{
cost: 50e9,
mults: {str: 1.7, def: 1.7},

@ -25,7 +25,8 @@ import {Bladeburner} from "./Bladeburner";
import {CharacterOverview} from "./CharacterOverview";
import {cinematicTextFlag} from "./CinematicText";
import {generateRandomContract} from "./CodingContractGenerator";
import {CompanyPositions, initCompanies} from "./Company";
import {CompanyPositions} from "./Company/CompanyPositions";
import {initCompanies} from "./Company/Companies";
import {Corporation} from "./CompanyManagement";
import {CONSTANTS} from "./Constants";
import {displayCreateProgramContent,
@ -338,7 +339,13 @@ const Engine = {
loadLocationContent: function() {
Engine.hideAllContent();
Engine.Display.locationContent.style.display = "block";
try {
displayLocationContent();
} catch(e) {
exceptionAlert(e);
console.error(e);
}
routing.navigateTo(Page.Location);
},
@ -539,8 +546,8 @@ const Engine = {
removeChildrenFromElement(Engine.Display.characterInfo);
var companyPosition = "";
if (Player.companyPosition != "") {
companyPosition = Player.companyPosition.positionName;
if (Player.companyPosition !== "") {
companyPosition = Player.companyPosition;
}
var intText = "";
@ -1204,7 +1211,6 @@ const Engine = {
initSourceFiles();
Engine.setDisplayElements(); //Sets variables for important DOM elements
Engine.init(); //Initialize buttons, work, etc.
CompanyPositions.init();
initAugmentations(); //Also calls Player.reapplyAllAugmentations()
Player.reapplyAllSourceFiles();
initStockSymbols();
@ -1312,7 +1318,6 @@ const Engine = {
initForeignServers();
initCompanies();
initFactions();
CompanyPositions.init();
initAugmentations();
initMessages();
initStockSymbols();

@ -32,6 +32,8 @@ class NumeralFormatter {
}
format(n, format) {
// numeraljs doesnt properly format numbers that are too big or too small
if (Math.abs(n) < 1e-6) { n = 0; }
return numeral(n).format(format);
}
}

@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl" : ".",
"lib" : ["es2016", "dom"],
"module": "commonjs",
"target": "es6",