Add bladeburner actions to sleeves

This adds bladeburner actions to sleeves. In addition this bulked out the IPerson functionality and updated bladeburner functions to be more sleeve compatible
This commit is contained in:
rderfler 2022-04-14 11:40:59 -04:00
parent acfd164927
commit 2613948bad
24 changed files with 687 additions and 268 deletions

4
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.5.0", "version": "1.6.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bitburner", "name": "bitburner",
"version": "1.5.0", "version": "1.6.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {

@ -5,6 +5,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"
import { BladeburnerConstants } from "./data/Constants"; import { BladeburnerConstants } from "./data/Constants";
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
import { IAction, ISuccessChanceParams } from "./IAction"; import { IAction, ISuccessChanceParams } from "./IAction";
import { IPerson } from "../PersonObjects/IPerson";
class StatsMultiplier { class StatsMultiplier {
[key: string]: number; [key: string]: number;
@ -154,8 +155,8 @@ export class Action implements IAction {
* Tests for success. Should be called when an action has completed * Tests for success. Should be called when an action has completed
* @param inst {Bladeburner} - Bladeburner instance * @param inst {Bladeburner} - Bladeburner instance
*/ */
attempt(inst: IBladeburner): boolean { attempt(inst: IBladeburner, person: IPerson): boolean {
return Math.random() < this.getSuccessChance(inst); return Math.random() < this.getSuccessChance(inst, person);
} }
// To be implemented by subtypes // To be implemented by subtypes
@ -163,13 +164,13 @@ export class Action implements IAction {
return 1; return 1;
} }
getActionTime(inst: IBladeburner): number { getActionTime(inst: IBladeburner, person: IPerson): number {
const difficulty = this.getDifficulty(); const difficulty = this.getDifficulty();
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor; let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
const skillFac = inst.skillMultipliers.actionTime; // Always < 1 const skillFac = inst.skillMultipliers.actionTime; // Always < 1
const effAgility = Player.agility * inst.skillMultipliers.effAgi; const effAgility = person.agility * inst.skillMultipliers.effAgi;
const effDexterity = Player.dexterity * inst.skillMultipliers.effDex; const effDexterity = person.dexterity * inst.skillMultipliers.effDex;
const statFac = const statFac =
0.5 * 0.5 *
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) + (Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
@ -213,12 +214,12 @@ export class Action implements IAction {
return 1; return 1;
} }
getEstSuccessChance(inst: IBladeburner): [number, number] { getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number] {
function clamp(x: number): number { function clamp(x: number): number {
return Math.max(0, Math.min(x, 1)); return Math.max(0, Math.min(x, 1));
} }
const est = this.getSuccessChance(inst, { est: true }); const est = this.getSuccessChance(inst, person, { est: true });
const real = this.getSuccessChance(inst); const real = this.getSuccessChance(inst, person);
const diff = Math.abs(real - est); const diff = Math.abs(real - est);
let low = real - diff; let low = real - diff;
let high = real + diff; let high = real + diff;
@ -234,7 +235,7 @@ export class Action implements IAction {
* @params - options: * @params - options:
* est (bool): Get success chance estimate instead of real success chance * 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) { if (inst == null) {
throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance"); throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");
} }
@ -242,7 +243,7 @@ export class Action implements IAction {
let competence = 0; let competence = 0;
for (const stat of Object.keys(this.weights)) { for (const stat of Object.keys(this.weights)) {
if (this.weights.hasOwnProperty(stat)) { 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); const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1);
let effMultiplier = inst.skillMultipliers[key]; let effMultiplier = inst.skillMultipliers[key];
if (effMultiplier == null) { if (effMultiplier == null) {

@ -15,6 +15,8 @@ import { Skill } from "./Skill";
import { City } from "./City"; import { City } from "./City";
import { IAction } from "./IAction"; import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { createTaskTracker, ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IPerson } from "../PersonObjects/IPerson";
import { IRouter, Page } from "../ui/Router"; import { IRouter, Page } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help"; import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { exceptionAlert } from "../utils/helpers/exceptionAlert";
@ -36,6 +38,7 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { BlackOperationNames } from "./data/BlackOperationNames"; import { BlackOperationNames } from "./data/BlackOperationNames";
import { KEY } from "../utils/helpers/keyCodes"; import { KEY } from "../utils/helpers/keyCodes";
import { Player } from "src/Player";
interface BlackOpsAttempt { interface BlackOpsAttempt {
error?: string; error?: string;
@ -53,6 +56,7 @@ export class Bladeburner implements IBladeburner {
totalSkillPoints = 0; totalSkillPoints = 0;
teamSize = 0; teamSize = 0;
sleeveSize = 0;
teamLost = 0; teamLost = 0;
hpLost = 0; hpLost = 0;
@ -159,7 +163,7 @@ export class Bladeburner implements IBladeburner {
return { isAvailable: true, action }; return { isAvailable: true, action };
} }
startAction(player: IPlayer, actionId: IActionIdentifier): void { startAction(person: IPerson, actionId: IActionIdentifier): void {
if (actionId == null) return; if (actionId == null) return;
this.action = actionId; this.action = actionId;
this.actionTimeCurrent = 0; this.actionTimeCurrent = 0;
@ -176,7 +180,7 @@ export class Bladeburner implements IBladeburner {
if (action.count < 1) { if (action.count < 1) {
return this.resetAction(); return this.resetAction();
} }
this.actionTimeToComplete = action.getActionTime(this); this.actionTimeToComplete = action.getActionTime(this, person);
} catch (e: any) { } catch (e: any) {
exceptionAlert(e); exceptionAlert(e);
} }
@ -193,7 +197,7 @@ export class Bladeburner implements IBladeburner {
if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) { if (actionId.name === "Raid" && this.getCurrentCity().comms === 0) {
return this.resetAction(); return this.resetAction();
} }
this.actionTimeToComplete = action.getActionTime(this); this.actionTimeToComplete = action.getActionTime(this, person);
} catch (e: any) { } catch (e: any) {
exceptionAlert(e); exceptionAlert(e);
} }
@ -211,14 +215,14 @@ export class Bladeburner implements IBladeburner {
if (testBlackOp.action === undefined) { if (testBlackOp.action === undefined) {
throw new Error("action should not be null"); throw new Error("action should not be null");
} }
this.actionTimeToComplete = testBlackOp.action.getActionTime(this); this.actionTimeToComplete = testBlackOp.action.getActionTime(this, person);
} catch (e: any) { } catch (e: any) {
exceptionAlert(e); exceptionAlert(e);
} }
break; break;
} }
case ActionTypes["Recruitment"]: case ActionTypes["Recruitment"]:
this.actionTimeToComplete = this.getRecruitmentTime(player); this.actionTimeToComplete = this.getRecruitmentTime(person);
break; break;
case ActionTypes["Training"]: case ActionTypes["Training"]:
case ActionTypes["FieldAnalysis"]: 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 action(Action obj) - Derived action class
* @param success(bool) - Whether action was successful * @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(); const difficulty = action.getDifficulty();
/** /**
@ -1018,34 +1022,48 @@ export class Bladeburner implements IBladeburner {
const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult;
const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult;
const skillMult = this.skillMultipliers.expGain; 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); return {
player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult); hack: unweightedGain * action.weights.hack * skillMult,
player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult); str: unweightedGain * action.weights.str * skillMult,
player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult); def: unweightedGain * action.weights.def * skillMult,
player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult); dex: unweightedGain * action.weights.dex * skillMult,
player.gainIntelligenceExp(unweightedIntGain * action.weights.int * 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) // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98)
const CharismaLinearFactor = 1e3; const CharismaLinearFactor = 1e3;
const CharismaExponentialFactor = 0.045; 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; return (100 - charismaEff) / 100;
} }
getRecruitmentSuccessChance(player: IPlayer): number { getRecruitmentSuccessChance(person: IPerson): number {
return Math.pow(player.charisma, 0.45) / (this.teamSize + 1); return Math.pow(person.charisma, 0.45) / (this.teamSize - this.sleeveSize + 1);
} }
getRecruitmentTime(player: IPlayer): number { getRecruitmentTime(person: IPerson): number {
const effCharisma = player.charisma * this.skillMultipliers.effCha; const effCharisma = person.charisma * this.skillMultipliers.effCha;
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); 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 { resetSkillMultipliers(): void {
this.skillMultipliers = { this.skillMultipliers = {
successChanceAll: 1, 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) { if (this.action.type !== ActionTypes.Operation) {
throw new Error("completeOperation() called even though current action is not an 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); const losses = getRandomInt(0, max);
this.teamSize -= losses; 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; this.teamLost += losses;
if (this.logging.ops && losses > 0) { if (this.logging.ops && losses > 0) {
this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); 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 { completeContract(success: boolean, actionIdent: IActionIdentifier): void {
if (this.action.type !== ActionTypes.Contract) { if (actionIdent.type !== ActionTypes.Contract) {
throw new Error("completeContract() called even though current action is not a Contract"); throw new Error("completeContract() called even though current action is not a Contract");
} }
const city = this.getCurrentCity(); const city = this.getCurrentCity();
if (success) { if (success) {
switch (this.action.name) { switch (actionIdent.name) {
case "Tracking": case "Tracking":
// Increase estimate accuracy by a relatively small amount // Increase estimate accuracy by a relatively small amount
city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); city.improvePopulationEstimateByCount(getRandomInt(100, 1e3));
@ -1234,20 +1261,21 @@ export class Bladeburner implements IBladeburner {
city.changeChaosByCount(0.04); city.changeChaosByCount(0.04);
break; break;
default: 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 { completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier): ITaskTracker {
switch (this.action.type) { let retValue = createTaskTracker();
switch (actionIdent.type) {
case ActionTypes["Contract"]: case ActionTypes["Contract"]:
case ActionTypes["Operation"]: { case ActionTypes["Operation"]: {
try { try {
const isOperation = this.action.type === ActionTypes["Operation"]; const isOperation = actionIdent.type === ActionTypes["Operation"];
const action = this.getActionObject(this.action); const action = this.getActionObject(actionIdent);
if (action == null) { 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 difficulty = action.getDifficulty();
const difficultyMultiplier = const difficultyMultiplier =
@ -1262,8 +1290,8 @@ export class Bladeburner implements IBladeburner {
} }
// Process Contract/Operation success/failure // Process Contract/Operation success/failure
if (action.attempt(this)) { if (action.attempt(this, person)) {
this.gainActionStats(player, action, true); retValue = this.getActionStats(action, true);
++action.successes; ++action.successes;
--action.count; --action.count;
@ -1271,7 +1299,7 @@ export class Bladeburner implements IBladeburner {
let moneyGain = 0; let moneyGain = 0;
if (!isOperation) { if (!isOperation) {
moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money;
player.gainMoney(moneyGain, "bladeburner"); retValue.money = moneyGain;
} }
if (isOperation) { if (isOperation) {
@ -1281,11 +1309,12 @@ export class Bladeburner implements IBladeburner {
} }
if (action.rankGain) { if (action.rankGain) {
const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10);
this.changeRank(player, gain); this.changeRank(person, gain);
if (isOperation && this.logging.ops) { 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) { } else if (!isOperation && this.logging.contracts) {
this.log( this.log(
`${person.whoAmI()}: ` +
action.name + action.name +
" contract successfully completed! Gained " + " contract successfully completed! Gained " +
formatNumber(gain, 3) + 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 { } else {
this.gainActionStats(player, action, false); retValue = this.getActionStats(action, false);
++action.failures; ++action.failures;
let loss = 0, let loss = 0,
damage = 0; damage = 0;
if (action.rankLoss) { if (action.rankLoss) {
loss = addOffset(action.rankLoss * rewardMultiplier, 10); loss = addOffset(action.rankLoss * rewardMultiplier, 10);
this.changeRank(player, -1 * loss); this.changeRank(person, -1 * loss);
} }
if (action.hpLoss) { if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
this.hpLost += damage; this.hpLost += damage;
const cost = calculateHospitalizationCost(player, damage); const cost = calculateHospitalizationCost(person, damage);
if (player.takeDamage(damage)) { if (person.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += cost; this.moneyLost += cost;
} }
@ -1322,16 +1351,15 @@ export class Bladeburner implements IBladeburner {
logLossText += "Took " + formatNumber(damage, 0) + " damage."; logLossText += "Took " + formatNumber(damage, 0) + " damage.";
} }
if (isOperation && this.logging.ops) { 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) { } 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) { if (action.autoLevel) {
action.level = action.maxLevel; action.level = action.maxLevel;
} // Autolevel } // Autolevel
this.startAction(player, this.action); // Repeat action
} catch (e: any) { } catch (e: any) {
exceptionAlert(e); exceptionAlert(e);
} }
@ -1340,9 +1368,9 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["BlackOp"]: case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]: { case ActionTypes["BlackOperation"]: {
try { try {
const action = this.getActionObject(this.action); const action = this.getActionObject(actionIdent);
if (action == null || !(action instanceof BlackOperation)) { 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 difficulty = action.getDifficulty();
const difficultyMultiplier = const difficultyMultiplier =
@ -1359,39 +1387,33 @@ export class Bladeburner implements IBladeburner {
const teamCount = action.teamCount; const teamCount = action.teamCount;
let teamLossMax; let teamLossMax;
if (action.attempt(this)) { if (action.attempt(this, person)) {
this.gainActionStats(player, action, true); retValue = this.getActionStats(action, true);
action.count = 0; action.count = 0;
this.blackops[action.name] = true; this.blackops[action.name] = true;
let rankGain = 0; let rankGain = 0;
if (action.rankGain) { if (action.rankGain) {
rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10);
this.changeRank(player, rankGain); this.changeRank(person, rankGain);
} }
teamLossMax = Math.ceil(teamCount / 2); teamLossMax = Math.ceil(teamCount / 2);
// Operation Daedalus
if (action.name === BlackOperationNames.OperationDaedalus) {
this.resetAction();
return router.toBitVerse(false, false);
}
if (this.logging.blackops) { 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 { } else {
this.gainActionStats(player, action, false); retValue = this.getActionStats(action, false);
let rankLoss = 0; let rankLoss = 0;
let damage = 0; let damage = 0;
if (action.rankLoss) { if (action.rankLoss) {
rankLoss = addOffset(action.rankLoss, 10); rankLoss = addOffset(action.rankLoss, 10);
this.changeRank(player, -1 * rankLoss); this.changeRank(person, -1 * rankLoss);
} }
if (action.hpLoss) { if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
const cost = calculateHospitalizationCost(player, damage); const cost = calculateHospitalizationCost(person, damage);
if (player.takeDamage(damage)) { if (person.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += cost; this.moneyLost += cost;
} }
@ -1400,6 +1422,7 @@ export class Bladeburner implements IBladeburner {
if (this.logging.blackops) { if (this.logging.blackops) {
this.log( this.log(
`${person.whoAmI()}: ` +
action.name + action.name +
" failed! Lost " + " failed! Lost " +
formatNumber(rankLoss, 1) + formatNumber(rankLoss, 1) +
@ -1416,9 +1439,18 @@ export class Bladeburner implements IBladeburner {
if (teamCount >= 1) { if (teamCount >= 1) {
const losses = getRandomInt(1, teamLossMax); const losses = getRandomInt(1, teamLossMax);
this.teamSize -= losses; 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; this.teamLost += losses;
if (this.logging.blackops) { 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) { } catch (e: any) {
@ -1428,18 +1460,19 @@ export class Bladeburner implements IBladeburner {
} }
case ActionTypes["Training"]: { case ActionTypes["Training"]: {
this.stamina -= 0.5 * BladeburnerConstants.BaseStaminaLoss; this.stamina -= 0.5 * BladeburnerConstants.BaseStaminaLoss;
const strExpGain = 30 * player.strength_exp_mult, const strExpGain = 30 * person.strength_exp_mult,
defExpGain = 30 * player.defense_exp_mult, defExpGain = 30 * person.defense_exp_mult,
dexExpGain = 30 * player.dexterity_exp_mult, dexExpGain = 30 * person.dexterity_exp_mult,
agiExpGain = 30 * player.agility_exp_mult, agiExpGain = 30 * person.agility_exp_mult,
staminaGain = 0.04 * this.skillMultipliers.stamina; staminaGain = 0.04 * this.skillMultipliers.stamina;
player.gainStrengthExp(strExpGain); retValue.str = strExpGain;
player.gainDefenseExp(defExpGain); retValue.def = defExpGain;
player.gainDexterityExp(dexExpGain); retValue.dex = dexExpGain;
player.gainAgilityExp(agiExpGain); retValue.agi = agiExpGain;
this.staminaBonus += staminaGain; this.staminaBonus += staminaGain;
if (this.logging.general) { if (this.logging.general) {
this.log( this.log(
`${person.whoAmI()}: ` +
"Training completed. Gained: " + "Training completed. Gained: " +
formatNumber(strExpGain, 1) + formatNumber(strExpGain, 1) +
" str exp, " + " str exp, " +
@ -1453,80 +1486,77 @@ export class Bladeburner implements IBladeburner {
" max stamina", " max stamina",
); );
} }
this.startAction(player, this.action); // Repeat action
break; break;
} }
case ActionTypes["FieldAnalysis"]: case ActionTypes["FieldAnalysis"]:
case ActionTypes["Field Analysis"]: { case ActionTypes["Field Analysis"]: {
// Does not use stamina. Effectiveness depends on hacking, int, and cha // Does not use stamina. Effectiveness depends on hacking, int, and cha
let eff = let eff =
0.04 * Math.pow(player.hacking, 0.3) + 0.04 * Math.pow(person.hacking, 0.3) +
0.04 * Math.pow(player.intelligence, 0.9) + 0.04 * Math.pow(person.intelligence, 0.9) +
0.02 * Math.pow(player.charisma, 0.3); 0.02 * Math.pow(person.charisma, 0.3);
eff *= player.bladeburner_analysis_mult; eff *= person.bladeburner_analysis_mult;
if (isNaN(eff) || eff < 0) { if (isNaN(eff) || eff < 0) {
throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");
} }
const hackingExpGain = 20 * player.hacking_exp_mult; const hackingExpGain = 20 * person.hacking_exp_mult;
const charismaExpGain = 20 * player.charisma_exp_mult; const charismaExpGain = 20 * person.charisma_exp_mult;
const rankGain = 0.1 * BitNodeMultipliers.BladeburnerRank; const rankGain = 0.1 * BitNodeMultipliers.BladeburnerRank;
player.gainHackingExp(hackingExpGain); retValue.hack = hackingExpGain;
player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); retValue.cha = charismaExpGain;
player.gainCharismaExp(charismaExpGain); retValue.int = BladeburnerConstants.BaseIntGain;
this.changeRank(player, rankGain); this.changeRank(person, rankGain);
this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate);
if (this.logging.general) { if (this.logging.general) {
this.log( this.log(
`${person.whoAmI()}: ` +
`Field analysis completed. Gained ${formatNumber(rankGain, 2)} rank, ` + `Field analysis completed. Gained ${formatNumber(rankGain, 2)} rank, ` +
`${formatNumber(hackingExpGain, 1)} hacking exp, and ` + `${formatNumber(hackingExpGain, 1)} hacking exp, and ` +
`${formatNumber(charismaExpGain, 1)} charisma exp`, `${formatNumber(charismaExpGain, 1)} charisma exp`,
); );
} }
this.startAction(player, this.action); // Repeat action
break; break;
} }
case ActionTypes["Recruitment"]: { case ActionTypes["Recruitment"]: {
const successChance = this.getRecruitmentSuccessChance(player); const successChance = this.getRecruitmentSuccessChance(person);
const recruitTime = this.getRecruitmentTime(person) * 1000;
if (Math.random() < successChance) { if (Math.random() < successChance) {
const expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; const expGain = 2 * BladeburnerConstants.BaseStatGain * recruitTime;
player.gainCharismaExp(expGain); retValue.cha = expGain;
++this.teamSize; ++this.teamSize;
if (this.logging.general) { 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 { } else {
const expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; const expGain = BladeburnerConstants.BaseStatGain * recruitTime;
player.gainCharismaExp(expGain); retValue.cha = expGain;
if (this.logging.general) { 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; break;
} }
case ActionTypes["Diplomacy"]: { case ActionTypes["Diplomacy"]: {
const eff = this.getDiplomacyEffectiveness(player); const eff = this.getDiplomacyEffectiveness(person);
this.getCurrentCity().chaos *= eff; this.getCurrentCity().chaos *= eff;
if (this.getCurrentCity().chaos < 0) { if (this.getCurrentCity().chaos < 0) {
this.getCurrentCity().chaos = 0; this.getCurrentCity().chaos = 0;
} }
if (this.logging.general) { if (this.logging.general) {
this.log( 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; break;
} }
case ActionTypes["Hyperbolic Regeneration Chamber"]: { case ActionTypes["Hyperbolic Regeneration Chamber"]: {
player.regenerateHp(BladeburnerConstants.HrcHpGain); person.regenerateHp(BladeburnerConstants.HrcHpGain);
const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100);
this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain);
this.startAction(player, this.action);
if (this.logging.general) { if (this.logging.general) {
this.log( this.log(
`Rested in Hyperbolic Regeneration Chamber. Restored ${ `${person.whoAmI()}: Rested in Hyperbolic Regeneration Chamber. Restored ${
BladeburnerConstants.HrcHpGain BladeburnerConstants.HrcHpGain
} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`, } 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; this.operations[operation].count += (60 * 3 * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
} }
if (this.logging.general) { 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)) { for (const cityName of Object.keys(this.cities)) {
const city = this.cities[cityName]; const city = this.cities[cityName];
city.chaos += 10; city.chaos += 10;
city.chaos += city.chaos / (Math.log(city.chaos) / Math.log(10)); city.chaos += city.chaos / (Math.log(city.chaos) / Math.log(10));
} }
this.startAction(player, this.action);
break; break;
} }
default: default:
console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); console.error(`Bladeburner.completeAction() called for invalid action: ${actionIdent.type}`);
break; 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)) { if (isNaN(change)) {
throw new Error("NaN passed into Bladeburner.changeRank()"); throw new Error("NaN passed into Bladeburner.changeRank()");
} }
@ -1583,7 +1624,7 @@ export class Bladeburner implements IBladeburner {
if (bladeburnerFac.isMember) { if (bladeburnerFac.isMember) {
const favorBonus = 1 + bladeburnerFac.favor / 100; const favorBonus = 1 + bladeburnerFac.favor / 100;
bladeburnerFac.playerReputation += 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; this.actionTimeOverflow = 0;
if (this.actionTimeCurrent >= this.actionTimeToComplete) { if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = 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 { getActionTimeNetscriptFn(person: IPerson, type: string, name: string): number|string {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
const actionId = this.getActionIdFromTypeAndName(type, name); const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) { if (actionId == null) {
workerScript.log("bladeburner.getActionTime", () => errorLogText); return "bladeburner.getActionTime";
return -1;
} }
const actionObj = this.getActionObject(actionId); const actionObj = this.getActionObject(actionId);
if (actionObj == null) { if (actionObj == null) {
workerScript.log("bladeburner.getActionTime", () => errorLogText); return "bladeburner.getActionTime";
return -1;
} }
switch (actionId.type) { switch (actionId.type) {
case ActionTypes["Contract"]: case ActionTypes["Contract"]:
case ActionTypes["Operation"]: case ActionTypes["Operation"]:
case ActionTypes["BlackOp"]: case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]: case ActionTypes["BlackOperation"]:
return actionObj.getActionTime(this) * 1000; return actionObj.getActionTime(this, person) * 1000;
case ActionTypes["Training"]: case ActionTypes["Training"]:
case ActionTypes["Field Analysis"]: case ActionTypes["Field Analysis"]:
case ActionTypes["FieldAnalysis"]: case ActionTypes["FieldAnalysis"]:
return 30000; return 30000;
case ActionTypes["Recruitment"]: case ActionTypes["Recruitment"]:
return this.getRecruitmentTime(player) * 1000; return this.getRecruitmentTime(person) * 1000;
case ActionTypes["Diplomacy"]: case ActionTypes["Diplomacy"]:
case ActionTypes["Hyperbolic Regeneration Chamber"]: case ActionTypes["Hyperbolic Regeneration Chamber"]:
case ActionTypes["Incite Violence"]: case ActionTypes["Incite Violence"]:
return 60000; return 60000;
default: default:
workerScript.log("bladeburner.getActionTime", () => errorLogText); return "bladeburner.getActionTime";
return -1;
} }
} }
getActionEstimatedSuccessChanceNetscriptFn( getActionEstimatedSuccessChanceNetscriptFn(
player: IPlayer, person: IPerson,
type: string, type: string,
name: string, name: string,
workerScript: WorkerScript, ): [number, number]|string {
): [number, number] {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
const actionId = this.getActionIdFromTypeAndName(type, name); const actionId = this.getActionIdFromTypeAndName(type, name);
if (actionId == null) { if (actionId == null) {
workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText); return "bladeburner.getActionEstimatedSuccessChance";
return [-1, -1];
} }
const actionObj = this.getActionObject(actionId); const actionObj = this.getActionObject(actionId);
if (actionObj == null) { if (actionObj == null) {
workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText); return "bladeburner.getActionEstimatedSuccessChance";
return [-1, -1];
} }
switch (actionId.type) { switch (actionId.type) {
case ActionTypes["Contract"]: case ActionTypes["Contract"]:
case ActionTypes["Operation"]: case ActionTypes["Operation"]:
case ActionTypes["BlackOp"]: case ActionTypes["BlackOp"]:
case ActionTypes["BlackOperation"]: case ActionTypes["BlackOperation"]:
return actionObj.getEstSuccessChance(this); return actionObj.getEstSuccessChance(this, person);
case ActionTypes["Training"]: case ActionTypes["Training"]:
case ActionTypes["Field Analysis"]: case ActionTypes["Field Analysis"]:
case ActionTypes["FieldAnalysis"]: case ActionTypes["FieldAnalysis"]:
@ -2162,12 +2205,11 @@ export class Bladeburner implements IBladeburner {
case ActionTypes["Incite Violence"]: case ActionTypes["Incite Violence"]:
return [1, 1]; return [1, 1];
case ActionTypes["Recruitment"]: { case ActionTypes["Recruitment"]: {
const recChance = this.getRecruitmentSuccessChance(player); const recChance = this.getRecruitmentSuccessChance(person);
return [recChance, recChance]; return [recChance, recChance];
} }
default: default:
workerScript.log("bladeburner.getActionEstimatedSuccessChance", () => errorLogText); return "bladeburner.getActionEstimatedSuccessChance";
return [-1, -1];
} }
} }

@ -1,3 +1,4 @@
import { IPerson } from "../PersonObjects/IPerson";
import { IBladeburner } from "./IBladeburner"; import { IBladeburner } from "./IBladeburner";
interface IStatsMultiplier { interface IStatsMultiplier {
@ -55,15 +56,15 @@ export interface IAction {
teamCount: number; teamCount: number;
getDifficulty(): number; getDifficulty(): number;
attempt(inst: IBladeburner): boolean; attempt(inst: IBladeburner, person: IPerson): boolean;
getActionTimePenalty(): number; getActionTimePenalty(): number;
getActionTime(inst: IBladeburner): number; getActionTime(inst: IBladeburner, person: IPerson): number;
getTeamSuccessBonus(inst: IBladeburner): number; getTeamSuccessBonus(inst: IBladeburner): number;
getActionTypeSkillSuccessBonus(inst: IBladeburner): number; getActionTypeSkillSuccessBonus(inst: IBladeburner): number;
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number; getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number;
getChaosDifficultyBonus(inst: IBladeburner): number; getChaosDifficultyBonus(inst: IBladeburner): number;
getEstSuccessChance(inst: IBladeburner): [number, number]; getEstSuccessChance(inst: IBladeburner, person: IPerson): [number, number];
getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number; getSuccessChance(inst: IBladeburner, person: IPerson, params: ISuccessChanceParams): number;
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number; getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number;
setMaxLevel(baseSuccessesPerLevel: number): void; setMaxLevel(baseSuccessesPerLevel: number): void;
toJSON(): any; toJSON(): any;

@ -3,6 +3,8 @@ import { City } from "./City";
import { Skill } from "./Skill"; import { Skill } from "./Skill";
import { IAction } from "./IAction"; import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IPerson } from "../PersonObjects/IPerson";
import { ITaskTracker } from "../PersonObjects/ITaskTracker";
import { IRouter } from "../ui/Router"; import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
@ -70,13 +72,12 @@ export interface IBladeburner {
getGeneralActionNamesNetscriptFn(): string[]; getGeneralActionNamesNetscriptFn(): string[];
getSkillNamesNetscriptFn(): string[]; getSkillNamesNetscriptFn(): string[];
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean; 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( getActionEstimatedSuccessChanceNetscriptFn(
player: IPlayer, person: IPerson,
type: string, type: string,
name: string, name: string,
workerScript: WorkerScript, ): [number, number]|string;
): [number, number];
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number; getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number; getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number; getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
@ -95,20 +96,22 @@ export interface IBladeburner {
triggerMigration(sourceCityName: string): void; triggerMigration(sourceCityName: string): void;
triggerPotentialMigration(sourceCityName: string, chance: number): void; triggerPotentialMigration(sourceCityName: string, chance: number): void;
randomEvent(): void; randomEvent(): void;
gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
getDiplomacyEffectiveness(player: IPlayer): number; getDiplomacyEffectiveness(player: IPlayer): number;
getRecruitmentSuccessChance(player: IPlayer): number; getRecruitmentSuccessChance(player: IPerson): number;
getRecruitmentTime(player: IPlayer): number; getRecruitmentTime(player: IPerson): number;
resetSkillMultipliers(): void; resetSkillMultipliers(): void;
updateSkillMultipliers(): void; updateSkillMultipliers(): void;
completeOperation(success: boolean): void; completeOperation(success: boolean, player: IPlayer): void;
getActionObject(actionId: IActionIdentifier): IAction | null; getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void; completeContract(success: boolean, actionIdent: IActionIdentifier): void;
completeAction(router: IRouter, player: IPlayer): void; completeAction(player: IPlayer, person: IPerson, actionIdent: IActionIdentifier): ITaskTracker;
infiltrateSynthoidCommunities(): void;
changeRank(player: IPlayer, change: number): void; changeRank(player: IPlayer, change: number): void;
processAction(router: IRouter, player: IPlayer, seconds: number): void; processAction(router: IRouter, player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number; calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void; calculateMaxStamina(player: IPlayer): void;
create(): void; create(): void;
process(router: IRouter, player: IPlayer): void; process(router: IRouter, player: IPlayer): void;
getActionStats(action: IAction, success: boolean): ITaskTracker;
sleeveSupport(joining: boolean): void;
} }

@ -37,7 +37,7 @@ export function BlackOpElem(props: IProps): React.ReactElement {
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.bladeburner.action.type === ActionTypes["BlackOperation"] &&
props.action.name === props.bladeburner.action.name; 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 hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,

@ -32,7 +32,7 @@ export function ContractElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, 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]; const actionData = Contracts[props.action.name];
if (actionData === undefined) { if (actionData === undefined) {

@ -33,7 +33,7 @@ export function OperationElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, 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]; const actionData = Operations[props.action.name];
if (actionData === undefined) { if (actionData === undefined) {

@ -4,6 +4,7 @@ import { StealthIcon } from "./StealthIcon";
import { KillIcon } from "./KillIcon"; import { KillIcon } from "./KillIcon";
import { IAction } from "../IAction"; import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { Player } from "../../Player";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,7 +12,7 @@ interface IProps {
} }
export function SuccessChance(props: IProps): React.ReactElement { export function SuccessChance(props: IProps): React.ReactElement {
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner); const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner, Player);
let chance = <></>; let chance = <></>;
if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) { if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) {

@ -1,6 +1,6 @@
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve"; import { IPerson } from "../PersonObjects/IPerson";
import { IRouter } from "../ui/Router"; import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
@ -108,7 +108,7 @@ export class Crime {
return this.time; return this.time;
} }
successRate(p: IPlayerOrSleeve): number { successRate(p: IPerson): number {
let chance: number = let chance: number =
this.hacking_success_weight * p.hacking + this.hacking_success_weight * p.hacking +
this.strength_success_weight * p.strength + this.strength_success_weight * p.strength +

@ -1,7 +1,7 @@
import { CONSTANTS } from "../Constants"; 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) { if (p.money < 0) {
return 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); 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; const oldhp = p.hp;
p.hp -= damage; p.hp -= damage;
const cost = getHospitalizationCost(p); const cost = getHospitalizationCost(p);

@ -134,7 +134,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner"); if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try { 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) { } catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e); throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e);
} }
@ -150,7 +157,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner"); if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try { 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) { } catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e); throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
} }

@ -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;
}

@ -30,8 +30,9 @@ import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServer } from "../Hacknet/HacknetServer"; import { HacknetServer } from "../Hacknet/HacknetServer";
import { ISkillProgress } from "./formulas/skill"; import { ISkillProgress } from "./formulas/skill";
import { PlayerAchievement } from "../Achievements/Achievements"; import { PlayerAchievement } from "../Achievements/Achievements";
import { IPerson } from "./IPerson";
export interface IPlayer { export interface IPlayer extends IPerson {
// Class members // Class members
augmentations: IPlayerOwnedAugmentation[]; augmentations: IPlayerOwnedAugmentation[];
bitNodeN: number; bitNodeN: number;
@ -185,13 +186,6 @@ export interface IPlayer {
canAccessGang(): boolean; canAccessGang(): boolean;
canAccessGrafting(): boolean; canAccessGrafting(): boolean;
canAfford(cost: number): 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; gainMoney(money: number, source: string): void;
getCurrentServer(): BaseServer; getCurrentServer(): BaseServer;
getGangFaction(): Faction; getGangFaction(): Faction;
@ -213,7 +207,6 @@ export interface IPlayer {
process(router: IRouter, numCycles?: number): void; process(router: IRouter, numCycles?: number): void;
reapplyAllAugmentations(resetMultipliers?: boolean): void; reapplyAllAugmentations(resetMultipliers?: boolean): void;
reapplyAllSourceFiles(): void; reapplyAllSourceFiles(): void;
regenerateHp(amt: number): void;
setMoney(amt: number): void; setMoney(amt: number): void;
singularityStopWork(): string; singularityStopWork(): string;
startBladeburner(p: any): void; startBladeburner(p: any): void;
@ -240,12 +233,9 @@ export interface IPlayer {
startGang(facName: string, isHacking: boolean): void; startGang(facName: string, isHacking: boolean): void;
startWork(companyName: string): void; startWork(companyName: string): void;
startWorkPartTime(companyName: string): void; startWorkPartTime(companyName: string): void;
takeDamage(amt: number): boolean;
travel(to: CityName): boolean; travel(to: CityName): boolean;
giveExploit(exploit: Exploit): void; giveExploit(exploit: Exploit): void;
giveAchievement(achievementId: string): void; giveAchievement(achievementId: string): void;
queryStatFromString(str: string): number;
getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number; getCasinoWinnings(): number;
quitJob(company: string): void; quitJob(company: string): void;
hasJob(): boolean; hasJob(): boolean;
@ -266,7 +256,6 @@ export interface IPlayer {
resetMultipliers(): void; resetMultipliers(): void;
prestigeAugmentation(): void; prestigeAugmentation(): void;
prestigeSourceFile(): void; prestigeSourceFile(): void;
calculateSkill(exp: number, mult?: number): number;
calculateSkillProgress(exp: number, mult?: number): ISkillProgress; calculateSkillProgress(exp: number, mult?: number): ISkillProgress;
resetWorkStatus(generalType?: string, group?: string, workType?: string): void; resetWorkStatus(generalType?: string, group?: string, workType?: string): void;
getWorkHackExpGain(): number; getWorkHackExpGain(): number;

@ -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;
}

@ -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,
};
}

@ -1,4 +1,4 @@
// Base class representing a person-like object import * as generalMethods from "./Player/PlayerObjectGeneralMethods";
import { Augmentation } from "../Augmentation/Augmentation"; import { Augmentation } from "../Augmentation/Augmentation";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@ -6,32 +6,12 @@ import { CityName } from "../Locations/data/CityNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { calculateSkill } from "./formulas/skill"; import { calculateSkill } from "./formulas/skill";
import { calculateIntelligenceBonus } from "./formulas/intelligence"; 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 // Base class representing a person-like object
// earnings for tasks export abstract class Person implements IPerson {
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 {
/** /**
* Stats * Stats
*/ */
@ -44,6 +24,7 @@ export abstract class Person {
intelligence = 1; intelligence = 1;
hp = 10; hp = 10;
max_hp = 10; max_hp = 10;
money = 0;
/** /**
* Experience * Experience
@ -240,4 +221,35 @@ export abstract class Person {
getIntelligenceBonus(weight: number): number { getIntelligenceBonus(weight: number): number {
return calculateIntelligenceBonus(this.intelligence, weight); 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;

@ -37,6 +37,7 @@ import { ISkillProgress } from "../formulas/skill";
import { PlayerAchievement } from "../../Achievements/Achievements"; import { PlayerAchievement } from "../../Achievements/Achievements";
import { cyrb53 } from "../../utils/StringHelperFunctions"; import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { ITaskTracker } from "../ITaskTracker";
export class PlayerObject implements IPlayer { export class PlayerObject implements IPlayer {
// Class members // Class members
@ -201,6 +202,7 @@ export class PlayerObject implements IPlayer {
gainAgilityExp: (exp: number) => void; gainAgilityExp: (exp: number) => void;
gainCharismaExp: (exp: number) => void; gainCharismaExp: (exp: number) => void;
gainIntelligenceExp: (exp: number) => void; gainIntelligenceExp: (exp: number) => void;
gainStats: (retValue: ITaskTracker) => void;
gainMoney: (money: number, source: string) => void; gainMoney: (money: number, source: string) => void;
getCurrentServer: () => BaseServer; getCurrentServer: () => BaseServer;
getGangFaction: () => Faction; getGangFaction: () => Faction;
@ -302,6 +304,9 @@ export class PlayerObject implements IPlayer {
graftAugmentationWork: (numCycles: number) => boolean; graftAugmentationWork: (numCycles: number) => boolean;
finishGraftAugmentationWork: (cancelled: boolean) => string; finishGraftAugmentationWork: (cancelled: boolean) => string;
applyEntropy: (stacks?: number) => void; applyEntropy: (stacks?: number) => void;
whoAmI(): string{
return 'Player';
}
constructor() { constructor() {
//Skills and stats //Skills and stats
@ -521,6 +526,7 @@ export class PlayerObject implements IPlayer {
this.gainAgilityExp = generalMethods.gainAgilityExp; this.gainAgilityExp = generalMethods.gainAgilityExp;
this.gainCharismaExp = generalMethods.gainCharismaExp; this.gainCharismaExp = generalMethods.gainCharismaExp;
this.gainIntelligenceExp = generalMethods.gainIntelligenceExp; this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;
this.gainStats = generalMethods.gainStats;
this.queryStatFromString = generalMethods.queryStatFromString; this.queryStatFromString = generalMethods.queryStatFromString;
this.resetWorkStatus = generalMethods.resetWorkStatus; this.resetWorkStatus = generalMethods.resetWorkStatus;
this.processWorkEarnings = generalMethods.processWorkEarnings; this.processWorkEarnings = generalMethods.processWorkEarnings;

@ -66,6 +66,8 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work"; import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements"; import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { ITaskTracker } from "../ITaskTracker";
import { IPerson } from "../IPerson";
export function init(this: IPlayer): void { export function init(this: IPlayer): void {
/* Initialize Player's home computer */ /* 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 //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); 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); 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)) { if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainStrengthExp()"); console.error("ERR: NaN passed into Player.gainStrengthExp()");
return; return;
@ -404,7 +406,7 @@ export function gainStrengthExp(this: IPlayer, exp: number): void {
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier); 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)) { if (isNaN(exp)) {
console.error("ERR: NaN passed into player.gainDefenseExp()"); console.error("ERR: NaN passed into player.gainDefenseExp()");
return; return;
@ -420,7 +422,7 @@ export function gainDefenseExp(this: IPlayer, exp: number): void {
this.hp = Math.round(this.max_hp * ratio); 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)) { if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainDexterityExp()"); console.error("ERR: NaN passed into Player.gainDexterityExp()");
return; 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)) { if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainAgilityExp()"); console.error("ERR: NaN passed into Player.gainAgilityExp()");
return; return;
@ -449,7 +451,7 @@ export function gainAgilityExp(this: IPlayer, exp: number): void {
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier); 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)) { if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainCharismaExp()"); console.error("ERR: NaN passed into Player.gainCharismaExp()");
return; return;
@ -462,17 +464,27 @@ export function gainCharismaExp(this: IPlayer, exp: number): void {
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier); 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)) { if (isNaN(exp)) {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()"); console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
return; return;
} }
if (SourceFileFlags[5] > 0 || this.intelligence > 0) { if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
this.intelligence_exp += exp; 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 //Given a string expression like "str" or "strength", returns the given stat
export function queryStatFromString(this: IPlayer, str: string): number { export function queryStatFromString(this: IPlayer, str: string): number {
const tempStr = str.toLowerCase(); 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") { if (typeof amt !== "number") {
console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`); console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
return; return;

@ -9,7 +9,8 @@
import { SleeveTaskType } from "./SleeveTaskTypesEnum"; import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer"; import { IPlayer } from "../IPlayer";
import { Person, ITaskTracker, createTaskTracker } from "../Person"; import { Person } from "../Person";
import { ITaskTracker, createTaskTracker } from "../ITaskTracker";
import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentation } from "../../Augmentation/Augmentation";
@ -33,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames"; import { LocationName } from "../../Locations/data/LocationNames";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
export class Sleeve extends Person { export class Sleeve extends Person {
/** /**
@ -58,6 +60,7 @@ export class Sleeve extends Person {
* Faction/Company Work: Name of Faction/Company * Faction/Company Work: Name of Faction/Company
* Crime: Money earned if successful * Crime: Money earned if successful
* Class/Gym: Name of university/gym * Class/Gym: Name of university/gym
* Bladeburner: success chance
*/ */
currentTaskLocation = ""; currentTaskLocation = "";
@ -101,6 +104,16 @@ export class Sleeve extends Person {
*/ */
gymStatType = ""; 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 * Keeps track of events/notifications for this sleeve
*/ */
@ -151,7 +164,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; 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.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.agi = crime.agility_exp * this.agility_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.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.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money); this.currentTaskLocation = String(this.gainRatesForTask.money);
@ -182,7 +196,7 @@ export class Sleeve extends Person {
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType); const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) { if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`); console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus(); this.resetTaskStatus(p);
return retValue; return retValue;
} }
if (Math.random() < crime.successRate(this)) { if (Math.random() < crime.successRate(this)) {
@ -206,11 +220,57 @@ export class Sleeve extends Person {
this.currentTaskTime = 0; this.currentTaskTime = 0;
return retValue; return retValue;
} }
} else { } else if (this.currentTask === SleeveTaskType.Bladeburner) {
// For other crimes... I dont think anything else needs to be done // 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; return retValue;
} }
@ -260,50 +320,56 @@ export class Sleeve extends Person {
const pDexExp = exp.dex * multFac; const pDexExp = exp.dex * multFac;
const pAgiExp = exp.agi * multFac; const pAgiExp = exp.agi * multFac;
const pChaExp = exp.cha * multFac; const pChaExp = exp.cha * multFac;
const pIntExp = exp.int * multFac;
// Experience is gained by both this sleeve and player // Experience is gained by both this sleeve and player
if (pHackExp > 0) { if (pHackExp > 0) {
this.hacking_exp += pHackExp; this.gainHackingExp(pHackExp);
p.gainHackingExp(pHackExp); p.gainHackingExp(pHackExp);
this.earningsForPlayer.hack += pHackExp; this.earningsForPlayer.hack += pHackExp;
this.earningsForTask.hack += pHackExp; this.earningsForTask.hack += pHackExp;
} }
if (pStrExp > 0) { if (pStrExp > 0) {
this.strength_exp += pStrExp; this.gainStrengthExp(pStrExp);
p.gainStrengthExp(pStrExp); p.gainStrengthExp(pStrExp);
this.earningsForPlayer.str += pStrExp; this.earningsForPlayer.str += pStrExp;
this.earningsForTask.str += pStrExp; this.earningsForTask.str += pStrExp;
} }
if (pDefExp > 0) { if (pDefExp > 0) {
this.defense_exp += pDefExp; this.gainDefenseExp(pDefExp);
p.gainDefenseExp(pDefExp); p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp; this.earningsForPlayer.def += pDefExp;
this.earningsForTask.def += pDefExp; this.earningsForTask.def += pDefExp;
} }
if (pDexExp > 0) { if (pDexExp > 0) {
this.dexterity_exp += pDexExp; this.gainDexterityExp(pDexExp);
p.gainDexterityExp(pDexExp); p.gainDexterityExp(pDexExp);
this.earningsForPlayer.dex += pDexExp; this.earningsForPlayer.dex += pDexExp;
this.earningsForTask.dex += pDexExp; this.earningsForTask.dex += pDexExp;
} }
if (pAgiExp > 0) { if (pAgiExp > 0) {
this.agility_exp += pAgiExp; this.gainAgilityExp(pAgiExp);
p.gainAgilityExp(pAgiExp); p.gainAgilityExp(pAgiExp);
this.earningsForPlayer.agi += pAgiExp; this.earningsForPlayer.agi += pAgiExp;
this.earningsForTask.agi += pAgiExp; this.earningsForTask.agi += pAgiExp;
} }
if (pChaExp > 0) { if (pChaExp > 0) {
this.charisma_exp += pChaExp; this.gainCharismaExp(pChaExp);
p.gainCharismaExp(pChaExp); p.gainCharismaExp(pChaExp);
this.earningsForPlayer.cha += pChaExp; this.earningsForPlayer.cha += pChaExp;
this.earningsForTask.cha += pChaExp; this.earningsForTask.cha += pChaExp;
} }
if (pIntExp > 0) {
this.gainIntelligenceExp(pIntExp);
p.gainIntelligenceExp(pIntExp);
}
// Record earnings for other sleeves // Record earnings for other sleeves
this.earningsForSleeves.hack += pHackExp * (this.sync / 100); this.earningsForSleeves.hack += pHackExp * (this.sync / 100);
this.earningsForSleeves.str += pStrExp * (this.sync / 100); this.earningsForSleeves.str += pStrExp * (this.sync / 100);
@ -320,7 +386,8 @@ export class Sleeve extends Person {
dex: pDexExp * (this.sync / 100), dex: pDexExp * (this.sync / 100),
agi: pAgiExp * (this.sync / 100), agi: pAgiExp * (this.sync / 100),
cha: pChaExp * (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; this.charisma_exp = 0;
// Reset task-related stuff // Reset task-related stuff
this.resetTaskStatus(); this.resetTaskStatus(p);
this.earningsForSleeves = createTaskTracker(); this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker(); this.earningsForPlayer = createTaskTracker();
this.shockRecovery(p); this.shockRecovery(p);
@ -537,18 +604,18 @@ export class Sleeve extends Person {
} }
case SleeveTaskType.Recovery: case SleeveTaskType.Recovery:
this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed); this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);
if (this.shock >= 100) this.resetTaskStatus(); if (this.shock >= 100) this.resetTaskStatus(p);
break; break;
case SleeveTaskType.Synchro: case SleeveTaskType.Synchro:
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed); 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; break;
default: default:
break; break;
} }
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) { 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); retValue = this.finishTask(p);
} else { } else {
this.finishTask(p); this.finishTask(p);
@ -565,7 +632,15 @@ export class Sleeve extends Person {
/** /**
* Resets all parameters used to keep information about the current task * 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.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker(); this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle; this.currentTask = SleeveTaskType.Idle;
@ -576,13 +651,15 @@ export class Sleeve extends Person {
this.currentTaskLocation = ""; this.currentTaskLocation = "";
this.gymStatType = ""; this.gymStatType = "";
this.className = ""; this.className = "";
this.bbAction = "";
this.bbContract = "";
} }
shockRecovery(p: IPlayer): boolean { shockRecovery(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
this.currentTask = SleeveTaskType.Recovery; this.currentTask = SleeveTaskType.Recovery;
@ -593,7 +670,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
this.currentTask = SleeveTaskType.Synchro; this.currentTask = SleeveTaskType.Synchro;
@ -607,7 +684,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
// Set exp/money multipliers based on which university. // Set exp/money multipliers based on which university.
@ -801,7 +878,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
const company: Company | null = Companies[companyName]; const company: Company | null = Companies[companyName];
@ -867,7 +944,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
const factionInfo = faction.getInfo(); const factionInfo = faction.getInfo();
@ -918,7 +995,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) { if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p); this.finishTask(p);
} else { } else {
this.resetTaskStatus(); this.resetTaskStatus(p);
} }
// Set exp/money multipliers based on which university. // Set exp/money multipliers based on which university.
@ -986,6 +1063,151 @@ export class Sleeve extends Person {
return true; 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. * Serialize the current object to a JSON save state.
*/ */

@ -9,6 +9,7 @@ export enum SleeveTaskType {
Crime, Crime,
Class, Class,
Gym, Gym,
Bladeburner,
Recovery, Recovery,
Synchro, Synchro,
} }

@ -40,7 +40,7 @@ export function SleeveElem(props: IProps): React.ReactElement {
const [abc, setABC] = useState(["------", "------", "------"]); const [abc, setABC] = useState(["------", "------", "------"]);
function setTask(): void { function setTask(): void {
props.sleeve.resetTaskStatus(); // sets to idle props.sleeve.resetTaskStatus(player); // sets to idle
switch (abc[0]) { switch (abc[0]) {
case "------": case "------":
break; break;
@ -59,6 +59,9 @@ export function SleeveElem(props: IProps): React.ReactElement {
case "Workout at Gym": case "Workout at Gym":
props.sleeve.workoutAtGym(player, abc[2], abc[1]); props.sleeve.workoutAtGym(player, abc[2], abc[1]);
break; break;
case "Perform Bladeburner Actions":
props.sleeve.bladeburner(player, abc[1], abc[2]);
break;
case "Shock Recovery": case "Shock Recovery":
props.sleeve.shockRecovery(player); props.sleeve.shockRecovery(player);
break; break;
@ -116,6 +119,13 @@ export function SleeveElem(props: IProps): React.ReactElement {
case SleeveTaskType.Gym: case SleeveTaskType.Gym:
desc = <>This sleeve is currently working out at {props.sleeve.currentTaskLocation}.</>; desc = <>This sleeve is currently working out at {props.sleeve.currentTaskLocation}.</>;
break; 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: case SleeveTaskType.Recovery:
desc = ( desc = (
<> <>
@ -178,8 +188,10 @@ export function SleeveElem(props: IProps): React.ReactElement {
<Button onClick={setTask} sx={{ width: '100%' }}>Set Task</Button> <Button onClick={setTask} sx={{ width: '100%' }}>Set Task</Button>
<Typography>{desc}</Typography> <Typography>{desc}</Typography>
<Typography> <Typography>
{props.sleeve.currentTask === SleeveTaskType.Crime && {(props.sleeve.currentTask === SleeveTaskType.Crime
createProgressBarText({ || props.sleeve.currentTask === SleeveTaskType.Bladeburner)
&& props.sleeve.currentTaskMaxTime > 0
&& createProgressBarText({
progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime, progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,
totalTicks: 25, totalTicks: 25,
})} })}

@ -10,6 +10,7 @@ import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import { FactionNames } from "../../../Faction/data/FactionNames"; import { FactionNames } from "../../../Faction/data/FactionNames";
import { Contract } from "../../../Bladeburner/Contract";
const universitySelectorOptions: string[] = [ const universitySelectorOptions: string[] = [
"Study Computer Science", "Study Computer Science",
@ -22,6 +23,8 @@ const universitySelectorOptions: string[] = [
const gymSelectorOptions: string[] = ["Train Strength", "Train Defense", "Train Dexterity", "Train Agility"]; 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 { interface IProps {
sleeve: Sleeve; sleeve: Sleeve;
player: IPlayer; 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: { const tasks: {
[key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => ITaskDetails); [key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => ITaskDetails);
["------"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails; ["------"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
@ -90,6 +114,7 @@ const tasks: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails; ["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails; ["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Workout at Gym"]: (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; ["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails; ["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
} = { } = {
@ -166,6 +191,17 @@ const tasks: {
return { first: gymSelectorOptions, second: () => gyms }; 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 => { "Shock Recovery": (): ITaskDetails => {
return { first: ["------"], second: () => ["------"] }; return { first: ["------"], second: () => ["------"] };
}, },
@ -182,6 +218,7 @@ const canDo: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => boolean; ["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => boolean; ["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Workout at Gym"]: (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; ["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Synchronize"]: (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), [CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),
"Workout at Gym": (player: IPlayer, sleeve: Sleeve) => "Workout at Gym": (player: IPlayer, sleeve: Sleeve) =>
[CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city), [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, "Shock Recovery": (player: IPlayer, sleeve: Sleeve) => sleeve.shock < 100,
Synchronize: (player: IPlayer, sleeve: Sleeve) => sleeve.sync < 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]; return ["Take University Course", sleeve.className, sleeve.currentTaskLocation];
case SleeveTaskType.Gym: case SleeveTaskType.Gym:
return ["Workout at Gym", sleeve.gymStatType, sleeve.currentTaskLocation]; return ["Workout at Gym", sleeve.gymStatType, sleeve.currentTaskLocation];
case SleeveTaskType.Bladeburner:
return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
case SleeveTaskType.Recovery: case SleeveTaskType.Recovery:
return ["Shock Recovery", "------", "------"]; return ["Shock Recovery", "------", "------"];
case SleeveTaskType.Synchro: case SleeveTaskType.Synchro:

@ -26,7 +26,7 @@ export function TravelModal(props: IProps): React.ReactElement {
} }
props.sleeve.city = city as CityName; props.sleeve.city = city as CityName;
player.loseMoney(CONSTANTS.TravelCost, "sleeve"); player.loseMoney(CONSTANTS.TravelCost, "sleeve");
props.sleeve.resetTaskStatus(); props.sleeve.resetTaskStatus(player);
props.rerender(); props.rerender();
props.onClose(); props.onClose();
} }