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",
"version": "1.5.0",
"version": "1.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "1.5.0",
"version": "1.6.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {

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

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

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

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

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

@ -32,7 +32,7 @@ export function ContractElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete,
);
const actionTime = props.action.getActionTime(props.bladeburner);
const actionTime = props.action.getActionTime(props.bladeburner, props.player);
const actionData = Contracts[props.action.name];
if (actionData === undefined) {

@ -33,7 +33,7 @@ export function OperationElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete,
);
const actionTime = props.action.getActionTime(props.bladeburner);
const actionTime = props.action.getActionTime(props.bladeburner, props.player);
const actionData = Operations[props.action.name];
if (actionData === undefined) {

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

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

@ -1,7 +1,7 @@
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IPerson } from "../PersonObjects/IPerson";
export function getHospitalizationCost(p: IPlayer): number {
export function getHospitalizationCost(p: IPerson): number {
if (p.money < 0) {
return 0;
}
@ -9,7 +9,7 @@ export function getHospitalizationCost(p: IPlayer): number {
return Math.min(p.money * 0.1, (p.max_hp - p.hp) * CONSTANTS.HospitalCostPerHp);
}
export function calculateHospitalizationCost(p: IPlayer, damage: number): number {
export function calculateHospitalizationCost(p: IPerson, damage: number): number {
const oldhp = p.hp;
p.hp -= damage;
const cost = getHospitalizationCost(p);

@ -134,7 +134,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionTimeNetscriptFn(player, type, name, workerScript);
let time = bladeburner.getActionTimeNetscriptFn(player, type, name);
if(typeof time === 'string'){
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
workerScript.log("bladeburner.getActionTime", () => errorLogText);
return -1;
} else {
return time;
}
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionTime", e);
}
@ -150,7 +157,14 @@ export function NetscriptBladeburner(
const bladeburner = player.bladeburner;
if (bladeburner === null) throw new Error("Should not be called without Bladeburner");
try {
return bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name, workerScript);
let chance = bladeburner.getActionEstimatedSuccessChanceNetscriptFn(player, type, name);
if(typeof chance === 'string'){
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
workerScript.log("bladeburner.getActionTime", () => errorLogText);
return [-1, -1];
} else {
return chance;
}
} catch (e: any) {
throw helper.makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e);
}

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

@ -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 { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@ -6,32 +6,12 @@ import { CityName } from "../Locations/data/CityNames";
import { CONSTANTS } from "../Constants";
import { calculateSkill } from "./formulas/skill";
import { calculateIntelligenceBonus } from "./formulas/intelligence";
import { IPerson } from "./IPerson";
import { Reviver } from "../utils/JSONReviver";
import { ITaskTracker } from "./ITaskTracker";
// Interface that defines a generic object used to track experience/money
// earnings for tasks
export interface ITaskTracker {
hack: number;
str: number;
def: number;
dex: number;
agi: number;
cha: number;
money: number;
}
export function createTaskTracker(): ITaskTracker {
return {
hack: 0,
str: 0,
def: 0,
dex: 0,
agi: 0,
cha: 0,
money: 0,
};
}
export abstract class Person {
// Base class representing a person-like object
export abstract class Person implements IPerson {
/**
* Stats
*/
@ -44,6 +24,7 @@ export abstract class Person {
intelligence = 1;
hp = 10;
max_hp = 10;
money = 0;
/**
* Experience
@ -240,4 +221,35 @@ export abstract class Person {
getIntelligenceBonus(weight: number): number {
return calculateIntelligenceBonus(this.intelligence, weight);
}
abstract takeDamage(amt: number): boolean;
abstract whoAmI(): string;
gainHackingExp: (exp: number) => void;
gainStrengthExp: (exp: number) => void;
gainDefenseExp: (exp: number) => void;
gainDexterityExp: (exp: number) => void;
gainAgilityExp: (exp: number) => void;
gainCharismaExp: (exp: number) => void;
gainIntelligenceExp: (exp: number) => void;
gainStats: (retValue: ITaskTracker) => void;
calculateSkill: (exp: number, mult: number) => number;
regenerateHp: (amt: number) => void;
queryStatFromString: (str: string) => number;
constructor() {
this.gainHackingExp = generalMethods.gainHackingExp;
this.gainStrengthExp = generalMethods.gainStrengthExp;
this.gainDefenseExp = generalMethods.gainDefenseExp;
this.gainDexterityExp = generalMethods.gainDexterityExp;
this.gainAgilityExp = generalMethods.gainAgilityExp;
this.gainCharismaExp = generalMethods.gainCharismaExp;
this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;
this.gainStats = generalMethods.gainStats;
this.calculateSkill = generalMethods.calculateSkill;
this.regenerateHp = generalMethods.regenerateHp;
this.queryStatFromString = generalMethods.queryStatFromString;
}
}
Reviver.constructors.Person = Person;

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

@ -66,6 +66,8 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames";
import { ITaskTracker } from "../ITaskTracker";
import { IPerson } from "../IPerson";
export function init(this: IPlayer): void {
/* Initialize Player's home computer */
@ -227,7 +229,7 @@ export function receiveInvite(this: IPlayer, factionName: string): void {
}
//Calculates skill level based on experience. The same formula will be used for every skill
export function calculateSkill(this: IPlayer, exp: number, mult = 1): number {
export function calculateSkill(this: IPerson, exp: number, mult = 1): number {
return calculateSkillF(exp, mult);
}
@ -391,7 +393,7 @@ export function gainHackingExp(this: IPlayer, exp: number): void {
this.hacking = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
}
export function gainStrengthExp(this: IPlayer, exp: number): void {
export function gainStrengthExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainStrengthExp()");
return;
@ -404,7 +406,7 @@ export function gainStrengthExp(this: IPlayer, exp: number): void {
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
}
export function gainDefenseExp(this: IPlayer, exp: number): void {
export function gainDefenseExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into player.gainDefenseExp()");
return;
@ -420,7 +422,7 @@ export function gainDefenseExp(this: IPlayer, exp: number): void {
this.hp = Math.round(this.max_hp * ratio);
}
export function gainDexterityExp(this: IPlayer, exp: number): void {
export function gainDexterityExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainDexterityExp()");
return;
@ -436,7 +438,7 @@ export function gainDexterityExp(this: IPlayer, exp: number): void {
);
}
export function gainAgilityExp(this: IPlayer, exp: number): void {
export function gainAgilityExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainAgilityExp()");
return;
@ -449,7 +451,7 @@ export function gainAgilityExp(this: IPlayer, exp: number): void {
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
}
export function gainCharismaExp(this: IPlayer, exp: number): void {
export function gainCharismaExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainCharismaExp()");
return;
@ -462,17 +464,27 @@ export function gainCharismaExp(this: IPlayer, exp: number): void {
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
}
export function gainIntelligenceExp(this: IPlayer, exp: number): void {
export function gainIntelligenceExp(this: IPerson, exp: number): void {
if (isNaN(exp)) {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
return;
}
if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
this.intelligence_exp += exp;
this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp));
this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp, 1));
}
}
export function gainStats(this: IPerson, retValue: ITaskTracker): void {
this.gainHackingExp(retValue.hack * this.hacking_exp_mult);
this.gainStrengthExp(retValue.str * this.strength_exp_mult);
this.gainDefenseExp(retValue.def * this.defense_exp_mult);
this.gainDexterityExp(retValue.dex * this.dexterity_exp_mult);
this.gainAgilityExp(retValue.agi * this.agility_exp_mult);
this.gainCharismaExp(retValue.cha * this.charisma_exp_mult);
this.gainIntelligenceExp(retValue.int);
}
//Given a string expression like "str" or "strength", returns the given stat
export function queryStatFromString(this: IPlayer, str: string): number {
const tempStr = str.toLowerCase();
@ -1718,7 +1730,7 @@ export function takeDamage(this: IPlayer, amt: number): boolean {
}
}
export function regenerateHp(this: IPlayer, amt: number): void {
export function regenerateHp(this: IPerson, amt: number): void {
if (typeof amt !== "number") {
console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
return;

@ -9,7 +9,8 @@
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
import { Person, ITaskTracker, createTaskTracker } from "../Person";
import { Person } from "../Person";
import { ITaskTracker, createTaskTracker } from "../ITaskTracker";
import { Augmentation } from "../../Augmentation/Augmentation";
@ -33,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
export class Sleeve extends Person {
/**
@ -58,6 +60,7 @@ export class Sleeve extends Person {
* Faction/Company Work: Name of Faction/Company
* Crime: Money earned if successful
* Class/Gym: Name of university/gym
* Bladeburner: success chance
*/
currentTaskLocation = "";
@ -101,6 +104,16 @@ export class Sleeve extends Person {
*/
gymStatType = "";
/**
* String that stores what stat the sleeve is training at the gym
*/
bbAction = "";
/**
* String that stores what stat the sleeve is training at the gym
*/
bbContract = "";
/**
* Keeps track of events/notifications for this sleeve
*/
@ -151,7 +164,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;
@ -160,6 +173,7 @@ export class Sleeve extends Person {
this.gainRatesForTask.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.agi = crime.agility_exp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.int = crime.intelligence_exp;
this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money);
@ -175,14 +189,14 @@ export class Sleeve extends Person {
*/
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus();
this.resetTaskStatus(p);
return retValue;
}
if (Math.random() < crime.successRate(this)) {
@ -206,11 +220,57 @@ export class Sleeve extends Person {
this.currentTaskTime = 0;
return retValue;
}
} else {
// For other crimes... I dont think anything else needs to be done
} else if (this.currentTask === SleeveTaskType.Bladeburner) {
// For bladeburner, all experience and money is gained at the end
const bb = p.bladeburner;
if (bb === null) {
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`)
this.resetTaskStatus(p);
return retValue;
}
if (this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.bbAction === "Infiltrate synthoids") {
bb.infiltrateSynthoidCommunities();
this.currentTaskTime = 0;
return retValue;
}
let type: string;
let name: string;
if (this.bbAction === "Take on Contracts") {
type = 'Contracts';
name = this.bbContract;
} else {
type = 'General';
name = this.bbAction;
}
const actionIdent = bb.getActionIdFromTypeAndName(type, name);
if(actionIdent === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`)
this.resetTaskStatus(p);
return retValue;
}
const action = bb.getActionObject(actionIdent);
if((action?.count ?? 0) > 0) {
const bbRetValue = bb.completeAction(p, this, actionIdent);
if(bbRetValue) {
retValue = this.gainExperience(p, bbRetValue);
this.gainMoney(p, bbRetValue);
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
}
}
}
}
this.resetTaskStatus();
this.resetTaskStatus(p);
return retValue;
}
@ -260,50 +320,56 @@ export class Sleeve extends Person {
const pDexExp = exp.dex * multFac;
const pAgiExp = exp.agi * multFac;
const pChaExp = exp.cha * multFac;
const pIntExp = exp.int * multFac;
// Experience is gained by both this sleeve and player
if (pHackExp > 0) {
this.hacking_exp += pHackExp;
this.gainHackingExp(pHackExp);
p.gainHackingExp(pHackExp);
this.earningsForPlayer.hack += pHackExp;
this.earningsForTask.hack += pHackExp;
}
if (pStrExp > 0) {
this.strength_exp += pStrExp;
this.gainStrengthExp(pStrExp);
p.gainStrengthExp(pStrExp);
this.earningsForPlayer.str += pStrExp;
this.earningsForTask.str += pStrExp;
}
if (pDefExp > 0) {
this.defense_exp += pDefExp;
this.gainDefenseExp(pDefExp);
p.gainDefenseExp(pDefExp);
this.earningsForPlayer.def += pDefExp;
this.earningsForTask.def += pDefExp;
}
if (pDexExp > 0) {
this.dexterity_exp += pDexExp;
this.gainDexterityExp(pDexExp);
p.gainDexterityExp(pDexExp);
this.earningsForPlayer.dex += pDexExp;
this.earningsForTask.dex += pDexExp;
}
if (pAgiExp > 0) {
this.agility_exp += pAgiExp;
this.gainAgilityExp(pAgiExp);
p.gainAgilityExp(pAgiExp);
this.earningsForPlayer.agi += pAgiExp;
this.earningsForTask.agi += pAgiExp;
}
if (pChaExp > 0) {
this.charisma_exp += pChaExp;
this.gainCharismaExp(pChaExp);
p.gainCharismaExp(pChaExp);
this.earningsForPlayer.cha += pChaExp;
this.earningsForTask.cha += pChaExp;
}
if (pIntExp > 0) {
this.gainIntelligenceExp(pIntExp);
p.gainIntelligenceExp(pIntExp);
}
// Record earnings for other sleeves
this.earningsForSleeves.hack += pHackExp * (this.sync / 100);
this.earningsForSleeves.str += pStrExp * (this.sync / 100);
@ -320,7 +386,8 @@ export class Sleeve extends Person {
dex: pDexExp * (this.sync / 100),
agi: pAgiExp * (this.sync / 100),
cha: pChaExp * (this.sync / 100),
money: 0,
int: pIntExp * (this.sync / 100),
money: exp.money,
};
}
@ -445,7 +512,7 @@ export class Sleeve extends Person {
this.charisma_exp = 0;
// Reset task-related stuff
this.resetTaskStatus();
this.resetTaskStatus(p);
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.shockRecovery(p);
@ -537,18 +604,18 @@ export class Sleeve extends Person {
}
case SleeveTaskType.Recovery:
this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);
if (this.shock >= 100) this.resetTaskStatus();
if (this.shock >= 100) this.resetTaskStatus(p);
break;
case SleeveTaskType.Synchro:
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);
if (this.sync >= 100) this.resetTaskStatus();
if (this.sync >= 100) this.resetTaskStatus(p);
break;
default:
break;
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.currentTask === SleeveTaskType.Crime) {
if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) {
retValue = this.finishTask(p);
} else {
this.finishTask(p);
@ -565,7 +632,15 @@ export class Sleeve extends Person {
/**
* Resets all parameters used to keep information about the current task
*/
resetTaskStatus(): void {
resetTaskStatus(p: IPlayer): void {
if (this.bbAction == 'Support main sleeve') {
p.bladeburner?.sleeveSupport(false);
}
if (this.currentTask == SleeveTaskType.Class) {
let retVal = createTaskTracker();
retVal.int = CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.currentTaskTime / 1000);
this.gainExperience(p, retVal);//Wont be shared with other sleeves
}
this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle;
@ -576,13 +651,15 @@ export class Sleeve extends Person {
this.currentTaskLocation = "";
this.gymStatType = "";
this.className = "";
this.bbAction = "";
this.bbContract = "";
}
shockRecovery(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Recovery;
@ -593,7 +670,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Synchro;
@ -607,7 +684,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
// Set exp/money multipliers based on which university.
@ -801,7 +878,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
const company: Company | null = Companies[companyName];
@ -867,7 +944,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
const factionInfo = faction.getInfo();
@ -918,7 +995,7 @@ export class Sleeve extends Person {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus();
this.resetTaskStatus(p);
}
// Set exp/money multipliers based on which university.
@ -986,6 +1063,151 @@ export class Sleeve extends Person {
return true;
}
/**
* Begin a bladeburner task
*/
bladeburner(p: IPlayer, action: string, contract: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
this.gainRatesForTask.hack = 0;
this.gainRatesForTask.str = 0;
this.gainRatesForTask.def = 0;
this.gainRatesForTask.dex = 0;
this.gainRatesForTask.agi = 0;
this.gainRatesForTask.cha = 0;
this.gainRatesForTask.money = 0;
this.currentTaskLocation = '';
let time = 0;
switch (action) {
case "Field Analysis":
time = this.getBladeburnerActionTime(p, 'General', action);
this.gainRatesForTask.hack = 20 * this.hacking_exp_mult;
this.gainRatesForTask.cha = 20 * this.charisma_exp_mult;
break;
case "Recruitment":
time = this.getBladeburnerActionTime(p, 'General', action);
const recruitTime = p.bladeburner?.getRecruitmentTime(this) ?? 0 * 1000;
this.gainRatesForTask.cha = 2 * BladeburnerConstants.BaseStatGain * recruitTime;
this.currentTaskLocation = (p.bladeburner?.getRecruitmentSuccessChance(this) ?? 0).toString() + '%';
break;
case "Diplomacy":
time = this.getBladeburnerActionTime(p, 'General', action);
break;
case "Infiltrate synthoids":
time = 60000;
break;
case "Support main sleeve":
p.bladeburner?.sleeveSupport(true);
time = 0;
break;
case "Take on Contracts":
time = this.getBladeburnerActionTime(p, 'Contracts', contract);
this.contractGainRates(p, 'Contracts', contract);
this.currentTaskLocation = this.contractSuccessChance(p, 'Contracts', contract);
break;
}
this.bbAction = action;
this.bbContract = contract;
this.currentTaskMaxTime = time;
this.currentTask = SleeveTaskType.Bladeburner;
return true;
}
contractSuccessChance(p: IPlayer, type: string, name: string): string {
const bb = p.bladeburner;
if(bb === null){
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.contractSuccessChance; Message: '${errorLogText}'`)
return '0%';
}
const chances = bb.getActionEstimatedSuccessChanceNetscriptFn(this, type, name);
if(typeof chances === 'string'){
console.error(`Function: sleeves.contractSuccessChance; Message: '${chances}'`)
return '0%';
}
if(chances[0] >= 1) {
return '100%';
} else {
return `${chances[0]*100}% - ${chances[1]*100}%`;
}
}
contractGainRates(p: IPlayer, type: string, name: string): void {
const bb = p.bladeburner;
if(bb === null){
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`)
return;
}
const actionIdent = bb.getActionIdFromTypeAndName(type, name);
if(actionIdent === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`)
this.resetTaskStatus(p);
return;
}
const action = bb.getActionObject(actionIdent);
if(action === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`)
this.resetTaskStatus(p);
return;
}
const retValue = bb.getActionStats(action, true);
this.gainRatesForTask.hack = retValue.hack;
this.gainRatesForTask.str = retValue.str;
this.gainRatesForTask.def = retValue.def;
this.gainRatesForTask.dex = retValue.dex;
this.gainRatesForTask.agi = retValue.agi;
this.gainRatesForTask.cha = retValue.cha;
const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
this.gainRatesForTask.money = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money;;
}
getBladeburnerActionTime(p: IPlayer, type: string, name: string): number{//Maybe find workerscript and use original
const bb = p.bladeburner;
if(bb === null){
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`)
return -1;
}
const time = bb.getActionTimeNetscriptFn(this, type, name);
if(typeof time === 'string'){
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`)
return -1;
} else {
return time;
}
}
takeDamage(amt: number):boolean {
if (typeof amt !== "number") {
console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
return false;
}
this.hp -= amt;
if (this.hp <= 0) {
this.shock += 0.5;
this.hp = this.max_hp;
return true;
} else {
return false;
}
}
whoAmI(): string {
return 'Sleeve';
}
/**
* Serialize the current object to a JSON save state.
*/

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

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

@ -10,6 +10,7 @@ import { FactionWorkType } from "../../../Faction/FactionWorkTypeEnum";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import { FactionNames } from "../../../Faction/data/FactionNames";
import { Contract } from "../../../Bladeburner/Contract";
const universitySelectorOptions: string[] = [
"Study Computer Science",
@ -22,6 +23,8 @@ const universitySelectorOptions: string[] = [
const gymSelectorOptions: string[] = ["Train Strength", "Train Defense", "Train Dexterity", "Train Agility"];
const bladeburnerSelectorOptions: string[] = ["Field Analysis", "Recruitment", "Diplomacy", "Infiltrate synthoids", "Support main sleeve", "Take on Contracts"];
interface IProps {
sleeve: Sleeve;
player: IPlayer;
@ -82,6 +85,27 @@ function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
});
}
function possibleContracts(player: IPlayer, sleeve: Sleeve): string[] {
const bb = player.bladeburner;
if(bb === null){
return ["------"];
}
let contracts = bb.getContractNamesNetscriptFn();
for (const otherSleeve of player.sleeves) {
if (sleeve === otherSleeve) {
continue;
}
if (otherSleeve.currentTask === SleeveTaskType.Bladeburner
&& otherSleeve.bbAction == 'Take on Contracts') {
contracts = contracts.filter(x => x != otherSleeve.bbContract);
}
}
if(contracts.length === 0){
return ["------"];
}
return contracts;
}
const tasks: {
[key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => ITaskDetails);
["------"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
@ -90,6 +114,7 @@ const tasks: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Workout at Gym"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Perform Bladeburner Actions"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;
} = {
@ -166,6 +191,17 @@ const tasks: {
return { first: gymSelectorOptions, second: () => gyms };
},
"Perform Bladeburner Actions": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {
return {
first: bladeburnerSelectorOptions,
second: (s1: string) => {
if(s1 === "Take on Contracts"){
return possibleContracts(player, sleeve);
} else {
return ["------"];
}
} };
},
"Shock Recovery": (): ITaskDetails => {
return { first: ["------"], second: () => ["------"] };
},
@ -182,6 +218,7 @@ const canDo: {
["Commit Crime"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Take University Course"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Workout at Gym"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Perform Bladeburner Actions"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Shock Recovery"]: (player: IPlayer, sleeve: Sleeve) => boolean;
["Synchronize"]: (player: IPlayer, sleeve: Sleeve) => boolean;
} = {
@ -193,6 +230,7 @@ const canDo: {
[CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),
"Workout at Gym": (player: IPlayer, sleeve: Sleeve) =>
[CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),
"Perform Bladeburner Actions": (player: IPlayer, sleeve: Sleeve) => player.inBladeburner(),
"Shock Recovery": (player: IPlayer, sleeve: Sleeve) => sleeve.shock < 100,
Synchronize: (player: IPlayer, sleeve: Sleeve) => sleeve.sync < 100,
};
@ -224,6 +262,8 @@ function getABC(sleeve: Sleeve): [string, string, string] {
return ["Take University Course", sleeve.className, sleeve.currentTaskLocation];
case SleeveTaskType.Gym:
return ["Workout at Gym", sleeve.gymStatType, sleeve.currentTaskLocation];
case SleeveTaskType.Bladeburner:
return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
case SleeveTaskType.Recovery:
return ["Shock Recovery", "------", "------"];
case SleeveTaskType.Synchro:

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