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