bitburner-src/src/PersonObjects/Sleeve/Sleeve.ts

1253 lines
40 KiB
TypeScript
Raw Normal View History

/**
2019-01-10 09:20:04 +01:00
* Sleeves are bodies that contain the player's cloned consciousness.
* The player can use these bodies to perform different tasks synchronously.
*
* Each sleeve is its own individual, meaning it has its own stats/exp
*
* Sleeves are unlocked in BitNode-10.
*/
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
import { Person } from "../Person";
import { ITaskTracker, createTaskTracker } from "../ITaskTracker";
2019-02-26 09:51:48 +01:00
import { Augmentation } from "../../Augmentation/Augmentation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Crime } from "../../Crime/Crime";
import { Crimes } from "../../Crime/Crimes";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions";
import { CONSTANTS } from "../../Constants";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { FactionWorkType } from "../../Faction/FactionWorkTypeEnum";
import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames";
2021-09-25 20:42:57 +02:00
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { capitalizeFirstLetter, capitalizeEachWord } from "../../utils/StringHelperFunctions";
export class Sleeve extends Person {
2021-09-05 01:09:30 +02:00
/**
* Stores the name of the class that the player is currently taking
*/
className = "";
/**
* Stores the type of crime the sleeve is currently attempting
* Must match the name of a Crime object
*/
crimeType = "";
/**
* Enum value for current task
*/
currentTask: SleeveTaskType = SleeveTaskType.Idle;
/**
* Contains details about the sleeve's current task. The info stored
* in this depends on the task type
*
* Faction/Company Work: Name of Faction/Company
* Crime: Money earned if successful
* Class/Gym: Name of university/gym
* Bladeburner: success chance
2021-09-05 01:09:30 +02:00
*/
currentTaskLocation = "";
/**
* Maximum amount of time (in milliseconds) that can be spent on current task.
*/
currentTaskMaxTime = 0;
/**
* Milliseconds spent on current task
*/
currentTaskTime = 0;
/**
* Keeps track of experience earned for other sleeves
*/
earningsForSleeves: ITaskTracker = createTaskTracker();
/**
* Keeps track of experience + money earned for player
*/
earningsForPlayer: ITaskTracker = createTaskTracker();
/**
* Keeps track of experienced earned in the current task/action
*/
earningsForTask: ITaskTracker = createTaskTracker();
/**
* Keeps track of what type of work sleeve is doing for faction, if applicable
*/
factionWorkType: FactionWorkType = FactionWorkType.None;
/**
* Records experience gain rate for the current task
*/
gainRatesForTask: ITaskTracker = createTaskTracker();
/**
* String that stores what stat the sleeve is training at the gym
*/
gymStatType = "";
/**
* String that stores what stat the sleeve is training at the gym
*/
bbAction = "";
/**
* String that stores what stat the sleeve is training at the gym
*/
bbContract = "";
2021-09-05 01:09:30 +02:00
/**
* Keeps track of events/notifications for this sleeve
*/
logs: string[] = [];
/**
* Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
*/
memory = 1;
/**
* Sleeve shock. Number between 0 and 100
* Trauma/shock that comes with being in a sleeve. Experience earned
* is multipled by shock%. This gets applied before synchronization
*
* Reputation earned is also multiplied by shock%
*/
shock = 1;
/**
* Stored number of game "loop" cycles
*/
storedCycles = 0;
/**
* Synchronization. Number between 0 and 100
* When experience is earned by sleeve, both the player and the sleeve get
* sync% of the experience earned. Other sleeves get sync^2% of exp
*/
sync = 1;
constructor(p: IPlayer | null = null) {
super();
if (p != null) {
this.shockRecovery(p);
}
}
/**
* Commit crimes
*/
commitCrime(p: IPlayer, crimeKey: string): boolean {
2021-10-18 20:59:18 +02:00
const crime: Crime | null = Crimes[crimeKey] || Object.values(Crimes).find((crime) => crime.name === crimeKey);
if (!crime) {
2021-09-05 01:09:30 +02:00
return false;
}
2021-09-05 01:09:30 +02:00
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
}
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.str = crime.strength_exp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.def = crime.defense_exp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.agi = crime.agility_exp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.int = crime.intelligence_exp;
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = String(this.gainRatesForTask.money);
2021-10-18 20:59:18 +02:00
this.crimeType = crime.name;
2021-09-05 01:09:30 +02:00
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
return true;
}
/**
* Called to stop the current task
*/
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
2022-04-14 17:57:01 +02:00
2021-09-05 01:09:30 +02:00
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
2021-10-18 20:59:18 +02:00
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) {
2021-09-09 05:47:34 +02:00
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
return retValue;
}
2021-09-05 01:09:30 +02:00
if (Math.random() < crime.successRate(this)) {
// Success
const successGainRates: ITaskTracker = createTaskTracker();
2021-09-09 05:47:34 +02:00
const keysForIteration: (keyof ITaskTracker)[] = Object.keys(successGainRates) as (keyof ITaskTracker)[];
2021-09-05 01:09:30 +02:00
for (let i = 0; i < keysForIteration.length; ++i) {
const key = keysForIteration[i];
successGainRates[key] = this.gainRatesForTask[key] * 2;
}
retValue = this.gainExperience(p, successGainRates);
this.gainMoney(p, this.gainRatesForTask);
p.karma -= crime.karma * (this.sync / 100);
} else {
2021-09-05 01:09:30 +02:00
retValue = this.gainExperience(p, this.gainRatesForTask);
}
2021-09-05 01:09:30 +02:00
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
2021-09-05 01:09:30 +02:00
}
} else if (this.currentTask === SleeveTaskType.Bladeburner) {
if (this.currentTaskMaxTime === 0) {
this.currentTaskTime = 0;
return retValue;
}
// For bladeburner, all experience and money is gained at the end
const bb = p.bladeburner;
if (bb === null) {
const errorLogText = `bladeburner is null`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return retValue;
}
2022-04-14 17:57:01 +02:00
if (this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.bbAction === "Infiltrate synthoids") {
bb.infiltrateSynthoidCommunities();
this.currentTaskTime = 0;
return retValue;
}
let type: string;
let name: string;
if (this.bbAction === "Take on contracts") {
2022-04-14 18:00:17 +02:00
type = "Contracts";
name = this.bbContract;
} else {
2022-04-14 18:00:17 +02:00
type = "General";
name = this.bbAction;
}
const actionIdent = bb.getActionIdFromTypeAndName(type, name);
2022-04-14 18:00:17 +02:00
if (actionIdent === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return retValue;
}
const action = bb.getActionObject(actionIdent);
2022-04-14 18:00:17 +02:00
if ((action?.count ?? 0) > 0) {
const bbRetValue = bb.completeAction(p, this, actionIdent, false);
2022-04-14 18:00:17 +02:00
if (bbRetValue) {
retValue = this.gainExperience(p, bbRetValue);
this.gainMoney(p, bbRetValue);
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
}
}
}
}
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
return retValue;
}
/**
* Earn experience for any stats (supports multiple)
* This function also handles experience propogating to Player and other sleeves
*/
2021-09-09 05:47:34 +02:00
gainExperience(p: IPlayer, exp: ITaskTracker, numCycles = 1, fromOtherSleeve = false): ITaskTracker {
2021-09-05 01:09:30 +02:00
// If the experience is coming from another sleeve, it is not multiplied by anything.
// Also the player does not earn anything
if (fromOtherSleeve) {
if (exp.hack > 0) {
this.hacking_exp += exp.hack;
}
if (exp.str > 0) {
this.strength_exp += exp.str;
}
if (exp.def > 0) {
this.defense_exp += exp.def;
}
if (exp.dex > 0) {
this.dexterity_exp += exp.dex;
}
if (exp.agi > 0) {
this.agility_exp += exp.agi;
}
if (exp.cha > 0) {
this.charisma_exp += exp.cha;
}
return createTaskTracker();
}
2021-09-05 01:09:30 +02:00
// Experience is first multiplied by shock. Then 'synchronization'
// is accounted for
2021-09-05 01:09:30 +02:00
const multFac = (this.shock / 100) * (this.sync / 100) * numCycles;
const pHackExp = exp.hack * multFac;
const pStrExp = exp.str * multFac;
const pDefExp = exp.def * multFac;
const pDexExp = exp.dex * multFac;
const pAgiExp = exp.agi * multFac;
const pChaExp = exp.cha * multFac;
const pIntExp = exp.int * multFac;
2021-09-05 01:09:30 +02:00
// Experience is gained by both this sleeve and player
if (pHackExp > 0) {
this.gainHackingExp(pHackExp);
2021-09-05 01:09:30 +02:00
p.gainHackingExp(pHackExp);
this.earningsForPlayer.hack += pHackExp;
this.earningsForTask.hack += pHackExp;
}
2021-09-05 01:09:30 +02:00
if (pStrExp > 0) {
this.gainStrengthExp(pStrExp);
2021-09-05 01:09:30 +02:00
p.gainStrengthExp(pStrExp);
this.earningsForPlayer.str += pStrExp;
this.earningsForTask.str += pStrExp;
}
2021-09-05 01:09:30 +02:00
if (pDefExp > 0) {
this.gainDefenseExp(pDefExp);
2021-09-05 01:09:30 +02:00
p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp;
this.earningsForTask.def += pDefExp;
}
2021-09-05 01:09:30 +02:00
if (pDexExp > 0) {
this.gainDexterityExp(pDexExp);
2021-09-05 01:09:30 +02:00
p.gainDexterityExp(pDexExp);
this.earningsForPlayer.dex += pDexExp;
this.earningsForTask.dex += pDexExp;
}
2021-09-05 01:09:30 +02:00
if (pAgiExp > 0) {
this.gainAgilityExp(pAgiExp);
2021-09-05 01:09:30 +02:00
p.gainAgilityExp(pAgiExp);
this.earningsForPlayer.agi += pAgiExp;
this.earningsForTask.agi += pAgiExp;
}
2021-09-05 01:09:30 +02:00
if (pChaExp > 0) {
this.gainCharismaExp(pChaExp);
2021-09-05 01:09:30 +02:00
p.gainCharismaExp(pChaExp);
this.earningsForPlayer.cha += pChaExp;
this.earningsForTask.cha += pChaExp;
}
if (pIntExp > 0) {
this.gainIntelligenceExp(pIntExp);
p.gainIntelligenceExp(pIntExp);
}
2021-09-05 01:09:30 +02:00
// Record earnings for other sleeves
this.earningsForSleeves.hack += pHackExp * (this.sync / 100);
this.earningsForSleeves.str += pStrExp * (this.sync / 100);
this.earningsForSleeves.def += pDefExp * (this.sync / 100);
this.earningsForSleeves.dex += pDexExp * (this.sync / 100);
this.earningsForSleeves.agi += pAgiExp * (this.sync / 100);
this.earningsForSleeves.cha += pChaExp * (this.sync / 100);
// Return the experience to be gained by other sleeves
return {
hack: pHackExp * (this.sync / 100),
str: pStrExp * (this.sync / 100),
def: pDefExp * (this.sync / 100),
dex: pDexExp * (this.sync / 100),
agi: pAgiExp * (this.sync / 100),
cha: pChaExp * (this.sync / 100),
int: pIntExp * (this.sync / 100),
money: exp.money,
2021-09-05 01:09:30 +02:00
};
}
/**
* Earn money for player
*/
gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void {
const gain: number = task.money * numCycles;
this.earningsForTask.money += gain;
this.earningsForPlayer.money += gain;
2021-10-27 20:18:33 +02:00
p.gainMoney(gain, "sleeves");
2021-09-05 01:09:30 +02:00
}
/**
* Returns the cost of upgrading this sleeve's memory by a certain amount
*/
getMemoryUpgradeCost(n: number): number {
const amt = Math.round(n);
if (amt < 0) {
return 0;
}
2021-09-05 01:09:30 +02:00
if (this.memory + amt > 100) {
return this.getMemoryUpgradeCost(100 - this.memory);
}
2021-09-05 01:09:30 +02:00
const mult = 1.02;
const baseCost = 1e12;
let currCost = 0;
let currMemory = this.memory - 1;
for (let i = 0; i < n; ++i) {
currCost += Math.pow(mult, currMemory);
++currMemory;
}
2021-09-05 01:09:30 +02:00
return currCost * baseCost;
}
/**
* Gets reputation gain for the current task
* Only applicable when working for company or faction
*/
getRepGain(p: IPlayer): number {
if (this.currentTask === SleeveTaskType.Faction) {
let favorMult = 1;
const fac: Faction | null = Factions[this.currentTaskLocation];
if (fac != null) {
favorMult = 1 + fac.favor / 100;
}
switch (this.factionWorkType) {
case FactionWorkType.Hacking:
2021-09-09 05:47:34 +02:00
return this.getFactionHackingWorkRepGain() * (this.shock / 100) * favorMult;
2021-09-05 01:09:30 +02:00
case FactionWorkType.Field:
2021-09-09 05:47:34 +02:00
return this.getFactionFieldWorkRepGain() * (this.shock / 100) * favorMult;
2021-09-05 01:09:30 +02:00
case FactionWorkType.Security:
2021-09-09 05:47:34 +02:00
return this.getFactionSecurityWorkRepGain() * (this.shock / 100) * favorMult;
2021-09-05 01:09:30 +02:00
default:
2021-09-09 05:47:34 +02:00
console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`);
2021-09-05 01:09:30 +02:00
return 0;
}
} else if (this.currentTask === SleeveTaskType.Company) {
const companyName: string = this.currentTaskLocation;
const company: Company | null = Companies[companyName];
if (company == null) {
2021-09-09 05:47:34 +02:00
console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`);
2021-09-05 01:09:30 +02:00
return 0;
}
2021-09-09 05:47:34 +02:00
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
2021-09-05 01:09:30 +02:00
if (companyPosition == null) {
2021-09-09 05:47:34 +02:00
console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`);
2021-09-05 01:09:30 +02:00
return 0;
}
const jobPerformance: number = companyPosition.calculateJobPerformance(
2021-11-05 22:12:52 +01:00
this.hacking,
2021-09-05 01:09:30 +02:00
this.strength,
this.defense,
this.dexterity,
this.agility,
this.charisma,
);
const favorMult = 1 + company.favor / 100;
return jobPerformance * this.company_rep_mult * favorMult;
} else {
return 0;
}
}
installAugmentation(aug: Augmentation): void {
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.applyAugmentation(aug);
this.augmentations.push({ name: aug.name, level: 1 });
this.updateStatLevels();
}
log(entry: string): void {
const MaxLogSize = 50;
this.logs.push(entry);
if (this.logs.length > MaxLogSize) {
this.logs.shift();
}
}
/**
2022-02-03 14:50:08 +01:00
* Called on every sleeve for a Source File Prestige
2021-09-05 01:09:30 +02:00
*/
prestige(p: IPlayer): void {
// Reset exp
this.hacking_exp = 0;
this.strength_exp = 0;
this.defense_exp = 0;
this.dexterity_exp = 0;
this.agility_exp = 0;
this.charisma_exp = 0;
// Reset task-related stuff
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.shockRecovery(p);
// Reset augs and multipliers
this.augmentations = [];
this.resetMultipliers();
2022-02-03 14:50:08 +01:00
// Reset Location
this.city = CityName.Sector12;
2021-09-05 01:09:30 +02:00
// Reset sleeve-related stats
this.shock = 1;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.logs = [];
}
/**
* Process loop
* Returns an object containing the amount of experience that should be
* transferred to all other sleeves
*/
process(p: IPlayer, numCycles = 1): ITaskTracker | null {
// Only process once every second (5 cycles)
const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle;
this.storedCycles += numCycles;
if (this.storedCycles < CyclesPerSecond) {
return null;
}
2021-09-05 01:09:30 +02:00
let cyclesUsed = this.storedCycles;
cyclesUsed = Math.min(cyclesUsed, 15);
let time = cyclesUsed * CONSTANTS.MilliPerCycle;
2021-09-09 05:47:34 +02:00
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) {
2021-09-05 01:09:30 +02:00
time = this.currentTaskMaxTime - this.currentTaskTime;
cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle);
if (time < 0 || cyclesUsed < 0) {
console.warn(`Sleeve.process() calculated negative cycle usage`);
time = 0;
cyclesUsed = 0;
}
}
2021-10-09 21:07:42 +02:00
2021-09-05 01:09:30 +02:00
this.currentTaskTime += time;
// Shock gradually goes towards 100
this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);
2021-09-05 01:09:30 +02:00
let retValue: ITaskTracker = createTaskTracker();
switch (this.currentTask) {
case SleeveTaskType.Idle:
break;
case SleeveTaskType.Class:
case SleeveTaskType.Gym:
this.updateTaskGainRates(p);
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
break;
case SleeveTaskType.Faction: {
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
// Gain faction reputation
const fac: Faction = Factions[this.currentTaskLocation];
if (!(fac instanceof Faction)) {
2021-09-09 05:47:34 +02:00
console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`);
2021-09-05 01:09:30 +02:00
break;
}
// If the player has a gang with the faction the sleeve is working
// for, we need to reset the sleeve's task
if (p.gang) {
if (fac.name === p.gang.facName) {
this.resetTaskStatus(p);
}
}
2021-09-05 01:09:30 +02:00
fac.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}
case SleeveTaskType.Company: {
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
const company: Company = Companies[this.currentTaskLocation];
if (!(company instanceof Company)) {
2021-09-09 05:47:34 +02:00
console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`);
2021-09-05 01:09:30 +02:00
break;
}
2021-09-05 01:09:30 +02:00
company.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}
case SleeveTaskType.Recovery:
this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);
if (this.shock >= 100) this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
break;
case SleeveTaskType.Synchro:
2021-09-09 05:47:34 +02:00
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);
if (this.sync >= 100) this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
break;
default:
break;
}
2021-09-09 05:47:34 +02:00
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) {
2021-09-05 01:09:30 +02:00
retValue = this.finishTask(p);
} else {
this.finishTask(p);
}
}
2021-09-05 01:09:30 +02:00
this.updateStatLevels();
this.storedCycles -= cyclesUsed;
return retValue;
}
/**
* Resets all parameters used to keep information about the current task
*/
resetTaskStatus(p: IPlayer): void {
2022-04-14 18:00:17 +02:00
if (this.bbAction == "Support main sleeve") {
p.bladeburner?.sleeveSupport(false);
}
if (this.currentTask == SleeveTaskType.Class) {
2022-04-14 17:57:01 +02:00
const retVal = createTaskTracker();
retVal.int = CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.currentTaskTime / 1000);
const r = this.gainExperience(p, retVal);
p.sleeves.filter((s) => s != this).forEach((s) => s.gainExperience(p, r, 1, true));
}
2021-09-05 01:09:30 +02:00
this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle;
this.currentTaskTime = 0;
this.currentTaskMaxTime = 0;
this.factionWorkType = FactionWorkType.None;
this.crimeType = "";
this.currentTaskLocation = "";
this.gymStatType = "";
this.className = "";
this.bbAction = "";
this.bbContract = "------";
2021-09-05 01:09:30 +02:00
}
shockRecovery(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
this.currentTask = SleeveTaskType.Recovery;
return true;
}
2021-09-05 01:09:30 +02:00
synchronize(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
2021-09-05 01:09:30 +02:00
this.currentTask = SleeveTaskType.Synchro;
return true;
}
/**
* Take a course at a university
*/
2021-09-09 05:47:34 +02:00
takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean {
2021-09-05 01:09:30 +02:00
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2019-02-26 09:51:48 +01:00
}
2021-09-05 01:09:30 +02:00
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult = 1;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (this.city !== CityName.Aevum) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.AevumSummitUniversity;
costMult = 4;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (this.city !== CityName.Sector12) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.Sector12RothmanUniversity;
costMult = 3;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (this.city !== CityName.Volhaven) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology;
costMult = 5;
break;
default:
return false;
}
2021-09-05 01:09:30 +02:00
// Set experience/money gains based on class
switch (className.toLowerCase()) {
case "study computer science":
break;
case "data structures":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult);
2021-09-05 01:09:30 +02:00
break;
case "networks":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassNetworksBaseCost * costMult);
2021-09-05 01:09:30 +02:00
break;
case "algorithms":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult);
2021-09-05 01:09:30 +02:00
break;
case "management":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassManagementBaseCost * costMult);
2021-09-05 01:09:30 +02:00
break;
case "leadership":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult);
2021-09-05 01:09:30 +02:00
break;
default:
return false;
}
2021-09-05 01:09:30 +02:00
this.className = className;
this.currentTask = SleeveTaskType.Class;
return true;
}
2021-09-05 01:09:30 +02:00
/**
* Travel to another City. Costs money from player
*/
travel(p: IPlayer, newCity: CityName): boolean {
2021-10-27 20:18:33 +02:00
p.loseMoney(CONSTANTS.TravelCost, "sleeves");
2021-09-05 01:09:30 +02:00
this.city = newCity;
2021-09-05 01:09:30 +02:00
return true;
}
2021-09-05 01:09:30 +02:00
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.baseCost)) {
2021-09-05 01:09:30 +02:00
return false;
}
2021-09-05 01:09:30 +02:00
// Verify that this sleeve does not already have that augmentation.
if (this.augmentations.some((a) => a.name === aug.name)) {
return false;
}
p.loseMoney(aug.baseCost, "sleeves");
2021-09-05 01:09:30 +02:00
this.installAugmentation(aug);
return true;
}
updateTaskGainRates(p: IPlayer): void {
if (this.currentTask === SleeveTaskType.Class) {
let expMult = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
expMult = 3;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
expMult = 2;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
expMult = 4;
break;
default:
return;
}
const totalExpMult = expMult * p.hashManager.getStudyMult();
switch (this.className.toLowerCase()) {
case "study computer science":
this.gainRatesForTask.hack =
2021-09-09 05:47:34 +02:00
CONSTANTS.ClassStudyComputerScienceBaseExp * totalExpMult * this.hacking_exp_mult;
2021-09-05 01:09:30 +02:00
break;
case "data structures":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = CONSTANTS.ClassDataStructuresBaseExp * totalExpMult * this.hacking_exp_mult;
2021-09-05 01:09:30 +02:00
break;
case "networks":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = CONSTANTS.ClassNetworksBaseExp * totalExpMult * this.hacking_exp_mult;
2021-09-05 01:09:30 +02:00
break;
case "algorithms":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = CONSTANTS.ClassAlgorithmsBaseExp * totalExpMult * this.hacking_exp_mult;
2021-09-05 01:09:30 +02:00
break;
case "management":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.cha = CONSTANTS.ClassManagementBaseExp * totalExpMult * this.charisma_exp_mult;
2021-09-05 01:09:30 +02:00
break;
case "leadership":
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.cha = CONSTANTS.ClassLeadershipBaseExp * totalExpMult * this.charisma_exp_mult;
2021-09-05 01:09:30 +02:00
break;
default:
break;
}
return;
2019-02-26 09:51:48 +01:00
}
2021-09-05 01:09:30 +02:00
if (this.currentTask === SleeveTaskType.Gym) {
// Get gym exp multiplier
let expMult = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
expMult = 2;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
expMult = 5;
break;
case LocationName.Sector12IronGym.toLowerCase():
expMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
expMult = 10;
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
2021-09-05 01:09:30 +02:00
expMult = 4;
break;
default:
return;
}
// Set stat gain rate
const baseGymExp = 1;
const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult;
const sanitizedStat: string = this.gymStatType.toLowerCase();
if (sanitizedStat.includes("str")) {
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.str = baseGymExp * totalExpMultiplier * this.strength_exp_mult;
2021-09-05 01:09:30 +02:00
} else if (sanitizedStat.includes("def")) {
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.def = baseGymExp * totalExpMultiplier * this.defense_exp_mult;
2021-09-05 01:09:30 +02:00
} else if (sanitizedStat.includes("dex")) {
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.dex = baseGymExp * totalExpMultiplier * this.dexterity_exp_mult;
2021-09-05 01:09:30 +02:00
} else if (sanitizedStat.includes("agi")) {
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.agi = baseGymExp * totalExpMultiplier * this.agility_exp_mult;
2021-09-05 01:09:30 +02:00
}
return;
2019-02-26 09:51:48 +01:00
}
2021-09-09 05:47:34 +02:00
console.warn(`Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`);
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
2021-09-05 01:09:30 +02:00
this.memory = Math.min(100, Math.round(this.memory + n));
}
/**
* Start work for one of the player's companies
* Returns boolean indicating success
*/
workForCompany(p: IPlayer, companyName: string): boolean {
2021-09-09 05:47:34 +02:00
if (!(Companies[companyName] instanceof Company) || p.jobs[companyName] == null) {
2021-09-05 01:09:30 +02:00
return false;
}
2021-09-05 01:09:30 +02:00
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2021-09-05 01:09:30 +02:00
}
2019-07-16 06:40:13 +02:00
2021-09-05 01:09:30 +02:00
const company: Company | null = Companies[companyName];
2021-09-09 05:47:34 +02:00
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
2021-09-05 01:09:30 +02:00
if (company == null) {
return false;
}
if (companyPosition == null) {
return false;
}
this.gainRatesForTask.money =
companyPosition.baseSalary *
company.salaryMultiplier *
this.work_money_mult *
BitNodeMultipliers.CompanyWorkMoney;
this.gainRatesForTask.hack =
companyPosition.hackingExpGain *
company.expMultiplier *
this.hacking_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.str =
companyPosition.strengthExpGain *
company.expMultiplier *
this.strength_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.def =
companyPosition.defenseExpGain *
company.expMultiplier *
this.defense_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.dex =
companyPosition.dexterityExpGain *
company.expMultiplier *
this.dexterity_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.agi =
companyPosition.agilityExpGain *
company.expMultiplier *
this.agility_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.cha =
companyPosition.charismaExpGain *
company.expMultiplier *
this.charisma_exp_mult *
BitNodeMultipliers.CompanyWorkExpGain;
this.currentTaskLocation = companyName;
this.currentTask = SleeveTaskType.Company;
return true;
}
/**
* Start work for one of the player's factions
* Returns boolean indicating success
*/
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
2022-04-07 01:30:08 +02:00
const faction = Factions[factionName];
if (factionName === "" || !faction || !(faction instanceof Faction) || !p.factions.includes(factionName)) {
2021-09-05 01:09:30 +02:00
return false;
}
2019-07-16 06:40:13 +02:00
2021-09-05 01:09:30 +02:00
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2019-07-16 06:40:13 +02:00
}
const factionInfo = faction.getInfo();
2021-09-05 01:09:30 +02:00
// Set type of work (hacking/field/security), and the experience gains
const sanitizedWorkType: string = workType.toLowerCase();
if (sanitizedWorkType.includes("hack")) {
if (!factionInfo.offerHackingWork) {
return false;
}
this.factionWorkType = FactionWorkType.Hacking;
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
2021-09-05 01:09:30 +02:00
} else if (sanitizedWorkType.includes("field")) {
if (!factionInfo.offerFieldWork) {
return false;
}
this.factionWorkType = FactionWorkType.Field;
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = 0.1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.def = 0.1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.dex = 0.1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.agi = 0.1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.cha = 0.1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
2021-09-05 01:09:30 +02:00
} else if (sanitizedWorkType.includes("security")) {
if (!factionInfo.offerSecurityWork) {
return false;
}
this.factionWorkType = FactionWorkType.Security;
2021-09-09 05:47:34 +02:00
this.gainRatesForTask.hack = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.def = 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.dex = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.agi = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
2021-09-05 01:09:30 +02:00
} else {
return false;
}
2019-07-16 06:40:13 +02:00
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = factionName;
this.currentTask = SleeveTaskType.Faction;
return true;
}
/**
* Begin a gym workout task
*/
workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
2019-07-16 06:40:13 +02:00
}
2021-09-05 01:09:30 +02:00
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult = 1;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.AevumCrushFitnessGym;
costMult = 3;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.AevumSnapFitnessGym;
costMult = 10;
break;
case LocationName.Sector12IronGym.toLowerCase():
if (this.city != CityName.Sector12) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.Sector12IronGym;
costMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (this.city != CityName.Sector12) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.Sector12PowerhouseGym;
costMult = 20;
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (this.city != CityName.Volhaven) {
return false;
}
2021-09-05 01:09:30 +02:00
this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym;
costMult = 7;
break;
default:
return false;
}
2021-09-05 01:09:30 +02:00
// Set experience/money gains based on class
const sanitizedStat: string = stat.toLowerCase();
2021-09-05 01:09:30 +02:00
// Set cost
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult);
2019-07-16 06:40:13 +02:00
2021-09-05 01:09:30 +02:00
// Validate "stat" argument
if (
!sanitizedStat.includes("str") &&
!sanitizedStat.includes("def") &&
!sanitizedStat.includes("dex") &&
!sanitizedStat.includes("agi")
) {
return false;
}
2021-09-05 01:09:30 +02:00
this.gymStatType = stat;
this.currentTask = SleeveTaskType.Gym;
return true;
}
/**
* Begin a bladeburner task
*/
bladeburner(p: IPlayer, action: string, contract: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
this.gainRatesForTask.hack = 0;
this.gainRatesForTask.str = 0;
this.gainRatesForTask.def = 0;
this.gainRatesForTask.dex = 0;
this.gainRatesForTask.agi = 0;
this.gainRatesForTask.cha = 0;
this.gainRatesForTask.money = 0;
2022-04-14 18:00:17 +02:00
this.currentTaskLocation = "";
let time = 0;
this.bbContract = "------";
switch (action) {
case "Field analysis":
2022-04-14 18:00:17 +02:00
time = this.getBladeburnerActionTime(p, "General", action);
this.gainRatesForTask.hack = 20 * this.hacking_exp_mult;
this.gainRatesForTask.cha = 20 * this.charisma_exp_mult;
break;
case "Recruitment":
2022-04-14 18:00:17 +02:00
time = this.getBladeburnerActionTime(p, "General", action);
2022-04-14 21:28:13 +02:00
this.gainRatesForTask.cha =
2 * BladeburnerConstants.BaseStatGain * (p.bladeburner?.getRecruitmentTime(this) ?? 0) * 1000;
this.currentTaskLocation = `(Success Rate: ${numeralWrapper.formatPercentage(
this.recrutmentSuccessChance(p),
)})`;
break;
case "Diplomacy":
2022-04-14 18:00:17 +02:00
time = this.getBladeburnerActionTime(p, "General", action);
break;
case "Infiltrate synthoids":
time = 60000;
this.currentTaskLocation = "This will generate additional contracts and operations";
break;
case "Support main sleeve":
p.bladeburner?.sleeveSupport(true);
time = 0;
break;
case "Take on contracts":
2022-04-14 18:00:17 +02:00
time = this.getBladeburnerActionTime(p, "Contracts", contract);
this.contractGainRates(p, "Contracts", contract);
this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract);
this.bbContract = capitalizeEachWord(contract.toLowerCase());
break;
}
this.bbAction = capitalizeFirstLetter(action.toLowerCase());
this.currentTaskMaxTime = time;
this.currentTask = SleeveTaskType.Bladeburner;
return true;
}
recrutmentSuccessChance(p: IPlayer): number {
return Math.max(0, Math.min(1, p.bladeburner?.getRecruitmentSuccessChance(this) ?? 0));
}
contractSuccessChance(p: IPlayer, type: string, name: string): string {
const bb = p.bladeburner;
2022-04-14 18:00:17 +02:00
if (bb === null) {
const errorLogText = `bladeburner is null`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.contractSuccessChance; Message: '${errorLogText}'`);
return "0%";
}
const chances = bb.getActionEstimatedSuccessChanceNetscriptFn(this, type, name);
2022-04-14 18:00:17 +02:00
if (typeof chances === "string") {
console.error(`Function: sleeves.contractSuccessChance; Message: '${chances}'`);
return "0%";
}
2022-04-14 18:00:17 +02:00
if (chances[0] >= 1) {
return "100%";
} else {
return `${numeralWrapper.formatPercentage(chances[0])} - ${numeralWrapper.formatPercentage(chances[1])}`;
}
}
contractGainRates(p: IPlayer, type: string, name: string): void {
const bb = p.bladeburner;
2022-04-14 18:00:17 +02:00
if (bb === null) {
const errorLogText = `bladeburner is null`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
return;
}
const actionIdent = bb.getActionIdFromTypeAndName(type, name);
2022-04-14 18:00:17 +02:00
if (actionIdent === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return;
}
const action = bb.getActionObject(actionIdent);
2022-04-14 18:00:17 +02:00
if (action === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return;
}
const retValue = bb.getActionStats(action, true);
this.gainRatesForTask.hack = retValue.hack;
this.gainRatesForTask.str = retValue.str;
this.gainRatesForTask.def = retValue.def;
this.gainRatesForTask.dex = retValue.dex;
this.gainRatesForTask.agi = retValue.agi;
this.gainRatesForTask.cha = retValue.cha;
const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
2022-04-14 18:00:17 +02:00
this.gainRatesForTask.money =
BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money;
}
2022-04-14 18:00:17 +02:00
getBladeburnerActionTime(p: IPlayer, type: string, name: string): number {
//Maybe find workerscript and use original
const bb = p.bladeburner;
2022-04-14 18:00:17 +02:00
if (bb === null) {
const errorLogText = `bladeburner is null`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`);
return -1;
}
2022-04-14 17:57:01 +02:00
const time = bb.getActionTimeNetscriptFn(this, type, name);
2022-04-14 18:00:17 +02:00
if (typeof time === "string") {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
2022-04-14 18:00:17 +02:00
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`);
return -1;
} else {
return time;
}
}
2022-04-14 18:00:17 +02:00
takeDamage(amt: number): boolean {
if (typeof amt !== "number") {
console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
return false;
}
this.hp -= amt;
if (this.hp <= 0) {
this.shock += 0.5;
this.hp = this.max_hp;
return true;
} else {
return false;
}
}
whoAmI(): string {
2022-04-14 18:00:17 +02:00
return "Sleeve";
}
2021-09-05 01:09:30 +02:00
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Sleeve", this);
}
/**
* Initiatizes a Sleeve object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): Sleeve {
return Generic_fromJSON(Sleeve, value.data);
}
}
Reviver.constructors.Sleeve = Sleeve;