diff --git a/src/Company/GetNextCompanyPosition.ts b/src/Company/GetNextCompanyPosition.ts index 61b1a8c2e..b6ca6118c 100644 --- a/src/Company/GetNextCompanyPosition.ts +++ b/src/Company/GetNextCompanyPosition.ts @@ -3,7 +3,7 @@ import { CompanyPosition } from "./CompanyPosition"; import { CompanyPositions } from "./CompanyPositions"; -export function getNextCompanyPosition(currPos: CompanyPosition | null): CompanyPosition | null { +export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null { if (currPos == null) { return null; } const nextPosName: string | null = currPos.nextPosition; diff --git a/src/Locations/LocationsHelpers.ts b/src/Locations/LocationsHelpers.ts index 2ee71338c..49ca28c8f 100644 --- a/src/Locations/LocationsHelpers.ts +++ b/src/Locations/LocationsHelpers.ts @@ -107,7 +107,7 @@ export function createPurchaseServerPopup(ram: number, p: IPlayer) { * Create a popup that lets the player start a Corporation */ export function createStartCorporationPopup(p: IPlayer) { - if (p.hasCorporation) { return; } + if (!p.canAccessCorporation() || p.hasCorporation) { return; } const popupId = "create-corporation-popup"; const txt = createElement("p", { diff --git a/src/Locations/ui/ApplyToJobButton.tsx b/src/Locations/ui/ApplyToJobButton.tsx index 993cfce68..58f464257 100644 --- a/src/Locations/ui/ApplyToJobButton.tsx +++ b/src/Locations/ui/ApplyToJobButton.tsx @@ -15,6 +15,7 @@ type IProps = { entryPosType: CompanyPosition; onClick: (e: React.MouseEvent) => void; p: IPlayer; + style?: object; text: string; } @@ -38,6 +39,7 @@ export class ApplyToJobButton extends React.Component { return ( diff --git a/src/Locations/ui/City.tsx b/src/Locations/ui/City.tsx index 91ad06f1a..9e93d3e3b 100644 --- a/src/Locations/ui/City.tsx +++ b/src/Locations/ui/City.tsx @@ -19,8 +19,8 @@ export class LocationCity extends React.Component { render() { const locationButtons = this.props.city.locations.map((locName) => { return ( -
  • - +
  • +
  • ) }); diff --git a/src/Locations/ui/CompanyLocation.tsx b/src/Locations/ui/CompanyLocation.tsx index 204c28310..e67842f2a 100644 --- a/src/Locations/ui/CompanyLocation.tsx +++ b/src/Locations/ui/CompanyLocation.tsx @@ -16,10 +16,12 @@ import { beginInfiltration } from "../../Infiltration"; import { Companies } from "../../Company/Companies"; import { Company } from "../../Company/Company"; +import { CompanyPosition } from "../../Company/CompanyPosition"; import { CompanyPositions } from "../../Company/CompanyPositions"; import * as posNames from "../../Company/data/companypositionnames"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { numeralWrapper } from "../../ui/numeralFormat"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { @@ -28,21 +30,43 @@ type IProps = { p: IPlayer; } -export class CompanyLocation extends React.Component { +type IState = { + employedHere: boolean; +} + +export class CompanyLocation extends React.Component { /** * We'll keep a reference to the Company that this component is being rendered for, * so we don't have to look it up every time */ company: Company; + /** + * CompanyPosition object for the job that the player holds at this company + * (if he has one) + */ + companyPosition: CompanyPosition | null = null; + + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + /** * Reference to the Location that this component is being rendered for */ location: Location; + /** + * Name of company position that player holds, if applicable + */ + jobTitle: string | null = null; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.applyForAgentJob = this.applyForAgentJob.bind(this); this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this); this.applyForBusinessJob = this.applyForBusinessJob.bind(this); @@ -54,7 +78,9 @@ export class CompanyLocation extends React.Component { this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this); this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this); this.applyForWaiterJob = this.applyForWaiterJob.bind(this); + this.checkIfEmployedHere = this.checkIfEmployedHere.bind(this); this.startInfiltration = this.startInfiltration.bind(this); + this.work = this.work.bind(this); this.location = Locations[props.locName]; if (this.location == null) { @@ -65,61 +91,91 @@ export class CompanyLocation extends React.Component { if (this.company == null) { throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`); } + + this.state = { + employedHere: false, + } + + this.checkIfEmployedHere(false); } applyForAgentJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForAgentJob(); + this.checkIfEmployedHere(); } applyForBusinessConsultantJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForBusinessConsultantJob(); + this.checkIfEmployedHere(); } applyForBusinessJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForBusinessJob(); + this.checkIfEmployedHere(); } applyForEmployeeJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForEmployeeJob(); + this.checkIfEmployedHere(); } applyForItJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForItJob(); + this.checkIfEmployedHere(); } applyForPartTimeEmployeeJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForPartTimeEmployeeJob(); + this.checkIfEmployedHere(); } applyForPartTimeWaiterJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForPartTimeWaiterJob(); + this.checkIfEmployedHere(); } applyForSecurityJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForSecurityJob(); + this.checkIfEmployedHere(); } applyForSoftwareConsultantJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForSoftwareConsultantJob(); + this.checkIfEmployedHere(); } applyForSoftwareJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForSoftwareJob(); + this.checkIfEmployedHere(); } applyForWaiterJob(e: React.MouseEvent) { if (!e.isTrusted) { return false; } this.props.p.applyForWaiterJob(); + this.checkIfEmployedHere(); + } + + checkIfEmployedHere(updateState=true) { + this.jobTitle = this.props.p.jobs[this.props.locName]; + if (this.jobTitle != null) { + this.companyPosition = CompanyPositions[this.jobTitle]; + } + + if (updateState) { + this.setState({ + employedHere: this.jobTitle != null + }); + } } startInfiltration(e: React.MouseEvent) { @@ -133,9 +189,54 @@ export class CompanyLocation extends React.Component { beginInfiltration(this.props.locName, data.startingSecurityLevel, data.baseRewardValue, data.maxClearanceLevel, data.difficulty); } + work(e: React.MouseEvent) { + if (!e.isTrusted) { return false; } + + const pos = this.companyPosition; + if (pos instanceof CompanyPosition) { + if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) { + this.props.p.startWorkPartTime(this.props.locName); + } else { + this.props.p.startWork(this.props.locName); + } + } + } + render() { + const isEmployedHere = this.jobTitle != null; + const favorGain = this.company.getFavorGain(); + return (
    + { + isEmployedHere && +
    +

    Job Title: {this.jobTitle}

    +

    --------------------

    +

    + Company reputation: {numeralWrapper.format(this.company.playerReputation, "0,0.000")} + + You will earn ${numeralWrapper.format(favorGain[0], "0,0")} company + favor upon resetting after installing Augmentations + +

    +

    --------------------

    +

    + Company Favor: {numeralWrapper.format(this.company.favor, "0,0")} + + Company favor increases the rate at which you earn reputation for this company by + 1% per favor. Company favor is gained whenever you reset after installing Augmentations. The amount + of favor you gain depends on how much reputation you have with the comapny. + +

    + +
    + } { this.company.hasAgentPositions() && { entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]} onClick={this.applyForAgentJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Agent Job"} /> } @@ -153,6 +255,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]} onClick={this.applyForBusinessConsultantJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Business Consultant Job"} /> } @@ -163,6 +266,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]} onClick={this.applyForBusinessJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Business Job"} /> } @@ -173,6 +277,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]} onClick={this.applyForEmployeeJob} p={this.props.p} + style={this.btnStyle} text={"Apply to be an Employee"} /> } @@ -183,6 +288,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]} onClick={this.applyForPartTimeEmployeeJob} p={this.props.p} + style={this.btnStyle} text={"Apply to be a part-time Employee"} /> } @@ -193,6 +299,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]} onClick={this.applyForItJob} p={this.props.p} + style={this.btnStyle} text={"Apply for IT Job"} /> } @@ -203,6 +310,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]} onClick={this.applyForSecurityJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Security Job"} /> } @@ -213,6 +321,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]} onClick={this.applyForSoftwareConsultantJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Software Consultant Job"} /> } @@ -223,6 +332,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]} onClick={this.applyForSoftwareJob} p={this.props.p} + style={this.btnStyle} text={"Apply for Software Job"} /> } @@ -233,6 +343,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]} onClick={this.applyForWaiterJob} p={this.props.p} + style={this.btnStyle} text={"Apply to be a Waiter"} /> } @@ -243,6 +354,7 @@ export class CompanyLocation extends React.Component { entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]} onClick={this.applyForPartTimeWaiterJob} p={this.props.p} + style={this.btnStyle} text={"Apply to be a part-time Waiter"} /> } @@ -250,6 +362,7 @@ export class CompanyLocation extends React.Component { (this.location.infiltrationData != null) && } diff --git a/src/Locations/ui/GenericLocation.tsx b/src/Locations/ui/GenericLocation.tsx index 6ed4038fa..d04e15fce 100644 --- a/src/Locations/ui/GenericLocation.tsx +++ b/src/Locations/ui/GenericLocation.tsx @@ -33,6 +33,17 @@ type IProps = { } export class GenericLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + + constructor(props: IProps) { + super(props); + + this.btnStyle = { display: "block" }; + } + /** * Determine what needs to be rendered for this location based on the locations * type. Returns an array of React components that should be rendered @@ -44,6 +55,7 @@ export class GenericLocation extends React.Component { content.push( @@ -53,6 +65,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.Gym)) { content.push( @@ -62,6 +75,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.Hospital)) { content.push( ) @@ -70,6 +84,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.Slums)) { content.push( ) @@ -79,6 +94,7 @@ export class GenericLocation extends React.Component { content.push( @@ -88,6 +104,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.TechVendor)) { content.push( @@ -97,6 +114,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.TravelAgency)) { content.push( @@ -106,6 +124,7 @@ export class GenericLocation extends React.Component { if (this.props.loc.types.includes(LocationType.University)) { content.push( @@ -120,8 +139,7 @@ export class GenericLocation extends React.Component { return (
    - -
    +

    {this.props.loc.name}

    {locContent}
    diff --git a/src/Locations/ui/GymLocation.tsx b/src/Locations/ui/GymLocation.tsx index 7c54c9863..e3a4c1519 100644 --- a/src/Locations/ui/GymLocation.tsx +++ b/src/Locations/ui/GymLocation.tsx @@ -19,9 +19,16 @@ type IProps = { } export class GymLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.trainStrength = this.trainStrength.bind(this); this.trainDefense = this.trainDefense.bind(this); this.trainDexterity = this.trainDexterity.bind(this); @@ -58,18 +65,22 @@ export class GymLocation extends React.Component {
    diff --git a/src/Locations/ui/HospitalLocation.tsx b/src/Locations/ui/HospitalLocation.tsx index cfd4c5e38..ca033af01 100644 --- a/src/Locations/ui/HospitalLocation.tsx +++ b/src/Locations/ui/HospitalLocation.tsx @@ -18,9 +18,16 @@ type IProps = { } export class HospitalLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.getCost = this.getCost.bind(this); this.getHealed = this.getHealed.bind(this); } @@ -48,6 +55,7 @@ export class HospitalLocation extends React.Component { return ( ) diff --git a/src/Locations/ui/Root.tsx b/src/Locations/ui/Root.tsx index fa73a7a6a..351741c19 100644 --- a/src/Locations/ui/Root.tsx +++ b/src/Locations/ui/Root.tsx @@ -20,6 +20,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { dialogBoxCreate } from "../../../utils/DialogBox"; type IProps = { + initiallyInCity?: boolean; engine: IEngine; p: IPlayer; } @@ -36,16 +37,17 @@ export class LocationRoot extends React.Component { this.state = { city: props.p.city, - inCity: true, + inCity: props.initiallyInCity == null ? true : props.initiallyInCity, location: props.p.location, } + this.enterLocation = this.enterLocation.bind(this); this.returnToCity = this.returnToCity.bind(this); this.travel = this.travel.bind(this); } enterLocation(to: LocationName): void { - this.props.p.location = to; + this.props.p.gotoLocation(to); this.setState({ inCity: false, location: to, @@ -133,6 +135,7 @@ export class LocationRoot extends React.Component { if (this.props.p.travel(to)) { this.setState({ + inCity: true, city: to }); } diff --git a/src/Locations/ui/SlumsLocation.tsx b/src/Locations/ui/SlumsLocation.tsx index ea9a5cc8f..7afa53bba 100644 --- a/src/Locations/ui/SlumsLocation.tsx +++ b/src/Locations/ui/SlumsLocation.tsx @@ -16,9 +16,16 @@ type IProps = { } export class SlumsLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.shoplift = this.shoplift.bind(this); this.robStore = this.robStore.bind(this); this.mug = this.mug.bind(this); @@ -40,12 +47,12 @@ export class SlumsLocation extends React.Component { robStore(e: React.MouseEvent): void { if (!e.isTrusted) { return; } - Crimes.RobSTore.commit(this.props.p); + Crimes.RobStore.commit(this.props.p); } mug(e: React.MouseEvent): void { if (!e.isTrusted) { return; } - Crimes.mug.commit(this.props.p); + Crimes.Mug.commit(this.props.p); } larceny(e: React.MouseEvent): void { @@ -112,72 +119,84 @@ export class SlumsLocation extends React.Component { diff --git a/src/Locations/ui/SpecialLocation.tsx b/src/Locations/ui/SpecialLocation.tsx index afa1b3955..ba51d8cfa 100644 --- a/src/Locations/ui/SpecialLocation.tsx +++ b/src/Locations/ui/SpecialLocation.tsx @@ -35,9 +35,16 @@ type IState = { } export class SpecialLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.createCorporationPopup = this.createCorporationPopup.bind(this); this.handleBladeburner = this.handleBladeburner.bind(this); this.handleResleeving = this.handleResleeving.bind(this); @@ -89,29 +96,35 @@ export class SpecialLocation extends React.Component { } renderBladeburner(): React.ReactNode { + if (!this.props.p.canAccessBladeburner()) { return null; } const text = this.state.inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division"; return ( ) } renderCreateCorporation(): React.ReactNode { + if (!this.props.p.canAccessCorporation()) { return null; } return ( ) } renderResleeving(): React.ReactNode { + if (!this.props.p.canAccessResleeving()) { return null; } return ( ) diff --git a/src/Locations/ui/TechVendorLocation.tsx b/src/Locations/ui/TechVendorLocation.tsx index 90aadfacd..584f87892 100644 --- a/src/Locations/ui/TechVendorLocation.tsx +++ b/src/Locations/ui/TechVendorLocation.tsx @@ -25,9 +25,16 @@ type IProps = { } export class TechVendorLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.state = { torPurchased: props.p.hasTorRouter(), } @@ -59,6 +66,7 @@ export class TechVendorLocation extends React.Component { createPurchaseServerPopup(i, this.props.p)} + style={this.btnStyle} text={`Purchase ${i}GB Server - ${numeralWrapper.formatMoney(cost)}`} /> ) @@ -70,11 +78,13 @@ export class TechVendorLocation extends React.Component { { this.state.torPurchased ? ( ) : ( ) @@ -82,10 +92,12 @@ export class TechVendorLocation extends React.Component { }
    diff --git a/src/Locations/ui/TravelAgencyLocation.tsx b/src/Locations/ui/TravelAgencyLocation.tsx index 4b5364b87..3f21b6183 100644 --- a/src/Locations/ui/TravelAgencyLocation.tsx +++ b/src/Locations/ui/TravelAgencyLocation.tsx @@ -20,8 +20,15 @@ type IProps = { } export class TravelAgencyLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + + this.btnStyle = { display: "block" }; } render() { @@ -36,6 +43,7 @@ export class TravelAgencyLocation extends React.Component { ) @@ -44,8 +52,8 @@ export class TravelAgencyLocation extends React.Component { return (

    - From here, you can travel to any other city! A ticket costs - {numeralWrapper.formatMoney(CONSTANTS.TravelCost)} + From here, you can travel to any other city! A ticket + costs {numeralWrapper.formatMoney(CONSTANTS.TravelCost)}

    {travelBtns}
    diff --git a/src/Locations/ui/UniversityLocation.tsx b/src/Locations/ui/UniversityLocation.tsx index 934b66856..a5b4d4b0c 100644 --- a/src/Locations/ui/UniversityLocation.tsx +++ b/src/Locations/ui/UniversityLocation.tsx @@ -19,10 +19,18 @@ type IProps = { } export class UniversityLocation extends React.Component { + /** + * Stores button styling that sets them all to block display + */ + btnStyle: object; + constructor(props: IProps) { super(props); + this.btnStyle = { display: "block" }; + this.take = this.take.bind(this); + this.study = this.study.bind(this); this.dataStructures = this.dataStructures.bind(this); this.networks = this.networks.bind(this); this.algorithms = this.algorithms.bind(this); @@ -72,26 +80,32 @@ export class UniversityLocation extends React.Component {
    diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index ed89e6084..7f60e2409 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -33,6 +33,7 @@ export interface IPlayer { hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server hashManager: HashManager; hasWseAccount: boolean; + homeComputer: string; hp: number; jobs: IMap; karma: number; @@ -111,6 +112,9 @@ export interface IPlayer { applyForSoftwareConsultantJob(sing?: boolean): boolean | void; applyForSoftwareJob(sing?: boolean): boolean | void; applyForWaiterJob(sing?: boolean): boolean | void; + canAccessBladeburner(): boolean; + canAccessCorporation(): boolean; + canAccessResleeving(): boolean; canAfford(cost: number): boolean; gainHackingExp(exp: number): void; gainStrengthExp(exp: number): void; @@ -122,6 +126,7 @@ export interface IPlayer { getHomeComputer(): Server; getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition; getUpgradeHomeRamCost(): number; + gotoLocation(to: LocationName): boolean; hasCorporation(): boolean; hasTorRouter(): boolean; inBladeburner(): boolean; @@ -145,5 +150,7 @@ export interface IPlayer { money: number, time: number, singParams: any): void; + startWork(companyName: string): void; + startWorkPartTime(companyName: string): void; travel(to: CityName): boolean; } diff --git a/src/PersonObjects/Player/PlayerObject.js b/src/PersonObjects/Player/PlayerObject.js new file mode 100644 index 000000000..7f9ec86bb --- /dev/null +++ b/src/PersonObjects/Player/PlayerObject.js @@ -0,0 +1,212 @@ +import * as generalMethods from "./PlayerObjectGeneralMethods"; +import * as serverMethods from "./PlayerObjectServerMethods"; +import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods"; +import * as corporationMethods from "./PlayerObjectCorporationMethods"; + +import { HashManager } from "../../Hacknet/HashManager"; +import { CityName } from "../../Locations/data/CityNames"; + +import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../../utils/JSONReviver"; + +import Decimal from "decimal.js"; + +export function PlayerObject() { + //Skills and stats + this.hacking_skill = 1; + + //Combat stats + this.hp = 10; + this.max_hp = 10; + this.strength = 1; + this.defense = 1; + this.dexterity = 1; + this.agility = 1; + + //Labor stats + this.charisma = 1; + + //Special stats + this.intelligence = 0; + + //Hacking multipliers + this.hacking_chance_mult = 1; + this.hacking_speed_mult = 1; + this.hacking_money_mult = 1; + this.hacking_grow_mult = 1; + + //Experience and multipliers + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + this.intelligence_exp= 0; + + this.hacking_mult = 1; + this.strength_mult = 1; + this.defense_mult = 1; + this.dexterity_mult = 1; + this.agility_mult = 1; + this.charisma_mult = 1; + + this.hacking_exp_mult = 1; + this.strength_exp_mult = 1; + this.defense_exp_mult = 1; + this.dexterity_exp_mult = 1; + this.agility_exp_mult = 1; + this.charisma_exp_mult = 1; + + this.company_rep_mult = 1; + this.faction_rep_mult = 1; + + //Money + this.money = new Decimal(1000); + + //IP Address of Starting (home) computer + this.homeComputer = ""; + + //Location information + this.city = CityName.Sector12; + this.location = ""; + + // Jobs that the player holds + // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object) + // The CompanyPosition name must match a key value in CompanyPositions + this.jobs = {}; + + // Company at which player is CURRENTLY working (only valid when the player is actively working) + this.companyName = ""; // Name of Company. Must match a key value in Companies map + + // Servers + this.currentServer = ""; //IP address of Server currently being accessed through terminal + this.purchasedServers = []; //IP Addresses of purchased servers + + // Hacknet Nodes/Servers + this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers + this.hashManager = new HashManager(); + + //Factions + this.factions = []; //Names of all factions player has joined + this.factionInvitations = []; //Outstanding faction invitations + + //Augmentations + this.queuedAugmentations = []; + this.augmentations = []; + + this.sourceFiles = []; + + //Crime statistics + this.numPeopleKilled = 0; + this.karma = 0; + + this.crime_money_mult = 1; + this.crime_success_mult = 1; + + //Flags/variables for working (Company, Faction, Creating Program, Taking Class) + this.isWorking = false; + this.workType = ""; + + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + this.workMoneyLossRate = 0; + + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; + + this.createProgramName = ""; + this.createProgramReqLvl = 0; + + this.className = ""; + + this.crimeType = ""; + + this.timeWorked = 0; //in ms + this.timeWorkedCreateProgram = 0; + this.timeNeededToCompleteWork = 0; + + this.work_money_mult = 1; + + //Hacknet Node multipliers + this.hacknet_node_money_mult = 1; + this.hacknet_node_purchase_cost_mult = 1; + this.hacknet_node_ram_cost_mult = 1; + this.hacknet_node_core_cost_mult = 1; + this.hacknet_node_level_cost_mult = 1; + + //Stock Market + this.hasWseAccount = false; + this.hasTixApiAccess = false; + this.has4SData = false; + this.has4SDataTixApi = false; + + //Gang + this.gang = 0; + + //Corporation + this.corporation = 0; + + //Bladeburner + this.bladeburner = 0; + this.bladeburner_max_stamina_mult = 1; + this.bladeburner_stamina_gain_mult = 1; + this.bladeburner_analysis_mult = 1; //Field Analysis Only + this.bladeburner_success_chance_mult = 1; + + // Sleeves & Re-sleeving + this.sleeves = []; + this.resleeves = []; + this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant + + //bitnode + this.bitNodeN = 1; + + //Flags for determining whether certain "thresholds" have been achieved + this.firstFacInvRecvd = false; + this.firstAugPurchased = false; + this.firstTimeTraveled = false; + this.firstProgramAvailable = false; + + //Used to store the last update time. + this.lastUpdate = 0; + this.totalPlaytime = 0; + this.playtimeSinceLastAug = 0; + this.playtimeSinceLastBitnode = 0; + + // Keep track of where money comes from + this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation + this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run + + // Production since last Augmentation installation + this.scriptProdSinceLastAug = 0; +}; + +Object.assign(PlayerObject.prototype, generalMethods, serverMethods, bladeburnerMethods, corporationMethods); + +PlayerObject.prototype.toJSON = function() { + return Generic_toJSON("PlayerObject", this); +} + +PlayerObject.fromJSON = function(value) { + return Generic_fromJSON(PlayerObject, value.data); +} + +Reviver.constructors.PlayerObject = PlayerObject; diff --git a/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js new file mode 100644 index 000000000..479021e33 --- /dev/null +++ b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js @@ -0,0 +1,17 @@ +import { Bladeburner } from "../../Bladeburner"; +import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; + +export function canAccessBladeburner() { + if (this.bitNodeN === 8) { return false; } + + return (this.bitNodeN === 6) || (this.bitNodeN === 7) || (SourceFileFlags[6] > 0); +} + +export function inBladeburner() { + if (this.bladeburner == null) { return false; } + return (this.bladeburner instanceof Bladeburner); +} + +export function startBladeburner() { + this.bladeburner = new Bladeburner({ new: true }); +} diff --git a/src/PersonObjects/Player/PlayerObjectCorporationMethods.js b/src/PersonObjects/Player/PlayerObjectCorporationMethods.js new file mode 100644 index 000000000..a1d358232 --- /dev/null +++ b/src/PersonObjects/Player/PlayerObjectCorporationMethods.js @@ -0,0 +1,19 @@ +import { Corporation } from "../../Corporation/Corporation"; +import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; + +export function canAccessCorporation() { + return this.bitNodeN === 3 || (SourceFileFlags[3] > 0); +} + +export function hasCorporation() { + if (this.corporation == null) { return false; } + return (this.corporation instanceof Corporation); +} + +export function startCorporation(corpName, additionalShares=0) { + this.corporation = new Corporation({ + name: corpName + }); + + this.corporation.totalShares += additionalShares; +} diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js new file mode 100644 index 000000000..cfaa4536d --- /dev/null +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js @@ -0,0 +1,2301 @@ +import { Augmentations } from "../../Augmentation/Augmentations"; +import { applyAugmentation } from "../../Augmentation/AugmentationHelpers"; +import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; +import { Bladeburner } from "../../Bladeburner"; +import { CodingContractRewardType } from "../../CodingContracts"; +import { Company } from "../../Company/Company"; +import { Companies } from "../../Company/Companies"; +import { getNextCompanyPositionHelper } 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 "../../Corporation/Corporation"; +import { Programs } from "../../Programs/Programs"; +import { determineCrimeSuccess } from "../../Crime/CrimeHelpers"; +import { Crimes } from "../../Crime/Crimes"; +import {Engine} from "../../engine"; +import { Faction } from "../../Faction/Faction"; +import { Factions } from "../../Faction/Factions"; +import { displayFactionContent } from "../../Faction/FactionHelpers"; +import {Gang, resetGangs} from "../../Gang"; +import { hasHacknetServers } from "../../Hacknet/HacknetHelpers"; +import { HashManager } from "../../Hacknet/HashManager"; +import { Cities } from "../../Locations/Cities"; +import { Locations } from "../../Locations/Locations"; +import { CityName } from "../../Locations/data/CityNames"; +import { LocationName } from "../../Locations/data/LocationNames"; +import {hasBn11SF, hasWallStreetSF,hasAISF} from "../../NetscriptFunctions"; +import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; +import { AllServers, + AddToAllServers } from "../../Server/AllServers"; +import { Server } from "../../Server/Server"; +import {Settings} from "../../Settings/Settings"; +import {SpecialServerIps, SpecialServerNames} from "../../Server/SpecialServerIps"; +import {SourceFiles, applySourceFile} from "../../SourceFile"; +import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; + +import Decimal from "decimal.js"; + +import {numeralWrapper} from "../../ui/numeralFormat"; +import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; +import {dialogBoxCreate} from "../../../utils/DialogBox"; +import {clearEventListeners} from "../../../utils/uiHelpers/clearEventListeners"; +import {createRandomIp} from "../../../utils/IPAddress"; +import {Reviver, Generic_toJSON, + Generic_fromJSON} from "../../../utils/JSONReviver"; +import {convertTimeMsToTimeElapsedString} from "../../../utils/StringHelperFunctions"; + +const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle; + +export function init() { + /* Initialize Player's home computer */ + var t_homeComp = new Server({ + ip:createRandomIp(), hostname:"home", organizationName:"Home PC", + isConnectedTo:true, adminRights:true, purchasedByPlayer:true, maxRam:8 + }); + this.homeComputer = t_homeComp.ip; + this.currentServer = t_homeComp.ip; + AddToAllServers(t_homeComp); + + this.getHomeComputer().programs.push(Programs.NukeProgram.name); +} + +export function prestigeAugmentation() { + var homeComp = this.getHomeComputer(); + this.currentServer = homeComp.ip; + this.homeComputer = homeComp.ip; + + this.numPeopleKilled = 0; + this.karma = 0; + + //Reset stats + this.hacking_skill = 1; + + this.strength = 1; + this.defense = 1; + this.dexterity = 1; + this.agility = 1; + + this.charisma = 1; + + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + + this.money = new Decimal(1000); + + this.city = CityName.Sector12; + this.location = ""; + + this.companyName = ""; + this.jobs = {}; + + this.purchasedServers = []; + + this.factions = []; + this.factionInvitations = []; + + this.queuedAugmentations = []; + + this.resleeves = []; + + for (let i = 0; i < this.sleeves.length; ++i) { + if (this.sleeves[i] instanceof Sleeve) { + if (this.sleeves[i].shock >= 100) { + this.sleeves[i].synchronize(this); + } else { + this.sleeves[i].shockRecovery(this); + } + } + } + + this.isWorking = false; + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + this.createProgramName = ""; + this.className = ""; + this.crimeType = ""; + + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; + + this.timeWorked = 0; + + this.lastUpdate = new Date().getTime(); + + // Statistics Trackers + this.playtimeSinceLastAug = 0; + this.scriptProdSinceLastAug = 0; + this.moneySourceA.reset(); + + this.hacknetNodes.length = 0; + + //Re-calculate skills and reset HP + this.updateSkillLevels(); + this.hp = this.max_hp; +} + +export function prestigeSourceFile() { + var homeComp = this.getHomeComputer(); + this.currentServer = homeComp.ip; + this.homeComputer = homeComp.ip; + + this.numPeopleKilled = 0; + this.karma = 0; + + //Reset stats + this.hacking_skill = 1; + + this.strength = 1; + this.defense = 1; + this.dexterity = 1; + this.agility = 1; + + this.charisma = 1; + + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + + this.money = new Decimal(1000); + + this.city = CityName.Sector12; + this.location = ""; + + this.companyName = ""; + this.jobs = {}; + + this.purchasedServers = []; + + this.factions = []; + this.factionInvitations = []; + + this.queuedAugmentations = []; + this.augmentations = []; + + this.resleeves = []; + + // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) + this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant; + for (let i = 0; i < this.sleeves.length; ++i) { + if (this.sleeves[i] instanceof Sleeve) { + this.sleeves[i].prestige(this); + } else { + this.sleeves[i] = new Sleeve(this); + } + } + + this.isWorking = false; + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + this.createProgramName = ""; + this.className = ""; + this.crimeType = ""; + + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; + + this.timeWorked = 0; + + this.lastUpdate = new Date().getTime(); + + this.hacknetNodes.length = 0; + + //Gang + this.gang = null; + resetGangs(); + + //Reset Stock market + this.hasWseAccount = false; + this.hasTixApiAccess = false; + this.has4SData = false; + this.has4SDataTixApi = false; + + //BitNode 3: Corporatocracy + this.corporation = 0; + + // Statistics trackers + this.playtimeSinceLastAug = 0; + this.playtimeSinceLastBitnode = 0; + this.scriptProdSinceLastAug = 0; + this.moneySourceA.reset(); + this.moneySourceB.reset(); + + this.updateSkillLevels(); + this.hp = this.max_hp; +} + +export function receiveInvite(factionName) { + if(this.factionInvitations.includes(factionName) || this.factions.includes(factionName)) { + return; + } + this.firstFacInvRecvd = true; + this.factionInvitations.push(factionName); +} + +//Calculates skill level based on experience. The same formula will be used for every skill +export function calculateSkill(exp, mult=1) { + return Math.max(Math.floor(mult*(32 * Math.log(exp + 534.5) - 200)), 1); +} + +export function updateSkillLevels() { + this.hacking_skill = Math.max(1, Math.floor(this.calculateSkill(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier))); + this.strength = Math.max(1, Math.floor(this.calculateSkill(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier))); + this.defense = Math.max(1, Math.floor(this.calculateSkill(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier))); + this.dexterity = Math.max(1, Math.floor(this.calculateSkill(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier))); + this.agility = Math.max(1, Math.floor(this.calculateSkill(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier))); + this.charisma = Math.max(1, Math.floor(this.calculateSkill(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier))); + + if (this.intelligence > 0) { + this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp)); + } else { + this.intelligence = 0; + } + + var ratio = this.hp / this.max_hp; + this.max_hp = Math.floor(10 + this.defense / 10); + this.hp = Math.round(this.max_hp * ratio); +} + +export function resetMultipliers() { + this.hacking_chance_mult = 1; + this.hacking_speed_mult = 1; + this.hacking_money_mult = 1; + this.hacking_grow_mult = 1; + + this.hacking_mult = 1; + this.strength_mult = 1; + this.defense_mult = 1; + this.dexterity_mult = 1; + this.agility_mult = 1; + this.charisma_mult = 1; + + this.hacking_exp_mult = 1; + this.strength_exp_mult = 1; + this.defense_exp_mult = 1; + this.dexterity_exp_mult = 1; + this.agility_exp_mult = 1; + this.charisma_exp_mult = 1; + + this.company_rep_mult = 1; + this.faction_rep_mult = 1; + + this.crime_money_mult = 1; + this.crime_success_mult = 1; + + this.hacknet_node_money_mult = 1; + this.hacknet_node_purchase_cost_mult = 1; + this.hacknet_node_ram_cost_mult = 1; + this.hacknet_node_core_cost_mult = 1; + this.hacknet_node_level_cost_mult = 1; + + this.work_money_mult = 1; + + this.bladeburner_max_stamina_mult = 1; + this.bladeburner_stamina_gain_mult = 1; + this.bladeburner_analysis_mult = 1; + this.bladeburner_success_chance_mult = 1; +} + +export function hasProgram(programName) { + const home = this.getHomeComputer(); + if (home == null) { return false; } + + for (var i = 0; i < home.programs.length; ++i) { + if (programName.toLowerCase() == home.programs[i].toLowerCase()) {return true;} + } + return false; +} + +export function setMoney(money) { + if (isNaN(money)) { + console.log("ERR: NaN passed into Player.setMoney()"); return; + } + this.money = money; +} + +export function gainMoney(money) { + if (isNaN(money)) { + console.log("ERR: NaN passed into Player.gainMoney()"); return; + } + this.money = this.money.plus(money); +} + +export function loseMoney(money) { + if (isNaN(money)) { + console.log("ERR: NaN passed into Player.loseMoney()"); return; + } + this.money = this.money.minus(money); +} + +export function canAfford(cost) { + if (isNaN(cost)) { + console.error(`NaN passed into Player.canAfford()`); + return false; + } + return this.money.gte(cost); +} + +export function recordMoneySource(amt, source) { + if (!(this.moneySourceA instanceof MoneySourceTracker)) { + console.warn(`Player.moneySourceA was not properly initialized. Resetting`); + this.moneySourceA = new MoneySourceTracker(); + } + if (!(this.moneySourceB instanceof MoneySourceTracker)) { + console.warn(`Player.moneySourceB was not properly initialized. Resetting`); + this.moneySourceB = new MoneySourceTracker(); + } + this.moneySourceA.record(amt, source); + this.moneySourceB.record(amt, source); +} + +export function gainHackingExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into Player.gainHackingExp()"); return; + } + this.hacking_exp += exp; + if(this.hacking_exp < 0) { + this.hacking_exp = 0; + } +} + +export function gainStrengthExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into Player.gainStrengthExp()"); return; + } + this.strength_exp += exp; + if(this.strength_exp < 0) { + this.strength_exp = 0; + } +} + +export function gainDefenseExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into player.gainDefenseExp()"); return; + } + this.defense_exp += exp; + if(this.defense_exp < 0) { + this.defense_exp = 0; + } +} + +export function gainDexterityExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into Player.gainDexterityExp()"); return; + } + this.dexterity_exp += exp; + if(this.dexterity_exp < 0) { + this.dexterity_exp = 0; + } +} + +export function gainAgilityExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into Player.gainAgilityExp()"); return; + } + this.agility_exp += exp; + if(this.agility_exp < 0) { + this.agility_exp = 0; + } +} + +export function gainCharismaExp(exp) { + if (isNaN(exp)) { + console.log("ERR: NaN passed into Player.gainCharismaExp()"); return; + } + this.charisma_exp += exp; + if(this.charisma_exp < 0) { + this.charisma_exp = 0; + } +} + +export function gainIntelligenceExp(exp) { + if (isNaN(exp)) { + console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return; + } + if (hasAISF || this.intelligence > 0) { + this.intelligence_exp += exp; + } +} + +//Given a string expression like "str" or "strength", returns the given stat +export function queryStatFromString(str) { + const tempStr = str.toLowerCase(); + if (tempStr.includes("hack")) { return this.hacking_skill; } + if (tempStr.includes("str")) { return this.strength; } + if (tempStr.includes("def")) { return this.defense; } + if (tempStr.includes("dex")) { return this.dexterity; } + if (tempStr.includes("agi")) { return this.agility; } + if (tempStr.includes("cha")) { return this.charisma; } + if (tempStr.includes("int")) { return this.intelligence; } +} + +/******* Working functions *******/ +export function resetWorkStatus() { + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + this.workMoneyLossRate = 0; + + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; + + this.timeWorked = 0; + this.timeWorkedCreateProgram = 0; + + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + this.createProgramName = ""; + this.className = ""; + + document.getElementById("work-in-progress-text").innerHTML = ""; +} + +export function processWorkEarnings(numCycles=1) { + 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); + this.gainDefenseExp(defExpGain); + this.gainDexterityExp(dexExpGain); + this.gainAgilityExp(agiExpGain); + this.gainCharismaExp(chaExpGain); + this.gainMoney(moneyGain); + this.recordMoneySource(moneyGain, "work"); + this.workHackExpGained += hackExpGain; + this.workStrExpGained += strExpGain; + this.workDefExpGained += defExpGain; + this.workDexExpGained += dexExpGain; + this.workAgiExpGained += agiExpGain; + this.workChaExpGained += chaExpGain; + this.workRepGained += this.workRepGainRate * numCycles; + this.workMoneyGained += this.workMoneyGainRate * numCycles; + this.workMoneyGained -= this.workMoneyLossRate * numCycles; +} + +/* Working for Company */ +export function startWork(companyName) { + this.resetWorkStatus(); + this.isWorking = true; + this.companyName = companyName; + this.workType = CONSTANTS.WorkTypeCompany; + + this.workHackExpGainRate = this.getWorkHackExpGain(); + this.workStrExpGainRate = this.getWorkStrExpGain(); + this.workDefExpGainRate = this.getWorkDefExpGain(); + this.workDexExpGainRate = this.getWorkDexExpGain(); + this.workAgiExpGainRate = this.getWorkAgiExpGain(); + this.workChaExpGainRate = this.getWorkChaExpGain(); + this.workRepGainRate = this.getWorkRepGain(); + this.workMoneyGainRate = this.getWorkMoneyGain(); + + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; + + //Remove all old event listeners from Cancel button + var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Cancel Work"; + newCancelButton.addEventListener("click", () => { + this.finishWork(true); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function work(numCycles) { + //Cap the number of cycles being processed to whatever would put you at + //the work time limit (8 hours) + var overMax = false; + if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); + } + this.timeWorked += Engine._idleSpeed * numCycles; + + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); + + //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWork(false); + } + + var comp = Companies[this.companyName], companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.error(`Could not find Company: ${this.companyName}`); + } else { + companyRep = comp.playerReputation; + } + + const position = this.jobs[this.companyName]; + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are currently working as a " + position + + " at " + this.companyName + " (Current Company Reputation: " + + numeralWrapper.format(companyRep, '0,0') + ")

    " + + "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "You have earned:

    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this company

    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + + "You will automatically finish after working for 8 hours. You can cancel earlier if you wish, " + + "but you will only gain half of the reputation you've earned so far." +} + +export function finishWork(cancelled, sing=false) { + //Since the work was cancelled early, player only gains half of what they've earned so far + if (cancelled) { + this.workRepGained /= 2; + } + + var company = Companies[this.companyName]; + company.playerReputation += (this.workRepGained); + + this.updateSkillLevels(); + + var txt = "You earned a total of:
    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the company
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; + + if (cancelled) { + txt = "You worked a short shift of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "Since you cancelled your work early, you only gained half of the reputation you earned.

    " + txt; + } else { + txt = "You worked a full shift of 8 hours!

    " + txt; + } + if (!sing) {dialogBoxCreate(txt);} + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + Engine.loadLocationContent(false); + + if (sing) { + var res = "You worked a short shift of " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + + "earned $" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + ", " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation, " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp."; + this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); +} + +export function startWorkPartTime(companyName) { + this.resetWorkStatus(); + this.isWorking = true; + this.companyName = companyName; + this.workType = CONSTANTS.WorkTypeCompanyPartTime; + + this.workHackExpGainRate = this.getWorkHackExpGain(); + this.workStrExpGainRate = this.getWorkStrExpGain(); + this.workDefExpGainRate = this.getWorkDefExpGain(); + this.workDexExpGainRate = this.getWorkDexExpGain(); + this.workAgiExpGainRate = this.getWorkAgiExpGain(); + this.workChaExpGainRate = this.getWorkChaExpGain(); + this.workRepGainRate = this.getWorkRepGain(); + this.workMoneyGainRate = this.getWorkMoneyGain(); + + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; + + var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Stop Working"; + newCancelButton.addEventListener("click", () => { + this.finishWorkPartTime(); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function workPartTime(numCycles) { + //Cap the number of cycles being processed to whatever would put you at the + //work time limit (8 hours) + var overMax = false; + if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); + } + this.timeWorked += Engine._idleSpeed * numCycles; + + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); + + //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWorkPartTime(); + } + + var comp = Companies[this.companyName], companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.log("ERROR: Could not find Company: " + this.companyName); + } else { + companyRep = comp.playerReputation; + } + + const position = this.jobs[this.companyName]; + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are currently working as a " + position + + " at " + this.companyName + " (Current Company Reputation: " + + numeralWrapper.format(companyRep, '0,0') + ")

    " + + "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "You have earned:

    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this company

    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + + "You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
    " + + "and there will be no penalty because this is a part-time job."; + +} + +export function finishWorkPartTime(sing=false) { + var company = Companies[this.companyName]; + company.playerReputation += (this.workRepGained); + + this.updateSkillLevels(); + + var txt = "You earned a total of:
    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the company
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; + txt = "You worked for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + txt; + if (!sing) {dialogBoxCreate(txt);} + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + Engine.loadLocationContent(false); + if (sing) { + var res = "You worked for " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + + "earned a total of " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + ", " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation, " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp"; + this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); +} + +/* Working for Faction */ +export function startFactionWork(faction) { + //Update reputation gain rate to account for faction favor + var favorMult = 1 + (faction.favor / 100); + if (isNaN(favorMult)) {favorMult = 1;} + this.workRepGainRate *= favorMult; + this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; + + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeFaction; + this.currentWorkFactionName = faction.name; + + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours; + + var cancelButton = clearEventListeners("work-in-progress-cancel-button"); + cancelButton.innerHTML = "Stop Faction Work"; + cancelButton.addEventListener("click", () => { + this.finishFactionWork(true); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function startFactionHackWork(faction) { + this.resetWorkStatus(); + + this.workHackExpGainRate = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult; + + this.factionWorkType = CONSTANTS.FactionWorkHacking; + this.currentWorkFactionDescription = "carrying out hacking contracts"; + + this.startFactionWork(faction); +} + +export function startFactionFieldWork(faction) { + this.resetWorkStatus(); + + this.workHackExpGainRate = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workStrExpGainRate = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDefExpGainRate = .1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = this.getFactionFieldWorkRepGain(); + + this.factionWorkType = CONSTANTS.FactionWorkField; + this.currentWorkFactionDescription = "carrying out field missions" + + this.startFactionWork(faction); +} + +export function startFactionSecurityWork(faction) { + this.resetWorkStatus(); + + this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workStrExpGainRate = 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDefExpGainRate = 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = this.getFactionSecurityWorkRepGain(); + + this.factionWorkType = CONSTANTS.FactionWorkSecurity; + this.currentWorkFactionDescription = "performing security detail" + + this.startFactionWork(faction); +} + +export function workForFaction(numCycles) { + var faction = Factions[this.currentWorkFactionName]; + + //Constantly update the rep gain rate + switch (this.factionWorkType) { + case CONSTANTS.FactionWorkHacking: + this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult; + break; + case CONSTANTS.FactionWorkField: + this.workRepGainRate = this.getFactionFieldWorkRepGain(); + break; + case CONSTANTS.FactionWorkSecurity: + this.workRepGainRate = this.getFactionSecurityWorkRepGain(); + break; + default: + break; + } + + //Update reputation gain rate to account for faction favor + var favorMult = 1 + (faction.favor / 100); + if (isNaN(favorMult)) {favorMult = 1;} + this.workRepGainRate *= favorMult; + this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; + + //Cap the number of cycles being processed to whatever would put you at limit (20 hours) + var overMax = false; + if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) { + overMax = true; + numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed); + } + this.timeWorked += Engine._idleSpeed * numCycles; + + this.processWorkEarnings(numCycles); + + //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { + return this.finishFactionWork(false); + } + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are currently " + this.currentWorkFactionDescription + " for your faction " + faction.name + + " (Current Faction Reputation: " + numeralWrapper.format(faction.playerReputation, '0,0') + ").
    " + + "You have been doing this for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "You have earned:

    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " (" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this faction

    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + + + "You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
    " + + "There is no penalty for cancelling earlier."; +} + +export function finishFactionWork(cancelled, sing=false) { + var faction = Factions[this.currentWorkFactionName]; + faction.playerReputation += (this.workRepGained); + + this.updateSkillLevels(); + + var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "You earned a total of:
    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the faction
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; + if (!sing) {dialogBoxCreate(txt);} + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + + this.isWorking = false; + + Engine.loadFactionContent(); + displayFactionContent(faction.name); + if (sing) { + var res="You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + ". " + + "You earned " + + numeralWrapper.format(this.workRepGained, '0,0.0000') + " rep, " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, and " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."; + this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); +} + +//Money gained per game cycle +export function getWorkMoneyGain() { + // 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 companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (companyPosition == null) { + console.error(`Could not find CompanyPosition object for ${companyPositionName}. Work salary will be 0`); + return 0; + } + + return companyPosition.baseSalary * company.salaryMultiplier * this.work_money_mult * BitNodeMultipliers.CompanyWorkMoney * bn11Mult; +} + +//Hack exp gained per game cycle +export function getWorkHackExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkStrExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkDefExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkDexExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkAgiExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkChaExpGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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 +export function getWorkRepGain() { + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error([`Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `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); + + //Intelligence provides a flat bonus to job performance + jobPerformance += (this.intelligence / CONSTANTS.MaxSkillLevel); + + //Update reputation gain rate to account for company favor + var favorMult = 1 + (company.favor / 100); + if (isNaN(favorMult)) { favorMult = 1; } + return jobPerformance * this.company_rep_mult * favorMult; +} + +export function getFactionSecurityWorkRepGain() { + var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + + this.strength / CONSTANTS.MaxSkillLevel + + this.defense / CONSTANTS.MaxSkillLevel + + this.dexterity / CONSTANTS.MaxSkillLevel + + this.agility / CONSTANTS.MaxSkillLevel) / 4.5; + return t * this.faction_rep_mult; +} + +export function getFactionFieldWorkRepGain() { + var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + + this.strength / CONSTANTS.MaxSkillLevel + + this.defense / CONSTANTS.MaxSkillLevel + + this.dexterity / CONSTANTS.MaxSkillLevel + + this.agility / CONSTANTS.MaxSkillLevel + + this.charisma / CONSTANTS.MaxSkillLevel + + this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5; + return t * this.faction_rep_mult; +} + +/* Creating a Program */ +export function startCreateProgramWork(programName, time, reqLevel) { + this.resetWorkStatus(); + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeCreateProgram; + + //Time needed to complete work affected by hacking skill (linearly based on + //ratio of (your skill - required level) to MAX skill) + //var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking_skill - reqLevel)) / CONSTANTS.MaxSkillLevel; + //if (timeMultiplier > 1) {timeMultiplier = 1;} + //if (timeMultiplier < 0.01) {timeMultiplier = 0.01;} + this.createProgramReqLvl = reqLevel; + + this.timeNeededToCompleteWork = time; + //Check for incomplete program + for (var i = 0; i < this.getHomeComputer().programs.length; ++i) { + var programFile = this.getHomeComputer().programs[i]; + if (programFile.startsWith(programName) && programFile.endsWith("%-INC")) { + var res = programFile.split("-"); + if (res.length != 3) {break;} + var percComplete = Number(res[1].slice(0, -1)); + if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {break;} + this.timeWorkedCreateProgram = percComplete / 100 * this.timeNeededToCompleteWork; + this.getHomeComputer().programs.splice(i, 1); + } + } + + this.createProgramName = programName; + + var cancelButton = clearEventListeners("work-in-progress-cancel-button"); + cancelButton.innerHTML = "Cancel work on creating program"; + cancelButton.addEventListener("click", () => { + this.finishCreateProgramWork(true); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function createProgramWork(numCycles) { + //Higher hacking skill will allow you to create programs faster + var reqLvl = this.createProgramReqLvl; + var skillMult = (this.hacking_skill / reqLvl); //This should always be greater than 1; + skillMult = 1 + ((skillMult - 1) / 5); //The divider constant can be adjusted as necessary + + //Skill multiplier directly applied to "time worked" + this.timeWorked += (Engine._idleSpeed * numCycles); + this.timeWorkedCreateProgram += (Engine._idleSpeed * numCycles * skillMult); + var programName = this.createProgramName; + + if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) { + this.finishCreateProgramWork(false); + } + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are currently working on coding " + programName + ".

    " + + "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "The program is " + (this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 100).toFixed(2) + "% complete.
    " + + "If you cancel, your work will be saved and you can come back to complete the program later."; +} + +export function finishCreateProgramWork(cancelled, sing=false) { + var programName = this.createProgramName; + if (cancelled === false) { + dialogBoxCreate("You've finished creating " + programName + "!
    " + + "The new program can be found on your home computer."); + + this.getHomeComputer().programs.push(programName); + } else { + var perc = (Math.floor(this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 10000)/100).toString(); + var incompleteName = programName + "-" + perc + "%-INC"; + this.getHomeComputer().programs.push(incompleteName); + } + + if (!cancelled) { + this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain); + } + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + + this.isWorking = false; + + Engine.loadTerminalContent(); + this.resetWorkStatus(); +} + +/* Studying/Taking Classes */ +export function startClass(costMult, expMult, className) { + this.resetWorkStatus(); + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeStudyClass; + + this.className = className; + + var gameCPS = 1000 / Engine._idleSpeed; + + //Base exp gains per second + var baseStudyComputerScienceExp = 0.5; + var baseDataStructuresExp = 1; + var baseNetworksExp = 2; + var baseAlgorithmsExp = 4; + var baseManagementExp = 2; + var baseLeadershipExp = 4; + var baseGymExp = 1; + + //Find cost and exp gain per game cycle + var cost = 0; + var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0; + const hashManager = this.hashManager; + switch (className) { + case CONSTANTS.ClassStudyComputerScience: + hackExp = baseStudyComputerScienceExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassDataStructures: + cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS; + hackExp = baseDataStructuresExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassNetworks: + cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS; + hackExp = baseNetworksExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassAlgorithms: + cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS; + hackExp = baseAlgorithmsExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassManagement: + cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS; + chaExp = baseManagementExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassLeadership: + cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS; + chaExp = baseLeadershipExp * expMult / gameCPS * hashManager.getStudyMult(); + break; + case CONSTANTS.ClassGymStrength: + cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; + strExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymDefense: + cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; + defExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymDexterity: + cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; + dexExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymAgility: + cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; + agiExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); + break; + default: + throw new Error("ERR: Invalid/unrecognized class name"); + return; + } + + this.workMoneyLossRate = cost; + this.workHackExpGainRate = hackExp * this.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workStrExpGainRate = strExp * this.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain;; + this.workDefExpGainRate = defExp * this.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain;; + this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain;; + this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain;; + this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain;; + + var cancelButton = clearEventListeners("work-in-progress-cancel-button"); + if (className == CONSTANTS.ClassGymStrength || + className == CONSTANTS.ClassGymDefense || + className == CONSTANTS.ClassGymDexterity || + className == CONSTANTS.ClassGymAgility) { + cancelButton.innerHTML = "Stop training at gym"; + } else { + cancelButton.innerHTML = "Stop taking course"; + } + cancelButton.addEventListener("click", () => { + this.finishClass(); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function takeClass(numCycles) { + this.timeWorked += Engine._idleSpeed * numCycles; + var className = this.className; + + this.processWorkEarnings(numCycles); + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You have been " + className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + + "This has cost you:
    " + + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyLossRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + + "You have gained:
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp
    " + + "You may cancel at any time"; +} + +//The 'sing' argument defines whether or not this function was called +//through a Singularity Netscript function +export function finishClass(sing=false) { + this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000)); + + if (this.workMoneyGained > 0) { + throw new Error("ERR: Somehow gained money while taking class"); + } + + this.updateSkillLevels(); + var txt = "After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ",
    " + + "you spent a total of $" + numeralWrapper.format(this.workMoneyGained * -1, '0,0.00') + ".

    " + + "You earned a total of:
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; + if (!sing) {dialogBoxCreate(txt);} + + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + + this.isWorking = false; + + Engine.loadLocationContent(false); + if (sing) { + var res="After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ", " + + "you spent a total of $" + numeralWrapper.format(this.workMoneyGained * -1, '0,0.00') + ". " + + "You earned a total of: " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp"; + this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); +} + +//The EXP and $ gains are hardcoded. Time is in ms +export function startCrime(crimeType, hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams=null) { + this.crimeType = crimeType; + + this.resetWorkStatus(); + this.isWorking = true; + this.workType = CONSTANTS.WorkTypeCrime; + + if (singParams && singParams.workerscript) { + this.committingCrimeThruSingFn = true; + this.singFnCrimeWorkerScript = singParams.workerscript; + } + + this.workHackExpGained = hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workStrExpGained = strExp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workDefExpGained = defExp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workDexExpGained = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workAgiExpGained = agiExp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workChaExpGained = chaExp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; + + this.timeNeededToCompleteWork = time; + + //Remove all old event listeners from Cancel button + var newCancelButton = clearEventListeners("work-in-progress-cancel-button") + newCancelButton.innerHTML = "Cancel crime" + newCancelButton.addEventListener("click", () => { + this.finishCrime(true); + return false; + }); + + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); +} + +export function commitCrime(numCycles) { + this.timeWorked += Engine._idleSpeed * numCycles; + + if (this.timeWorked >= this.timeNeededToCompleteWork) {this.finishCrime(false); return;} + + var percent = Math.round(this.timeWorked / this.timeNeededToCompleteWork * 100); + var numBars = Math.round(percent / 5); + if (numBars < 0) {numBars = 0;} + if (numBars > 20) {numBars = 20;} + var progressBar = "[" + Array(numBars+1).join("|") + Array(20 - numBars + 1).join(" ") + "]"; + + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = "You are attempting to " + this.crimeType + ".
    " + + "Time remaining: " + convertTimeMsToTimeElapsedString(this.timeNeededToCompleteWork - this.timeWorked) + "
    " + + progressBar.replace( / /g, " " ); +} + +export function finishCrime(cancelled) { + //Determine crime success/failure + if (!cancelled) { + var statusText = ""; // TODO, unique message for each crime when you succeed + if (determineCrimeSuccess(this, this.crimeType)) { + //Handle Karma and crime statistics + let crime = null; + for(const i in Crimes) { + if(Crimes[i].type == this.crimeType) { + crime = Crimes[i]; + break; + } + } + if(crime == null) { + console.log(this.crimeType); + dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer"); + } + this.gainMoney(this.workMoneyGained); + this.recordMoneySource(this.workMoneyGained, "crime"); + this.karma -= crime.karma; + this.numPeopleKilled += crime.kills; + if(crime.intelligence_exp > 0) { + this.gainIntelligenceExp(crime.intelligence_exp); + } + + //On a crime success, gain 2x exp + this.workHackExpGained *= 2; + this.workStrExpGained *= 2; + this.workDefExpGained *= 2; + this.workDexExpGained *= 2; + this.workAgiExpGained *= 2; + this.workChaExpGained *= 2; + if (this.committingCrimeThruSingFn) { + if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { + this.singFnCrimeWorkerScript.scriptRef.log("Crime successful! Gained " + + numeralWrapper.format(this.workMoneyGained, "$0.000a") + ", " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hack exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."); + } + } else { + dialogBoxCreate("Crime successful!

    " + + "You gained:
    "+ + "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking experience
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength experience
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense experience
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity experience
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility experience
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma experience"); + } + + } else { + //Exp halved on failure + this.workHackExpGained /= 2; + this.workStrExpGained /= 2; + this.workDefExpGained /= 2; + this.workDexExpGained /= 2; + this.workAgiExpGained /= 2; + this.workChaExpGained /= 2; + if (this.committingCrimeThruSingFn) { + if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { + this.singFnCrimeWorkerScript.scriptRef.log("Crime failed! Gained " + + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hack exp, " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."); + } + } else { + dialogBoxCreate("Crime failed!

    " + + "You gained:
    "+ + numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking experience
    " + + numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength experience
    " + + numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense experience
    " + + numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity experience
    " + + numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility experience
    " + + numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma experience"); + } + } + + this.gainHackingExp(this.workHackExpGained); + this.gainStrengthExp(this.workStrExpGained); + this.gainDefenseExp(this.workDefExpGained); + this.gainDexterityExp(this.workDexExpGained); + this.gainAgilityExp(this.workAgiExpGained); + this.gainCharismaExp(this.workChaExpGained); + } + this.committingCrimeThruSingFn = false; + this.singFnCrimeWorkerScript = null; + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + this.resetWorkStatus(); + Engine.loadLocationContent(false); +} + +//Cancels the player's current "work" assignment and gives the proper rewards +//Used only for Singularity functions, so no popups are created +export function singularityStopWork() { + if (!this.isWorking) {return "";} + var res; //Earnings text for work + switch (this.workType) { + case CONSTANTS.WorkTypeStudyClass: + res = this.finishClass(true); + break; + case CONSTANTS.WorkTypeCompany: + res = this.finishWork(true, true); + break; + case CONSTANTS.WorkTypeCompanyPartTime: + res = this.finishWorkPartTime(true); + break; + case CONSTANTS.WorkTypeFaction: + res = this.finishFactionWork(true, true); + break; + case CONSTANTS.WorkTypeCreateProgram: + res = this.finishCreateProgramWork(true, true); + break; + case CONSTANTS.WorkTypeCrime: + res = this.finishCrime(true); + break; + default: + console.log("ERROR: Unrecognized work type"); + return ""; + } + return res; +} + + +// Returns true if hospitalized, false otherwise +export function takeDamage(amt) { + if (typeof amt !== "number") { + console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`); + return; + } + + this.hp -= amt; + if (this.hp <= 0) { + this.hospitalize(); + return true; + } else { + return false; + } +} + +export function regenerateHp(amt) { + if (typeof amt !== "number") { + console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`); + return; + } + this.hp += amt; + if (this.hp > this.max_hp) { this.hp = this.max_hp; } +} + +export function hospitalize() { + if (Settings.SuppressHospitalizationPopup === false) { + dialogBoxCreate( + "You were in critical condition! You were taken to the hospital where " + + "luckily they were able to save your life. You were charged " + + numeralWrapper.format(this.max_hp * CONSTANTS.HospitalCostPerHp, '$0.000a') + ); + } + + this.loseMoney(this.max_hp * CONSTANTS.HospitalCostPerHp); + this.hp = this.max_hp; +} + +/********* Company job application **********/ +//Determines the job that the Player should get (if any) at the current company +//The 'sing' argument designates whether or not this is being called from +//the applyToCompany() Netscript Singularity function +export function applyForJob(entryPosType, sing=false) { + // Get current company and job + let currCompany = null; + if (this.companyName !== "") { + currCompany = Companies[this.companyName]; + } + const currPositionName = this.jobs[this.companyName]; + + // 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; + } + } + + let pos = entryPosType; + + if (!this.isQualified(company, pos)) { + var reqText = getJobRequirementText(company, pos); + if (sing) {return false;} + dialogBoxCreate("Unforunately, you do not qualify for this position
    " + reqText); + return; + } + + while (true) { + let newPos = getNextCompanyPositionHelper(pos); + if (newPos == null) {break;} + + //Check if this company has this position + if (company.hasPosition(newPos)) { + if (!this.isQualified(company, newPos)) { + //If player not qualified for next job, break loop so player will be given current job + break; + } + pos = newPos; + } else { + break; + } + } + + //Check if the determined job is the same as the player's current job + if (currCompany != null) { + if (currCompany.name == company.name && pos.name == currPositionName) { + var nextPos = getNextCompanyPositionHelper(pos); + if (nextPos == null) { + if (sing) {return false;} + dialogBoxCreate("You are already at the highest position for your field! No promotion available"); + } else if (company.hasPosition(nextPos)) { + if (sing) {return false;} + var reqText = getJobRequirementText(company, nextPos); + dialogBoxCreate("Unfortunately, you do not qualify for a promotion
    " + reqText); + } else { + if (sing) {return false;} + dialogBoxCreate("You are already at the highest position for your field! No promotion available"); + } + return; //Same job, do nothing + } + } + + this.companyName = company.name; + this.jobs[company.name] = pos.name; + + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + + if (sing) { return true; } + dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!"); + + Engine.loadLocationContent(false); +} + +//Returns your next position at a company given the field (software, business, etc.) +export function getNextCompanyPosition(company, entryPosType) { + var currCompany = null; + if (this.companyName !== "") { + currCompany = Companies[this.companyName]; + } + + //Not employed at this company, so return the entry position + 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. + const currentPositionName = this.jobs[this.companyName]; + const currentPosition = CompanyPositions[currentPositionName]; + 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 getNextCompanyPositionHelper(currentPosition); + } + + return entryPosType; +} + +export function applyForSoftwareJob(sing=false) { + return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing); +} + +export function applyForSoftwareConsultantJob(sing=false) { + return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing); +} + +export function applyForItJob(sing=false) { + return this.applyForJob(CompanyPositions[posNames.ITCompanyPositions[0]], sing); +} + +export function applyForSecurityEngineerJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + 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"); + } +} + +export function applyForNetworkEngineerJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + 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"); + } +} + +export function applyForBusinessJob(sing=false) { + return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing); +} + +export function applyForBusinessConsultantJob(sing=false) { + return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing); +} + +export function applyForSecurityJob(sing=false) { + // TODO Police Jobs + // Indexing starts at 2 because 0 is for police officer + return this.applyForJob(CompanyPositions[posNames.SecurityCompanyPositions[2]], sing); +} + +export function applyForAgentJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + 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"); + } +} + +export function applyForEmployeeJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) { + this.companyName = company.name; + this.jobs[company.name] = posNames.MiscCompanyPositions[1]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) {return true;} + dialogBoxCreate("Congratulations, you are now employed at " + this.companyName); + Engine.loadLocationContent(false); + } else { + if (sing) {return false;} + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } +} + +export function applyForPartTimeEmployeeJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) { + this.companyName = company.name; + this.jobs[company.name] = posNames.PartTimeCompanyPositions[1]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) {return true;} + dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName); + Engine.loadLocationContent(false); + } else { + if (sing) {return false;} + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } +} + +export function applyForWaiterJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) { + this.companyName = company.name; + this.jobs[company.name] = posNames.MiscCompanyPositions[0]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) {return true;} + dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName); + Engine.loadLocationContent(false); + } else { + if (sing) {return false;} + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } +} + +export function applyForPartTimeWaiterJob(sing=false) { + var company = Companies[this.location]; //Company being applied to + if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) { + this.companyName = company.name; + this.jobs[company.name] = posNames.PartTimeCompanyPositions[0]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) {return true;} + dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName); + Engine.loadLocationContent(false); + } else { + if (sing) {return false;} + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } +} + +//Checks if the Player is qualified for a certain position +export function isQualified(company, position) { + var offset = company.jobStatReqOffset; + var reqHacking = position.requiredHacking > 0 ? position.requiredHacking+offset : 0; + var reqStrength = position.requiredStrength > 0 ? position.requiredStrength+offset : 0; + var reqDefense = position.requiredDefense > 0 ? position.requiredDefense+offset : 0; + var reqDexterity = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; + var reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; + var reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma+offset : 0; + + if (this.hacking_skill >= reqHacking && + this.strength >= reqStrength && + this.defense >= reqDefense && + this.dexterity >= reqDexterity && + this.agility >= reqAgility && + this.charisma >= reqCharisma && + company.playerReputation >= position.requiredReputation) { + return true; + } + return false; +} + +/********** Reapplying Augmentations and Source File ***********/ +export function reapplyAllAugmentations(resetMultipliers=true) { + console.log("Re-applying augmentations"); + if (resetMultipliers) { + this.resetMultipliers(); + } + + for (let i = 0; i < this.augmentations.length; ++i) { + //Compatibility with new version + if (this.augmentations[i].name === "HacknetNode NIC Architecture Neural-Upload") { + this.augmentations[i].name = "Hacknet Node NIC Architecture Neural-Upload"; + } + + const augName = this.augmentations[i].name; + var aug = Augmentations[augName]; + if (aug == null) { + console.log(`WARNING: Invalid augmentation name in Player.reapplyAllAugmentations(). Aug ${augName} will be skipped`); + continue; + } + aug.owned = true; + if (aug.name == AugmentationNames.NeuroFluxGovernor) { + for (let j = 0; j < aug.level; ++j) { + applyAugmentation(this.augmentations[i], true); + } + continue; + } + applyAugmentation(this.augmentations[i], true); + } +} + +export function reapplyAllSourceFiles() { + console.log("Re-applying source files"); + //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset + //this.resetMultipliers(); + + for (let i = 0; i < this.sourceFiles.length; ++i) { + var srcFileKey = "SourceFile" + this.sourceFiles[i].n; + var sourceFileObject = SourceFiles[srcFileKey]; + if (sourceFileObject == null) { + console.log("ERROR: Invalid source file number: " + this.sourceFiles[i].n); + continue; + } + applySourceFile(this.sourceFiles[i]); + } +} + +/*************** Check for Faction Invitations *************/ +//This function sets the requirements to join a Faction. It checks whether the Player meets +//those requirements and will return an array of all factions that the Player should +//receive an invitation to +export function checkForFactionInvitations() { + let invitedFactions = []; //Array which will hold all Factions the player should be invited to + + var numAugmentations = this.augmentations.length; + + const allCompanies = Object.keys(this.jobs); + const allPositions = Object.values(this.jobs); + + // Given a company name, safely returns the reputation (returns 0 if invalid company is specified) + function getCompanyRep(companyName) { + const company = Companies[companyName]; + if (company == null) { + return 0; + } else { + return company.playerReputation; + } + } + + // Helper function that returns a boolean indicating whether the Player meets + // the requirements for the specified company. There are two requirements: + // 1. High enough reputation + // 2. Player is employed at the company + function checkMegacorpRequirements(companyName, repNeeded=CONSTANTS.CorpFactionRepRequirement) { + return allCompanies.includes(companyName) && (getCompanyRep(companyName) > repNeeded); + } + + //Illuminati + var illuminatiFac = Factions["Illuminati"]; + if (!illuminatiFac.isBanned && !illuminatiFac.isMember && !illuminatiFac.alreadyInvited && + numAugmentations >= 30 && + this.money.gte(150000000000) && + this.hacking_skill >= 1500 && + this.strength >= 1200 && this.defense >= 1200 && + this.dexterity >= 1200 && this.agility >= 1200) { + invitedFactions.push(illuminatiFac); + } + + //Daedalus + var daedalusFac = Factions["Daedalus"]; + if (!daedalusFac.isBanned && !daedalusFac.isMember && !daedalusFac.alreadyInvited && + numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) && + this.money.gte(100000000000) && + (this.hacking_skill >= 2500 || + (this.strength >= 1500 && this.defense >= 1500 && + this.dexterity >= 1500 && this.agility >= 1500))) { + invitedFactions.push(daedalusFac); + } + + //The Covenant + var covenantFac = Factions["The Covenant"]; + if (!covenantFac.isBanned && !covenantFac.isMember && !covenantFac.alreadyInvited && + numAugmentations >= 20 && + this.money.gte(75000000000) && + this.hacking_skill >= 850 && + this.strength >= 850 && + this.defense >= 850 && + this.dexterity >= 850 && + this.agility >= 850) { + invitedFactions.push(covenantFac); + } + + //ECorp + var ecorpFac = Factions["ECorp"]; + if (!ecorpFac.isBanned && !ecorpFac.isMember && !ecorpFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumECorp)) { + invitedFactions.push(ecorpFac); + } + + //MegaCorp + var megacorpFac = Factions["MegaCorp"]; + if (!megacorpFac.isBanned && !megacorpFac.isMember && !megacorpFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12MegaCorp)) { + invitedFactions.push(megacorpFac); + } + + //Bachman & Associates + var bachmanandassociatesFac = Factions["Bachman & Associates"]; + if (!bachmanandassociatesFac.isBanned && !bachmanandassociatesFac.isMember && + !bachmanandassociatesFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumBachmanAndAssociates)) { + invitedFactions.push(bachmanandassociatesFac); + } + + //Blade Industries + var bladeindustriesFac = Factions["Blade Industries"]; + if (!bladeindustriesFac.isBanned && !bladeindustriesFac.isMember && !bladeindustriesFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12BladeIndustries)) { + invitedFactions.push(bladeindustriesFac); + } + + //NWO + var nwoFac = Factions["NWO"]; + if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited && + checkMegacorpRequirements(LocationName.VolhavenNWO)) { + invitedFactions.push(nwoFac); + } + + //Clarke Incorporated + var clarkeincorporatedFac = Factions["Clarke Incorporated"]; + if (!clarkeincorporatedFac.isBanned && !clarkeincorporatedFac.isMember && !clarkeincorporatedFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumClarkeIncorporated)) { + invitedFactions.push(clarkeincorporatedFac); + } + + //OmniTek Incorporated + var omnitekincorporatedFac = Factions["OmniTek Incorporated"]; + if (!omnitekincorporatedFac.isBanned && !omnitekincorporatedFac.isMember && !omnitekincorporatedFac.alreadyInvited && + checkMegacorpRequirements(LocationName.VolhavenOmniTekIncorporated)) { + invitedFactions.push(omnitekincorporatedFac); + } + + //Four Sigma + var foursigmaFac = Factions["Four Sigma"]; + if (!foursigmaFac.isBanned && !foursigmaFac.isMember && !foursigmaFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12FourSigma)) { + invitedFactions.push(foursigmaFac); + } + + //KuaiGong International + var kuaigonginternationalFac = Factions["KuaiGong International"]; + if (!kuaigonginternationalFac.isBanned && !kuaigonginternationalFac.isMember && + !kuaigonginternationalFac.alreadyInvited && + checkMegacorpRequirements(LocationName.ChongqingKuaiGongInternational)) { + invitedFactions.push(kuaigonginternationalFac); + } + + //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company + var fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"]; + var fulcrumSecretServer = AllServers[SpecialServerIps[SpecialServerNames.FulcrumSecretTechnologies]]; + if (fulcrumSecretServer == null) { + console.log("ERROR: Could not find Fulcrum Secret Technologies Server"); + } else { + if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember && + !fulcrumsecrettechonologiesFac.alreadyInvited && + fulcrumSecretServer.manuallyHacked && + checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) { + invitedFactions.push(fulcrumsecrettechonologiesFac); + } + } + + //BitRunners + var bitrunnersFac = Factions["BitRunners"]; + var homeComp = this.getHomeComputer(); + var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; + if (bitrunnersServer == null) { + console.log("ERROR: Could not find BitRunners Server"); + } else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.manuallyHacked && + !bitrunnersFac.alreadyInvited && this.hacking_skill >= 500 && homeComp.maxRam >= 128) { + invitedFactions.push(bitrunnersFac); + } + + //The Black Hand + var theblackhandFac = Factions["The Black Hand"]; + var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; + if (blackhandServer == null) { + console.log("ERROR: Could not find The Black Hand Server"); + } else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.manuallyHacked && + !theblackhandFac.alreadyInvited && this.hacking_skill >= 350 && homeComp.maxRam >= 64) { + invitedFactions.push(theblackhandFac); + } + + //NiteSec + var nitesecFac = Factions["NiteSec"]; + var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; + if (nitesecServer == null) { + console.log("ERROR: Could not find NiteSec Server"); + } else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.manuallyHacked && + !nitesecFac.alreadyInvited && this.hacking_skill >= 200 && homeComp.maxRam >= 32) { + invitedFactions.push(nitesecFac); + } + + //Chongqing + var chongqingFac = Factions["Chongqing"]; + if (!chongqingFac.isBanned && !chongqingFac.isMember && !chongqingFac.alreadyInvited && + this.money.gte(20000000) && this.city == CityName.Chongqing) { + invitedFactions.push(chongqingFac); + } + + //Sector-12 + var sector12Fac = Factions["Sector-12"]; + if (!sector12Fac.isBanned && !sector12Fac.isMember && !sector12Fac.alreadyInvited && + this.money.gte(15000000) && this.city == CityName.Sector12) { + invitedFactions.push(sector12Fac); + } + + //New Tokyo + var newtokyoFac = Factions["New Tokyo"]; + if (!newtokyoFac.isBanned && !newtokyoFac.isMember && !newtokyoFac.alreadyInvited && + this.money.gte(20000000) && this.city == CityName.NewTokyo) { + invitedFactions.push(newtokyoFac); + } + + //Aevum + var aevumFac = Factions["Aevum"]; + if (!aevumFac.isBanned && !aevumFac.isMember && !aevumFac.alreadyInvited && + this.money.gte(40000000) && this.city == CityName.Aevum) { + invitedFactions.push(aevumFac); + } + + //Ishima + var ishimaFac = Factions["Ishima"]; + if (!ishimaFac.isBanned && !ishimaFac.isMember && !ishimaFac.alreadyInvited && + this.money.gte(30000000) && this.city == CityName.Ishima) { + invitedFactions.push(ishimaFac); + } + + //Volhaven + var volhavenFac = Factions["Volhaven"]; + if (!volhavenFac.isBanned && !volhavenFac.isMember && !volhavenFac.alreadyInvited && + this.money.gte(50000000) && this.city == CityName.Volhaven) { + invitedFactions.push(volhavenFac); + } + + //Speakers for the Dead + var speakersforthedeadFac = Factions["Speakers for the Dead"]; + if (!speakersforthedeadFac.isBanned && !speakersforthedeadFac.isMember && !speakersforthedeadFac.alreadyInvited && + this.hacking_skill >= 100 && this.strength >= 300 && this.defense >= 300 && + this.dexterity >= 300 && this.agility >= 300 && this.numPeopleKilled >= 30 && + this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && + !allCompanies.includes(LocationName.Sector12NSA)) { + invitedFactions.push(speakersforthedeadFac); + } + + //The Dark Army + var thedarkarmyFac = Factions["The Dark Army"]; + if (!thedarkarmyFac.isBanned && !thedarkarmyFac.isMember && !thedarkarmyFac.alreadyInvited && + this.hacking_skill >= 300 && this.strength >= 300 && this.defense >= 300 && + this.dexterity >= 300 && this.agility >= 300 && this.city == CityName.Chongqing && + this.numPeopleKilled >= 5 && this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && + !allCompanies.includes(LocationName.Sector12NSA)) { + invitedFactions.push(thedarkarmyFac); + } + + //The Syndicate + var thesyndicateFac = Factions["The Syndicate"]; + if (!thesyndicateFac.isBanned && !thesyndicateFac.isMember && !thesyndicateFac.alreadyInvited && + this.hacking_skill >= 200 && this.strength >= 200 && this.defense >= 200 && + this.dexterity >= 200 && this.agility >= 200 && + (this.city == CityName.Aevum || this.city == CityName.Sector12) && + this.money.gte(10000000) && this.karma <= -90 && + !allCompanies.includes(LocationName.Sector12CIA) && !allCompanies.includes(LocationName.Sector12NSA)) { + invitedFactions.push(thesyndicateFac); + } + + //Silhouette + var silhouetteFac = Factions["Silhouette"]; + if (!silhouetteFac.isBanned && !silhouetteFac.isMember && !silhouetteFac.alreadyInvited && + (allPositions.includes("Chief Technology Officer") || + allPositions.includes("Chief Financial Officer") || + allPositions.includes("Chief Executive Officer")) && + this.money.gte(15000000) && this.karma <= -22) { + invitedFactions.push(silhouetteFac); + } + + //Tetrads + var tetradsFac = Factions["Tetrads"]; + if (!tetradsFac.isBanned && !tetradsFac.isMember && !tetradsFac.alreadyInvited && + (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || + this.city == CityName.Ishima) && this.strength >= 75 && this.defense >= 75 && + this.dexterity >= 75 && this.agility >= 75 && this.karma <= -18) { + invitedFactions.push(tetradsFac); + } + + //SlumSnakes + var slumsnakesFac = Factions["Slum Snakes"]; + if (!slumsnakesFac.isBanned && !slumsnakesFac.isMember && !slumsnakesFac.alreadyInvited && + this.strength >= 30 && this.defense >= 30 && this.dexterity >= 30 && + this.agility >= 30 && this.karma <= -9 && this.money.gte(1000000)) { + invitedFactions.push(slumsnakesFac); + } + + //Netburners + var netburnersFac = Factions["Netburners"]; + var totalHacknetRam = 0; + var totalHacknetCores = 0; + var totalHacknetLevels = 0; + for (let i = 0; i < this.hacknetNodes.length; ++i) { + if (hasHacknetServers()) { + const hserver = AllServers[this.hacknetNodes[i]]; + if (hserver) { + totalHacknetLevels += hserver.level; + totalHacknetRam += hserver.maxRam; + totalHacknetCores += hserver.cores; + } + } else { + totalHacknetLevels += this.hacknetNodes[i].level; + totalHacknetRam += this.hacknetNodes[i].ram; + totalHacknetCores += this.hacknetNodes[i].cores; + } + } + if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited && + this.hacking_skill >= 80 && totalHacknetRam >= 8 && + totalHacknetCores >= 4 && totalHacknetLevels >= 100) { + invitedFactions.push(netburnersFac); + } + + //Tian Di Hui + var tiandihuiFac = Factions["Tian Di Hui"]; + if (!tiandihuiFac.isBanned && !tiandihuiFac.isMember && !tiandihuiFac.alreadyInvited && + this.money.gte(1000000) && this.hacking_skill >= 50 && + (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || + this.city == CityName.Ishima)) { + invitedFactions.push(tiandihuiFac); + } + + //CyberSec + var cybersecFac = Factions["CyberSec"]; + var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; + if (cybersecServer == null) { + console.log("ERROR: Could not find CyberSec Server"); + } else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.manuallyHacked && + !cybersecFac.alreadyInvited && this.hacking_skill >= 50) { + invitedFactions.push(cybersecFac); + } + + return invitedFactions; +} + + +/*************** Gang ****************/ +//Returns true if Player is in a gang and false otherwise +export function inGang() { + if (this.gang == null || this.gang == undefined) {return false;} + return (this.gang instanceof Gang); +} + +export function startGang(factionName, hacking) { + this.gang = new Gang(factionName, hacking); +} + +/************* BitNodes **************/ +export function setBitNodeNumber(n) { + this.bitNodeN = n; +} + +export function queueAugmentation(name) { + for(const i in this.queuedAugmentations) { + if(this.queuedAugmentations[i].name == name) { + console.log('tried to queue '+name+' twice, this may be a bug'); + return; + } + } + + for(const i in this.augmentations) { + if(this.augmentations[i].name == name) { + console.log('tried to queue '+name+' but we already have that aug'); + return; + } + } + + this.firstAugPurchased = true; + this.queuedAugmentations.push(new PlayerOwnedAugmentation(name)); +} + +/************* Coding Contracts **************/ +export function gainCodingContractReward(reward, difficulty=1) { + if (reward == null || reward.type == null || reward == null) { + return `No reward for this contract`; + } + + /* eslint-disable no-case-declarations */ + switch (reward.type) { + case CodingContractRewardType.FactionReputation: + if (reward.name == null || !(Factions[reward.name] instanceof Faction)) { + // If no/invalid faction was designated, just give rewards to all factions + reward.type = CodingContractRewardType.FactionReputationAll; + return this.gainCodingContractReward(reward); + } + var repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; + Factions[reward.name].playerReputation += repGain; + return `Gained ${repGain} faction reputation for ${reward.name}`; + case CodingContractRewardType.FactionReputationAll: + const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; + + // Ignore Bladeburners and other special factions for this calculation + const specialFactions = ["Bladeburners"]; + var factions = this.factions.slice(); + factions = factions.filter((f) => { + return !specialFactions.includes(f); + }); + + // If the player was only part of the special factions, we'll just give money + if (factions.length == 0) { + reward.type = CodingContractRewardType.Money; + return this.gainCodingContractReward(reward, difficulty); + } + + const gainPerFaction = Math.floor(totalGain / factions.length); + for (const facName of factions) { + if (!(Factions[facName] instanceof Faction)) { continue; } + Factions[facName].playerReputation += gainPerFaction; + } + return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.toString()}`; + break; + case CodingContractRewardType.CompanyReputation: + if (reward.name == null || !(Companies[reward.name] instanceof Company)) { + //If no/invalid company was designated, just give rewards to all factions + reward.type = CodingContractRewardType.FactionReputationAll; + return this.gainCodingContractReward(reward); + } + var repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty; + Companies[reward.name].playerReputation += repGain; + return `Gained ${repGain} company reputation for ${reward.name}`; + break; + case CodingContractRewardType.Money: + default: + var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney; + this.gainMoney(moneyGain); + this.recordMoneySource(moneyGain, "codingcontract"); + return `Gained ${numeralWrapper.format(moneyGain, '$0.000a')}`; + break; + } + /* eslint-enable no-case-declarations */ +} + +export function travel(to) { + if (Cities[to] == null) { + console.warn(`Player.travel() called with invalid city: ${to}`); + return false; + } + this.city = to; + + return true; +} + +export function gotoLocation(to) { + if (Locations[to] == null) { + console.warn(`Player.gotoLocation() called with invalid location: ${to}`); + return false; + } + this.location = to; + + return true; +} + +export function canAccessResleeving() { + return this.bitNodeN === 10 || (SourceFileFlags[10] > 0); +} diff --git a/src/PersonObjects/Player/PlayerObjectServerMethods.ts b/src/PersonObjects/Player/PlayerObjectServerMethods.ts new file mode 100644 index 000000000..aced9460b --- /dev/null +++ b/src/PersonObjects/Player/PlayerObjectServerMethods.ts @@ -0,0 +1,30 @@ +import { IPlayer } from "../IPlayer"; + +import { CONSTANTS } from "../../Constants"; +import { AllServers } from "../../Server/AllServers"; +import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; +import { SpecialServerIps } from "../../Server/SpecialServerIps"; + +export function hasTorRouter(this: IPlayer) { + return SpecialServerIps.hasOwnProperty("Darkweb Server"); +} + +export function getCurrentServer(this: IPlayer) { + return AllServers[this.currentServer]; +} + +export function getHomeComputer(this: IPlayer) { + return AllServers[this.homeComputer]; +} + +export function getUpgradeHomeRamCost(this: IPlayer) { + //Calculate how many times ram has been upgraded (doubled) + const currentRam = this.getHomeComputer().maxRam; + const numUpgrades = Math.log2(currentRam); + + //Calculate cost + //Have cost increase by some percentage each time RAM has been upgraded + const mult = Math.pow(1.58, numUpgrades); + var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * BitNodeMultipliers.HomeComputerRamCost; + return cost; +} diff --git a/src/Player.js b/src/Player.js index 65ab700ee..fe8140fdf 100644 --- a/src/Player.js +++ b/src/Player.js @@ -1,2542 +1,9 @@ -import { Augmentations } from "./Augmentation/Augmentations"; -import { applyAugmentation } from "./Augmentation/AugmentationHelpers"; -import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation"; -import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; -import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { Bladeburner } from "./Bladeburner"; -import { CodingContractRewardType } from "./CodingContracts"; -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 "./Corporation/Corporation"; -import { Programs } from "./Programs/Programs"; -import { determineCrimeSuccess } from "./Crime/CrimeHelpers"; -import { Crimes } from "./Crime/Crimes"; -import {Engine} from "./engine"; -import { Faction } from "./Faction/Faction"; -import { Factions } from "./Faction/Factions"; -import { displayFactionContent } from "./Faction/FactionHelpers"; -import {Gang, resetGangs} from "./Gang"; -import { hasHacknetServers } from "./Hacknet/HacknetHelpers"; -import { HashManager } from "./Hacknet/HashManager"; -import { Cities } from "./Locations/Cities"; -import { Locations } from "./Locations/Locations"; -import { CityName } from "./Locations/data/CityNames"; -import { LocationName } from "./Locations/data/LocationNames"; -import {hasBn11SF, hasWallStreetSF,hasAISF} from "./NetscriptFunctions"; -import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; -import { AllServers, - AddToAllServers } from "./Server/AllServers"; -import { Server } from "./Server/Server"; -import {Settings} from "./Settings/Settings"; -import {SpecialServerIps, SpecialServerNames} from "./Server/SpecialServerIps"; -import {SourceFiles, applySourceFile} from "./SourceFile"; -import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; +import { Corporation } from "./Corporation/Corporation"; +import { PlayerObject } from "./PersonObjects/Player/PlayerObject"; -import Decimal from "decimal.js"; +import { Reviver } from "../utils/JSONReviver"; -import {numeralWrapper} from "./ui/numeralFormat"; -import { MoneySourceTracker } from "./utils/MoneySourceTracker"; -import {dialogBoxCreate} from "./../utils/DialogBox"; -import {clearEventListeners} from "./../utils/uiHelpers/clearEventListeners"; -import {createRandomIp} from "./../utils/IPAddress"; -import {Reviver, Generic_toJSON, - Generic_fromJSON} from "../utils/JSONReviver"; -import {convertTimeMsToTimeElapsedString} from "../utils/StringHelperFunctions"; - -const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle; - -function PlayerObject() { - //Skills and stats - this.hacking_skill = 1; - - //Combat stats - this.hp = 10; - this.max_hp = 10; - this.strength = 1; - this.defense = 1; - this.dexterity = 1; - this.agility = 1; - - //Labor stats - this.charisma = 1; - - //Special stats - this.intelligence = 0; - - //Hacking multipliers - this.hacking_chance_mult = 1; - this.hacking_speed_mult = 1; - this.hacking_money_mult = 1; - this.hacking_grow_mult = 1; - - //Experience and multipliers - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - this.intelligence_exp= 0; - - this.hacking_mult = 1; - this.strength_mult = 1; - this.defense_mult = 1; - this.dexterity_mult = 1; - this.agility_mult = 1; - this.charisma_mult = 1; - - this.hacking_exp_mult = 1; - this.strength_exp_mult = 1; - this.defense_exp_mult = 1; - this.dexterity_exp_mult = 1; - this.agility_exp_mult = 1; - this.charisma_exp_mult = 1; - - this.company_rep_mult = 1; - this.faction_rep_mult = 1; - - //Money - this.money = new Decimal(1000); - - //IP Address of Starting (home) computer - this.homeComputer = ""; - - //Location information - this.city = CityName.Sector12; - this.location = ""; - - // Jobs that the player holds - // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object) - // The CompanyPosition name must match a key value in CompanyPositions - this.jobs = {}; - - // Company at which player is CURRENTLY working (only valid when the player is actively working) - this.companyName = ""; // Name of Company. Must match a key value in Companies map - - // Servers - this.currentServer = ""; //IP address of Server currently being accessed through terminal - this.purchasedServers = []; //IP Addresses of purchased servers - - // Hacknet Nodes/Servers - this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers - this.hashManager = new HashManager(); - - //Factions - this.factions = []; //Names of all factions player has joined - this.factionInvitations = []; //Outstanding faction invitations - - //Augmentations - this.queuedAugmentations = []; - this.augmentations = []; - - this.sourceFiles = []; - - //Crime statistics - this.numPeopleKilled = 0; - this.karma = 0; - - this.crime_money_mult = 1; - this.crime_success_mult = 1; - - //Flags/variables for working (Company, Faction, Creating Program, Taking Class) - this.isWorking = false; - this.workType = ""; - - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - this.workMoneyLossRate = 0; - - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; - - this.createProgramName = ""; - this.createProgramReqLvl = 0; - - this.className = ""; - - this.crimeType = ""; - - this.timeWorked = 0; //in ms - this.timeWorkedCreateProgram = 0; - this.timeNeededToCompleteWork = 0; - - this.work_money_mult = 1; - - //Hacknet Node multipliers - this.hacknet_node_money_mult = 1; - this.hacknet_node_purchase_cost_mult = 1; - this.hacknet_node_ram_cost_mult = 1; - this.hacknet_node_core_cost_mult = 1; - this.hacknet_node_level_cost_mult = 1; - - //Stock Market - this.hasWseAccount = false; - this.hasTixApiAccess = false; - this.has4SData = false; - this.has4SDataTixApi = false; - - //Gang - this.gang = 0; - - //Corporation - this.corporation = 0; - - //Bladeburner - this.bladeburner = 0; - this.bladeburner_max_stamina_mult = 1; - this.bladeburner_stamina_gain_mult = 1; - this.bladeburner_analysis_mult = 1; //Field Analysis Only - this.bladeburner_success_chance_mult = 1; - - // Sleeves & Re-sleeving - this.sleeves = []; - this.resleeves = []; - this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant - - //bitnode - this.bitNodeN = 1; - - //Flags for determining whether certain "thresholds" have been achieved - this.firstFacInvRecvd = false; - this.firstAugPurchased = false; - this.firstTimeTraveled = false; - this.firstProgramAvailable = false; - - //Used to store the last update time. - this.lastUpdate = 0; - this.totalPlaytime = 0; - this.playtimeSinceLastAug = 0; - this.playtimeSinceLastBitnode = 0; - - // Keep track of where money comes from - this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation - this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run - - // Production since last Augmentation installation - this.scriptProdSinceLastAug = 0; -}; - -PlayerObject.prototype.init = function() { - /* Initialize Player's home computer */ - var t_homeComp = new Server({ - ip:createRandomIp(), hostname:"home", organizationName:"Home PC", - isConnectedTo:true, adminRights:true, purchasedByPlayer:true, maxRam:8 - }); - this.homeComputer = t_homeComp.ip; - this.currentServer = t_homeComp.ip; - AddToAllServers(t_homeComp); - - this.getHomeComputer().programs.push(Programs.NukeProgram.name); -} - -PlayerObject.prototype.prestigeAugmentation = function() { - var homeComp = this.getHomeComputer(); - this.currentServer = homeComp.ip; - this.homeComputer = homeComp.ip; - - this.numPeopleKilled = 0; - this.karma = 0; - - //Reset stats - this.hacking_skill = 1; - - this.strength = 1; - this.defense = 1; - this.dexterity = 1; - this.agility = 1; - - this.charisma = 1; - - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - - this.money = new Decimal(1000); - - this.city = CityName.Sector12; - this.location = ""; - - this.companyName = ""; - this.jobs = {}; - - this.purchasedServers = []; - - this.factions = []; - this.factionInvitations = []; - - this.queuedAugmentations = []; - - this.resleeves = []; - - for (let i = 0; i < this.sleeves.length; ++i) { - if (this.sleeves[i] instanceof Sleeve) { - if (this.sleeves[i].shock >= 100) { - this.sleeves[i].synchronize(this); - } else { - this.sleeves[i].shockRecovery(this); - } - } - } - - this.isWorking = false; - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - this.createProgramName = ""; - this.className = ""; - this.crimeType = ""; - - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; - - this.timeWorked = 0; - - this.lastUpdate = new Date().getTime(); - - // Statistics Trackers - this.playtimeSinceLastAug = 0; - this.scriptProdSinceLastAug = 0; - this.moneySourceA.reset(); - - this.hacknetNodes.length = 0; - - //Re-calculate skills and reset HP - this.updateSkillLevels(); - this.hp = this.max_hp; -} - -PlayerObject.prototype.prestigeSourceFile = function() { - var homeComp = this.getHomeComputer(); - this.currentServer = homeComp.ip; - this.homeComputer = homeComp.ip; - - this.numPeopleKilled = 0; - this.karma = 0; - - //Reset stats - this.hacking_skill = 1; - - this.strength = 1; - this.defense = 1; - this.dexterity = 1; - this.agility = 1; - - this.charisma = 1; - - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - - this.money = new Decimal(1000); - - this.city = CityName.Sector12; - this.location = ""; - - this.companyName = ""; - this.jobs = {}; - - this.purchasedServers = []; - - this.factions = []; - this.factionInvitations = []; - - this.queuedAugmentations = []; - this.augmentations = []; - - this.resleeves = []; - - // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) - this.sleeves.length = SourceFileFlags[10] + this.sleevesFromCovenant; - for (let i = 0; i < this.sleeves.length; ++i) { - if (this.sleeves[i] instanceof Sleeve) { - this.sleeves[i].prestige(this); - } else { - this.sleeves[i] = new Sleeve(this); - } - } - - this.isWorking = false; - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - this.createProgramName = ""; - this.className = ""; - this.crimeType = ""; - - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; - - this.timeWorked = 0; - - this.lastUpdate = new Date().getTime(); - - this.hacknetNodes.length = 0; - - //Gang - this.gang = null; - resetGangs(); - - //Reset Stock market - this.hasWseAccount = false; - this.hasTixApiAccess = false; - this.has4SData = false; - this.has4SDataTixApi = false; - - //BitNode 3: Corporatocracy - this.corporation = 0; - - // Statistics trackers - this.playtimeSinceLastAug = 0; - this.playtimeSinceLastBitnode = 0; - this.scriptProdSinceLastAug = 0; - this.moneySourceA.reset(); - this.moneySourceB.reset(); - - this.updateSkillLevels(); - this.hp = this.max_hp; -} - -PlayerObject.prototype.receiveInvite = function(factionName) { - if(this.factionInvitations.includes(factionName) || this.factions.includes(factionName)) { - return; - } - this.firstFacInvRecvd = true; - this.factionInvitations.push(factionName); -} - -//Calculates skill level based on experience. The same formula will be used for every skill -PlayerObject.prototype.calculateSkill = function(exp, mult=1) { - return Math.max(Math.floor(mult*(32 * Math.log(exp + 534.5) - 200)), 1); -} - -PlayerObject.prototype.updateSkillLevels = function() { - this.hacking_skill = Math.max(1, Math.floor(this.calculateSkill(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier))); - this.strength = Math.max(1, Math.floor(this.calculateSkill(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier))); - this.defense = Math.max(1, Math.floor(this.calculateSkill(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier))); - this.dexterity = Math.max(1, Math.floor(this.calculateSkill(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier))); - this.agility = Math.max(1, Math.floor(this.calculateSkill(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier))); - this.charisma = Math.max(1, Math.floor(this.calculateSkill(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier))); - - if (this.intelligence > 0) { - this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp)); - } else { - this.intelligence = 0; - } - - var ratio = this.hp / this.max_hp; - this.max_hp = Math.floor(10 + this.defense / 10); - this.hp = Math.round(this.max_hp * ratio); -} - -PlayerObject.prototype.resetMultipliers = function() { - this.hacking_chance_mult = 1; - this.hacking_speed_mult = 1; - this.hacking_money_mult = 1; - this.hacking_grow_mult = 1; - - this.hacking_mult = 1; - this.strength_mult = 1; - this.defense_mult = 1; - this.dexterity_mult = 1; - this.agility_mult = 1; - this.charisma_mult = 1; - - this.hacking_exp_mult = 1; - this.strength_exp_mult = 1; - this.defense_exp_mult = 1; - this.dexterity_exp_mult = 1; - this.agility_exp_mult = 1; - this.charisma_exp_mult = 1; - - this.company_rep_mult = 1; - this.faction_rep_mult = 1; - - this.crime_money_mult = 1; - this.crime_success_mult = 1; - - this.hacknet_node_money_mult = 1; - this.hacknet_node_purchase_cost_mult = 1; - this.hacknet_node_ram_cost_mult = 1; - this.hacknet_node_core_cost_mult = 1; - this.hacknet_node_level_cost_mult = 1; - - this.work_money_mult = 1; - - this.bladeburner_max_stamina_mult = 1; - this.bladeburner_stamina_gain_mult = 1; - this.bladeburner_analysis_mult = 1; - this.bladeburner_success_chance_mult = 1; -} - -PlayerObject.prototype.hasProgram = function(programName) { - const home = this.getHomeComputer(); - if (home == null) { return false; } - - for (var i = 0; i < home.programs.length; ++i) { - if (programName.toLowerCase() == home.programs[i].toLowerCase()) {return true;} - } - return false; -} - -PlayerObject.prototype.setMoney = function(money) { - if (isNaN(money)) { - console.log("ERR: NaN passed into Player.setMoney()"); return; - } - this.money = money; -} - -PlayerObject.prototype.gainMoney = function(money) { - if (isNaN(money)) { - console.log("ERR: NaN passed into Player.gainMoney()"); return; - } - this.money = this.money.plus(money); -} - -PlayerObject.prototype.loseMoney = function(money) { - if (isNaN(money)) { - console.log("ERR: NaN passed into Player.loseMoney()"); return; - } - this.money = this.money.minus(money); -} - -PlayerObject.prototype.canAfford = function(cost) { - if (isNaN(cost)) { - console.error(`NaN passed into Player.canAfford()`); - return false; - } - return this.money.gte(cost); -} - -PlayerObject.prototype.recordMoneySource = function(amt, source) { - if (!(this.moneySourceA instanceof MoneySourceTracker)) { - console.warn(`Player.moneySourceA was not properly initialized. Resetting`); - this.moneySourceA = new MoneySourceTracker(); - } - if (!(this.moneySourceB instanceof MoneySourceTracker)) { - console.warn(`Player.moneySourceB was not properly initialized. Resetting`); - this.moneySourceB = new MoneySourceTracker(); - } - this.moneySourceA.record(amt, source); - this.moneySourceB.record(amt, source); -} - -PlayerObject.prototype.gainHackingExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into Player.gainHackingExp()"); return; - } - this.hacking_exp += exp; - if(this.hacking_exp < 0) { - this.hacking_exp = 0; - } -} - -PlayerObject.prototype.gainStrengthExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into Player.gainStrengthExp()"); return; - } - this.strength_exp += exp; - if(this.strength_exp < 0) { - this.strength_exp = 0; - } -} - -PlayerObject.prototype.gainDefenseExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into player.gainDefenseExp()"); return; - } - this.defense_exp += exp; - if(this.defense_exp < 0) { - this.defense_exp = 0; - } -} - -PlayerObject.prototype.gainDexterityExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into Player.gainDexterityExp()"); return; - } - this.dexterity_exp += exp; - if(this.dexterity_exp < 0) { - this.dexterity_exp = 0; - } -} - -PlayerObject.prototype.gainAgilityExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into Player.gainAgilityExp()"); return; - } - this.agility_exp += exp; - if(this.agility_exp < 0) { - this.agility_exp = 0; - } -} - -PlayerObject.prototype.gainCharismaExp = function(exp) { - if (isNaN(exp)) { - console.log("ERR: NaN passed into Player.gainCharismaExp()"); return; - } - this.charisma_exp += exp; - if(this.charisma_exp < 0) { - this.charisma_exp = 0; - } -} - -PlayerObject.prototype.gainIntelligenceExp = function(exp) { - if (isNaN(exp)) { - console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return; - } - if (hasAISF || this.intelligence > 0) { - this.intelligence_exp += exp; - } -} - -//Given a string expression like "str" or "strength", returns the given stat -PlayerObject.prototype.queryStatFromString = function(str) { - const tempStr = str.toLowerCase(); - if (tempStr.includes("hack")) { return this.hacking_skill; } - if (tempStr.includes("str")) { return this.strength; } - if (tempStr.includes("def")) { return this.defense; } - if (tempStr.includes("dex")) { return this.dexterity; } - if (tempStr.includes("agi")) { return this.agility; } - if (tempStr.includes("cha")) { return this.charisma; } - if (tempStr.includes("int")) { return this.intelligence; } -} - -/******* Working functions *******/ -PlayerObject.prototype.resetWorkStatus = function() { - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - this.workMoneyLossRate = 0; - - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; - - this.timeWorked = 0; - this.timeWorkedCreateProgram = 0; - - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - this.createProgramName = ""; - this.className = ""; - - document.getElementById("work-in-progress-text").innerHTML = ""; -} - -PlayerObject.prototype.processWorkEarnings = function(numCycles=1) { - 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); - this.gainDefenseExp(defExpGain); - this.gainDexterityExp(dexExpGain); - this.gainAgilityExp(agiExpGain); - this.gainCharismaExp(chaExpGain); - this.gainMoney(moneyGain); - this.recordMoneySource(moneyGain, "work"); - this.workHackExpGained += hackExpGain; - this.workStrExpGained += strExpGain; - this.workDefExpGained += defExpGain; - this.workDexExpGained += dexExpGain; - this.workAgiExpGained += agiExpGain; - this.workChaExpGained += chaExpGain; - this.workRepGained += this.workRepGainRate * numCycles; - this.workMoneyGained += this.workMoneyGainRate * numCycles; - this.workMoneyGained -= this.workMoneyLossRate * numCycles; -} - -/* Working for Company */ -PlayerObject.prototype.startWork = function(companyName) { - this.resetWorkStatus(); - this.isWorking = true; - this.companyName = companyName; - this.workType = CONSTANTS.WorkTypeCompany; - - this.workHackExpGainRate = this.getWorkHackExpGain(); - this.workStrExpGainRate = this.getWorkStrExpGain(); - this.workDefExpGainRate = this.getWorkDefExpGain(); - this.workDexExpGainRate = this.getWorkDexExpGain(); - this.workAgiExpGainRate = this.getWorkAgiExpGain(); - this.workChaExpGainRate = this.getWorkChaExpGain(); - this.workRepGainRate = this.getWorkRepGain(); - this.workMoneyGainRate = this.getWorkMoneyGain(); - - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; - - //Remove all old event listeners from Cancel button - var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); - newCancelButton.innerHTML = "Cancel Work"; - newCancelButton.addEventListener("click", function() { - this.finishWork(true); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.work = function(numCycles) { - //Cap the number of cycles being processed to whatever would put you at - //the work time limit (8 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; - - this.workRepGainRate = this.getWorkRepGain(); - this.processWorkEarnings(numCycles); - - //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - return this.finishWork(false); - } - - var comp = Companies[this.companyName], companyRep = "0"; - if (comp == null || !(comp instanceof Company)) { - console.error(`Could not find Company: ${this.companyName}`); - } else { - companyRep = comp.playerReputation; - } - - const position = this.jobs[this.companyName]; - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are currently working as a " + position + - " at " + this.companyName + " (Current Company Reputation: " + - numeralWrapper.format(companyRep, '0,0') + ")

    " + - "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "You have earned:

    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this company

    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + - "You will automatically finish after working for 8 hours. You can cancel earlier if you wish, " + - "but you will only gain half of the reputation you've earned so far." -} - -PlayerObject.prototype.finishWork = function(cancelled, sing=false) { - //Since the work was cancelled early, player only gains half of what they've earned so far - if (cancelled) { - this.workRepGained /= 2; - } - - var company = Companies[this.companyName]; - company.playerReputation += (this.workRepGained); - - this.updateSkillLevels(); - - var txt = "You earned a total of:
    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the company
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; - - if (cancelled) { - txt = "You worked a short shift of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "Since you cancelled your work early, you only gained half of the reputation you earned.

    " + txt; - } else { - txt = "You worked a full shift of 8 hours!

    " + txt; - } - if (!sing) {dialogBoxCreate(txt);} - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - Engine.loadLocationContent(); - - if (sing) { - var res = "You worked a short shift of " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + - "earned $" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + ", " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation, " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp."; - this.resetWorkStatus(); - return res; - } - this.resetWorkStatus(); -} - -PlayerObject.prototype.startWorkPartTime = function(companyName) { - this.resetWorkStatus(); - this.isWorking = true; - this.companyName = companyName; - this.workType = CONSTANTS.WorkTypeCompanyPartTime; - - this.workHackExpGainRate = this.getWorkHackExpGain(); - this.workStrExpGainRate = this.getWorkStrExpGain(); - this.workDefExpGainRate = this.getWorkDefExpGain(); - this.workDexExpGainRate = this.getWorkDexExpGain(); - this.workAgiExpGainRate = this.getWorkAgiExpGain(); - this.workChaExpGainRate = this.getWorkChaExpGain(); - this.workRepGainRate = this.getWorkRepGain(); - this.workMoneyGainRate = this.getWorkMoneyGain(); - - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; - - var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); - newCancelButton.innerHTML = "Stop Working"; - newCancelButton.addEventListener("click", function() { - this.finishWorkPartTime(); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.workPartTime = function(numCycles) { - //Cap the number of cycles being processed to whatever would put you at the - //work time limit (8 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; - - this.workRepGainRate = this.getWorkRepGain(); - this.processWorkEarnings(numCycles); - - //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - return this.finishWorkPartTime(); - } - - var comp = Companies[this.companyName], companyRep = "0"; - if (comp == null || !(comp instanceof Company)) { - console.log("ERROR: Could not find Company: " + this.companyName); - } else { - companyRep = comp.playerReputation; - } - - const position = this.jobs[this.companyName]; - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are currently working as a " + position + - " at " + this.companyName + " (Current Company Reputation: " + - numeralWrapper.format(companyRep, '0,0') + ")

    " + - "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "You have earned:

    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this company

    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + - "You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
    " + - "and there will be no penalty because this is a part-time job."; - -} - -PlayerObject.prototype.finishWorkPartTime = function(sing=false) { - var company = Companies[this.companyName]; - company.playerReputation += (this.workRepGained); - - this.updateSkillLevels(); - - var txt = "You earned a total of:
    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the company
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; - txt = "You worked for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + txt; - if (!sing) {dialogBoxCreate(txt);} - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - Engine.loadLocationContent(); - if (sing) { - var res = "You worked for " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + - "earned a total of " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + ", " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation, " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp"; - this.resetWorkStatus(); - return res; - } - this.resetWorkStatus(); -} - -/* Working for Faction */ -PlayerObject.prototype.startFactionWork = function(faction) { - //Update reputation gain rate to account for faction favor - var favorMult = 1 + (faction.favor / 100); - if (isNaN(favorMult)) {favorMult = 1;} - this.workRepGainRate *= favorMult; - this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; - - this.isWorking = true; - this.workType = CONSTANTS.WorkTypeFaction; - this.currentWorkFactionName = faction.name; - - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours; - - var cancelButton = clearEventListeners("work-in-progress-cancel-button"); - cancelButton.innerHTML = "Stop Faction Work"; - cancelButton.addEventListener("click", function() { - this.finishFactionWork(true); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.startFactionHackWork = function(faction) { - this.resetWorkStatus(); - - this.workHackExpGainRate = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult; - - this.factionWorkType = CONSTANTS.FactionWorkHacking; - this.currentWorkFactionDescription = "carrying out hacking contracts"; - - this.startFactionWork(faction); -} - -PlayerObject.prototype.startFactionFieldWork = function(faction) { - this.resetWorkStatus(); - - this.workHackExpGainRate = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workStrExpGainRate = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDefExpGainRate = .1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = this.getFactionFieldWorkRepGain(); - - this.factionWorkType = CONSTANTS.FactionWorkField; - this.currentWorkFactionDescription = "carrying out field missions" - - this.startFactionWork(faction); -} - -PlayerObject.prototype.startFactionSecurityWork = function(faction) { - this.resetWorkStatus(); - - this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workStrExpGainRate = 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDefExpGainRate = 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = this.getFactionSecurityWorkRepGain(); - - this.factionWorkType = CONSTANTS.FactionWorkSecurity; - this.currentWorkFactionDescription = "performing security detail" - - this.startFactionWork(faction); -} - -PlayerObject.prototype.workForFaction = function(numCycles) { - var faction = Factions[this.currentWorkFactionName]; - - //Constantly update the rep gain rate - switch (this.factionWorkType) { - case CONSTANTS.FactionWorkHacking: - this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult; - break; - case CONSTANTS.FactionWorkField: - this.workRepGainRate = this.getFactionFieldWorkRepGain(); - break; - case CONSTANTS.FactionWorkSecurity: - this.workRepGainRate = this.getFactionSecurityWorkRepGain(); - break; - default: - break; - } - - //Update reputation gain rate to account for faction favor - var favorMult = 1 + (faction.favor / 100); - if (isNaN(favorMult)) {favorMult = 1;} - this.workRepGainRate *= favorMult; - this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; - - //Cap the number of cycles being processed to whatever would put you at limit (20 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; - - this.processWorkEarnings(numCycles); - - //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { - return this.finishFactionWork(false); - } - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are currently " + this.currentWorkFactionDescription + " for your faction " + faction.name + - " (Current Faction Reputation: " + numeralWrapper.format(faction.playerReputation, '0,0') + ").
    " + - "You have been doing this for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "You have earned:

    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " (" + numeralWrapper.format(this.workMoneyGainRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " (" + numeralWrapper.format(this.workRepGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) reputation for this faction

    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp

    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp

    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp

    " + - - "You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
    " + - "There is no penalty for cancelling earlier."; -} - -PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) { - var faction = Factions[this.currentWorkFactionName]; - faction.playerReputation += (this.workRepGained); - - this.updateSkillLevels(); - - var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "You earned a total of:
    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " reputation for the faction
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; - if (!sing) {dialogBoxCreate(txt);} - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - - this.isWorking = false; - - Engine.loadFactionContent(); - displayFactionContent(faction.name); - if (sing) { - var res="You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + ". " + - "You earned " + - numeralWrapper.format(this.workRepGained, '0,0.0000') + " rep, " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, and " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."; - this.resetWorkStatus(); - return res; - } - this.resetWorkStatus(); -} - -//Money gained per game cycle -PlayerObject.prototype.getWorkMoneyGain = function() { - // 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 companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (companyPosition == null) { - console.error(`Could not find CompanyPosition object for ${companyPositionName}. Work salary will be 0`); - return 0; - } - - return companyPosition.baseSalary * company.salaryMultiplier * this.work_money_mult * BitNodeMultipliers.CompanyWorkMoney * bn11Mult; -} - -//Hack exp gained per game cycle -PlayerObject.prototype.getWorkHackExpGain = function() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `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); - - //Intelligence provides a flat bonus to job performance - jobPerformance += (this.intelligence / CONSTANTS.MaxSkillLevel); - - //Update reputation gain rate to account for company favor - var favorMult = 1 + (company.favor / 100); - if (isNaN(favorMult)) { favorMult = 1; } - return jobPerformance * this.company_rep_mult * favorMult; -} - -PlayerObject.prototype.getFactionSecurityWorkRepGain = function() { - var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + - this.strength / CONSTANTS.MaxSkillLevel + - this.defense / CONSTANTS.MaxSkillLevel + - this.dexterity / CONSTANTS.MaxSkillLevel + - this.agility / CONSTANTS.MaxSkillLevel) / 4.5; - return t * this.faction_rep_mult; -} - -PlayerObject.prototype.getFactionFieldWorkRepGain = function() { - var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + - this.strength / CONSTANTS.MaxSkillLevel + - this.defense / CONSTANTS.MaxSkillLevel + - this.dexterity / CONSTANTS.MaxSkillLevel + - this.agility / CONSTANTS.MaxSkillLevel + - this.charisma / CONSTANTS.MaxSkillLevel + - this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5; - return t * this.faction_rep_mult; -} - -/* Creating a Program */ -PlayerObject.prototype.startCreateProgramWork = function(programName, time, reqLevel) { - this.resetWorkStatus(); - this.isWorking = true; - this.workType = CONSTANTS.WorkTypeCreateProgram; - - //Time needed to complete work affected by hacking skill (linearly based on - //ratio of (your skill - required level) to MAX skill) - //var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking_skill - reqLevel)) / CONSTANTS.MaxSkillLevel; - //if (timeMultiplier > 1) {timeMultiplier = 1;} - //if (timeMultiplier < 0.01) {timeMultiplier = 0.01;} - this.createProgramReqLvl = reqLevel; - - this.timeNeededToCompleteWork = time; - //Check for incomplete program - for (var i = 0; i < this.getHomeComputer().programs.length; ++i) { - var programFile = this.getHomeComputer().programs[i]; - if (programFile.startsWith(programName) && programFile.endsWith("%-INC")) { - var res = programFile.split("-"); - if (res.length != 3) {break;} - var percComplete = Number(res[1].slice(0, -1)); - if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {break;} - this.timeWorkedCreateProgram = percComplete / 100 * this.timeNeededToCompleteWork; - this.getHomeComputer().programs.splice(i, 1); - } - } - - this.createProgramName = programName; - - var cancelButton = clearEventListeners("work-in-progress-cancel-button"); - cancelButton.innerHTML = "Cancel work on creating program"; - cancelButton.addEventListener("click", function() { - this.finishCreateProgramWork(true); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.createProgramWork = function(numCycles) { - //Higher hacking skill will allow you to create programs faster - var reqLvl = this.createProgramReqLvl; - var skillMult = (this.hacking_skill / reqLvl); //This should always be greater than 1; - skillMult = 1 + ((skillMult - 1) / 5); //The divider constant can be adjusted as necessary - - //Skill multiplier directly applied to "time worked" - this.timeWorked += (Engine._idleSpeed * numCycles); - this.timeWorkedCreateProgram += (Engine._idleSpeed * numCycles * skillMult); - var programName = this.createProgramName; - - if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) { - this.finishCreateProgramWork(false); - } - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are currently working on coding " + programName + ".

    " + - "You have been working for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "The program is " + (this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 100).toFixed(2) + "% complete.
    " + - "If you cancel, your work will be saved and you can come back to complete the program later."; -} - -PlayerObject.prototype.finishCreateProgramWork = function(cancelled, sing=false) { - var programName = this.createProgramName; - if (cancelled === false) { - dialogBoxCreate("You've finished creating " + programName + "!
    " + - "The new program can be found on your home computer."); - - this.getHomeComputer().programs.push(programName); - } else { - var perc = (Math.floor(this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 10000)/100).toString(); - var incompleteName = programName + "-" + perc + "%-INC"; - this.getHomeComputer().programs.push(incompleteName); - } - - if (!cancelled) { - this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain); - } - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - - this.isWorking = false; - - Engine.loadTerminalContent(); - this.resetWorkStatus(); -} - -/* Studying/Taking Classes */ -PlayerObject.prototype.startClass = function(costMult, expMult, className) { - this.resetWorkStatus(); - this.isWorking = true; - this.workType = CONSTANTS.WorkTypeStudyClass; - - this.className = className; - - var gameCPS = 1000 / Engine._idleSpeed; - - //Base exp gains per second - var baseStudyComputerScienceExp = 0.5; - var baseDataStructuresExp = 1; - var baseNetworksExp = 2; - var baseAlgorithmsExp = 4; - var baseManagementExp = 2; - var baseLeadershipExp = 4; - var baseGymExp = 1; - - //Find cost and exp gain per game cycle - var cost = 0; - var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0; - const hashManager = this.hashManager; - switch (className) { - case CONSTANTS.ClassStudyComputerScience: - hackExp = baseStudyComputerScienceExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassDataStructures: - cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS; - hackExp = baseDataStructuresExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassNetworks: - cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS; - hackExp = baseNetworksExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassAlgorithms: - cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS; - hackExp = baseAlgorithmsExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassManagement: - cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS; - chaExp = baseManagementExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassLeadership: - cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS; - chaExp = baseLeadershipExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassGymStrength: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - strExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymDefense: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - defExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymDexterity: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - dexExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymAgility: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - agiExp = baseGymExp * expMult / gameCPS * hashManager.getTrainingMult(); - break; - default: - throw new Error("ERR: Invalid/unrecognized class name"); - return; - } - - this.workMoneyLossRate = cost; - this.workHackExpGainRate = hackExp * this.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workStrExpGainRate = strExp * this.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain;; - this.workDefExpGainRate = defExp * this.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain;; - this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain;; - this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain;; - this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain;; - - var cancelButton = clearEventListeners("work-in-progress-cancel-button"); - if (className == CONSTANTS.ClassGymStrength || - className == CONSTANTS.ClassGymDefense || - className == CONSTANTS.ClassGymDexterity || - className == CONSTANTS.ClassGymAgility) { - cancelButton.innerHTML = "Stop training at gym"; - } else { - cancelButton.innerHTML = "Stop taking course"; - } - cancelButton.addEventListener("click", function() { - this.finishClass(); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.takeClass = function(numCycles) { - this.timeWorked += Engine._idleSpeed * numCycles; - var className = this.className; - - this.processWorkEarnings(numCycles); - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You have been " + className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + "

    " + - "This has cost you:
    " + - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + " ($" + numeralWrapper.format(this.workMoneyLossRate * CYCLES_PER_SEC, '0,0.00') + " / sec)

    " + - "You have gained:
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workHackExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) hacking exp
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workStrExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDefExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workDexExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workAgiExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) agility exp
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " (" + numeralWrapper.format(this.workChaExpGainRate * CYCLES_PER_SEC, '0,0.0000') + " / sec) charisma exp
    " + - "You may cancel at any time"; -} - -//The 'sing' argument defines whether or not this function was called -//through a Singularity Netscript function -PlayerObject.prototype.finishClass = function(sing=false) { - this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000)); - - if (this.workMoneyGained > 0) { - throw new Error("ERR: Somehow gained money while taking class"); - } - - this.updateSkillLevels(); - var txt = "After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ",
    " + - "you spent a total of $" + numeralWrapper.format(this.workMoneyGained * -1, '0,0.00') + ".

    " + - "You earned a total of:
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp
    "; - if (!sing) {dialogBoxCreate(txt);} - - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - - this.isWorking = false; - - Engine.loadLocationContent(); - if (sing) { - var res="After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ", " + - "you spent a total of $" + numeralWrapper.format(this.workMoneyGained * -1, '0,0.00') + ". " + - "You earned a total of: " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility exp, and " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma exp"; - this.resetWorkStatus(); - return res; - } - this.resetWorkStatus(); -} - -//The EXP and $ gains are hardcoded. Time is in ms -PlayerObject.prototype.startCrime = function(crimeType, hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams=null) { - this.crimeType = crimeType; - - this.resetWorkStatus(); - this.isWorking = true; - this.workType = CONSTANTS.WorkTypeCrime; - - if (singParams && singParams.workerscript) { - this.committingCrimeThruSingFn = true; - this.singFnCrimeWorkerScript = singParams.workerscript; - } - - this.workHackExpGained = hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workStrExpGained = strExp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workDefExpGained = defExp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workDexExpGained = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workAgiExpGained = agiExp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workChaExpGained = chaExp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; - - this.timeNeededToCompleteWork = time; - - //Remove all old event listeners from Cancel button - var newCancelButton = clearEventListeners("work-in-progress-cancel-button") - newCancelButton.innerHTML = "Cancel crime" - newCancelButton.addEventListener("click", function() { - this.finishCrime(true); - return false; - }); - - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); -} - -PlayerObject.prototype.commitCrime = function (numCycles) { - this.timeWorked += Engine._idleSpeed * numCycles; - - if (this.timeWorked >= this.timeNeededToCompleteWork) {this.finishCrime(false); return;} - - var percent = Math.round(this.timeWorked / this.timeNeededToCompleteWork * 100); - var numBars = Math.round(percent / 5); - if (numBars < 0) {numBars = 0;} - if (numBars > 20) {numBars = 20;} - var progressBar = "[" + Array(numBars+1).join("|") + Array(20 - numBars + 1).join(" ") + "]"; - - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are attempting to " + this.crimeType + ".
    " + - "Time remaining: " + convertTimeMsToTimeElapsedString(this.timeNeededToCompleteWork - this.timeWorked) + "
    " + - progressBar.replace( / /g, " " ); -} - -PlayerObject.prototype.finishCrime = function(cancelled) { - //Determine crime success/failure - if (!cancelled) { - var statusText = ""; //TODO, unique message for each crime when you succeed - if (determineCrimeSuccess(Player, this.crimeType)) { - //Handle Karma and crime statistics - let crime = null; - for(const i in Crimes) { - if(Crimes[i].type == this.crimeType) { - crime = Crimes[i]; - break; - } - } - if(crime == null) { - console.log(this.crimeType); - dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer"); - } - this.gainMoney(this.workMoneyGained); - this.recordMoneySource(this.workMoneyGained, "crime"); - this.karma -= crime.karma; - this.numPeopleKilled += crime.kills; - if(crime.intelligence_exp > 0) { - this.gainIntelligenceExp(crime.intelligence_exp); - } - - //On a crime success, gain 2x exp - this.workHackExpGained *= 2; - this.workStrExpGained *= 2; - this.workDefExpGained *= 2; - this.workDexExpGained *= 2; - this.workAgiExpGained *= 2; - this.workChaExpGained *= 2; - if (this.committingCrimeThruSingFn) { - if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { - this.singFnCrimeWorkerScript.scriptRef.log("Crime successful! Gained " + - numeralWrapper.format(this.workMoneyGained, "$0.000a") + ", " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hack exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."); - } - } else { - dialogBoxCreate("Crime successful!

    " + - "You gained:
    "+ - "$" + numeralWrapper.format(this.workMoneyGained, '0,0.00') + "
    " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking experience
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength experience
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense experience
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity experience
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility experience
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma experience"); - } - - } else { - //Exp halved on failure - this.workHackExpGained /= 2; - this.workStrExpGained /= 2; - this.workDefExpGained /= 2; - this.workDexExpGained /= 2; - this.workAgiExpGained /= 2; - this.workChaExpGained /= 2; - if (this.committingCrimeThruSingFn) { - if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { - this.singFnCrimeWorkerScript.scriptRef.log("Crime failed! Gained " + - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hack exp, " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " str exp, " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " def exp, " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dex exp, " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agi exp, " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " cha exp."); - } - } else { - dialogBoxCreate("Crime failed!

    " + - "You gained:
    "+ - numeralWrapper.format(this.workHackExpGained, '0,0.0000') + " hacking experience
    " + - numeralWrapper.format(this.workStrExpGained, '0,0.0000') + " strength experience
    " + - numeralWrapper.format(this.workDefExpGained, '0,0.0000') + " defense experience
    " + - numeralWrapper.format(this.workDexExpGained, '0,0.0000') + " dexterity experience
    " + - numeralWrapper.format(this.workAgiExpGained, '0,0.0000') + " agility experience
    " + - numeralWrapper.format(this.workChaExpGained, '0,0.0000') + " charisma experience"); - } - } - - this.gainHackingExp(this.workHackExpGained); - this.gainStrengthExp(this.workStrExpGained); - this.gainDefenseExp(this.workDefExpGained); - this.gainDexterityExp(this.workDexExpGained); - this.gainAgilityExp(this.workAgiExpGained); - this.gainCharismaExp(this.workChaExpGained); - } - this.committingCrimeThruSingFn = false; - this.singFnCrimeWorkerScript = null; - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - this.resetWorkStatus(); - Engine.loadLocationContent(); -} - -//Cancels the player's current "work" assignment and gives the proper rewards -//Used only for Singularity functions, so no popups are created -PlayerObject.prototype.singularityStopWork = function() { - if (!this.isWorking) {return "";} - var res; //Earnings text for work - switch (this.workType) { - case CONSTANTS.WorkTypeStudyClass: - res = this.finishClass(true); - break; - case CONSTANTS.WorkTypeCompany: - res = this.finishWork(true, true); - break; - case CONSTANTS.WorkTypeCompanyPartTime: - res = this.finishWorkPartTime(true); - break; - case CONSTANTS.WorkTypeFaction: - res = this.finishFactionWork(true, true); - break; - case CONSTANTS.WorkTypeCreateProgram: - res = this.finishCreateProgramWork(true, true); - break; - case CONSTANTS.WorkTypeCrime: - res = this.finishCrime(true); - break; - default: - console.log("ERROR: Unrecognized work type"); - return ""; - } - return res; -} - - -// Returns true if hospitalized, false otherwise -PlayerObject.prototype.takeDamage = function(amt) { - if (typeof amt !== "number") { - console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`); - return; - } - - this.hp -= amt; - if (this.hp <= 0) { - this.hospitalize(); - return true; - } else { - return false; - } -} - -PlayerObject.prototype.regenerateHp = function(amt) { - if (typeof amt !== "number") { - console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`); - return; - } - this.hp += amt; - if (this.hp > this.max_hp) { this.hp = this.max_hp; } -} - -PlayerObject.prototype.hospitalize = function() { - if (Settings.SuppressHospitalizationPopup === false) { - dialogBoxCreate( - "You were in critical condition! You were taken to the hospital where " + - "luckily they were able to save your life. You were charged " + - numeralWrapper.format(this.max_hp * CONSTANTS.HospitalCostPerHp, '$0.000a') - ); - } - - this.loseMoney(this.max_hp * CONSTANTS.HospitalCostPerHp); - this.hp = this.max_hp; -} - -/********* Company job application **********/ -//Determines the job that the Player should get (if any) at the current company -//The 'sing' argument designates whether or not this is being called from -//the applyToCompany() Netscript Singularity function -PlayerObject.prototype.applyForJob = function(entryPosType, sing=false) { - // Get current company and job - let currCompany = null; - if (this.companyName !== "") { - currCompany = Companies[this.companyName]; - } - const currPositionName = this.jobs[this.companyName]; - - // 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; - } - } - - let pos = entryPosType; - - if (!this.isQualified(company, pos)) { - var reqText = getJobRequirementText(company, pos); - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position
    " + reqText); - return; - } - - while (true) { - let newPos = getNextCompanyPosition(pos); - if (newPos == null) {break;} - - //Check if this company has this position - if (company.hasPosition(newPos)) { - if (!this.isQualified(company, newPos)) { - //If player not qualified for next job, break loop so player will be given current job - break; - } - pos = newPos; - } else { - break; - } - } - - //Check if the determined job is the same as the player's current job - if (currCompany != null) { - if (currCompany.name == company.name && pos.name == currPositionName) { - var nextPos = getNextCompanyPosition(pos); - if (nextPos == null) { - if (sing) {return false;} - dialogBoxCreate("You are already at the highest position for your field! No promotion available"); - } else if (company.hasPosition(nextPos)) { - if (sing) {return false;} - var reqText = getJobRequirementText(company, nextPos); - dialogBoxCreate("Unfortunately, you do not qualify for a promotion
    " + reqText); - } else { - if (sing) {return false;} - dialogBoxCreate("You are already at the highest position for your field! No promotion available"); - } - return; //Same job, do nothing - } - } - - this.companyName = company.name; - this.jobs[company.name] = pos.name; - - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - - if (sing) { return true; } - dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!"); - - Engine.loadLocationContent(); -} - -//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 !== "") { - currCompany = Companies[this.companyName]; - } - - //Not employed at this company, so return the entry position - 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. - const currentPositionName = this.jobs[this.companyName]; - const currentPosition = CompanyPositions[currentPositionName]; - 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[posNames.SoftwareCompanyPositions[0]], sing); -} - -PlayerObject.prototype.applyForSoftwareConsultantJob = function(sing=false) { - return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing); -} - -PlayerObject.prototype.applyForItJob = function(sing=false) { - 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[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"); - } -} - -PlayerObject.prototype.applyForNetworkEngineerJob = function(sing=false) { - var company = Companies[this.location]; //Company being applied to - 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"); - } -} - -PlayerObject.prototype.applyForBusinessJob = function(sing=false) { - return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing); -} - -PlayerObject.prototype.applyForBusinessConsultantJob = function(sing=false) { - return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing); -} - -PlayerObject.prototype.applyForSecurityJob = function(sing=false) { - // 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[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"); - } -} - -PlayerObject.prototype.applyForEmployeeJob = function(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.MiscCompanyPositions[1]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed at " + this.companyName); - Engine.loadLocationContent(); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); - } -} - -PlayerObject.prototype.applyForPartTimeEmployeeJob = function(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.PartTimeCompanyPositions[1]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName); - Engine.loadLocationContent(); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); - } -} - -PlayerObject.prototype.applyForWaiterJob = function(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.MiscCompanyPositions[0]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName); - Engine.loadLocationContent(); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); - } -} - -PlayerObject.prototype.applyForPartTimeWaiterJob = function(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.PartTimeCompanyPositions[0]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName); - Engine.loadLocationContent(); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); - } -} - -//Checks if the Player is qualified for a certain position -PlayerObject.prototype.isQualified = function(company, position) { - var offset = company.jobStatReqOffset; - var reqHacking = position.requiredHacking > 0 ? position.requiredHacking+offset : 0; - var reqStrength = position.requiredStrength > 0 ? position.requiredStrength+offset : 0; - var reqDefense = position.requiredDefense > 0 ? position.requiredDefense+offset : 0; - var reqDexterity = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; - var reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; - var reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma+offset : 0; - - if (this.hacking_skill >= reqHacking && - this.strength >= reqStrength && - this.defense >= reqDefense && - this.dexterity >= reqDexterity && - this.agility >= reqAgility && - this.charisma >= reqCharisma && - company.playerReputation >= position.requiredReputation) { - return true; - } - return false; -} - -/********** Reapplying Augmentations and Source File ***********/ -PlayerObject.prototype.reapplyAllAugmentations = function(resetMultipliers=true) { - console.log("Re-applying augmentations"); - if (resetMultipliers) { - this.resetMultipliers(); - } - - for (let i = 0; i < this.augmentations.length; ++i) { - //Compatibility with new version - if (this.augmentations[i].name === "HacknetNode NIC Architecture Neural-Upload") { - this.augmentations[i].name = "Hacknet Node NIC Architecture Neural-Upload"; - } - - const augName = this.augmentations[i].name; - var aug = Augmentations[augName]; - if (aug == null) { - console.log(`WARNING: Invalid augmentation name in Player.reapplyAllAugmentations(). Aug ${augName} will be skipped`); - continue; - } - aug.owned = true; - if (aug.name == AugmentationNames.NeuroFluxGovernor) { - for (let j = 0; j < aug.level; ++j) { - applyAugmentation(this.augmentations[i], true); - } - continue; - } - applyAugmentation(this.augmentations[i], true); - } -} - -PlayerObject.prototype.reapplyAllSourceFiles = function() { - console.log("Re-applying source files"); - //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset - //this.resetMultipliers(); - - for (let i = 0; i < this.sourceFiles.length; ++i) { - var srcFileKey = "SourceFile" + this.sourceFiles[i].n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.log("ERROR: Invalid source file number: " + this.sourceFiles[i].n); - continue; - } - applySourceFile(this.sourceFiles[i]); - } -} - -/*************** Check for Faction Invitations *************/ -//This function sets the requirements to join a Faction. It checks whether the Player meets -//those requirements and will return an array of all factions that the Player should -//receive an invitation to -PlayerObject.prototype.checkForFactionInvitations = function() { - let invitedFactions = []; //Array which will hold all Factions the player should be invited to - - var numAugmentations = this.augmentations.length; - - const allCompanies = Object.keys(this.jobs); - const allPositions = Object.values(this.jobs); - - // Given a company name, safely returns the reputation (returns 0 if invalid company is specified) - function getCompanyRep(companyName) { - const company = Companies[companyName]; - if (company == null) { - return 0; - } else { - return company.playerReputation; - } - } - - // Helper function that returns a boolean indicating whether the Player meets - // the requirements for the specified company. There are two requirements: - // 1. High enough reputation - // 2. Player is employed at the company - function checkMegacorpRequirements(companyName, repNeeded=CONSTANTS.CorpFactionRepRequirement) { - return allCompanies.includes(companyName) && (getCompanyRep(companyName) > repNeeded); - } - - //Illuminati - var illuminatiFac = Factions["Illuminati"]; - if (!illuminatiFac.isBanned && !illuminatiFac.isMember && !illuminatiFac.alreadyInvited && - numAugmentations >= 30 && - this.money.gte(150000000000) && - this.hacking_skill >= 1500 && - this.strength >= 1200 && this.defense >= 1200 && - this.dexterity >= 1200 && this.agility >= 1200) { - invitedFactions.push(illuminatiFac); - } - - //Daedalus - var daedalusFac = Factions["Daedalus"]; - if (!daedalusFac.isBanned && !daedalusFac.isMember && !daedalusFac.alreadyInvited && - numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) && - this.money.gte(100000000000) && - (this.hacking_skill >= 2500 || - (this.strength >= 1500 && this.defense >= 1500 && - this.dexterity >= 1500 && this.agility >= 1500))) { - invitedFactions.push(daedalusFac); - } - - //The Covenant - var covenantFac = Factions["The Covenant"]; - if (!covenantFac.isBanned && !covenantFac.isMember && !covenantFac.alreadyInvited && - numAugmentations >= 20 && - this.money.gte(75000000000) && - this.hacking_skill >= 850 && - this.strength >= 850 && - this.defense >= 850 && - this.dexterity >= 850 && - this.agility >= 850) { - invitedFactions.push(covenantFac); - } - - //ECorp - var ecorpFac = Factions["ECorp"]; - if (!ecorpFac.isBanned && !ecorpFac.isMember && !ecorpFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumECorp)) { - invitedFactions.push(ecorpFac); - } - - //MegaCorp - var megacorpFac = Factions["MegaCorp"]; - if (!megacorpFac.isBanned && !megacorpFac.isMember && !megacorpFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12MegaCorp)) { - invitedFactions.push(megacorpFac); - } - - //Bachman & Associates - var bachmanandassociatesFac = Factions["Bachman & Associates"]; - if (!bachmanandassociatesFac.isBanned && !bachmanandassociatesFac.isMember && - !bachmanandassociatesFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumBachmanAndAssociates)) { - invitedFactions.push(bachmanandassociatesFac); - } - - //Blade Industries - var bladeindustriesFac = Factions["Blade Industries"]; - if (!bladeindustriesFac.isBanned && !bladeindustriesFac.isMember && !bladeindustriesFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12BladeIndustries)) { - invitedFactions.push(bladeindustriesFac); - } - - //NWO - var nwoFac = Factions["NWO"]; - if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited && - checkMegacorpRequirements(LocationName.VolhavenNWO)) { - invitedFactions.push(nwoFac); - } - - //Clarke Incorporated - var clarkeincorporatedFac = Factions["Clarke Incorporated"]; - if (!clarkeincorporatedFac.isBanned && !clarkeincorporatedFac.isMember && !clarkeincorporatedFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumClarkeIncorporated)) { - invitedFactions.push(clarkeincorporatedFac); - } - - //OmniTek Incorporated - var omnitekincorporatedFac = Factions["OmniTek Incorporated"]; - if (!omnitekincorporatedFac.isBanned && !omnitekincorporatedFac.isMember && !omnitekincorporatedFac.alreadyInvited && - checkMegacorpRequirements(LocationName.VolhavenOmniTekIncorporated)) { - invitedFactions.push(omnitekincorporatedFac); - } - - //Four Sigma - var foursigmaFac = Factions["Four Sigma"]; - if (!foursigmaFac.isBanned && !foursigmaFac.isMember && !foursigmaFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12FourSigma)) { - invitedFactions.push(foursigmaFac); - } - - //KuaiGong International - var kuaigonginternationalFac = Factions["KuaiGong International"]; - if (!kuaigonginternationalFac.isBanned && !kuaigonginternationalFac.isMember && - !kuaigonginternationalFac.alreadyInvited && - checkMegacorpRequirements(LocationName.ChongqingKuaiGongInternational)) { - invitedFactions.push(kuaigonginternationalFac); - } - - //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company - var fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"]; - var fulcrumSecretServer = AllServers[SpecialServerIps[SpecialServerNames.FulcrumSecretTechnologies]]; - if (fulcrumSecretServer == null) { - console.log("ERROR: Could not find Fulcrum Secret Technologies Server"); - } else { - if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember && - !fulcrumsecrettechonologiesFac.alreadyInvited && - fulcrumSecretServer.manuallyHacked && - checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) { - invitedFactions.push(fulcrumsecrettechonologiesFac); - } - } - - //BitRunners - var bitrunnersFac = Factions["BitRunners"]; - var homeComp = this.getHomeComputer(); - var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; - if (bitrunnersServer == null) { - console.log("ERROR: Could not find BitRunners Server"); - } else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.manuallyHacked && - !bitrunnersFac.alreadyInvited && this.hacking_skill >= 500 && homeComp.maxRam >= 128) { - invitedFactions.push(bitrunnersFac); - } - - //The Black Hand - var theblackhandFac = Factions["The Black Hand"]; - var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; - if (blackhandServer == null) { - console.log("ERROR: Could not find The Black Hand Server"); - } else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.manuallyHacked && - !theblackhandFac.alreadyInvited && this.hacking_skill >= 350 && homeComp.maxRam >= 64) { - invitedFactions.push(theblackhandFac); - } - - //NiteSec - var nitesecFac = Factions["NiteSec"]; - var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; - if (nitesecServer == null) { - console.log("ERROR: Could not find NiteSec Server"); - } else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.manuallyHacked && - !nitesecFac.alreadyInvited && this.hacking_skill >= 200 && homeComp.maxRam >= 32) { - invitedFactions.push(nitesecFac); - } - - //Chongqing - var chongqingFac = Factions["Chongqing"]; - if (!chongqingFac.isBanned && !chongqingFac.isMember && !chongqingFac.alreadyInvited && - this.money.gte(20000000) && this.city == CityName.Chongqing) { - invitedFactions.push(chongqingFac); - } - - //Sector-12 - var sector12Fac = Factions["Sector-12"]; - if (!sector12Fac.isBanned && !sector12Fac.isMember && !sector12Fac.alreadyInvited && - this.money.gte(15000000) && this.city == CityName.Sector12) { - invitedFactions.push(sector12Fac); - } - - //New Tokyo - var newtokyoFac = Factions["New Tokyo"]; - if (!newtokyoFac.isBanned && !newtokyoFac.isMember && !newtokyoFac.alreadyInvited && - this.money.gte(20000000) && this.city == CityName.NewTokyo) { - invitedFactions.push(newtokyoFac); - } - - //Aevum - var aevumFac = Factions["Aevum"]; - if (!aevumFac.isBanned && !aevumFac.isMember && !aevumFac.alreadyInvited && - this.money.gte(40000000) && this.city == CityName.Aevum) { - invitedFactions.push(aevumFac); - } - - //Ishima - var ishimaFac = Factions["Ishima"]; - if (!ishimaFac.isBanned && !ishimaFac.isMember && !ishimaFac.alreadyInvited && - this.money.gte(30000000) && this.city == CityName.Ishima) { - invitedFactions.push(ishimaFac); - } - - //Volhaven - var volhavenFac = Factions["Volhaven"]; - if (!volhavenFac.isBanned && !volhavenFac.isMember && !volhavenFac.alreadyInvited && - this.money.gte(50000000) && this.city == CityName.Volhaven) { - invitedFactions.push(volhavenFac); - } - - //Speakers for the Dead - var speakersforthedeadFac = Factions["Speakers for the Dead"]; - if (!speakersforthedeadFac.isBanned && !speakersforthedeadFac.isMember && !speakersforthedeadFac.alreadyInvited && - this.hacking_skill >= 100 && this.strength >= 300 && this.defense >= 300 && - this.dexterity >= 300 && this.agility >= 300 && this.numPeopleKilled >= 30 && - this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(speakersforthedeadFac); - } - - //The Dark Army - var thedarkarmyFac = Factions["The Dark Army"]; - if (!thedarkarmyFac.isBanned && !thedarkarmyFac.isMember && !thedarkarmyFac.alreadyInvited && - this.hacking_skill >= 300 && this.strength >= 300 && this.defense >= 300 && - this.dexterity >= 300 && this.agility >= 300 && this.city == CityName.Chongqing && - this.numPeopleKilled >= 5 && this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(thedarkarmyFac); - } - - //The Syndicate - var thesyndicateFac = Factions["The Syndicate"]; - if (!thesyndicateFac.isBanned && !thesyndicateFac.isMember && !thesyndicateFac.alreadyInvited && - this.hacking_skill >= 200 && this.strength >= 200 && this.defense >= 200 && - this.dexterity >= 200 && this.agility >= 200 && - (this.city == CityName.Aevum || this.city == CityName.Sector12) && - this.money.gte(10000000) && this.karma <= -90 && - !allCompanies.includes(LocationName.Sector12CIA) && !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(thesyndicateFac); - } - - //Silhouette - var silhouetteFac = Factions["Silhouette"]; - if (!silhouetteFac.isBanned && !silhouetteFac.isMember && !silhouetteFac.alreadyInvited && - (allPositions.includes("Chief Technology Officer") || - allPositions.includes("Chief Financial Officer") || - allPositions.includes("Chief Executive Officer")) && - this.money.gte(15000000) && this.karma <= -22) { - invitedFactions.push(silhouetteFac); - } - - //Tetrads - var tetradsFac = Factions["Tetrads"]; - if (!tetradsFac.isBanned && !tetradsFac.isMember && !tetradsFac.alreadyInvited && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || - this.city == CityName.Ishima) && this.strength >= 75 && this.defense >= 75 && - this.dexterity >= 75 && this.agility >= 75 && this.karma <= -18) { - invitedFactions.push(tetradsFac); - } - - //SlumSnakes - var slumsnakesFac = Factions["Slum Snakes"]; - if (!slumsnakesFac.isBanned && !slumsnakesFac.isMember && !slumsnakesFac.alreadyInvited && - this.strength >= 30 && this.defense >= 30 && this.dexterity >= 30 && - this.agility >= 30 && this.karma <= -9 && this.money.gte(1000000)) { - invitedFactions.push(slumsnakesFac); - } - - //Netburners - var netburnersFac = Factions["Netburners"]; - var totalHacknetRam = 0; - var totalHacknetCores = 0; - var totalHacknetLevels = 0; - for (let i = 0; i < this.hacknetNodes.length; ++i) { - if (hasHacknetServers()) { - const hserver = AllServers[this.hacknetNodes[i]]; - if (hserver) { - totalHacknetLevels += hserver.level; - totalHacknetRam += hserver.maxRam; - totalHacknetCores += hserver.cores; - } - } else { - totalHacknetLevels += this.hacknetNodes[i].level; - totalHacknetRam += this.hacknetNodes[i].ram; - totalHacknetCores += this.hacknetNodes[i].cores; - } - } - if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited && - this.hacking_skill >= 80 && totalHacknetRam >= 8 && - totalHacknetCores >= 4 && totalHacknetLevels >= 100) { - invitedFactions.push(netburnersFac); - } - - //Tian Di Hui - var tiandihuiFac = Factions["Tian Di Hui"]; - if (!tiandihuiFac.isBanned && !tiandihuiFac.isMember && !tiandihuiFac.alreadyInvited && - this.money.gte(1000000) && this.hacking_skill >= 50 && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || - this.city == CityName.Ishima)) { - invitedFactions.push(tiandihuiFac); - } - - //CyberSec - var cybersecFac = Factions["CyberSec"]; - var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; - if (cybersecServer == null) { - console.log("ERROR: Could not find CyberSec Server"); - } else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.manuallyHacked && - !cybersecFac.alreadyInvited && this.hacking_skill >= 50) { - invitedFactions.push(cybersecFac); - } - - return invitedFactions; -} - - -/*************** Gang ****************/ -//Returns true if Player is in a gang and false otherwise -PlayerObject.prototype.inGang = function() { - if (this.gang == null || this.gang == undefined) {return false;} - return (this.gang instanceof Gang); -} - -PlayerObject.prototype.startGang = function(factionName, hacking) { - this.gang = new Gang(factionName, hacking); -} - -/************* BitNodes **************/ -PlayerObject.prototype.setBitNodeNumber = function(n) { - this.bitNodeN = n; -} - -PlayerObject.prototype.queueAugmentation = function(name) { - for(const i in this.queuedAugmentations) { - if(this.queuedAugmentations[i].name == name) { - console.log('tried to queue '+name+' twice, this may be a bug'); - return; - } - } - - for(const i in this.augmentations) { - if(this.augmentations[i].name == name) { - console.log('tried to queue '+name+' but we already have that aug'); - return; - } - } - - this.firstAugPurchased = true; - this.queuedAugmentations.push(new PlayerOwnedAugmentation(name)); -} - -/************* Coding Contracts **************/ -PlayerObject.prototype.gainCodingContractReward = function(reward, difficulty=1) { - if (reward == null || reward.type == null || reward == null) { - return `No reward for this contract`; - } - - /* eslint-disable no-case-declarations */ - switch (reward.type) { - case CodingContractRewardType.FactionReputation: - if (reward.name == null || !(Factions[reward.name] instanceof Faction)) { - // If no/invalid faction was designated, just give rewards to all factions - reward.type = CodingContractRewardType.FactionReputationAll; - return this.gainCodingContractReward(reward); - } - var repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; - Factions[reward.name].playerReputation += repGain; - return `Gained ${repGain} faction reputation for ${reward.name}`; - case CodingContractRewardType.FactionReputationAll: - const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; - - // Ignore Bladeburners and other special factions for this calculation - const specialFactions = ["Bladeburners"]; - var factions = this.factions.slice(); - factions = factions.filter((f) => { - return !specialFactions.includes(f); - }); - - // If the player was only part of the special factions, we'll just give money - if (factions.length == 0) { - reward.type = CodingContractRewardType.Money; - return this.gainCodingContractReward(reward, difficulty); - } - - const gainPerFaction = Math.floor(totalGain / factions.length); - for (const facName of factions) { - if (!(Factions[facName] instanceof Faction)) { continue; } - Factions[facName].playerReputation += gainPerFaction; - } - return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.toString()}`; - break; - case CodingContractRewardType.CompanyReputation: - if (reward.name == null || !(Companies[reward.name] instanceof Company)) { - //If no/invalid company was designated, just give rewards to all factions - reward.type = CodingContractRewardType.FactionReputationAll; - return this.gainCodingContractReward(reward); - } - var repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty; - Companies[reward.name].playerReputation += repGain; - return `Gained ${repGain} company reputation for ${reward.name}`; - break; - case CodingContractRewardType.Money: - default: - var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney; - this.gainMoney(moneyGain); - this.recordMoneySource(moneyGain, "codingcontract"); - return `Gained ${numeralWrapper.format(moneyGain, '$0.000a')}`; - break; - } - /* eslint-enable no-case-declarations */ -} - -PlayerObject.prototype.travel = function(to) { - if (Cities[to] == null) { - console.warn(`Player.travel() called with invalid city: ${to}`); - return false; - } - this.city = to; - - return true; -} - -PlayerObject.prototype.gotoLocation = function(to) { - if (Locations[to] == null) { - console.warn(`Player.gotoLocation() called with invalid location: ${to}`); - return false; - } - this.location = to; - - return true; -} - -PlayerObject.prototype.hasTorRouter = function() { - return SpecialServerIps.hasOwnProperty("Darkweb Server"); -} - -PlayerObject.prototype.getCurrentServer = function() { - return AllServers[this.currentServer]; -} - -PlayerObject.prototype.getHomeComputer = function() { - return AllServers[this.homeComputer]; -} - -PlayerObject.prototype.getUpgradeHomeRamCost = function() { - //Calculate how many times ram has been upgraded (doubled) - const currentRam = this.getHomeComputer().maxRam; - const numUpgrades = Math.log2(currentRam); - - //Calculate cost - //Have cost increase by some percentage each time RAM has been upgraded - const mult = Math.pow(1.58, numUpgrades); - var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * BitNodeMultipliers.HomeComputerRamCost; - return cost; -} - -PlayerObject.prototype.inBladeburner = function() { - if (this.bladeburner == null) { return false; } - return (this.bladeburner instanceof Bladeburner); -} - -PlayerObject.prototype.startBladeburner = function() { - this.bladeburner = new Bladeburner({ new: true }); -} - -PlayerObject.prototype.hasCorporation = function() { - if (this.corporation == null) { return false; } - return (this.corporation instanceof Corporation); -} - -PlayerObject.prototype.startCorporation = function(corpName, additionalShares=0) { - this.corporation = new Corporation({ - name: corpName - }); - - this.corporation.totalShares += additionalShares; -} - -PlayerObject.prototype.toJSON = function() { - return Generic_toJSON("PlayerObject", this); -} - -PlayerObject.fromJSON = function(value) { - return Generic_fromJSON(PlayerObject, value.data); -} - -Reviver.constructors.PlayerObject = PlayerObject; +import Decimal from "decimal.js"; export let Player = new PlayerObject(); diff --git a/src/engine.jsx b/src/engine.jsx index 165375964..d7cd68d36 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -33,6 +33,7 @@ import { hasHacknetServers, processHacknetEarnings } from "./Hacknet/HacknetHelpers"; import {iTutorialStart} from "./InteractiveTutorial"; import {initLiterature} from "./Literature"; +import { LocationName } from "./Locations/data/LocationNames"; import { LocationRoot } from "./Locations/ui/Root"; import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; import {inMission, currMission} from "./Missions"; @@ -343,12 +344,14 @@ const Engine = { MainMenuLinks.DevMenu.classList.add("active"); }, - loadLocationContent: function() { + loadLocationContent: function(initiallyInCity=true) { Engine.hideAllContent(); Engine.Display.locationContent.style.display = "block"; + MainMenuLinks.City.classList.add("active"); routing.navigateTo(Page.Location); const rootComponent = @@ -356,18 +359,42 @@ const Engine = { }, loadTravelContent: function() { + // Same as loadLocationContent() except first set the location to the travel agency, + // and make sure that the 'City' main menu link doesnt become 'active' + Engine.hideAllContent(); Player.gotoLocation(LocationName.TravelAgency); - Engine.loadLocationContent(); + Engine.Display.locationContent.style.display = "block"; + MainMenuLinks.Travel.classList.add("active"); + + routing.navigateTo(Page.Location); + const rootComponent = + ReactDOM.render(rootComponent, Engine.Display.locationContent); }, loadJobContent: function() { + // Same as loadLocationContent(), except first set the location to the job. + // Make sure that the 'City' main menu link doesnt become 'active' if (Player.companyName == "") { dialogBoxCreate("You do not currently have a job! You can visit various companies " + "in the city and try to find a job."); return; } - Player.location = Player.companyName; - Engine.loadLocationContent(); + Engine.hideAllContent(); + Player.gotoLocation(Player.companyName); + Engine.Display.locationContent.style.display = "block"; + MainMenuLinks.Job.classList.add("active"); + + routing.navigateTo(Page.Location); + const rootComponent = + ReactDOM.render(rootComponent, Engine.Display.locationContent); }, loadWorkInProgressContent: function() { @@ -469,7 +496,7 @@ const Engine = { } }, - //Helper function that hides all content + // Helper function that hides all content hideAllContent: function() { Engine.Display.terminalContent.style.display = "none"; Engine.Display.characterContent.style.display = "none"; @@ -483,6 +510,7 @@ const Engine = { Engine.Display.augmentationsContent.style.display = "none"; Engine.Display.tutorialContent.style.display = "none"; Engine.Display.locationContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.locationContent); Engine.Display.workInProgressContent.style.display = "none"; Engine.Display.redPillContent.style.display = "none"; Engine.Display.cinematicTextContent.style.display = "none"; @@ -507,7 +535,15 @@ const Engine = { clearResleevesPage(); clearSleevesPage(); - //Make nav menu tabs inactive + // Make nav menu tabs inactive + Engine.inactivateMainMenuLinks(); + + // Close dev menu + closeDevMenu(); + }, + + // Remove 'active' css class from all main menu links + inactivateMainMenuLinks: function() { MainMenuLinks.Terminal.classList.remove("active"); MainMenuLinks.ScriptEditor.classList.remove("active"); MainMenuLinks.ActiveScripts.classList.remove("active"); @@ -527,9 +563,6 @@ const Engine = { MainMenuLinks.Tutorial.classList.remove("active"); MainMenuLinks.Options.classList.remove("active"); MainMenuLinks.DevMenu.classList.remove("active"); - - // Close dev menu - closeDevMenu(); }, displayCharacterOverviewInfo: function() { @@ -1334,13 +1367,11 @@ const Engine = { MainMenuLinks.Travel.addEventListener("click", function() { Engine.loadTravelContent(); - MainMenuLinks.Travel.classList.add("active"); return false; }); MainMenuLinks.Job.addEventListener("click", function() { Engine.loadJobContent(); - MainMenuLinks.Job.classList.add("active"); return false; }); diff --git a/src/ui/React/AutoupdatingStdButton.tsx b/src/ui/React/AutoupdatingStdButton.tsx index a2f9d5a6a..4ef6542d4 100644 --- a/src/ui/React/AutoupdatingStdButton.tsx +++ b/src/ui/React/AutoupdatingStdButton.tsx @@ -19,6 +19,10 @@ interface IState { i: number; } +type IInnerHTMLMarkup = { + __html: string; +} + export class AutoupdatingStdButton extends React.Component { /** * Timer ID for auto-updating implementation (returned value from setInterval()) @@ -48,21 +52,27 @@ export class AutoupdatingStdButton extends React.Component { } render() { - const hasTooltip = this.props.tooltip !== ""; + const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; let className = this.props.disabled ? "std-button-disabled" : "std-button"; if (hasTooltip) { className += " tooltip" } + // Tooltip will eb set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: this.props.tooltip! + } + } + return ( ) diff --git a/src/ui/React/StdButton.tsx b/src/ui/React/StdButton.tsx index ec3dfd5ee..d8bd957f4 100644 --- a/src/ui/React/StdButton.tsx +++ b/src/ui/React/StdButton.tsx @@ -6,28 +6,39 @@ import * as React from "react"; interface IStdButtonProps { disabled?: boolean; + id?: string; onClick?: (e: React.MouseEvent) => any; style?: object; text: string; tooltip?: string; } +type IInnerHTMLMarkup = { + __html: string; +} + export class StdButton extends React.Component { render() { - const hasTooltip = this.props.tooltip !== ""; + const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; let className = this.props.disabled ? "std-button-disabled" : "std-button"; if (hasTooltip) { className += " tooltip"; } + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: this.props.tooltip! + } + } + return ( - )