diff --git a/package-lock.json b/package-lock.json
index b7c794026..79a902002 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "bitburner",
- "version": "1.5.0",
+ "version": "1.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
- "version": "1.5.0",
+ "version": "1.6.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
diff --git a/src/Bladeburner/Action.tsx b/src/Bladeburner/Action.tsx
index c166ef567..b05eac04d 100644
--- a/src/Bladeburner/Action.tsx
+++ b/src/Bladeburner/Action.tsx
@@ -5,6 +5,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"
import { BladeburnerConstants } from "./data/Constants";
import { IBladeburner } from "./IBladeburner";
import { IAction, ISuccessChanceParams } from "./IAction";
+import { IPerson } from "../PersonObjects/IPerson";
class StatsMultiplier {
[key: string]: number;
@@ -154,8 +155,8 @@ export class Action implements IAction {
* Tests for success. Should be called when an action has completed
* @param inst {Bladeburner} - Bladeburner instance
*/
- attempt(inst: IBladeburner): boolean {
- return Math.random() < this.getSuccessChance(inst);
+ attempt(inst: IBladeburner, person: IPerson): boolean {
+ return Math.random() < this.getSuccessChance(inst, person);
}
// To be implemented by subtypes
@@ -163,13 +164,13 @@ export class Action implements IAction {
return 1;
}
- getActionTime(inst: IBladeburner): number {
+ getActionTime(inst: IBladeburner, person: IPerson): number {
const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
const skillFac = inst.skillMultipliers.actionTime; // Always < 1
- const effAgility = Player.agility * inst.skillMultipliers.effAgi;
- const effDexterity = Player.dexterity * inst.skillMultipliers.effDex;
+ const effAgility = person.agility * inst.skillMultipliers.effAgi;
+ const effDexterity = person.dexterity * inst.skillMultipliers.effDex;
const statFac =
0.5 *
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
@@ -213,12 +214,12 @@ export class Action implements IAction {
return 1;
}
- getEstSuccessChance(inst: IBladeburner): [number, number] {
+ getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number] {
function clamp(x: number): number {
return Math.max(0, Math.min(x, 1));
}
- const est = this.getSuccessChance(inst, { est: true });
- const real = this.getSuccessChance(inst);
+ const est = this.getSuccessChance(inst, person, { est: true });
+ const real = this.getSuccessChance(inst, person);
const diff = Math.abs(real - est);
let low = real - diff;
let high = real + diff;
@@ -234,7 +235,7 @@ export class Action implements IAction {
* @params - options:
* est (bool): Get success chance estimate instead of real success chance
*/
- getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams = { est: false }): number {
+ getSuccessChance(inst: IBladeburner, person: IPerson, params: ISuccessChanceParams = { est: false }): number {
if (inst == null) {
throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");
}
@@ -242,7 +243,7 @@ export class Action implements IAction {
let competence = 0;
for (const stat of Object.keys(this.weights)) {
if (this.weights.hasOwnProperty(stat)) {
- const playerStatLvl = Player.queryStatFromString(stat);
+ const playerStatLvl = person.queryStatFromString(stat);
const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1);
let effMultiplier = inst.skillMultipliers[key];
if (effMultiplier == null) {
diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx
index 11ebf3122..7dee7aae3 100644
--- a/src/Bladeburner/Bladeburner.tsx
+++ b/src/Bladeburner/Bladeburner.tsx
@@ -15,6 +15,8 @@ import { Skill } from "./Skill";
import { City } from "./City";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
+import { createTaskTracker, ITaskTracker } from "../PersonObjects/ITaskTracker";
+import { IPerson } from "../PersonObjects/IPerson";
import { IRouter, Page } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
@@ -36,6 +38,7 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { FactionNames } from "../Faction/data/FactionNames";
import { BlackOperationNames } from "./data/BlackOperationNames";
import { KEY } from "../utils/helpers/keyCodes";
+import { Player } from "src/Player";
interface BlackOpsAttempt {
error?: string;
@@ -53,6 +56,7 @@ export class Bladeburner implements IBladeburner {
totalSkillPoints = 0;
teamSize = 0;
+ sleeveSize = 0;
teamLost = 0;
hpLost = 0;
@@ -159,7 +163,7 @@ export class Bladeburner implements IBladeburner {
return { isAvailable: true, action };
}
- startAction(player: IPlayer, actionId: IActionIdentifier): void {
+ startAction(person: IPerson, actionId: IActionIdentifier): void {
if (actionId == null) return;
this.action = actionId;
this.actionTimeCurrent = 0;
@@ -176,7 +180,7 @@ export class Bladeburner implements IBladeburner {
if (action.count < 1) {
return this.resetAction();
}
- this.actionTimeToComplete = action.getActionTime(this);
+ this.actionTimeToComplete = action.getActionTime(this, person);
} catch (e: any) {
exceptionAlert(e);
}
@@ -193,7 +197,7 @@ export class Bladeburner implements IBladeburner {
if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) {
return this.resetAction();
}
- this.actionTimeToComplete = action.getActionTime(this);
+ this.actionTimeToComplete = action.getActionTime(this, person);
} catch (e: any) {
exceptionAlert(e);
}
@@ -211,14 +215,14 @@ export class Bladeburner implements IBladeburner {
if (testBlackOp.action === undefined) {
throw new Error("action should not be null");
}
- this.actionTimeToComplete = testBlackOp.action.getActionTime(this);
+ this.actionTimeToComplete = testBlackOp.action.getActionTime(this, person);
} catch (e: any) {
exceptionAlert(e);
}
break;
}
case ActionTypes["Recruitment"]:
- this.actionTimeToComplete = this.getRecruitmentTime(player);
+ this.actionTimeToComplete = this.getRecruitmentTime(person);
break;
case ActionTypes["Training"]:
case ActionTypes["FieldAnalysis"]:
@@ -997,11 +1001,11 @@ export class Bladeburner implements IBladeburner {
}
/**
- * Process stat gains from Contracts, Operations, and Black Operations
+ * Return stat to be gained from Contracts, Operations, and Black Operations
* @param action(Action obj) - Derived action class
* @param success(bool) - Whether action was successful
*/
- gainActionStats(player: IPlayer, action: IAction, success: boolean): void {
+ getActionStats(action: IAction, success: boolean): ITaskTracker {
const difficulty = action.getDifficulty();
/**
@@ -1018,34 +1022,48 @@ export class Bladeburner implements IBladeburner {
const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult;
const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult;
const skillMult = this.skillMultipliers.expGain;
- player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult);
- player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult);
- player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult);
- player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult);
- player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult);
- player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult);
- player.gainIntelligenceExp(unweightedIntGain * action.weights.int * skillMult);
+
+ return {
+ hack: unweightedGain * action.weights.hack * skillMult,
+ str: unweightedGain * action.weights.str * skillMult,
+ def: unweightedGain * action.weights.def * skillMult,
+ dex: unweightedGain * action.weights.dex * skillMult,
+ agi: unweightedGain * action.weights.agi * skillMult,
+ cha: unweightedGain * action.weights.cha * skillMult,
+ int: unweightedIntGain * action.weights.int * skillMult,
+ money: 0,
+ }
}
- getDiplomacyEffectiveness(player: IPlayer): number {
+ getDiplomacyEffectiveness(person: IPerson): number {
// Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98)
const CharismaLinearFactor = 1e3;
const CharismaExponentialFactor = 0.045;
- const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor;
+ const charismaEff = Math.pow(person.charisma, CharismaExponentialFactor) + person.charisma / CharismaLinearFactor;
return (100 - charismaEff) / 100;
}
- getRecruitmentSuccessChance(player: IPlayer): number {
- return Math.pow(player.charisma, 0.45) / (this.teamSize + 1);
+ getRecruitmentSuccessChance(person: IPerson): number {
+ return Math.pow(person.charisma, 0.45) / (this.teamSize - this.sleeveSize + 1);
}
- getRecruitmentTime(player: IPlayer): number {
- const effCharisma = player.charisma * this.skillMultipliers.effCha;
+ getRecruitmentTime(person: IPerson): number {
+ const effCharisma = person.charisma * this.skillMultipliers.effCha;
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
}
+ sleeveSupport(joining: boolean): void {
+ if(joining){
+ this.sleeveSize += 1;
+ this.teamSize += 1;
+ } else {
+ this.sleeveSize -= 1;
+ this.teamSize -= 1;
+ }
+ }
+
resetSkillMultipliers(): void {
this.skillMultipliers = {
successChanceAll: 1,
@@ -1097,7 +1115,7 @@ export class Bladeburner implements IBladeburner {
}
}
- completeOperation(success: boolean): void {
+ completeOperation(success: boolean, player: IPlayer): void {
if (this.action.type !== ActionTypes.Operation) {
throw new Error("completeOperation() called even though current action is not an Operation");
}
@@ -1117,6 +1135,15 @@ export class Bladeburner implements IBladeburner {
}
const losses = getRandomInt(0, max);
this.teamSize -= losses;
+ if(this.teamSize < this.sleeveSize) {
+ let sup = player.sleeves.filter(x => x.bbAction == 'Support main sleeve');
+ for(let i = 0; i > (this.teamSize-this.sleeveSize); i--){
+ const r = Math.floor(Math.random() * sup.length);
+ sup[r].takeDamage(sup[r].max_hp);
+ sup.splice(r, 1);
+ }
+ this.teamSize += this.sleeveSize;
+ }
this.teamLost += losses;
if (this.logging.ops && losses > 0) {
this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name);
@@ -1214,13 +1241,13 @@ export class Bladeburner implements IBladeburner {
}
}
- completeContract(success: boolean): void {
- if (this.action.type !== ActionTypes.Contract) {
+ completeContract(success: boolean, actionIdent: IActionIdentifier): void {
+ if (actionIdent.type !== ActionTypes.Contract) {
throw new Error("completeContract() called even though current action is not a Contract");
}
const city = this.getCurrentCity();
if (success) {
- switch (this.action.name) {
+ switch (actionIdent.name) {
case "Tracking":
// Increase estimate accuracy by a relatively small amount
city.improvePopulationEstimateByCount(getRandomInt(100, 1e3));
@@ -1234,20 +1261,21 @@ export class Bladeburner implements IBladeburner {
city.changeChaosByCount(0.04);
break;
default:
- throw new Error("Invalid Action name in completeContract: " + this.action.name);
+ throw new Error("Invalid Action name in completeContract: " + actionIdent.name);
}
}
}
- completeAction(router: IRouter, player: IPlayer): void {
- switch (this.action.type) {
+ completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier): ITaskTracker {
+ let retValue = createTaskTracker();
+ switch (actionIdent.type) {
case ActionTypes["Contract"]:
case ActionTypes["Operation"]: {
try {
- const isOperation = this.action.type === ActionTypes["Operation"];
- const action = this.getActionObject(this.action);
+ const isOperation = actionIdent.type === ActionTypes["Operation"];
+ const action = this.getActionObject(actionIdent);
if (action == null) {
- throw new Error("Failed to get Contract/Operation Object for: " + this.action.name);
+ throw new Error("Failed to get Contract/Operation Object for: " + actionIdent.name);
}
const difficulty = action.getDifficulty();
const difficultyMultiplier =
@@ -1262,8 +1290,8 @@ export class Bladeburner implements IBladeburner {
}
// Process Contract/Operation success/failure
- if (action.attempt(this)) {
- this.gainActionStats(player, action, true);
+ if (action.attempt(this, person)) {
+ retValue = this.getActionStats(action, true);
++action.successes;
--action.count;
@@ -1271,7 +1299,7 @@ export class Bladeburner implements IBladeburner {
let moneyGain = 0;
if (!isOperation) {
moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money;
- player.gainMoney(moneyGain, "bladeburner");
+ retValue.money = moneyGain;
}
if (isOperation) {
@@ -1281,11 +1309,12 @@ export class Bladeburner implements IBladeburner {
}
if (action.rankGain) {
const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10);
- this.changeRank(player, gain);
+ this.changeRank(person, gain);
if (isOperation && this.logging.ops) {
- this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank");
+ this.log(`${person.whoAmI()}: ` + action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank");
} else if (!isOperation && this.logging.contracts) {
this.log(
+ `${person.whoAmI()}: ` +
action.name +
" contract successfully completed! Gained " +
formatNumber(gain, 3) +
@@ -1294,22 +1323,22 @@ export class Bladeburner implements IBladeburner {
);
}
}
- isOperation ? this.completeOperation(true) : this.completeContract(true);
+ isOperation ? this.completeOperation(true, player) : this.completeContract(true, actionIdent);
} else {
- this.gainActionStats(player, action, false);
+ retValue = this.getActionStats(action, false);
++action.failures;
let loss = 0,
damage = 0;
if (action.rankLoss) {
loss = addOffset(action.rankLoss * rewardMultiplier, 10);
- this.changeRank(player, -1 * loss);
+ this.changeRank(person, -1 * loss);
}
if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10));
this.hpLost += damage;
- const cost = calculateHospitalizationCost(player, damage);
- if (player.takeDamage(damage)) {
+ const cost = calculateHospitalizationCost(person, damage);
+ if (person.takeDamage(damage)) {
++this.numHosp;
this.moneyLost += cost;
}
@@ -1322,16 +1351,15 @@ export class Bladeburner implements IBladeburner {
logLossText += "Took " + formatNumber(damage, 0) + " damage.";
}
if (isOperation && this.logging.ops) {
- this.log(action.name + " failed! " + logLossText);
+ this.log(`${person.whoAmI()}: ` + action.name + " failed! " + logLossText);
} else if (!isOperation && this.logging.contracts) {
- this.log(action.name + " contract failed! " + logLossText);
+ this.log(`${person.whoAmI()}: ` + action.name + " contract failed! " + logLossText);
}
- isOperation ? this.completeOperation(false) : this.completeContract(false);
+ isOperation ? this.completeOperation(false, player) : this.completeContract(false, actionIdent);
}
if (action.autoLevel) {
action.level = action.maxLevel;
} // Autolevel
- this.startAction(player, this.action); // Repeat action
} catch (e: any) {
exceptionAlert(e);
}
@@ -1340,9 +1368,9 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]: {
try {
- const action = this.getActionObject(this.action);
+ const action = this.getActionObject(actionIdent);
if (action == null || !(action instanceof BlackOperation)) {
- throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
+ throw new Error("Failed to get BlackOperation Object for: " + actionIdent.name);
}
const difficulty = action.getDifficulty();
const difficultyMultiplier =
@@ -1359,39 +1387,33 @@ export class Bladeburner implements IBladeburner {
const teamCount = action.teamCount;
let teamLossMax;
- if (action.attempt(this)) {
- this.gainActionStats(player, action, true);
+ if (action.attempt(this, person)) {
+ retValue = this.getActionStats(action, true);
action.count = 0;
this.blackops[action.name] = true;
let rankGain = 0;
if (action.rankGain) {
rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10);
- this.changeRank(player, rankGain);
+ this.changeRank(person, rankGain);
}
teamLossMax = Math.ceil(teamCount / 2);
- // Operation Daedalus
- if (action.name === BlackOperationNames.OperationDaedalus) {
- this.resetAction();
- return router.toBitVerse(false, false);
- }
-
if (this.logging.blackops) {
- this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank");
+ this.log(`${person.whoAmI()}: ` + action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank");
}
} else {
- this.gainActionStats(player, action, false);
+ retValue = this.getActionStats(action, false);
let rankLoss = 0;
let damage = 0;
if (action.rankLoss) {
rankLoss = addOffset(action.rankLoss, 10);
- this.changeRank(player, -1 * rankLoss);
+ this.changeRank(person, -1 * rankLoss);
}
if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10));
- const cost = calculateHospitalizationCost(player, damage);
- if (player.takeDamage(damage)) {
+ const cost = calculateHospitalizationCost(person, damage);
+ if (person.takeDamage(damage)) {
++this.numHosp;
this.moneyLost += cost;
}
@@ -1400,6 +1422,7 @@ export class Bladeburner implements IBladeburner {
if (this.logging.blackops) {
this.log(
+ `${person.whoAmI()}: ` +
action.name +
" failed! Lost " +
formatNumber(rankLoss, 1) +
@@ -1416,9 +1439,18 @@ export class Bladeburner implements IBladeburner {
if (teamCount >= 1) {
const losses = getRandomInt(1, teamLossMax);
this.teamSize -= losses;
+ if(this.teamSize < this.sleeveSize) {
+ let sup = player.sleeves.filter(x => x.bbAction == 'Support main sleeve');
+ for(let i = 0; i > (this.teamSize-this.sleeveSize); i--){
+ const r = Math.floor(Math.random() * sup.length);
+ sup[r].takeDamage(sup[r].max_hp);
+ sup.splice(r, 1);
+ }
+ this.teamSize += this.sleeveSize;
+ }
this.teamLost += losses;
if (this.logging.blackops) {
- this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name);
+ this.log(`${person.whoAmI()}: ` + "You lost " + formatNumber(losses, 0) + " team members during " + action.name);
}
}
} catch (e: any) {
@@ -1428,18 +1460,19 @@ export class Bladeburner implements IBladeburner {
}
case ActionTypes["Training"]: {
this.stamina -= 0.5 * BladeburnerConstants.BaseStaminaLoss;
- const strExpGain = 30 * player.strength_exp_mult,
- defExpGain = 30 * player.defense_exp_mult,
- dexExpGain = 30 * player.dexterity_exp_mult,
- agiExpGain = 30 * player.agility_exp_mult,
+ const strExpGain = 30 * person.strength_exp_mult,
+ defExpGain = 30 * person.defense_exp_mult,
+ dexExpGain = 30 * person.dexterity_exp_mult,
+ agiExpGain = 30 * person.agility_exp_mult,
staminaGain = 0.04 * this.skillMultipliers.stamina;
- player.gainStrengthExp(strExpGain);
- player.gainDefenseExp(defExpGain);
- player.gainDexterityExp(dexExpGain);
- player.gainAgilityExp(agiExpGain);
+ retValue.str = strExpGain;
+ retValue.def = defExpGain;
+ retValue.dex = dexExpGain;
+ retValue.agi = agiExpGain;
this.staminaBonus += staminaGain;
if (this.logging.general) {
this.log(
+ `${person.whoAmI()}: ` +
"Training completed. Gained: " +
formatNumber(strExpGain, 1) +
" str exp, " +
@@ -1453,80 +1486,77 @@ export class Bladeburner implements IBladeburner {
" max stamina",
);
}
- this.startAction(player, this.action); // Repeat action
break;
}
case ActionTypes["FieldAnalysis"]:
case ActionTypes["Field Analysis"]: {
// Does not use stamina. Effectiveness depends on hacking, int, and cha
let eff =
- 0.04 * Math.pow(player.hacking, 0.3) +
- 0.04 * Math.pow(player.intelligence, 0.9) +
- 0.02 * Math.pow(player.charisma, 0.3);
- eff *= player.bladeburner_analysis_mult;
+ 0.04 * Math.pow(person.hacking, 0.3) +
+ 0.04 * Math.pow(person.intelligence, 0.9) +
+ 0.02 * Math.pow(person.charisma, 0.3);
+ eff *= person.bladeburner_analysis_mult;
if (isNaN(eff) || eff < 0) {
throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
}
- const hackingExpGain = 20 * player.hacking_exp_mult;
- const charismaExpGain = 20 * player.charisma_exp_mult;
+ const hackingExpGain = 20 * person.hacking_exp_mult;
+ const charismaExpGain = 20 * person.charisma_exp_mult;
const rankGain = 0.1 * BitNodeMultipliers.BladeburnerRank;
- player.gainHackingExp(hackingExpGain);
- player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain);
- player.gainCharismaExp(charismaExpGain);
- this.changeRank(player, rankGain);
+ retValue.hack = hackingExpGain;
+ retValue.cha = charismaExpGain;
+ retValue.int = BladeburnerConstants.BaseIntGain;
+ this.changeRank(person, rankGain);
this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
if (this.logging.general) {
this.log(
+ `${person.whoAmI()}: ` +
`Field analysis completed. Gained ${formatNumber(rankGain, 2)} rank, ` +
`${formatNumber(hackingExpGain, 1)} hacking exp, and ` +
`${formatNumber(charismaExpGain, 1)} charisma exp`,
);
}
- this.startAction(player, this.action); // Repeat action
break;
}
case ActionTypes["Recruitment"]: {
- const successChance = this.getRecruitmentSuccessChance(player);
+ const successChance = this.getRecruitmentSuccessChance(person);
+ const recruitTime = this.getRecruitmentTime(person) * 1000;
if (Math.random() < successChance) {
- const expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete;
- player.gainCharismaExp(expGain);
+ const expGain = 2 * BladeburnerConstants.BaseStatGain * recruitTime;
+ retValue.cha = expGain;
++this.teamSize;
if (this.logging.general) {
- this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp");
+ this.log(`${person.whoAmI()}: ` + "Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp");
}
} else {
- const expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete;
- player.gainCharismaExp(expGain);
+ const expGain = BladeburnerConstants.BaseStatGain * recruitTime;
+ retValue.cha = expGain;
if (this.logging.general) {
- this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp");
+ this.log(`${person.whoAmI()}: ` + "Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp");
}
}
- this.startAction(player, this.action); // Repeat action
break;
}
case ActionTypes["Diplomacy"]: {
- const eff = this.getDiplomacyEffectiveness(player);
+ const eff = this.getDiplomacyEffectiveness(person);
this.getCurrentCity().chaos *= eff;
if (this.getCurrentCity().chaos < 0) {
this.getCurrentCity().chaos = 0;
}
if (this.logging.general) {
this.log(
- `Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`,
+ `${person.whoAmI()}: Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`,
);
}
- this.startAction(player, this.action); // Repeat Action
break;
}
case ActionTypes["Hyperbolic Regeneration Chamber"]: {
- player.regenerateHp(BladeburnerConstants.HrcHpGain);
+ person.regenerateHp(BladeburnerConstants.HrcHpGain);
const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100);
this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
- this.startAction(player, this.action);
if (this.logging.general) {
this.log(
- `Rested in Hyperbolic Regeneration Chamber. Restored ${
+ `${person.whoAmI()}: Rested in Hyperbolic Regeneration Chamber. Restored ${
BladeburnerConstants.HrcHpGain
} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`,
);
@@ -1545,24 +1575,35 @@ export class Bladeburner implements IBladeburner {
this.operations[operation].count += (60 * 3 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
}
if (this.logging.general) {
- this.log(`Incited violence in the synthoid communities.`);
+ this.log(`${person.whoAmI()}: ` + `Incited violence in the synthoid communities.`);
}
for (const cityName of Object.keys(this.cities)) {
const city = this.cities[cityName];
city.chaos += 10;
city.chaos += city.chaos / (Math.log(city.chaos) / Math.log(10));
}
-
- this.startAction(player, this.action);
break;
}
default:
- console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`);
+ console.error(`Bladeburner.completeAction() called for invalid action: ${actionIdent.type}`);
break;
}
+ return retValue;
}
- changeRank(player: IPlayer, change: number): void {
+ infiltrateSynthoidCommunities(): void {
+ for (const contract of Object.keys(this.contracts)) {
+ this.contracts[contract].count += 1;
+ }
+ for (const operation of Object.keys(this.operations)) {
+ this.operations[operation].count += 1;
+ }
+ if (this.logging.general) {
+ this.log(`Sleeve: Infiltrate the synthoid communities.`);
+ }
+ }
+
+ changeRank(person: IPerson, change: number): void {
if (isNaN(change)) {
throw new Error("NaN passed into Bladeburner.changeRank()");
}
@@ -1583,7 +1624,7 @@ export class Bladeburner implements IBladeburner {
if (bladeburnerFac.isMember) {
const favorBonus = 1 + bladeburnerFac.favor / 100;
bladeburnerFac.playerReputation +=
- BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus;
+ BladeburnerConstants.RankToFactionRepFactor * change * person.faction_rep_mult * favorBonus;
}
}
@@ -1614,7 +1655,19 @@ export class Bladeburner implements IBladeburner {
this.actionTimeOverflow = 0;
if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
- return this.completeAction(router, player);
+ let retValue = this.completeAction(player, player, this.action);
+ player.gainMoney(retValue.money, "bladeburner");
+ player.gainStats(retValue);
+ // Operation Daedalus
+ const action = this.getActionObject(this.action);
+ if (action == null || !(action instanceof BlackOperation)) {
+ throw new Error("Failed to get BlackOperation Object for: " + this.action.name);
+ } else if (action.name === BlackOperationNames.OperationDaedalus && this.blackops[action.name]) {
+ this.resetAction();
+ router.toBitVerse(false, false);
+ } else if(this.action.type != ActionTypes["BlackOperation"] && this.action.type != ActionTypes["BlackOp"]) {
+ this.startAction(player, this.action); // Repeat action
+ }
}
}
@@ -2093,67 +2146,57 @@ export class Bladeburner implements IBladeburner {
}
}
- getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ getActionTimeNetscriptFn(person: IPerson, type: string, name: string): number|string {
const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) {
- workerScript.log("bladeburner.getActionTime", () => errorLogText);
- return -1;
+ return "bladeburner.getActionTime";
}
const actionObj = this.getActionObject(actionId);
if (actionObj == null) {
- workerScript.log("bladeburner.getActionTime", () => errorLogText);
- return -1;
+ return "bladeburner.getActionTime";
}
-
switch (actionId.type) {
case ActionTypes["Contract"]:
case ActionTypes["Operation"]:
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]:
- return actionObj.getActionTime(this) * 1000;
+ return actionObj.getActionTime(this, person) * 1000;
case ActionTypes["Training"]:
case ActionTypes["Field Analysis"]:
case ActionTypes["FieldAnalysis"]:
return 30000;
case ActionTypes["Recruitment"]:
- return this.getRecruitmentTime(player) * 1000;
+ return this.getRecruitmentTime(person) * 1000;
case ActionTypes["Diplomacy"]:
case ActionTypes["Hyperbolic Regeneration Chamber"]:
case ActionTypes["Incite Violence"]:
return 60000;
default:
- workerScript.log("bladeburner.getActionTime", () => errorLogText);
- return -1;
+ return "bladeburner.getActionTime";
}
}
getActionEstimatedSuccessChanceNetscriptFn(
- player: IPlayer,
+ person: IPerson,
type: string,
name: string,
- workerScript: WorkerScript,
- ): [number, number] {
- const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ ): [number, number]|string {
const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) {
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText);
- return [-1, -1];
+ return "bladeburner.getActionEstimatedSuccessChance";
}
const actionObj = this.getActionObject(actionId);
if (actionObj == null) {
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText);
- return [-1, -1];
+ return "bladeburner.getActionEstimatedSuccessChance";
}
-
switch (actionId.type) {
case ActionTypes["Contract"]:
case ActionTypes["Operation"]:
case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]:
- return actionObj.getEstSuccessChance(this);
+ return actionObj.getEstSuccessChance(this, person);
case ActionTypes["Training"]:
case ActionTypes["Field Analysis"]:
case ActionTypes["FieldAnalysis"]:
@@ -2162,12 +2205,11 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["Incite Violence"]:
return [1, 1];
case ActionTypes["Recruitment"]: {
- const recChance = this.getRecruitmentSuccessChance(player);
- return [recChance, recChance];
- }
+ const recChance = this.getRecruitmentSuccessChance(person);
+ return [recChance, recChance];
+ }
default:
- workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText);
- return [-1, -1];
+ return "bladeburner.getActionEstimatedSuccessChance";
}
}
diff --git a/src/Bladeburner/IAction.tsx b/src/Bladeburner/IAction.tsx
index 274bae4c9..666ffbfe9 100644
--- a/src/Bladeburner/IAction.tsx
+++ b/src/Bladeburner/IAction.tsx
@@ -1,3 +1,4 @@
+import { IPerson } from "../PersonObjects/IPerson";
import { IBladeburner } from "./IBladeburner";
interface IStatsMultiplier {
@@ -55,15 +56,15 @@ export interface IAction {
teamCount: number;
getDifficulty(): number;
- attempt(inst: IBladeburner): boolean;
+ attempt(inst: IBladeburner, person: IPerson): boolean;
getActionTimePenalty(): number;
- getActionTime(inst: IBladeburner): number;
+ getActionTime(inst: IBladeburner, person: IPerson): number;
getTeamSuccessBonus(inst: IBladeburner): number;
getActionTypeSkillSuccessBonus(inst: IBladeburner): number;
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number;
getChaosDifficultyBonus(inst: IBladeburner): number;
- getEstSuccessChance(inst: IBladeburner): [number, number];
- getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number;
+ getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number];
+ getSuccessChance(inst: IBladeburner, person: IPerson, params: ISuccessChanceParams): number;
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number;
setMaxLevel(baseSuccessesPerLevel: number): void;
toJSON(): any;
diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts
index 840ed20f4..f757f9d66 100644
--- a/src/Bladeburner/IBladeburner.ts
+++ b/src/Bladeburner/IBladeburner.ts
@@ -3,6 +3,8 @@ import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
+import { IPerson } from "../PersonObjects/IPerson";
+import { ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
@@ -70,13 +72,12 @@ export interface IBladeburner {
getGeneralActionNamesNetscriptFn(): string[];
getSkillNamesNetscriptFn(): string[];
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
- getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
+ getActionTimeNetscriptFn(person: IPerson, type: string, name: string): number|string;
getActionEstimatedSuccessChanceNetscriptFn(
- player: IPlayer,
+ person: IPerson,
type: string,
name: string,
- workerScript: WorkerScript,
- ): [number, number];
+ ): [number, number]|string;
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
@@ -95,20 +96,22 @@ export interface IBladeburner {
triggerMigration(sourceCityName: string): void;
triggerPotentialMigration(sourceCityName: string, chance: number): void;
randomEvent(): void;
- gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
getDiplomacyEffectiveness(player: IPlayer): number;
- getRecruitmentSuccessChance(player: IPlayer): number;
- getRecruitmentTime(player: IPlayer): number;
+ getRecruitmentSuccessChance(player: IPerson): number;
+ getRecruitmentTime(player: IPerson): number;
resetSkillMultipliers(): void;
updateSkillMultipliers(): void;
- completeOperation(success: boolean): void;
+ completeOperation(success: boolean, player: IPlayer): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
- completeContract(success: boolean): void;
- completeAction(router: IRouter, player: IPlayer): void;
+ completeContract(success: boolean, actionIdent: IActionIdentifier): void;
+ completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier): ITaskTracker;
+ infiltrateSynthoidCommunities(): void;
changeRank(player: IPlayer, change: number): void;
processAction(router: IRouter, player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void;
create(): void;
process(router: IRouter, player: IPlayer): void;
+ getActionStats(action: IAction, success: boolean): ITaskTracker;
+ sleeveSupport(joining: boolean): void;
}
diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx
index ae406f14d..7c45836c3 100644
--- a/src/Bladeburner/ui/BlackOpElem.tsx
+++ b/src/Bladeburner/ui/BlackOpElem.tsx
@@ -37,7 +37,7 @@ export function BlackOpElem(props: IProps): React.ReactElement {
const isActive =
props.bladeburner.action.type === ActionTypes["BlackOperation"] &&
props.action.name === props.bladeburner.action.name;
- const actionTime = props.action.getActionTime(props.bladeburner);
+ const actionTime = props.action.getActionTime(props.bladeburner, props.player);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx
index fad0865df..5edf04842 100644
--- a/src/Bladeburner/ui/ContractElem.tsx
+++ b/src/Bladeburner/ui/ContractElem.tsx
@@ -32,7 +32,7 @@ export function ContractElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete,
);
- const actionTime = props.action.getActionTime(props.bladeburner);
+ const actionTime = props.action.getActionTime(props.bladeburner, props.player);
const actionData = Contracts[props.action.name];
if (actionData === undefined) {
diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx
index 6e50fd547..e217e4225 100644
--- a/src/Bladeburner/ui/OperationElem.tsx
+++ b/src/Bladeburner/ui/OperationElem.tsx
@@ -33,7 +33,7 @@ export function OperationElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete,
);
- const actionTime = props.action.getActionTime(props.bladeburner);
+ const actionTime = props.action.getActionTime(props.bladeburner, props.player);
const actionData = Operations[props.action.name];
if (actionData === undefined) {
diff --git a/src/Bladeburner/ui/SuccessChance.tsx b/src/Bladeburner/ui/SuccessChance.tsx
index 934b8338c..285a730d6 100644
--- a/src/Bladeburner/ui/SuccessChance.tsx
+++ b/src/Bladeburner/ui/SuccessChance.tsx
@@ -4,6 +4,7 @@ import { StealthIcon } from "./StealthIcon";
import { KillIcon } from "./KillIcon";
import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner";
+import { Player } from "../../Player";
interface IProps {
bladeburner: IBladeburner;
@@ -11,7 +12,7 @@ interface IProps {
}
export function SuccessChance(props: IProps): React.ReactElement {
- const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
+ const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner, Player);
let chance = <>>;
if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) {
diff --git a/src/Crime/Crime.ts b/src/Crime/Crime.ts
index 4551677a2..94820e466 100644
--- a/src/Crime/Crime.ts
+++ b/src/Crime/Crime.ts
@@ -1,6 +1,6 @@
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
-import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
+import { IPerson } from "../PersonObjects/IPerson";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
@@ -108,7 +108,7 @@ export class Crime {
return this.time;
}
- successRate(p: IPlayerOrSleeve): number {
+ successRate(p: IPerson): number {
let chance: number =
this.hacking_success_weight * p.hacking +
this.strength_success_weight * p.strength +
diff --git a/src/Hospital/Hospital.ts b/src/Hospital/Hospital.ts
index d811cae54..4122d9f03 100644
--- a/src/Hospital/Hospital.ts
+++ b/src/Hospital/Hospital.ts
@@ -1,7 +1,7 @@
import { CONSTANTS } from "../Constants";
-import { IPlayer } from "../PersonObjects/IPlayer";
+import { IPerson } from "../PersonObjects/IPerson";
-export function getHospitalizationCost(p: IPlayer): number {
+export function getHospitalizationCost(p: IPerson): number {
if (p.money < 0) {
return 0;
}
@@ -9,7 +9,7 @@ export function getHospitalizationCost(p: IPlayer): number {
return Math.min(p.money * 0.1, (p.max_hp - p.hp) * CONSTANTS.HospitalCostPerHp);
}
-export function calculateHospitalizationCost(p: IPlayer, damage: number): number {
+export function calculateHospitalizationCost(p: IPerson, damage: number): number {
const oldhp = p.hp;
p.hp -= damage;
const cost = getHospitalizationCost(p);
diff --git a/src/NetscriptFunctions/Bladeburner.ts b/src/NetscriptFunctions/Bladeburner.ts
index e6da8bbed..1692b124f 100644
--- a/src/NetscriptFunctions/Bladeburner.ts
+++ b/src/NetscriptFunctions/Bladeburner.ts
@@ -134,7 +134,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
- return bladeburner.getActionTimeNetscriptFn(player, type, name, workerScript);
+ let time = bladeburner.getActionTimeNetscriptFn(player, type, name);
+ if(typeof time === 'string'){
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ workerScript.log("bladeburner.getActionTime", () => errorLogText);
+ return -1;
+ } else {
+ return time;
+ }
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e);
}
@@ -150,7 +157,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
- return bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name, workerScript);
+ let chance = bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name);
+ if(typeof chance === 'string'){
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ workerScript.log("bladeburner.getActionTime", () => errorLogText);
+ return [-1, -1];
+ } else {
+ return chance;
+ }
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
}
diff --git a/src/PersonObjects/IPerson.ts b/src/PersonObjects/IPerson.ts
new file mode 100644
index 000000000..ab51f85c0
--- /dev/null
+++ b/src/PersonObjects/IPerson.ts
@@ -0,0 +1,64 @@
+// Interface that represents either the player (PlayerObject) or
+// a Sleeve. Used for functions that need to take in both.
+
+import { ITaskTracker } from "./ITaskTracker";
+
+export interface IPerson {
+ // Stats
+ hacking: number;
+ strength: number;
+ defense: number;
+ dexterity: number;
+ agility: number;
+ charisma: number;
+ intelligence: number;
+ hp: number;
+ max_hp: number;
+ money: number;
+
+ // Experience
+ hacking_exp: number;
+ strength_exp: number;
+ defense_exp: number;
+ dexterity_exp: number;
+ agility_exp: number;
+ charisma_exp: number;
+ intelligence_exp: number;
+
+ // Multipliers
+ hacking_exp_mult: number;
+ strength_exp_mult: number;
+ defense_exp_mult: number;
+ dexterity_exp_mult: number;
+ agility_exp_mult: number;
+ charisma_exp_mult: number;
+ hacking_mult: number;
+ strength_mult: number;
+ defense_mult: number;
+ dexterity_mult: number;
+ agility_mult: number;
+ charisma_mult: number;
+
+ company_rep_mult: number;
+ faction_rep_mult: number;
+
+ crime_money_mult: number;
+ crime_success_mult: number;
+
+ bladeburner_analysis_mult: number;
+
+ getIntelligenceBonus(weight: number): number;
+ gainHackingExp(exp: number): void;
+ gainStrengthExp(exp: number): void;
+ gainDefenseExp(exp: number): void;
+ gainDexterityExp(exp: number): void;
+ gainAgilityExp(exp: number): void;
+ gainCharismaExp(exp: number): void;
+ gainIntelligenceExp(exp: number): void;
+ gainStats(retValue: ITaskTracker): void;
+ calculateSkill(exp: number, mult?: number): number;
+ takeDamage(amt: number): boolean;
+ regenerateHp: (amt: number) => void;
+ queryStatFromString: (str: string) => number;
+ whoAmI: () => string;
+ }
\ No newline at end of file
diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts
index ca1f2940a..b4ca6c20f 100644
--- a/src/PersonObjects/IPlayer.ts
+++ b/src/PersonObjects/IPlayer.ts
@@ -30,8 +30,9 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { ISkillProgress } from "./formulas/skill";
import { PlayerAchievement } from "../Achievements/Achievements";
+import { IPerson } from "./IPerson";
-export interface IPlayer {
+export interface IPlayer extends IPerson {
// Class members
augmentations: IPlayerOwnedAugmentation[];
bitNodeN: number;
@@ -185,13 +186,6 @@ export interface IPlayer {
canAccessGang(): boolean;
canAccessGrafting(): boolean;
canAfford(cost: number): boolean;
- gainHackingExp(exp: number): void;
- gainStrengthExp(exp: number): void;
- gainDefenseExp(exp: number): void;
- gainDexterityExp(exp: number): void;
- gainAgilityExp(exp: number): void;
- gainCharismaExp(exp: number): void;
- gainIntelligenceExp(exp: number): void;
gainMoney(money: number, source: string): void;
getCurrentServer(): BaseServer;
getGangFaction(): Faction;
@@ -213,7 +207,6 @@ export interface IPlayer {
process(router: IRouter, numCycles?: number): void;
reapplyAllAugmentations(resetMultipliers?: boolean): void;
reapplyAllSourceFiles(): void;
- regenerateHp(amt: number): void;
setMoney(amt: number): void;
singularityStopWork(): string;
startBladeburner(p: any): void;
@@ -240,12 +233,9 @@ export interface IPlayer {
startGang(facName: string, isHacking: boolean): void;
startWork(companyName: string): void;
startWorkPartTime(companyName: string): void;
- takeDamage(amt: number): boolean;
travel(to: CityName): boolean;
giveExploit(exploit: Exploit): void;
giveAchievement(achievementId: string): void;
- queryStatFromString(str: string): number;
- getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number;
quitJob(company: string): void;
hasJob(): boolean;
@@ -266,7 +256,6 @@ export interface IPlayer {
resetMultipliers(): void;
prestigeAugmentation(): void;
prestigeSourceFile(): void;
- calculateSkill(exp: number, mult?: number): number;
calculateSkillProgress(exp: number, mult?: number): ISkillProgress;
resetWorkStatus(generalType?: string, group?: string, workType?: string): void;
getWorkHackExpGain(): number;
diff --git a/src/PersonObjects/IPlayerOrSleeve.ts b/src/PersonObjects/IPlayerOrSleeve.ts
deleted file mode 100644
index 366c31dde..000000000
--- a/src/PersonObjects/IPlayerOrSleeve.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// Interface that represents either the player (PlayerObject) or
-// a Sleeve. Used for functions that need to take in both.
-
-export interface IPlayerOrSleeve {
- // Stats
- hacking: number;
- strength: number;
- defense: number;
- dexterity: number;
- agility: number;
- charisma: number;
- intelligence: number;
-
- // Experience
- hacking_exp: number;
- strength_exp: number;
- defense_exp: number;
- dexterity_exp: number;
- agility_exp: number;
- charisma_exp: number;
-
- // Multipliers
- crime_success_mult: number;
-
- getIntelligenceBonus(weight: number): number;
-}
diff --git a/src/PersonObjects/ITaskTracker.ts b/src/PersonObjects/ITaskTracker.ts
new file mode 100644
index 000000000..8b2d92efa
--- /dev/null
+++ b/src/PersonObjects/ITaskTracker.ts
@@ -0,0 +1,25 @@
+// Interface that defines a generic object used to track experience/money
+// earnings for tasks
+export interface ITaskTracker {
+ hack: number;
+ str: number;
+ def: number;
+ dex: number;
+ agi: number;
+ cha: number;
+ int: number;
+ money: number;
+ }
+
+ export function createTaskTracker(): ITaskTracker {
+ return {
+ hack: 0,
+ str: 0,
+ def: 0,
+ dex: 0,
+ agi: 0,
+ cha: 0,
+ int: 0,
+ money: 0,
+ };
+ }
\ No newline at end of file
diff --git a/src/PersonObjects/Person.ts b/src/PersonObjects/Person.ts
index 18c39a2ed..0bee0658a 100644
--- a/src/PersonObjects/Person.ts
+++ b/src/PersonObjects/Person.ts
@@ -1,4 +1,4 @@
-// Base class representing a person-like object
+import * as generalMethods from "./Player/PlayerObjectGeneralMethods";
import { Augmentation } from "../Augmentation/Augmentation";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@@ -6,32 +6,12 @@ import { CityName } from "../Locations/data/CityNames";
import { CONSTANTS } from "../Constants";
import { calculateSkill } from "./formulas/skill";
import { calculateIntelligenceBonus } from "./formulas/intelligence";
+import { IPerson } from "./IPerson";
+import { Reviver } from "../utils/JSONReviver";
+import { ITaskTracker } from "./ITaskTracker";
-// Interface that defines a generic object used to track experience/money
-// earnings for tasks
-export interface ITaskTracker {
- hack: number;
- str: number;
- def: number;
- dex: number;
- agi: number;
- cha: number;
- money: number;
-}
-
-export function createTaskTracker(): ITaskTracker {
- return {
- hack: 0,
- str: 0,
- def: 0,
- dex: 0,
- agi: 0,
- cha: 0,
- money: 0,
- };
-}
-
-export abstract class Person {
+// Base class representing a person-like object
+export abstract class Person implements IPerson {
/**
* Stats
*/
@@ -44,6 +24,7 @@ export abstract class Person {
intelligence = 1;
hp = 10;
max_hp = 10;
+ money = 0;
/**
* Experience
@@ -240,4 +221,35 @@ export abstract class Person {
getIntelligenceBonus(weight: number): number {
return calculateIntelligenceBonus(this.intelligence, weight);
}
+
+ abstract takeDamage(amt: number): boolean;
+
+ abstract whoAmI(): string;
+
+ gainHackingExp: (exp: number) => void;
+ gainStrengthExp: (exp: number) => void;
+ gainDefenseExp: (exp: number) => void;
+ gainDexterityExp: (exp: number) => void;
+ gainAgilityExp: (exp: number) => void;
+ gainCharismaExp: (exp: number) => void;
+ gainIntelligenceExp: (exp: number) => void;
+ gainStats: (retValue: ITaskTracker) => void;
+ calculateSkill: (exp: number, mult: number) => number;
+ regenerateHp: (amt: number) => void;
+ queryStatFromString: (str: string) => number;
+ constructor() {
+ this.gainHackingExp = generalMethods.gainHackingExp;
+ this.gainStrengthExp = generalMethods.gainStrengthExp;
+ this.gainDefenseExp = generalMethods.gainDefenseExp;
+ this.gainDexterityExp = generalMethods.gainDexterityExp;
+ this.gainAgilityExp = generalMethods.gainAgilityExp;
+ this.gainCharismaExp = generalMethods.gainCharismaExp;
+ this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;
+ this.gainStats = generalMethods.gainStats;
+ this.calculateSkill = generalMethods.calculateSkill;
+ this.regenerateHp = generalMethods.regenerateHp;
+ this.queryStatFromString = generalMethods.queryStatFromString;
+ }
}
+
+Reviver.constructors.Person = Person;
\ No newline at end of file
diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts
index 943598b60..fec5ae596 100644
--- a/src/PersonObjects/Player/PlayerObject.ts
+++ b/src/PersonObjects/Player/PlayerObject.ts
@@ -37,6 +37,7 @@ import { ISkillProgress } from "../formulas/skill";
import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
+import { ITaskTracker } from "../ITaskTracker";
export class PlayerObject implements IPlayer {
// Class members
@@ -201,6 +202,7 @@ export class PlayerObject implements IPlayer {
gainAgilityExp: (exp: number) => void;
gainCharismaExp: (exp: number) => void;
gainIntelligenceExp: (exp: number) => void;
+ gainStats: (retValue: ITaskTracker) => void;
gainMoney: (money: number, source: string) => void;
getCurrentServer: () => BaseServer;
getGangFaction: () => Faction;
@@ -302,6 +304,9 @@ export class PlayerObject implements IPlayer {
graftAugmentationWork: (numCycles: number) => boolean;
finishGraftAugmentationWork: (cancelled: boolean) => string;
applyEntropy: (stacks?: number) => void;
+ whoAmI(): string{
+ return 'Player';
+ }
constructor() {
//Skills and stats
@@ -521,6 +526,7 @@ export class PlayerObject implements IPlayer {
this.gainAgilityExp = generalMethods.gainAgilityExp;
this.gainCharismaExp = generalMethods.gainCharismaExp;
this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;
+ this.gainStats = generalMethods.gainStats;
this.queryStatFromString = generalMethods.queryStatFromString;
this.resetWorkStatus = generalMethods.resetWorkStatus;
this.processWorkEarnings = generalMethods.processWorkEarnings;
diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
index 00f0acbd7..d28850706 100644
--- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
+++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
@@ -66,6 +66,8 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames";
+import { ITaskTracker } from "../ITaskTracker";
+import { IPerson } from "../IPerson";
export function init(this: IPlayer): void {
/* Initialize Player's home computer */
@@ -227,7 +229,7 @@ export function receiveInvite(this: IPlayer, factionName: string): void {
}
//Calculates skill level based on experience. The same formula will be used for every skill
-export function calculateSkill(this: IPlayer, exp: number, mult = 1): number {
+export function calculateSkill(this: IPerson, exp: number, mult = 1): number {
return calculateSkillF(exp, mult);
}
@@ -391,7 +393,7 @@ export function gainHackingExp(this: IPlayer, exp: number): void {
this.hacking = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
}
-export function gainStrengthExp(this: IPlayer, exp: number): void {
+export function gainStrengthExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainStrengthExp()");
return;
@@ -404,7 +406,7 @@ export function gainStrengthExp(this: IPlayer, exp: number): void {
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
}
-export function gainDefenseExp(this: IPlayer, exp: number): void {
+export function gainDefenseExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into player.gainDefenseExp()");
return;
@@ -420,7 +422,7 @@ export function gainDefenseExp(this: IPlayer, exp: number): void {
this.hp = Math.round(this.max_hp * ratio);
}
-export function gainDexterityExp(this: IPlayer, exp: number): void {
+export function gainDexterityExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainDexterityExp()");
return;
@@ -436,7 +438,7 @@ export function gainDexterityExp(this: IPlayer, exp: number): void {
);
}
-export function gainAgilityExp(this: IPlayer, exp: number): void {
+export function gainAgilityExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainAgilityExp()");
return;
@@ -449,7 +451,7 @@ export function gainAgilityExp(this: IPlayer, exp: number): void {
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
}
-export function gainCharismaExp(this: IPlayer, exp: number): void {
+export function gainCharismaExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainCharismaExp()");
return;
@@ -462,17 +464,27 @@ export function gainCharismaExp(this: IPlayer, exp: number): void {
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
}
-export function gainIntelligenceExp(this: IPlayer, exp: number): void {
+export function gainIntelligenceExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
return;
}
if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
this.intelligence_exp += exp;
- this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp));
+ this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp, 1));
}
}
+export function gainStats(this: IPerson, retValue: ITaskTracker): void {
+ this.gainHackingExp(retValue.hack * this.hacking_exp_mult);
+ this.gainStrengthExp(retValue.str * this.strength_exp_mult);
+ this.gainDefenseExp(retValue.def * this.defense_exp_mult);
+ this.gainDexterityExp(retValue.dex * this.dexterity_exp_mult);
+ this.gainAgilityExp(retValue.agi * this.agility_exp_mult);
+ this.gainCharismaExp(retValue.cha * this.charisma_exp_mult);
+ this.gainIntelligenceExp(retValue.int);
+}
+
//Given a string expression like "str" or "strength", returns the given stat
export function queryStatFromString(this: IPlayer, str: string): number {
const tempStr = str.toLowerCase();
@@ -1718,7 +1730,7 @@ export function takeDamage(this: IPlayer, amt: number): boolean {
}
}
-export function regenerateHp(this: IPlayer, amt: number): void {
+export function regenerateHp(this: IPerson, amt: number): void {
if (typeof amt !== "number") {
console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
return;
diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts
index 2b931d37f..74103f5dc 100644
--- a/src/PersonObjects/Sleeve/Sleeve.ts
+++ b/src/PersonObjects/Sleeve/Sleeve.ts
@@ -9,7 +9,8 @@
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
-import { Person, ITaskTracker, createTaskTracker } from "../Person";
+import { Person } from "../Person";
+import { ITaskTracker, createTaskTracker } from "../ITaskTracker";
import { Augmentation } from "../../Augmentation/Augmentation";
@@ -33,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
+import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
export class Sleeve extends Person {
/**
@@ -58,6 +60,7 @@ export class Sleeve extends Person {
* Faction/Company Work: Name of Faction/Company
* Crime: Money earned if successful
* Class/Gym: Name of university/gym
+ * Bladeburner: success chance
*/
currentTaskLocation = "";
@@ -101,6 +104,16 @@ export class Sleeve extends Person {
*/
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 = "";
+
/**
* Keeps track of events/notifications for this sleeve
*/
@@ -151,7 +164,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;
@@ -160,6 +173,7 @@ export class Sleeve extends Person {
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;
this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money);
@@ -175,14 +189,14 @@ export class Sleeve extends Person {
*/
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
-
+
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
return retValue;
}
if (Math.random() < crime.successRate(this)) {
@@ -206,11 +220,57 @@ export class Sleeve extends Person {
this.currentTaskTime = 0;
return retValue;
}
- } else {
- // For other crimes... I dont think anything else needs to be done
+ } else if (this.currentTask === SleeveTaskType.Bladeburner) {
+ // For bladeburner, all experience and money is gained at the end
+ const bb = p.bladeburner;
+ if (bb === null) {
+ const errorLogText = `bladeburner is null`;
+ console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`)
+ this.resetTaskStatus(p);
+ return retValue;
+ }
+
+ 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") {
+ type = 'Contracts';
+ name = this.bbContract;
+ } else {
+ type = 'General';
+ name = this.bbAction;
+ }
+
+ const actionIdent = bb.getActionIdFromTypeAndName(type, name);
+ if(actionIdent === null) {
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`)
+ this.resetTaskStatus(p);
+ return retValue;
+ }
+
+ const action = bb.getActionObject(actionIdent);
+ if((action?.count ?? 0) > 0) {
+ const bbRetValue = bb.completeAction(p, this, actionIdent);
+ if(bbRetValue) {
+ retValue = this.gainExperience(p, bbRetValue);
+ this.gainMoney(p, bbRetValue);
+
+ // Do not reset task to IDLE
+ this.currentTaskTime = 0;
+ return retValue;
+ }
+ }
+ }
}
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
return retValue;
}
@@ -260,50 +320,56 @@ export class Sleeve extends Person {
const pDexExp = exp.dex * multFac;
const pAgiExp = exp.agi * multFac;
const pChaExp = exp.cha * multFac;
+ const pIntExp = exp.int * multFac;
// Experience is gained by both this sleeve and player
if (pHackExp > 0) {
- this.hacking_exp += pHackExp;
+ this.gainHackingExp(pHackExp);
p.gainHackingExp(pHackExp);
this.earningsForPlayer.hack += pHackExp;
this.earningsForTask.hack += pHackExp;
}
if (pStrExp > 0) {
- this.strength_exp += pStrExp;
+ this.gainStrengthExp(pStrExp);
p.gainStrengthExp(pStrExp);
this.earningsForPlayer.str += pStrExp;
this.earningsForTask.str += pStrExp;
}
if (pDefExp > 0) {
- this.defense_exp += pDefExp;
+ this.gainDefenseExp(pDefExp);
p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp;
this.earningsForTask.def += pDefExp;
}
if (pDexExp > 0) {
- this.dexterity_exp += pDexExp;
+ this.gainDexterityExp(pDexExp);
p.gainDexterityExp(pDexExp);
this.earningsForPlayer.dex += pDexExp;
this.earningsForTask.dex += pDexExp;
}
if (pAgiExp > 0) {
- this.agility_exp += pAgiExp;
+ this.gainAgilityExp(pAgiExp);
p.gainAgilityExp(pAgiExp);
this.earningsForPlayer.agi += pAgiExp;
this.earningsForTask.agi += pAgiExp;
}
if (pChaExp > 0) {
- this.charisma_exp += pChaExp;
+ this.gainCharismaExp(pChaExp);
p.gainCharismaExp(pChaExp);
this.earningsForPlayer.cha += pChaExp;
this.earningsForTask.cha += pChaExp;
}
+ if (pIntExp > 0) {
+ this.gainIntelligenceExp(pIntExp);
+ p.gainIntelligenceExp(pIntExp);
+ }
+
// Record earnings for other sleeves
this.earningsForSleeves.hack += pHackExp * (this.sync / 100);
this.earningsForSleeves.str += pStrExp * (this.sync / 100);
@@ -320,7 +386,8 @@ export class Sleeve extends Person {
dex: pDexExp * (this.sync / 100),
agi: pAgiExp * (this.sync / 100),
cha: pChaExp * (this.sync / 100),
- money: 0,
+ int: pIntExp * (this.sync / 100),
+ money: exp.money,
};
}
@@ -445,7 +512,7 @@ export class Sleeve extends Person {
this.charisma_exp = 0;
// Reset task-related stuff
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.shockRecovery(p);
@@ -537,18 +604,18 @@ export class Sleeve extends Person {
}
case SleeveTaskType.Recovery:
this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);
- if (this.shock >= 100) this.resetTaskStatus();
+ if (this.shock >= 100) this.resetTaskStatus(p);
break;
case SleeveTaskType.Synchro:
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);
- if (this.sync >= 100) this.resetTaskStatus();
+ if (this.sync >= 100) this.resetTaskStatus(p);
break;
default:
break;
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
- if (this.currentTask === SleeveTaskType.Crime) {
+ if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) {
retValue = this.finishTask(p);
} else {
this.finishTask(p);
@@ -565,7 +632,15 @@ export class Sleeve extends Person {
/**
* Resets all parameters used to keep information about the current task
*/
- resetTaskStatus(): void {
+ resetTaskStatus(p: IPlayer): void {
+ if (this.bbAction == 'Support main sleeve') {
+ p.bladeburner?.sleeveSupport(false);
+ }
+ if (this.currentTask == SleeveTaskType.Class) {
+ let retVal = createTaskTracker();
+ retVal.int = CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.currentTaskTime / 1000);
+ this.gainExperience(p, retVal);//Wont be shared with other sleeves
+ }
this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle;
@@ -576,13 +651,15 @@ export class Sleeve extends Person {
this.currentTaskLocation = "";
this.gymStatType = "";
this.className = "";
+ this.bbAction = "";
+ this.bbContract = "";
}
shockRecovery(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Recovery;
@@ -593,7 +670,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Synchro;
@@ -607,7 +684,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
// Set exp/money multipliers based on which university.
@@ -801,7 +878,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
const company: Company | null = Companies[companyName];
@@ -867,7 +944,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
const factionInfo = faction.getInfo();
@@ -918,7 +995,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
- this.resetTaskStatus();
+ this.resetTaskStatus(p);
}
// Set exp/money multipliers based on which university.
@@ -986,6 +1063,151 @@ export class Sleeve extends Person {
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;
+ this.currentTaskLocation = '';
+
+ let time = 0;
+ switch (action) {
+ case "Field Analysis":
+ 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":
+ time = this.getBladeburnerActionTime(p, 'General', action);
+ const recruitTime = p.bladeburner?.getRecruitmentTime(this) ?? 0 * 1000;
+ this.gainRatesForTask.cha = 2 * BladeburnerConstants.BaseStatGain * recruitTime;
+ this.currentTaskLocation = (p.bladeburner?.getRecruitmentSuccessChance(this) ?? 0).toString() + '%';
+ break;
+ case "Diplomacy":
+ time = this.getBladeburnerActionTime(p, 'General', action);
+ break;
+ case "Infiltrate synthoids":
+ time = 60000;
+ break;
+ case "Support main sleeve":
+ p.bladeburner?.sleeveSupport(true);
+ time = 0;
+ break;
+ case "Take on Contracts":
+ time = this.getBladeburnerActionTime(p, 'Contracts', contract);
+ this.contractGainRates(p, 'Contracts', contract);
+ this.currentTaskLocation = this.contractSuccessChance(p, 'Contracts', contract);
+ break;
+ }
+
+ this.bbAction = action;
+ this.bbContract = contract;
+ this.currentTaskMaxTime = time;
+ this.currentTask = SleeveTaskType.Bladeburner;
+ return true;
+ }
+
+ contractSuccessChance(p: IPlayer, type: string, name: string): string {
+ const bb = p.bladeburner;
+ if(bb === null){
+ const errorLogText = `bladeburner is null`;
+ console.error(`Function: sleeves.contractSuccessChance; Message: '${errorLogText}'`)
+ return '0%';
+ }
+ const chances = bb.getActionEstimatedSuccessChanceNetscriptFn(this, type, name);
+ if(typeof chances === 'string'){
+ console.error(`Function: sleeves.contractSuccessChance; Message: '${chances}'`)
+ return '0%';
+ }
+ if(chances[0] >= 1) {
+ return '100%';
+ } else {
+ return `${chances[0]*100}% - ${chances[1]*100}%`;
+ }
+ }
+
+ contractGainRates(p: IPlayer, type: string, name: string): void {
+ const bb = p.bladeburner;
+ if(bb === null){
+ const errorLogText = `bladeburner is null`;
+ console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`)
+ return;
+ }
+ const actionIdent = bb.getActionIdFromTypeAndName(type, name);
+ if(actionIdent === null) {
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`)
+ this.resetTaskStatus(p);
+ return;
+ }
+ const action = bb.getActionObject(actionIdent);
+ if(action === null) {
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ 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);
+ this.gainRatesForTask.money = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money;;
+ }
+
+ getBladeburnerActionTime(p: IPlayer, type: string, name: string): number{//Maybe find workerscript and use original
+ const bb = p.bladeburner;
+ if(bb === null){
+ const errorLogText = `bladeburner is null`;
+ console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`)
+ return -1;
+ }
+
+ const time = bb.getActionTimeNetscriptFn(this, type, name);
+ if(typeof time === 'string'){
+ const errorLogText = `Invalid action: type='${type}' name='${name}'`;
+ console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`)
+ return -1;
+ } else {
+ return time;
+ }
+ }
+
+ 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 {
+ return 'Sleeve';
+ }
+
/**
* Serialize the current object to a JSON save state.
*/
diff --git a/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts b/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts
index 61f216d03..68881383f 100644
--- a/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts
+++ b/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts
@@ -9,6 +9,7 @@ export enum SleeveTaskType {
Crime,
Class,
Gym,
+ Bladeburner,
Recovery,
Synchro,
}
diff --git a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx
index 5d73a762d..f45e7a0d5 100644
--- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx
+++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx
@@ -40,7 +40,7 @@ export function SleeveElem(props: IProps): React.ReactElement {
const [abc, setABC] = useState(["------", "------", "------"]);
function setTask(): void {
- props.sleeve.resetTaskStatus(); // sets to idle
+ props.sleeve.resetTaskStatus(player); // sets to idle
switch (abc[0]) {
case "------":
break;
@@ -59,6 +59,9 @@ export function SleeveElem(props: IProps): React.ReactElement {
case "Workout at Gym":
props.sleeve.workoutAtGym(player, abc[2], abc[1]);
break;
+ case "Perform Bladeburner Actions":
+ props.sleeve.bladeburner(player, abc[1], abc[2]);
+ break;
case "Shock Recovery":
props.sleeve.shockRecovery(player);
break;
@@ -116,6 +119,13 @@ export function SleeveElem(props: IProps): React.ReactElement {
case SleeveTaskType.Gym:
desc = <>This sleeve is currently working out at {props.sleeve.currentTaskLocation}.>;
break;
+ case SleeveTaskType.Bladeburner:
+ let contract = '';
+ if (props.sleeve.bbContract !== '------') {
+ contract = ` - ${props.sleeve.bbContract} (Success Rate: ${props.sleeve.currentTaskLocation})`;
+ }
+ desc = <>This sleeve is currently attempting to {props.sleeve.bbAction}{contract}>
+ break;
case SleeveTaskType.Recovery:
desc = (
<>
@@ -178,8 +188,10 @@ export function SleeveElem(props: IProps): React.ReactElement {
{desc}
- {props.sleeve.currentTask === SleeveTaskType.Crime &&
- createProgressBarText({
+ {(props.sleeve.currentTask === SleeveTaskType.Crime
+ || props.sleeve.currentTask === SleeveTaskType.Bladeburner)
+ && props.sleeve.currentTaskMaxTime > 0
+ && createProgressBarText({
progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
totalTicks: 25,
})}
diff --git a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
index 70d5aac15..383a586e1 100644
--- a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
+++ b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
@@ -10,6 +10,7 @@ import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import { FactionNames } from "../../../Faction/data/FactionNames";
+import { Contract } from "../../../Bladeburner/Contract";
const universitySelectorOptions: string[] = [
"Study Computer Science",
@@ -22,6 +23,8 @@ const universitySelectorOptions: string[] = [
const gymSelectorOptions: string[] = ["Train Strength", "Train Defense", "Train Dexterity", "Train Agility"];
+const bladeburnerSelectorOptions: string[] = ["Field Analysis", "Recruitment", "Diplomacy", "Infiltrate synthoids", "Support main sleeve", "Take on Contracts"];
+
interface IProps {
sleeve: Sleeve;
player: IPlayer;
@@ -82,6 +85,27 @@ function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
});
}
+function possibleContracts(player: IPlayer, sleeve: Sleeve): string[] {
+ const bb = player.bladeburner;
+ if(bb === null){
+ return ["------"];
+ }
+ let contracts = bb.getContractNamesNetscriptFn();
+ for (const otherSleeve of player.sleeves) {
+ if (sleeve === otherSleeve) {
+ continue;
+ }
+ if (otherSleeve.currentTask === SleeveTaskType.Bladeburner
+ && otherSleeve.bbAction == 'Take on Contracts') {
+ contracts = contracts.filter(x => x != otherSleeve.bbContract);
+ }
+ }
+ if(contracts.length === 0){
+ return ["------"];
+ }
+ return contracts;
+}
+
const tasks: {
[key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => ITaskDetails);
["------"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
@@ -90,6 +114,7 @@ const tasks: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Workout at Gym"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
+ ["Perform Bladeburner Actions"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
} = {
@@ -166,6 +191,17 @@ const tasks: {
return { first: gymSelectorOptions, second: () => gyms };
},
+ "Perform Bladeburner Actions": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {
+ return {
+ first: bladeburnerSelectorOptions,
+ second: (s1: string) => {
+ if(s1 === "Take on Contracts"){
+ return possibleContracts(player, sleeve);
+ } else {
+ return ["------"];
+ }
+ } };
+ },
"Shock Recovery": (): ITaskDetails => {
return { first: ["------"], second: () => ["------"] };
},
@@ -182,6 +218,7 @@ const canDo: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Workout at Gym"]: (player: IPlayer, sleeve: Sleeve) => boolean;
+ ["Perform Bladeburner Actions"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => boolean;
} = {
@@ -193,6 +230,7 @@ const canDo: {
[CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),
"Workout at Gym": (player: IPlayer, sleeve: Sleeve) =>
[CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),
+ "Perform Bladeburner Actions": (player: IPlayer, sleeve: Sleeve) => player.inBladeburner(),
"Shock Recovery": (player: IPlayer, sleeve: Sleeve) => sleeve.shock < 100,
Synchronize: (player: IPlayer, sleeve: Sleeve) => sleeve.sync < 100,
};
@@ -224,6 +262,8 @@ function getABC(sleeve: Sleeve): [string, string, string] {
return ["Take University Course", sleeve.className, sleeve.currentTaskLocation];
case SleeveTaskType.Gym:
return ["Workout at Gym", sleeve.gymStatType, sleeve.currentTaskLocation];
+ case SleeveTaskType.Bladeburner:
+ return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
case SleeveTaskType.Recovery:
return ["Shock Recovery", "------", "------"];
case SleeveTaskType.Synchro:
diff --git a/src/PersonObjects/Sleeve/ui/TravelModal.tsx b/src/PersonObjects/Sleeve/ui/TravelModal.tsx
index 0883b1a8d..6bc4af1be 100644
--- a/src/PersonObjects/Sleeve/ui/TravelModal.tsx
+++ b/src/PersonObjects/Sleeve/ui/TravelModal.tsx
@@ -26,7 +26,7 @@ export function TravelModal(props: IProps): React.ReactElement {
}
props.sleeve.city = city as CityName;
player.loseMoney(CONSTANTS.TravelCost, "sleeve");
- props.sleeve.resetTaskStatus();
+ props.sleeve.resetTaskStatus(player);
props.rerender();
props.onClose();
}