mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-21 23:23:51 +01:00
BLADEBURNER: Typesafety / refactoring (#1154)
This commit is contained in:
parent
5f1a94a9d3
commit
6669c4da6a
@ -1,14 +1,13 @@
|
||||
import {
|
||||
AugmentationName,
|
||||
BlackOperationName,
|
||||
BladeSkillName,
|
||||
CityName,
|
||||
CompletedProgramName,
|
||||
CorpUnlockName,
|
||||
FactionName,
|
||||
IndustryType,
|
||||
} from "@enums";
|
||||
import { SkillNames } from "../Bladeburner/data/SkillNames";
|
||||
import { Skills } from "../Bladeburner/Skills";
|
||||
import { Skills } from "../Bladeburner/data/Skills";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { Exploit } from "../Exploits/Exploit";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
@ -31,6 +30,7 @@ import { workerScripts } from "../Netscript/WorkerScripts";
|
||||
|
||||
import { getRecordValues } from "../Types/Record";
|
||||
import { ServerConstants } from "../Server/data/Constants";
|
||||
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
|
||||
|
||||
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
|
||||
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
|
||||
@ -65,7 +65,7 @@ function bitNodeFinishedState(): boolean {
|
||||
const wd = GetServer(SpecialServers.WorldDaemon);
|
||||
if (!(wd instanceof Server)) return false;
|
||||
if (wd.backdoorInstalled) return true;
|
||||
return Player.bladeburner !== null && BlackOperationName.OperationDaedalus in Player.bladeburner.blackops;
|
||||
return Player.bladeburner !== null && Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length;
|
||||
}
|
||||
|
||||
function hasAccessToSF(bn: number): boolean {
|
||||
@ -432,8 +432,7 @@ export const achievements: Record<string, Achievement> = {
|
||||
Icon: "BLADEOVERCLOCK",
|
||||
Visible: () => hasAccessToSF(6),
|
||||
Condition: () =>
|
||||
Player.bladeburner !== null &&
|
||||
Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl,
|
||||
Player.bladeburner?.getSkillLevel(BladeSkillName.overclock) === Skills[BladeSkillName.overclock].maxLvl,
|
||||
},
|
||||
BLADEBURNER_UNSPENT_100000: {
|
||||
...achievementData.BLADEBURNER_UNSPENT_100000,
|
||||
|
@ -1,306 +0,0 @@
|
||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||
import { addOffset } from "../utils/helpers/addOffset";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
import { BladeburnerConstants } from "./data/Constants";
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { Person } from "../PersonObjects/Person";
|
||||
import { calculateIntelligenceBonus } from "../PersonObjects/formulas/intelligence";
|
||||
|
||||
interface ISuccessChanceParams {
|
||||
est: boolean;
|
||||
}
|
||||
|
||||
class StatsMultiplier {
|
||||
[key: string]: number;
|
||||
|
||||
hack = 0;
|
||||
str = 0;
|
||||
def = 0;
|
||||
dex = 0;
|
||||
agi = 0;
|
||||
cha = 0;
|
||||
int = 0;
|
||||
}
|
||||
|
||||
export interface IActionParams {
|
||||
name?: string;
|
||||
level?: number;
|
||||
maxLevel?: number;
|
||||
autoLevel?: boolean;
|
||||
baseDifficulty?: number;
|
||||
difficultyFac?: number;
|
||||
rewardFac?: number;
|
||||
successes?: number;
|
||||
failures?: number;
|
||||
rankGain?: number;
|
||||
rankLoss?: number;
|
||||
hpLoss?: number;
|
||||
hpLost?: number;
|
||||
isStealth?: boolean;
|
||||
isKill?: boolean;
|
||||
count?: number;
|
||||
weights?: StatsMultiplier;
|
||||
decays?: StatsMultiplier;
|
||||
teamCount?: number;
|
||||
}
|
||||
|
||||
export class Action {
|
||||
name = "";
|
||||
|
||||
// Difficulty scales with level. See getDifficulty() method
|
||||
level = 1;
|
||||
maxLevel = 1;
|
||||
autoLevel = true;
|
||||
baseDifficulty = 100;
|
||||
difficultyFac = 1.01;
|
||||
|
||||
// Rank increase/decrease is affected by this exponent
|
||||
rewardFac = 1.02;
|
||||
|
||||
successes = 0;
|
||||
failures = 0;
|
||||
|
||||
// All of these scale with level/difficulty
|
||||
rankGain = 0;
|
||||
rankLoss = 0;
|
||||
hpLoss = 0;
|
||||
hpLost = 0;
|
||||
|
||||
// Action Category. Current categories are stealth and kill
|
||||
isStealth = false;
|
||||
isKill = false;
|
||||
|
||||
/**
|
||||
* Number of this contract remaining, and its growth rate
|
||||
* Growth rate is an integer and the count will increase by that integer every "cycle"
|
||||
*/
|
||||
count: number = getRandomInt(1e3, 25e3);
|
||||
|
||||
// Weighting of each stat in determining action success rate
|
||||
weights: StatsMultiplier = {
|
||||
hack: 1 / 7,
|
||||
str: 1 / 7,
|
||||
def: 1 / 7,
|
||||
dex: 1 / 7,
|
||||
agi: 1 / 7,
|
||||
cha: 1 / 7,
|
||||
int: 1 / 7,
|
||||
};
|
||||
// Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)
|
||||
decays: StatsMultiplier = {
|
||||
hack: 0.9,
|
||||
str: 0.9,
|
||||
def: 0.9,
|
||||
dex: 0.9,
|
||||
agi: 0.9,
|
||||
cha: 0.9,
|
||||
int: 0.9,
|
||||
};
|
||||
teamCount = 0;
|
||||
|
||||
// Base Class for Contracts, Operations, and BlackOps
|
||||
constructor(params: IActionParams | null = null) {
|
||||
// | null = null
|
||||
if (params && params.name) this.name = params.name;
|
||||
|
||||
if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
|
||||
if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac;
|
||||
|
||||
if (params && params.rewardFac) this.rewardFac = params.rewardFac;
|
||||
if (params && params.rankGain) this.rankGain = params.rankGain;
|
||||
if (params && params.rankLoss) this.rankLoss = params.rankLoss;
|
||||
if (params && params.hpLoss) this.hpLoss = params.hpLoss;
|
||||
|
||||
if (params && params.isStealth) this.isStealth = params.isStealth;
|
||||
if (params && params.isKill) this.isKill = params.isKill;
|
||||
|
||||
if (params && params.count) this.count = params.count;
|
||||
|
||||
if (params && params.weights) this.weights = params.weights;
|
||||
if (params && params.decays) this.decays = params.decays;
|
||||
|
||||
// Check to make sure weights are summed properly
|
||||
let sum = 0;
|
||||
for (const weight of Object.keys(this.weights)) {
|
||||
if (Object.hasOwn(this.weights, weight)) {
|
||||
sum += this.weights[weight];
|
||||
}
|
||||
}
|
||||
if (sum - 1 >= 10 * Number.EPSILON) {
|
||||
throw new Error(
|
||||
"Invalid weights when constructing Action " +
|
||||
this.name +
|
||||
". The weights should sum up to 1. They sum up to :" +
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
for (const decay of Object.keys(this.decays)) {
|
||||
if (Object.hasOwn(this.decays, decay)) {
|
||||
if (this.decays[decay] > 1) {
|
||||
throw new Error(`Invalid decays when constructing Action ${this.name}. Decay value cannot be greater than 1`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDifficulty(): number {
|
||||
const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1);
|
||||
if (isNaN(difficulty)) {
|
||||
throw new Error("Calculated NaN in Action.getDifficulty()");
|
||||
}
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for success. Should be called when an action has completed
|
||||
* @param inst {Bladeburner} - Bladeburner instance
|
||||
*/
|
||||
attempt(inst: Bladeburner, person: Person): boolean {
|
||||
return Math.random() < this.getSuccessChance(inst, person);
|
||||
}
|
||||
|
||||
// To be implemented by subtypes
|
||||
getActionTimePenalty(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getActionTime(inst: Bladeburner, person: Person): number {
|
||||
const difficulty = this.getDifficulty();
|
||||
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
|
||||
const skillFac = inst.skillMultipliers.actionTime; // Always < 1
|
||||
|
||||
const effAgility = person.skills.agility * inst.skillMultipliers.effAgi;
|
||||
const effDexterity = person.skills.dexterity * inst.skillMultipliers.effDex;
|
||||
const statFac =
|
||||
0.5 *
|
||||
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
|
||||
Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) +
|
||||
effAgility / BladeburnerConstants.EffAgiLinearFactor +
|
||||
effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1
|
||||
|
||||
baseTime = Math.max(1, (baseTime * skillFac) / statFac);
|
||||
|
||||
return Math.ceil(baseTime * this.getActionTimePenalty());
|
||||
}
|
||||
|
||||
// Subtypes of Action implement these differently
|
||||
getTeamSuccessBonus(__inst: Bladeburner): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getActionTypeSkillSuccessBonus(__inst: Bladeburner): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getChaosCompetencePenalty(inst: Bladeburner, params: ISuccessChanceParams): number {
|
||||
const city = inst.getCurrentCity();
|
||||
if (params.est) {
|
||||
return Math.pow(city.popEst / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
|
||||
} else {
|
||||
return Math.pow(city.pop / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
|
||||
}
|
||||
}
|
||||
|
||||
getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
|
||||
const city = inst.getCurrentCity();
|
||||
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
|
||||
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
|
||||
const mult = Math.pow(diff, 0.5);
|
||||
return mult;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
getEstSuccessChance(inst: Bladeburner, person: Person): [number, number] {
|
||||
function clamp(x: number): number {
|
||||
return Math.max(0, Math.min(x, 1));
|
||||
}
|
||||
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;
|
||||
const city = inst.getCurrentCity();
|
||||
let r = city.pop / city.popEst;
|
||||
if (Number.isNaN(r)) r = 0;
|
||||
if (r < 1) low *= r;
|
||||
else high *= r;
|
||||
return [clamp(low), clamp(high)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inst - Bladeburner Object
|
||||
* @params - options:
|
||||
* est (bool): Get success chance estimate instead of real success chance
|
||||
*/
|
||||
getSuccessChance(inst: Bladeburner, person: Person, params: ISuccessChanceParams = { est: false }): number {
|
||||
if (inst == null) {
|
||||
throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");
|
||||
}
|
||||
let difficulty = this.getDifficulty();
|
||||
let competence = 0;
|
||||
for (const stat of Object.keys(this.weights)) {
|
||||
if (Object.hasOwn(this.weights, stat)) {
|
||||
const playerStatLvl = person.queryStatFromString(stat);
|
||||
const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1);
|
||||
let effMultiplier = inst.skillMultipliers[key];
|
||||
if (effMultiplier == null) {
|
||||
console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`);
|
||||
effMultiplier = 1;
|
||||
}
|
||||
competence += this.weights[stat] * Math.pow(effMultiplier * playerStatLvl, this.decays[stat]);
|
||||
}
|
||||
}
|
||||
competence *= calculateIntelligenceBonus(person.skills.intelligence, 0.75);
|
||||
competence *= inst.calculateStaminaPenalty();
|
||||
|
||||
competence *= this.getTeamSuccessBonus(inst);
|
||||
|
||||
competence *= this.getChaosCompetencePenalty(inst, params);
|
||||
difficulty *= this.getChaosDifficultyBonus(inst);
|
||||
|
||||
if (this.name == "Raid" && inst.getCurrentCity().comms <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Factor skill multipliers into success chance
|
||||
competence *= inst.skillMultipliers.successChanceAll;
|
||||
competence *= this.getActionTypeSkillSuccessBonus(inst);
|
||||
if (this.isStealth) {
|
||||
competence *= inst.skillMultipliers.successChanceStealth;
|
||||
}
|
||||
if (this.isKill) {
|
||||
competence *= inst.skillMultipliers.successChanceKill;
|
||||
}
|
||||
|
||||
// Augmentation multiplier
|
||||
competence *= person.mults.bladeburner_success_chance;
|
||||
|
||||
if (isNaN(competence)) {
|
||||
throw new Error("Competence calculated as NaN in Action.getSuccessChance()");
|
||||
}
|
||||
return Math.min(1, competence / difficulty);
|
||||
}
|
||||
|
||||
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number {
|
||||
return Math.ceil(0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)));
|
||||
}
|
||||
|
||||
setMaxLevel(baseSuccessesPerLevel: number): void {
|
||||
if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) {
|
||||
++this.maxLevel;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return Generic_toJSON("Action", this);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Action {
|
||||
return Generic_fromJSON(Action, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.Action = Action;
|
@ -1,26 +0,0 @@
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
|
||||
interface IParams {
|
||||
name?: string;
|
||||
type?: number;
|
||||
}
|
||||
|
||||
export class ActionIdentifier {
|
||||
name = "";
|
||||
type = -1;
|
||||
|
||||
constructor(params: IParams = {}) {
|
||||
if (params.name) this.name = params.name;
|
||||
if (params.type) this.type = params.type;
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return Generic_toJSON("ActionIdentifier", this);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): ActionIdentifier {
|
||||
return Generic_fromJSON(ActionIdentifier, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.ActionIdentifier = ActionIdentifier;
|
183
src/Bladeburner/Actions/Action.ts
Normal file
183
src/Bladeburner/Actions/Action.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Person } from "../../PersonObjects/Person";
|
||||
import type { Availability, SuccessChanceParams } from "../Types";
|
||||
import type { Skills as PersonSkills } from "../../PersonObjects/Skills";
|
||||
|
||||
import { addOffset } from "../../utils/helpers/addOffset";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelligence";
|
||||
import { BladeMultName } from "../Enums";
|
||||
import { getRecordKeys } from "../../Types/Record";
|
||||
|
||||
export interface ActionParams {
|
||||
desc: string;
|
||||
baseDifficulty?: number;
|
||||
rewardFac?: number;
|
||||
rankGain?: number;
|
||||
rankLoss?: number;
|
||||
hpLoss?: number;
|
||||
isStealth?: boolean;
|
||||
isKill?: boolean;
|
||||
weights?: PersonSkills;
|
||||
decays?: PersonSkills;
|
||||
}
|
||||
|
||||
export abstract class ActionClass {
|
||||
desc = "";
|
||||
// For LevelableActions, the base difficulty can be increased based on action level
|
||||
baseDifficulty = 100;
|
||||
|
||||
// All of these scale with level/difficulty
|
||||
rankGain = 0;
|
||||
rankLoss = 0;
|
||||
hpLoss = 0;
|
||||
|
||||
// Action Category. Current categories are stealth and kill
|
||||
isStealth = false;
|
||||
isKill = false;
|
||||
|
||||
// Weighting of each stat in determining action success rate
|
||||
weights: PersonSkills = {
|
||||
hacking: 1 / 7,
|
||||
strength: 1 / 7,
|
||||
defense: 1 / 7,
|
||||
dexterity: 1 / 7,
|
||||
agility: 1 / 7,
|
||||
charisma: 1 / 7,
|
||||
intelligence: 1 / 7,
|
||||
};
|
||||
// Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)
|
||||
decays: PersonSkills = {
|
||||
hacking: 0.9,
|
||||
strength: 0.9,
|
||||
defense: 0.9,
|
||||
dexterity: 0.9,
|
||||
agility: 0.9,
|
||||
charisma: 0.9,
|
||||
intelligence: 0.9,
|
||||
};
|
||||
|
||||
constructor(params: ActionParams | null = null) {
|
||||
if (!params) return;
|
||||
this.desc = params.desc;
|
||||
if (params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
|
||||
|
||||
if (params.rankGain) this.rankGain = params.rankGain;
|
||||
if (params.rankLoss) this.rankLoss = params.rankLoss;
|
||||
if (params.hpLoss) this.hpLoss = params.hpLoss;
|
||||
|
||||
if (params.isStealth) this.isStealth = params.isStealth;
|
||||
if (params.isKill) this.isKill = params.isKill;
|
||||
|
||||
if (params.weights) this.weights = params.weights;
|
||||
if (params.decays) this.decays = params.decays;
|
||||
}
|
||||
|
||||
/** Tests for success. Should be called when an action has completed */
|
||||
attempt(bladeburner: Bladeburner, person: Person): boolean {
|
||||
return Math.random() < this.getSuccessChance(bladeburner, person);
|
||||
}
|
||||
|
||||
// All the functions below are overwritten by certain subtypes of action, e.g. BlackOps ignore city stats
|
||||
getPopulationSuccessFactor(bladeburner: Bladeburner, { est }: SuccessChanceParams): number {
|
||||
const city = bladeburner.getCurrentCity();
|
||||
const pop = est ? city.popEst : city.pop;
|
||||
return Math.pow(pop / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);
|
||||
}
|
||||
|
||||
getChaosSuccessFactor(bladeburner: Bladeburner): number {
|
||||
const city = bladeburner.getCurrentCity();
|
||||
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
|
||||
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
|
||||
const mult = Math.pow(diff, 0.5);
|
||||
return mult;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
getActionTime(bladeburner: Bladeburner, person: Person): number {
|
||||
const difficulty = this.getDifficulty();
|
||||
let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;
|
||||
const skillFac = bladeburner.getSkillMult(BladeMultName.actionTime); // Always < 1
|
||||
|
||||
const effAgility = bladeburner.getEffectiveSkillLevel(person, "agility");
|
||||
const effDexterity = bladeburner.getEffectiveSkillLevel(person, "dexterity");
|
||||
const statFac =
|
||||
0.5 *
|
||||
(Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +
|
||||
Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) +
|
||||
effAgility / BladeburnerConstants.EffAgiLinearFactor +
|
||||
effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1
|
||||
|
||||
baseTime = Math.max(1, (baseTime * skillFac) / statFac);
|
||||
|
||||
return Math.ceil(baseTime * this.getActionTimePenalty());
|
||||
}
|
||||
|
||||
getTeamSuccessBonus(__bladeburner: Bladeburner): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getActionTypeSkillSuccessBonus(__bladeburner: Bladeburner): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getAvailability(__bladeburner: Bladeburner): Availability {
|
||||
return { available: true };
|
||||
}
|
||||
|
||||
getActionTimePenalty(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getDifficulty(): number {
|
||||
return this.baseDifficulty;
|
||||
}
|
||||
|
||||
getSuccessRange(bladeburner: Bladeburner, person: Person): [minChance: number, maxChance: number] {
|
||||
function clamp(x: number): number {
|
||||
return Math.max(0, Math.min(x, 1));
|
||||
}
|
||||
const est = this.getSuccessChance(bladeburner, person, { est: true });
|
||||
const real = this.getSuccessChance(bladeburner, person);
|
||||
const diff = Math.abs(real - est);
|
||||
let low = real - diff;
|
||||
let high = real + diff;
|
||||
const city = bladeburner.getCurrentCity();
|
||||
let r = city.pop / city.popEst;
|
||||
if (Number.isNaN(r)) r = 0;
|
||||
if (r < 1) low *= r;
|
||||
else high *= r;
|
||||
return [clamp(low), clamp(high)];
|
||||
}
|
||||
|
||||
getSuccessChance(inst: Bladeburner, person: Person, { est }: SuccessChanceParams = { est: false }): number {
|
||||
let difficulty = this.getDifficulty();
|
||||
let competence = 0;
|
||||
for (const stat of getRecordKeys(person.skills)) {
|
||||
competence += this.weights[stat] * Math.pow(inst.getEffectiveSkillLevel(person, stat), this.decays[stat]);
|
||||
}
|
||||
competence *= calculateIntelligenceBonus(person.skills.intelligence, 0.75);
|
||||
competence *= inst.calculateStaminaPenalty();
|
||||
|
||||
competence *= this.getTeamSuccessBonus(inst);
|
||||
|
||||
competence *= this.getPopulationSuccessFactor(inst, { est });
|
||||
difficulty *= this.getChaosSuccessFactor(inst);
|
||||
|
||||
// Factor skill multipliers into success chance
|
||||
competence *= inst.getSkillMult(BladeMultName.successChanceAll);
|
||||
competence *= this.getActionTypeSkillSuccessBonus(inst);
|
||||
if (this.isStealth) competence *= inst.getSkillMult(BladeMultName.successChanceStealth);
|
||||
if (this.isKill) competence *= inst.getSkillMult(BladeMultName.successChanceKill);
|
||||
|
||||
// Augmentation multiplier
|
||||
competence *= person.mults.bladeburner_success_chance;
|
||||
|
||||
if (isNaN(competence)) {
|
||||
throw new Error("Competence calculated as NaN in Action.getSuccessChance()");
|
||||
}
|
||||
return Math.min(1, competence / difficulty);
|
||||
}
|
||||
}
|
51
src/Bladeburner/Actions/BlackOperation.ts
Normal file
51
src/Bladeburner/Actions/BlackOperation.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Availability, ActionIdentifier } from "../Types";
|
||||
|
||||
import { BladeActionType, BladeBlackOpName } from "@enums";
|
||||
import { ActionClass, ActionParams } from "./Action";
|
||||
import { operationSkillSuccessBonus, operationTeamSuccessBonus } from "./Operation";
|
||||
|
||||
interface BlackOpParams {
|
||||
name: BladeBlackOpName;
|
||||
reqdRank: number;
|
||||
n: number;
|
||||
}
|
||||
|
||||
export class BlackOperation extends ActionClass {
|
||||
type: BladeActionType.blackOp = BladeActionType.blackOp;
|
||||
name: BladeBlackOpName;
|
||||
n: number;
|
||||
reqdRank: number;
|
||||
teamCount = 0;
|
||||
get id(): ActionIdentifier {
|
||||
return { type: this.type, name: this.name };
|
||||
}
|
||||
|
||||
constructor(params: ActionParams & BlackOpParams) {
|
||||
super(params);
|
||||
this.name = params.name;
|
||||
this.reqdRank = params.reqdRank;
|
||||
this.n = params.n;
|
||||
}
|
||||
|
||||
getAvailability(bladeburner: Bladeburner): Availability {
|
||||
if (bladeburner.numBlackOpsComplete < this.n) return { error: "Have not completed the previous Black Operation" };
|
||||
if (bladeburner.numBlackOpsComplete > this.n) return { error: "Already completed" };
|
||||
if (bladeburner.rank < this.reqdRank) return { error: "Insufficient rank" };
|
||||
return { available: true };
|
||||
}
|
||||
// To be implemented by subtypes
|
||||
getActionTimePenalty(): number {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
getPopulationSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getChaosSuccessFactor(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
|
||||
return 1;
|
||||
}
|
||||
getTeamSuccessBonus = operationTeamSuccessBonus;
|
||||
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
|
||||
}
|
33
src/Bladeburner/Actions/Contract.ts
Normal file
33
src/Bladeburner/Actions/Contract.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { ActionIdentifier } from "../Types";
|
||||
|
||||
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
|
||||
import { BladeActionType, BladeContractName, BladeMultName } from "../Enums";
|
||||
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
|
||||
|
||||
export class Contract extends LevelableActionClass {
|
||||
type: BladeActionType.contract = BladeActionType.contract;
|
||||
name: BladeContractName = BladeContractName.tracking;
|
||||
get id(): ActionIdentifier {
|
||||
return { type: this.type, name: this.name };
|
||||
}
|
||||
|
||||
constructor(params: (LevelableActionParams & { name: BladeContractName }) | null = null) {
|
||||
super(params);
|
||||
if (params) this.name = params.name;
|
||||
}
|
||||
|
||||
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
|
||||
return inst.getSkillMult(BladeMultName.successChanceContract);
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return this.save("Contract");
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Contract {
|
||||
return Generic_fromJSON(Contract, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.Contract = Contract;
|
35
src/Bladeburner/Actions/GeneralAction.ts
Normal file
35
src/Bladeburner/Actions/GeneralAction.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Person } from "../../PersonObjects/Person";
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { ActionIdentifier } from "../Types";
|
||||
|
||||
import { BladeActionType, BladeGeneralActionName } from "@enums";
|
||||
import { ActionClass, ActionParams } from "./Action";
|
||||
|
||||
type GeneralActionParams = ActionParams & {
|
||||
name: BladeGeneralActionName;
|
||||
getActionTime: (bladeburner: Bladeburner, person: Person) => number;
|
||||
getSuccessChance?: (bladeburner: Bladeburner, person: Person) => number;
|
||||
};
|
||||
|
||||
export class GeneralAction extends ActionClass {
|
||||
type: BladeActionType.general = BladeActionType.general;
|
||||
name: BladeGeneralActionName;
|
||||
get id(): ActionIdentifier {
|
||||
return { type: this.type, name: this.name };
|
||||
}
|
||||
|
||||
constructor(params: GeneralActionParams) {
|
||||
super(params);
|
||||
this.name = params.name;
|
||||
this.getActionTime = params.getActionTime;
|
||||
if (params.getSuccessChance) this.getSuccessChance = params.getSuccessChance;
|
||||
}
|
||||
|
||||
getSuccessChance(__bladeburner: Bladeburner, __person: Person): number {
|
||||
return 1;
|
||||
}
|
||||
getSuccessRange(bladeburner: Bladeburner, person: Person): [minChance: number, maxChance: number] {
|
||||
const chance = this.getSuccessChance(bladeburner, person);
|
||||
return [chance, chance];
|
||||
}
|
||||
}
|
111
src/Bladeburner/Actions/LevelableAction.ts
Normal file
111
src/Bladeburner/Actions/LevelableAction.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { IReviverValue } from "../../utils/JSONReviver";
|
||||
import type { Availability } from "../Types";
|
||||
|
||||
import { ActionClass, ActionParams } from "./Action";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { clampInteger } from "../../utils/helpers/clampNumber";
|
||||
|
||||
export type LevelableActionParams = ActionParams & {
|
||||
growthFunction: () => number;
|
||||
difficultyFac?: number;
|
||||
rewardFac?: number;
|
||||
minCount?: number;
|
||||
maxCount?: number;
|
||||
};
|
||||
|
||||
export abstract class LevelableActionClass extends ActionClass {
|
||||
// Static info, not included in save
|
||||
difficultyFac = 1.01;
|
||||
rewardFac = 1.02;
|
||||
growthFunction = () => 0;
|
||||
minCount = 1;
|
||||
maxCount = 150;
|
||||
|
||||
// Dynamic properties included in save
|
||||
count = 0;
|
||||
level = 1;
|
||||
maxLevel = 1;
|
||||
autoLevel = true;
|
||||
successes = 0;
|
||||
failures = 0;
|
||||
|
||||
constructor(params: LevelableActionParams | null = null) {
|
||||
super(params);
|
||||
if (!params) return;
|
||||
if (params.minCount) this.minCount = params.minCount;
|
||||
if (params.maxCount) this.maxCount = params.maxCount;
|
||||
if (params.difficultyFac) this.difficultyFac = params.difficultyFac;
|
||||
if (params.rewardFac) this.rewardFac = params.rewardFac;
|
||||
this.count = getRandomInt(this.minCount, this.maxCount);
|
||||
this.growthFunction = params.growthFunction;
|
||||
}
|
||||
|
||||
getAvailability(__bladeburner: Bladeburner): Availability {
|
||||
if (this.count < 1) return { error: "Insufficient action count" };
|
||||
return { available: true };
|
||||
}
|
||||
|
||||
setMaxLevel(baseSuccessesPerLevel: number): void {
|
||||
if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) {
|
||||
++this.maxLevel;
|
||||
}
|
||||
}
|
||||
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number {
|
||||
return Math.ceil(0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)));
|
||||
}
|
||||
|
||||
getDifficulty() {
|
||||
const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1);
|
||||
if (isNaN(difficulty)) {
|
||||
throw new Error("Calculated NaN in Action.getDifficulty()");
|
||||
}
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
/** Reset a levelable action's tracked stats */
|
||||
reset() {
|
||||
this.count = getRandomInt(this.minCount, this.maxCount);
|
||||
this.level = 1;
|
||||
this.maxLevel = 1;
|
||||
this.autoLevel = true;
|
||||
this.successes = 0;
|
||||
this.failures = 0;
|
||||
}
|
||||
|
||||
/** These are not loaded the same way as most game objects, to allow better typechecking on load + partially static loading */
|
||||
loadData(loadedObject: LevelableActionClass) {
|
||||
this.maxLevel = clampInteger(loadedObject.maxLevel, 1);
|
||||
this.level = clampInteger(loadedObject.level, 1, this.maxLevel);
|
||||
this.count = clampInteger(loadedObject.count, 0);
|
||||
this.autoLevel = !!loadedObject.autoLevel;
|
||||
this.successes = clampInteger(loadedObject.successes, 0);
|
||||
this.failures = clampInteger(loadedObject.failures, 0);
|
||||
}
|
||||
/** Create a basic object just containing the relevant data for a levelable action */
|
||||
save<T extends LevelableActionClass>(
|
||||
this: T,
|
||||
ctorName: string,
|
||||
...extraParams: (keyof T)[]
|
||||
): IReviverValue<LevelableActionSaveData> {
|
||||
const data = {
|
||||
...Object.fromEntries(extraParams.map((param) => [param, this[param]])),
|
||||
count: this.count,
|
||||
level: this.level,
|
||||
maxLevel: this.maxLevel,
|
||||
autoLevel: this.autoLevel,
|
||||
successes: this.successes,
|
||||
failures: this.failures,
|
||||
};
|
||||
return { ctor: ctorName, data };
|
||||
}
|
||||
}
|
||||
|
||||
export interface LevelableActionSaveData {
|
||||
count: number;
|
||||
level: number;
|
||||
maxLevel: number;
|
||||
autoLevel: boolean;
|
||||
successes: number;
|
||||
failures: number;
|
||||
}
|
84
src/Bladeburner/Actions/Operation.ts
Normal file
84
src/Bladeburner/Actions/Operation.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import type { Person } from "../../PersonObjects/Person";
|
||||
import type { BlackOperation } from "./BlackOperation";
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Availability, ActionIdentifier, SuccessChanceParams } from "../Types";
|
||||
|
||||
import { BladeActionType, BladeMultName, BladeOperationName } from "@enums";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { ActionClass } from "./Action";
|
||||
import { Generic_fromJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
|
||||
import { LevelableActionClass, LevelableActionParams } from "./LevelableAction";
|
||||
import { clampInteger } from "../../utils/helpers/clampNumber";
|
||||
|
||||
export interface OperationParams extends LevelableActionParams {
|
||||
name: BladeOperationName;
|
||||
getAvailability?: (bladeburner: Bladeburner) => Availability;
|
||||
}
|
||||
|
||||
export class Operation extends LevelableActionClass {
|
||||
type: BladeActionType.operation = BladeActionType.operation;
|
||||
name = BladeOperationName.investigation;
|
||||
teamCount = 0;
|
||||
get id(): ActionIdentifier {
|
||||
return { type: this.type, name: this.name };
|
||||
}
|
||||
|
||||
constructor(params: OperationParams | null = null) {
|
||||
super(params);
|
||||
if (!params) return;
|
||||
this.name = params.name;
|
||||
if (params.getAvailability) this.getAvailability = params.getAvailability;
|
||||
}
|
||||
|
||||
// These functions are shared between operations and blackops, so they are defined outside of Operation
|
||||
getTeamSuccessBonus = operationTeamSuccessBonus;
|
||||
getActionTypeSkillSuccessBonus = operationSkillSuccessBonus;
|
||||
|
||||
getChaosSuccessFactor(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
|
||||
const city = inst.getCurrentCity();
|
||||
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
|
||||
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
|
||||
const mult = Math.pow(diff, 0.5);
|
||||
return mult;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
getSuccessChance(inst: Bladeburner, person: Person, params: SuccessChanceParams) {
|
||||
if (this.name == BladeOperationName.raid && inst.getCurrentCity().comms <= 0) return 0;
|
||||
return ActionClass.prototype.getSuccessChance.call(this, inst, person, params);
|
||||
}
|
||||
|
||||
reset() {
|
||||
LevelableActionClass.prototype.reset.call(this);
|
||||
this.teamCount = 0;
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return this.save("Operation", "teamCount");
|
||||
}
|
||||
loadData(loadedObject: Operation): void {
|
||||
this.teamCount = clampInteger(loadedObject.teamCount, 0);
|
||||
LevelableActionClass.prototype.loadData.call(this, loadedObject);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Operation {
|
||||
return Generic_fromJSON(Operation, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.Operation = Operation;
|
||||
|
||||
// shared member functions for Operation and BlackOperation
|
||||
export const operationSkillSuccessBonus = (inst: Bladeburner) => {
|
||||
return inst.getSkillMult(BladeMultName.successChanceOperation);
|
||||
};
|
||||
export function operationTeamSuccessBonus(this: Operation | BlackOperation, inst: Bladeburner) {
|
||||
if (this.teamCount && this.teamCount > 0) {
|
||||
this.teamCount = Math.min(this.teamCount, inst.teamSize);
|
||||
const teamMultiplier = Math.pow(this.teamCount, 0.05);
|
||||
return teamMultiplier;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
7
src/Bladeburner/Actions/index.ts
Normal file
7
src/Bladeburner/Actions/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Barrel file for easier importing
|
||||
export { ActionClass } from "./Action";
|
||||
export { BlackOperation } from "./BlackOperation";
|
||||
export { Contract } from "./Contract";
|
||||
export { GeneralAction } from "./GeneralAction";
|
||||
export { Operation } from "./Operation";
|
||||
export { LevelableActionClass } from "./LevelableAction";
|
@ -1,32 +0,0 @@
|
||||
import { Operation, IOperationParams } from "./Operation";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
|
||||
export class BlackOperation extends Operation {
|
||||
constructor(params: IOperationParams | null = null) {
|
||||
super(params);
|
||||
this.count = 1;
|
||||
}
|
||||
|
||||
// To be implemented by subtypes
|
||||
getActionTimePenalty(): number {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
getChaosCompetencePenalty(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getChaosDifficultyBonus(/*inst: Bladeburner, params: ISuccessChanceParams*/): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return Generic_toJSON("BlackOperation", this);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Operation {
|
||||
return Generic_fromJSON(BlackOperation, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.BlackOperation = BlackOperation;
|
@ -1,571 +0,0 @@
|
||||
import { BlackOperation } from "./BlackOperation";
|
||||
import { BlackOperationName } from "@enums";
|
||||
|
||||
export const BlackOperations: Record<string, BlackOperation> = {};
|
||||
|
||||
(function () {
|
||||
BlackOperations[BlackOperationName.OperationTyphoon] = new BlackOperation({
|
||||
name: BlackOperationName.OperationTyphoon,
|
||||
baseDifficulty: 2000,
|
||||
reqdRank: 2.5e3,
|
||||
rankGain: 50,
|
||||
rankLoss: 10,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationZero] = new BlackOperation({
|
||||
name: BlackOperationName.OperationZero,
|
||||
baseDifficulty: 2500,
|
||||
reqdRank: 5e3,
|
||||
rankGain: 60,
|
||||
rankLoss: 15,
|
||||
hpLoss: 50,
|
||||
weights: {
|
||||
hack: 0.2,
|
||||
str: 0.15,
|
||||
def: 0.15,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationX] = new BlackOperation({
|
||||
name: BlackOperationName.OperationX,
|
||||
baseDifficulty: 3000,
|
||||
reqdRank: 7.5e3,
|
||||
rankGain: 75,
|
||||
rankLoss: 15,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationTitan] = new BlackOperation({
|
||||
name: BlackOperationName.OperationTitan,
|
||||
baseDifficulty: 4000,
|
||||
reqdRank: 10e3,
|
||||
rankGain: 100,
|
||||
rankLoss: 20,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationAres] = new BlackOperation({
|
||||
name: BlackOperationName.OperationAres,
|
||||
baseDifficulty: 5000,
|
||||
reqdRank: 12.5e3,
|
||||
rankGain: 125,
|
||||
rankLoss: 20,
|
||||
hpLoss: 200,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.25,
|
||||
def: 0.25,
|
||||
dex: 0.25,
|
||||
agi: 0.25,
|
||||
cha: 0,
|
||||
int: 0,
|
||||
},
|
||||
decays: {
|
||||
hack: 0,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationArchangel] = new BlackOperation({
|
||||
name: BlackOperationName.OperationArchangel,
|
||||
baseDifficulty: 7500,
|
||||
reqdRank: 15e3,
|
||||
rankGain: 200,
|
||||
rankLoss: 20,
|
||||
hpLoss: 25,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.3,
|
||||
agi: 0.3,
|
||||
cha: 0,
|
||||
int: 0,
|
||||
},
|
||||
decays: {
|
||||
hack: 0,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationJuggernaut] = new BlackOperation({
|
||||
name: BlackOperationName.OperationJuggernaut,
|
||||
baseDifficulty: 10e3,
|
||||
reqdRank: 20e3,
|
||||
rankGain: 300,
|
||||
rankLoss: 40,
|
||||
hpLoss: 300,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.25,
|
||||
def: 0.25,
|
||||
dex: 0.25,
|
||||
agi: 0.25,
|
||||
cha: 0,
|
||||
int: 0,
|
||||
},
|
||||
decays: {
|
||||
hack: 0,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationRedDragon] = new BlackOperation({
|
||||
name: BlackOperationName.OperationRedDragon,
|
||||
baseDifficulty: 12.5e3,
|
||||
reqdRank: 25e3,
|
||||
rankGain: 500,
|
||||
rankLoss: 50,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hack: 0.05,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.25,
|
||||
agi: 0.25,
|
||||
cha: 0,
|
||||
int: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationK] = new BlackOperation({
|
||||
name: BlackOperationName.OperationK,
|
||||
baseDifficulty: 15e3,
|
||||
reqdRank: 30e3,
|
||||
rankGain: 750,
|
||||
rankLoss: 60,
|
||||
hpLoss: 1000,
|
||||
weights: {
|
||||
hack: 0.05,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.25,
|
||||
agi: 0.25,
|
||||
cha: 0,
|
||||
int: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationDeckard] = new BlackOperation({
|
||||
name: BlackOperationName.OperationDeckard,
|
||||
baseDifficulty: 20e3,
|
||||
reqdRank: 40e3,
|
||||
rankGain: 1e3,
|
||||
rankLoss: 75,
|
||||
hpLoss: 200,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.24,
|
||||
def: 0.24,
|
||||
dex: 0.24,
|
||||
agi: 0.24,
|
||||
cha: 0,
|
||||
int: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hack: 0,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationTyrell] = new BlackOperation({
|
||||
name: BlackOperationName.OperationTyrell,
|
||||
baseDifficulty: 25e3,
|
||||
reqdRank: 50e3,
|
||||
rankGain: 1.5e3,
|
||||
rankLoss: 100,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationWallace] = new BlackOperation({
|
||||
name: BlackOperationName.OperationWallace,
|
||||
baseDifficulty: 30e3,
|
||||
reqdRank: 75e3,
|
||||
rankGain: 2e3,
|
||||
rankLoss: 150,
|
||||
hpLoss: 1500,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.24,
|
||||
def: 0.24,
|
||||
dex: 0.24,
|
||||
agi: 0.24,
|
||||
cha: 0,
|
||||
int: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationShoulderOfOrion] = new BlackOperation({
|
||||
name: BlackOperationName.OperationShoulderOfOrion,
|
||||
baseDifficulty: 35e3,
|
||||
reqdRank: 100e3,
|
||||
rankGain: 2.5e3,
|
||||
rankLoss: 500,
|
||||
hpLoss: 1500,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationHyron] = new BlackOperation({
|
||||
name: BlackOperationName.OperationHyron,
|
||||
baseDifficulty: 40e3,
|
||||
reqdRank: 125e3,
|
||||
rankGain: 3e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationMorpheus] = new BlackOperation({
|
||||
name: BlackOperationName.OperationMorpheus,
|
||||
baseDifficulty: 45e3,
|
||||
reqdRank: 150e3,
|
||||
rankGain: 4e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hack: 0.05,
|
||||
str: 0.15,
|
||||
def: 0.15,
|
||||
dex: 0.3,
|
||||
agi: 0.3,
|
||||
cha: 0,
|
||||
int: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationIonStorm] = new BlackOperation({
|
||||
name: BlackOperationName.OperationIonStorm,
|
||||
baseDifficulty: 50e3,
|
||||
reqdRank: 175e3,
|
||||
rankGain: 5e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 5000,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.24,
|
||||
def: 0.24,
|
||||
dex: 0.24,
|
||||
agi: 0.24,
|
||||
cha: 0,
|
||||
int: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationAnnihilus] = new BlackOperation({
|
||||
name: BlackOperationName.OperationAnnihilus,
|
||||
baseDifficulty: 55e3,
|
||||
reqdRank: 200e3,
|
||||
rankGain: 7.5e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hack: 0,
|
||||
str: 0.24,
|
||||
def: 0.24,
|
||||
dex: 0.24,
|
||||
agi: 0.24,
|
||||
cha: 0,
|
||||
int: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationUltron] = new BlackOperation({
|
||||
name: BlackOperationName.OperationUltron,
|
||||
baseDifficulty: 60e3,
|
||||
reqdRank: 250e3,
|
||||
rankGain: 10e3,
|
||||
rankLoss: 2e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationCenturion] = new BlackOperation({
|
||||
name: BlackOperationName.OperationCenturion,
|
||||
baseDifficulty: 70e3,
|
||||
reqdRank: 300e3,
|
||||
rankGain: 15e3,
|
||||
rankLoss: 5e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationVindictus] = new BlackOperation({
|
||||
name: BlackOperationName.OperationVindictus,
|
||||
baseDifficulty: 75e3,
|
||||
reqdRank: 350e3,
|
||||
rankGain: 20e3,
|
||||
rankLoss: 20e3,
|
||||
hpLoss: 20e3,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
});
|
||||
BlackOperations[BlackOperationName.OperationDaedalus] = new BlackOperation({
|
||||
name: BlackOperationName.OperationDaedalus,
|
||||
baseDifficulty: 80e3,
|
||||
reqdRank: 400e3,
|
||||
rankGain: 40e3,
|
||||
rankLoss: 10e3,
|
||||
hpLoss: 100e3,
|
||||
weights: {
|
||||
hack: 0.1,
|
||||
str: 0.2,
|
||||
def: 0.2,
|
||||
dex: 0.2,
|
||||
agi: 0.2,
|
||||
cha: 0,
|
||||
int: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hack: 0.6,
|
||||
str: 0.8,
|
||||
def: 0.8,
|
||||
dex: 0.8,
|
||||
agi: 0.8,
|
||||
cha: 0,
|
||||
int: 0.75,
|
||||
},
|
||||
});
|
||||
})();
|
1435
src/Bladeburner/Bladeburner.ts
Normal file
1435
src/Bladeburner/Bladeburner.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,35 +1,15 @@
|
||||
import { CityName } from "@enums";
|
||||
import { BladeburnerConstants } from "./data/Constants";
|
||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
import { addOffset } from "../utils/helpers/addOffset";
|
||||
import { CityName } from "@enums";
|
||||
|
||||
interface IChangePopulationByCountParams {
|
||||
/** How much the estimate should change by. */
|
||||
estChange: number;
|
||||
/** Add offset to estimate (offset by percentage). */
|
||||
estOffset: number;
|
||||
}
|
||||
|
||||
interface IChangePopulationByPercentageParams {
|
||||
nonZero: boolean;
|
||||
changeEstEqually: boolean;
|
||||
}
|
||||
import { clampInteger, clampNumber } from "../utils/helpers/clampNumber";
|
||||
|
||||
export class City {
|
||||
/** Name of the city. */
|
||||
name: CityName;
|
||||
|
||||
/** Population of the city. */
|
||||
pop = 0;
|
||||
|
||||
/** Population estimation of the city. */
|
||||
popEst = 0;
|
||||
|
||||
/** Number of communities in the city. */
|
||||
comms = 0;
|
||||
|
||||
/** Chaos level of the city. */
|
||||
pop = 0; // Population
|
||||
popEst = 0; // Population estimate
|
||||
comms = 0; // Number of communities
|
||||
chaos = 0;
|
||||
|
||||
constructor(name = CityName.Sector12) {
|
||||
@ -44,118 +24,63 @@ export class City {
|
||||
this.chaos = 0;
|
||||
}
|
||||
|
||||
/** p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) */
|
||||
/** @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5% */
|
||||
changeChaosByPercentage(p: number): void {
|
||||
if (isNaN(p)) {
|
||||
throw new Error("NaN passed into City.chaosChaosByPercentage()");
|
||||
}
|
||||
if (p === 0) {
|
||||
return;
|
||||
}
|
||||
this.chaos += this.chaos * (p / 100);
|
||||
if (this.chaos < 0) {
|
||||
this.chaos = 0;
|
||||
}
|
||||
this.chaos = clampNumber(this.chaos * (1 + p / 100), 0);
|
||||
}
|
||||
|
||||
improvePopulationEstimateByCount(n: number): void {
|
||||
if (isNaN(n)) {
|
||||
throw new Error("NaN passed into City.improvePopulationEstimateByCount()");
|
||||
}
|
||||
if (this.popEst < this.pop) {
|
||||
this.popEst += n;
|
||||
if (this.popEst > this.pop) {
|
||||
this.popEst = this.pop;
|
||||
}
|
||||
} else if (this.popEst > this.pop) {
|
||||
this.popEst -= n;
|
||||
if (this.popEst < this.pop) {
|
||||
this.popEst = this.pop;
|
||||
}
|
||||
}
|
||||
n = clampInteger(n, 0);
|
||||
const diff = Math.abs(this.popEst - this.pop);
|
||||
// Chgnge would overshoot actual population -> make estimate accurate
|
||||
if (diff <= n) this.popEst = this.pop;
|
||||
// Otherwise make enstimate closer by n
|
||||
else if (this.popEst < this.pop) this.popEst += n;
|
||||
else this.popEst -= n;
|
||||
}
|
||||
|
||||
/** p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) */
|
||||
/** @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5% */
|
||||
improvePopulationEstimateByPercentage(p: number, skillMult = 1): void {
|
||||
p = p * skillMult;
|
||||
if (isNaN(p)) {
|
||||
throw new Error("NaN passed into City.improvePopulationEstimateByPercentage()");
|
||||
}
|
||||
if (this.popEst < this.pop) {
|
||||
++this.popEst; // In case estimate is 0
|
||||
this.popEst *= 1 + p / 100;
|
||||
if (this.popEst > this.pop) {
|
||||
this.popEst = this.pop;
|
||||
}
|
||||
} else if (this.popEst > this.pop) {
|
||||
this.popEst *= 1 - p / 100;
|
||||
if (this.popEst < this.pop) {
|
||||
this.popEst = this.pop;
|
||||
}
|
||||
}
|
||||
p = clampNumber((p * skillMult) / 100);
|
||||
const diff = Math.abs(this.popEst - this.pop);
|
||||
// Chgnge would overshoot actual population -> make estimate accurate
|
||||
if (diff <= p * this.popEst) this.popEst = this.pop;
|
||||
// Otherwise make enstimate closer by n
|
||||
else if (this.popEst < this.pop) this.popEst = clampNumber(this.popEst * (1 + p));
|
||||
else this.popEst = clampNumber(this.popEst * (1 - p));
|
||||
}
|
||||
|
||||
changePopulationByCount(n: number, params: IChangePopulationByCountParams = { estChange: 0, estOffset: 0 }): void {
|
||||
if (isNaN(n)) {
|
||||
throw new Error("NaN passed into City.changePopulationByCount()");
|
||||
}
|
||||
this.pop += n;
|
||||
/**
|
||||
* @param params.estChange - Number to change the estimate by
|
||||
* @param params.estOffset - Offset percentage to apply to estimate */
|
||||
changePopulationByCount(n: number, params = { estChange: 0, estOffset: 0 }): void {
|
||||
n = clampInteger(n);
|
||||
this.pop = clampInteger(this.pop + n, 0);
|
||||
if (params.estChange && !isNaN(params.estChange)) {
|
||||
this.popEst += params.estChange;
|
||||
}
|
||||
if (params.estOffset) {
|
||||
this.popEst = addOffset(this.popEst, params.estOffset);
|
||||
}
|
||||
this.popEst = Math.max(this.popEst, 0);
|
||||
this.popEst = clampInteger(this.popEst, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5%
|
||||
* @params options:
|
||||
* changeEstEqually(bool) - Change the population estimate by an equal amount
|
||||
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
|
||||
*/
|
||||
changePopulationByPercentage(
|
||||
p: number,
|
||||
params: IChangePopulationByPercentageParams = {
|
||||
nonZero: false,
|
||||
changeEstEqually: false,
|
||||
},
|
||||
): number {
|
||||
if (isNaN(p)) {
|
||||
throw new Error("NaN passed into City.changePopulationByPercentage()");
|
||||
}
|
||||
if (p === 0) {
|
||||
return 0;
|
||||
}
|
||||
let change = Math.round(this.pop * (p / 100));
|
||||
* @param {number} p - the percentage change, not the multiplier. e.g. pass in p = 5 for 5%
|
||||
* @param {boolean} params.changeEstEqually - Whether to change the population estimate by an equal amount
|
||||
* @param {boolean} params.nonZero - Whether to ensure that population always changes by at least 1 */
|
||||
changePopulationByPercentage(p: number, params = { nonZero: false, changeEstEqually: false }): number {
|
||||
let change = clampInteger(this.pop * (p / 100));
|
||||
|
||||
// Population always changes by at least 1
|
||||
if (params.nonZero && change === 0) {
|
||||
p > 0 ? (change = 1) : (change = -1);
|
||||
}
|
||||
if (params.nonZero && change === 0) change = p > 0 ? 1 : -1;
|
||||
|
||||
this.pop += change;
|
||||
if (params.changeEstEqually) {
|
||||
this.popEst += change;
|
||||
if (this.popEst < 0) {
|
||||
this.popEst = 0;
|
||||
}
|
||||
}
|
||||
this.pop = clampInteger(this.pop + change, 0);
|
||||
if (params.changeEstEqually) this.popEst = clampInteger(this.popEst + change, 0);
|
||||
return change;
|
||||
}
|
||||
|
||||
changeChaosByCount(n: number): void {
|
||||
if (isNaN(n)) {
|
||||
throw new Error("NaN passed into City.changeChaosByCount()");
|
||||
}
|
||||
if (n === 0) {
|
||||
return;
|
||||
}
|
||||
this.chaos += n;
|
||||
if (this.chaos < 0) {
|
||||
this.chaos = 0;
|
||||
}
|
||||
this.chaos = clampNumber(this.chaos + n, 0);
|
||||
}
|
||||
|
||||
/** Serialize the current object to a JSON save state. */
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { Action, IActionParams } from "./Action";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
|
||||
export class Contract extends Action {
|
||||
constructor(params: IActionParams | null = null) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
|
||||
return inst.skillMultipliers.successChanceContract;
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return Generic_toJSON("Contract", this);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Contract {
|
||||
return Generic_fromJSON(Contract, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.Contract = Contract;
|
@ -1,4 +1,31 @@
|
||||
export enum BlackOperationName {
|
||||
export enum BladeActionType {
|
||||
general = "General",
|
||||
contract = "Contracts",
|
||||
operation = "Operations",
|
||||
blackOp = "Black Operations",
|
||||
}
|
||||
export enum BladeGeneralActionName {
|
||||
training = "Training",
|
||||
fieldAnalysis = "Field Analysis",
|
||||
recruitment = "Recruitment",
|
||||
diplomacy = "Diplomacy",
|
||||
hyperbolicRegen = "Hyperbolic Regeneration Chamber",
|
||||
inciteViolence = "Incite Violence",
|
||||
}
|
||||
export enum BladeContractName {
|
||||
tracking = "Tracking",
|
||||
bountyHunter = "Bounty Hunter",
|
||||
retirement = "Retirement",
|
||||
}
|
||||
export enum BladeOperationName {
|
||||
investigation = "Investigation",
|
||||
undercover = "Undercover Operation",
|
||||
sting = "Sting Operation",
|
||||
raid = "Raid",
|
||||
stealthRetirement = "Stealth Retirement Operation",
|
||||
assassination = "Assassination",
|
||||
}
|
||||
export enum BladeBlackOpName {
|
||||
OperationTyphoon = "Operation Typhoon",
|
||||
OperationZero = "Operation Zero",
|
||||
OperationX = "Operation X",
|
||||
@ -21,3 +48,36 @@ export enum BlackOperationName {
|
||||
OperationVindictus = "Operation Vindictus",
|
||||
OperationDaedalus = "Operation Daedalus",
|
||||
}
|
||||
|
||||
export enum BladeSkillName {
|
||||
bladesIntuition = "Blade's Intuition",
|
||||
cloak = "Cloak",
|
||||
shortCircuit = "Short-Circuit",
|
||||
digitalObserver = "Digital Observer",
|
||||
tracer = "Tracer",
|
||||
overclock = "Overclock",
|
||||
reaper = "Reaper",
|
||||
evasiveSystem = "Evasive System",
|
||||
datamancer = "Datamancer",
|
||||
cybersEdge = "Cyber's Edge",
|
||||
handsOfMidas = "Hands of Midas",
|
||||
hyperdrive = "Hyperdrive",
|
||||
}
|
||||
|
||||
export enum BladeMultName {
|
||||
successChanceAll = "Total Success Chance",
|
||||
successChanceStealth = "Stealth Success Chance",
|
||||
successChanceKill = "Retirement Success Chance",
|
||||
successChanceContract = "Contract Success Chance",
|
||||
successChanceOperation = "Operation Success Chance",
|
||||
successChanceEstimate = "Synthoid Data Estimate",
|
||||
actionTime = "Action Time",
|
||||
effStr = "Effective Strength",
|
||||
effDef = "Effective Defense",
|
||||
effDex = "Effective Dexterity",
|
||||
effAgi = "Effective Agility",
|
||||
effCha = "Effective Charisma",
|
||||
stamina = "Stamina",
|
||||
money = "Contract Money",
|
||||
expGain = "Experience Gain",
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { Action } from "./Action";
|
||||
|
||||
export const GeneralActions: Record<string, Action> = {};
|
||||
|
||||
const actionNames: string[] = [
|
||||
"Training",
|
||||
"Field Analysis",
|
||||
"Recruitment",
|
||||
"Diplomacy",
|
||||
"Hyperbolic Regeneration Chamber",
|
||||
"Incite Violence",
|
||||
];
|
||||
|
||||
for (const actionName of actionNames) {
|
||||
GeneralActions[actionName] = new Action({
|
||||
name: actionName,
|
||||
});
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { BladeburnerConstants } from "./data/Constants";
|
||||
import { Action, IActionParams } from "./Action";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
|
||||
|
||||
export interface IOperationParams extends IActionParams {
|
||||
reqdRank?: number;
|
||||
teamCount?: number;
|
||||
}
|
||||
|
||||
export class Operation extends Action {
|
||||
reqdRank = 100;
|
||||
teamCount = 0;
|
||||
|
||||
constructor(params: IOperationParams | null = null) {
|
||||
super(params);
|
||||
if (params && params.reqdRank) this.reqdRank = params.reqdRank;
|
||||
if (params && params.teamCount) this.teamCount = params.teamCount;
|
||||
}
|
||||
|
||||
// For actions that have teams. To be implemented by subtypes.
|
||||
getTeamSuccessBonus(inst: Bladeburner): number {
|
||||
if (this.teamCount && this.teamCount > 0) {
|
||||
this.teamCount = Math.min(this.teamCount, inst.teamSize);
|
||||
const teamMultiplier = Math.pow(this.teamCount, 0.05);
|
||||
return teamMultiplier;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
getActionTypeSkillSuccessBonus(inst: Bladeburner): number {
|
||||
return inst.skillMultipliers.successChanceOperation;
|
||||
}
|
||||
|
||||
getChaosDifficultyBonus(inst: Bladeburner /*, params: ISuccessChanceParams*/): number {
|
||||
const city = inst.getCurrentCity();
|
||||
if (city.chaos > BladeburnerConstants.ChaosThreshold) {
|
||||
const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);
|
||||
const mult = Math.pow(diff, 0.5);
|
||||
return mult;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
toJSON(): IReviverValue {
|
||||
return Generic_toJSON("Operation", this);
|
||||
}
|
||||
|
||||
static fromJSON(value: IReviverValue): Operation {
|
||||
return Generic_fromJSON(Operation, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
constructorsForReviver.Operation = Operation;
|
@ -1,149 +1,51 @@
|
||||
import type { BladeMultName, BladeSkillName } from "@enums";
|
||||
|
||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { Availability } from "./Types";
|
||||
import { PositiveSafeInteger, isPositiveSafeInteger } from "../types";
|
||||
import { PartialRecord, getRecordEntries } from "../Types/Record";
|
||||
|
||||
interface ISkillParams {
|
||||
name: string;
|
||||
interface SkillParams {
|
||||
name: BladeSkillName;
|
||||
desc: string;
|
||||
|
||||
baseCost?: number;
|
||||
costInc?: number;
|
||||
maxLvl?: number;
|
||||
|
||||
successChanceAll?: number;
|
||||
successChanceStealth?: number;
|
||||
successChanceKill?: number;
|
||||
successChanceContract?: number;
|
||||
successChanceOperation?: number;
|
||||
successChanceEstimate?: number;
|
||||
|
||||
actionTime?: number;
|
||||
|
||||
effHack?: number;
|
||||
effStr?: number;
|
||||
effDef?: number;
|
||||
effDex?: number;
|
||||
effAgi?: number;
|
||||
effCha?: number;
|
||||
|
||||
stamina?: number;
|
||||
money?: number;
|
||||
expGain?: number;
|
||||
mults: PartialRecord<BladeMultName, number>;
|
||||
}
|
||||
|
||||
export class Skill {
|
||||
name: string;
|
||||
name: BladeSkillName;
|
||||
desc: string;
|
||||
// Cost is in Skill Points
|
||||
baseCost = 1;
|
||||
// Additive cost increase per level
|
||||
costInc = 1;
|
||||
maxLvl = 0;
|
||||
maxLvl = Number.MAX_SAFE_INTEGER;
|
||||
mults: PartialRecord<BladeMultName, number> = {};
|
||||
|
||||
/**
|
||||
* These benefits are additive. So total multiplier will be level (handled externally) times the
|
||||
* effects below
|
||||
*/
|
||||
successChanceAll = 0;
|
||||
successChanceStealth = 0;
|
||||
successChanceKill = 0;
|
||||
successChanceContract = 0;
|
||||
successChanceOperation = 0;
|
||||
|
||||
/**
|
||||
* This multiplier affects everything that increases synthoid population/community estimate
|
||||
* e.g. Field analysis, Investigation Op, Undercover Op
|
||||
*/
|
||||
successChanceEstimate = 0;
|
||||
actionTime = 0;
|
||||
effHack = 0;
|
||||
effStr = 0;
|
||||
effDef = 0;
|
||||
effDex = 0;
|
||||
effAgi = 0;
|
||||
effCha = 0;
|
||||
stamina = 0;
|
||||
money = 0;
|
||||
expGain = 0;
|
||||
|
||||
constructor(params: ISkillParams = { name: "foo", desc: "foo" }) {
|
||||
if (!params.name) {
|
||||
throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor");
|
||||
}
|
||||
if (!params.desc) {
|
||||
throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor");
|
||||
}
|
||||
constructor(params: SkillParams) {
|
||||
this.name = params.name;
|
||||
this.desc = params.desc;
|
||||
this.baseCost = params.baseCost ? params.baseCost : 1;
|
||||
this.costInc = params.costInc ? params.costInc : 1;
|
||||
|
||||
if (params.maxLvl) {
|
||||
this.maxLvl = params.maxLvl;
|
||||
this.baseCost = params.baseCost ?? 1;
|
||||
this.costInc = params.costInc ?? 1;
|
||||
this.maxLvl = params.maxLvl ?? 1;
|
||||
for (const [multName, mult] of getRecordEntries(params.mults)) this.mults[multName] = mult;
|
||||
}
|
||||
|
||||
if (params.successChanceAll) {
|
||||
this.successChanceAll = params.successChanceAll;
|
||||
}
|
||||
if (params.successChanceStealth) {
|
||||
this.successChanceStealth = params.successChanceStealth;
|
||||
}
|
||||
if (params.successChanceKill) {
|
||||
this.successChanceKill = params.successChanceKill;
|
||||
}
|
||||
if (params.successChanceContract) {
|
||||
this.successChanceContract = params.successChanceContract;
|
||||
}
|
||||
if (params.successChanceOperation) {
|
||||
this.successChanceOperation = params.successChanceOperation;
|
||||
}
|
||||
|
||||
if (params.successChanceEstimate) {
|
||||
this.successChanceEstimate = params.successChanceEstimate;
|
||||
}
|
||||
|
||||
if (params.actionTime) {
|
||||
this.actionTime = params.actionTime;
|
||||
}
|
||||
if (params.effHack) {
|
||||
this.effHack = params.effHack;
|
||||
}
|
||||
if (params.effStr) {
|
||||
this.effStr = params.effStr;
|
||||
}
|
||||
if (params.effDef) {
|
||||
this.effDef = params.effDef;
|
||||
}
|
||||
if (params.effDex) {
|
||||
this.effDex = params.effDex;
|
||||
}
|
||||
if (params.effAgi) {
|
||||
this.effAgi = params.effAgi;
|
||||
}
|
||||
if (params.effCha) {
|
||||
this.effCha = params.effCha;
|
||||
}
|
||||
|
||||
if (params.stamina) {
|
||||
this.stamina = params.stamina;
|
||||
}
|
||||
if (params.money) {
|
||||
this.money = params.money;
|
||||
}
|
||||
if (params.expGain) {
|
||||
this.expGain = params.expGain;
|
||||
}
|
||||
}
|
||||
|
||||
calculateCost(currentLevel: number, count = 1): number {
|
||||
calculateCost(currentLevel: number, count = 1 as PositiveSafeInteger): number {
|
||||
if (currentLevel + count > this.maxLvl) return Infinity;
|
||||
//Recursive mode does not handle invalid inputs properly, but it should never
|
||||
//be possible for it to run with them. For the sake of not crashing the game,
|
||||
const recursiveMode = (currentLevel: number, count: number): number => {
|
||||
const recursiveMode = (currentLevel: number, count: PositiveSafeInteger): number => {
|
||||
if (count <= 1) {
|
||||
return Math.floor((this.baseCost + currentLevel * this.costInc) * currentNodeMults.BladeburnerSkillCost);
|
||||
} else {
|
||||
const thisUpgrade = Math.floor(
|
||||
(this.baseCost + currentLevel * this.costInc) * currentNodeMults.BladeburnerSkillCost,
|
||||
);
|
||||
return this.calculateCost(currentLevel + 1, count - 1) + thisUpgrade;
|
||||
return this.calculateCost(currentLevel + 1, (count - 1) as PositiveSafeInteger) + thisUpgrade;
|
||||
}
|
||||
};
|
||||
|
||||
@ -166,26 +68,16 @@ export class Skill {
|
||||
}
|
||||
}
|
||||
|
||||
getMultiplier(name: string): number {
|
||||
if (name === "successChanceAll") return this.successChanceAll;
|
||||
if (name === "successChanceStealth") return this.successChanceStealth;
|
||||
if (name === "successChanceKill") return this.successChanceKill;
|
||||
if (name === "successChanceContract") return this.successChanceContract;
|
||||
if (name === "successChanceOperation") return this.successChanceOperation;
|
||||
if (name === "successChanceEstimate") return this.successChanceEstimate;
|
||||
canUpgrade(bladeburner: Bladeburner, count = 1): Availability<{ cost: number }> {
|
||||
const currentLevel = bladeburner.skills[this.name] ?? 0;
|
||||
if (!isPositiveSafeInteger(count)) return { error: `Invalid upgrade count ${count}` };
|
||||
if (currentLevel + count > this.maxLvl) return { error: `Upgraded level ${currentLevel + count} exceeds max` };
|
||||
const cost = this.calculateCost(currentLevel, count);
|
||||
if (cost > bladeburner.skillPoints) return { error: `Insufficient skill points for upgrade` };
|
||||
return { available: true, cost };
|
||||
}
|
||||
|
||||
if (name === "actionTime") return this.actionTime;
|
||||
|
||||
if (name === "effHack") return this.effHack;
|
||||
if (name === "effStr") return this.effStr;
|
||||
if (name === "effDef") return this.effDef;
|
||||
if (name === "effDex") return this.effDex;
|
||||
if (name === "effAgi") return this.effAgi;
|
||||
if (name === "effCha") return this.effCha;
|
||||
|
||||
if (name === "stamina") return this.stamina;
|
||||
if (name === "money") return this.money;
|
||||
if (name === "expGain") return this.expGain;
|
||||
return 0;
|
||||
getMultiplier(name: BladeMultName): number {
|
||||
return this.mults[name] ?? 0;
|
||||
}
|
||||
}
|
||||
|
31
src/Bladeburner/Types.ts
Normal file
31
src/Bladeburner/Types.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { BlackOperation, Contract, GeneralAction, Operation } from "./Actions";
|
||||
import type {
|
||||
BladeActionType,
|
||||
BladeBlackOpName,
|
||||
BladeContractName,
|
||||
BladeOperationName,
|
||||
BladeGeneralActionName,
|
||||
} from "@enums";
|
||||
|
||||
export interface SuccessChanceParams {
|
||||
/** Whether the success chance should be based on estimated statistics */
|
||||
est: boolean;
|
||||
}
|
||||
|
||||
type AvailabilitySuccess<T extends object> = { available: true } & T;
|
||||
type AvailabilityFailure = { available?: undefined; error: string };
|
||||
export type Availability<T extends object = object> = AvailabilitySuccess<T> | AvailabilityFailure;
|
||||
|
||||
type AttemptSuccess<T extends object> = { success: true; message?: string } & T;
|
||||
type AttemptFailure = { success?: undefined; message: string };
|
||||
export type Attempt<T extends object = object> = AttemptSuccess<T> | AttemptFailure;
|
||||
|
||||
export type Action = Contract | Operation | BlackOperation | GeneralAction;
|
||||
|
||||
export type ActionIdentifier =
|
||||
| { type: BladeActionType.blackOp; name: BladeBlackOpName }
|
||||
| { type: BladeActionType.contract; name: BladeContractName }
|
||||
| { type: BladeActionType.operation; name: BladeOperationName }
|
||||
| { type: BladeActionType.general; name: BladeGeneralActionName };
|
||||
|
||||
export type LevelableAction = Contract | Operation;
|
@ -1,29 +0,0 @@
|
||||
// Action Identifier enum
|
||||
export const ActionTypes: {
|
||||
[key: string]: number;
|
||||
Idle: number;
|
||||
Contract: number;
|
||||
Operation: number;
|
||||
BlackOp: number;
|
||||
BlackOperation: number;
|
||||
Training: number;
|
||||
Recruitment: number;
|
||||
FieldAnalysis: number;
|
||||
"Field Analysis": number;
|
||||
Diplomacy: number;
|
||||
"Hyperbolic Regeneration Chamber": number;
|
||||
"Incite Violence": number;
|
||||
} = {
|
||||
Idle: 1,
|
||||
Contract: 2,
|
||||
Operation: 3,
|
||||
BlackOp: 4,
|
||||
BlackOperation: 4,
|
||||
Training: 5,
|
||||
Recruitment: 6,
|
||||
FieldAnalysis: 7,
|
||||
"Field Analysis": 7,
|
||||
Diplomacy: 8,
|
||||
"Hyperbolic Regeneration Chamber": 9,
|
||||
"Incite Violence": 10,
|
||||
};
|
737
src/Bladeburner/data/BlackOperations.ts
Normal file
737
src/Bladeburner/data/BlackOperations.ts
Normal file
@ -0,0 +1,737 @@
|
||||
import { BlackOperation } from "../Actions/BlackOperation";
|
||||
import { BladeBlackOpName, CityName, FactionName } from "@enums";
|
||||
|
||||
export const BlackOperations: Record<BladeBlackOpName, BlackOperation> = {
|
||||
[BladeBlackOpName.OperationTyphoon]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationTyphoon,
|
||||
n: 0,
|
||||
baseDifficulty: 2000,
|
||||
reqdRank: 2.5e3,
|
||||
rankGain: 50,
|
||||
rankLoss: 10,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community " +
|
||||
"that Zenyatta, along with the rest of the PMC, is a Synthoid.\n\n" +
|
||||
`The goal of ${BladeBlackOpName.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means ` +
|
||||
"necessary. After the task is completed, the actions must be covered up from the general public.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationZero]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationZero,
|
||||
n: 1,
|
||||
baseDifficulty: 2500,
|
||||
reqdRank: 5e3,
|
||||
rankGain: 60,
|
||||
rankLoss: 15,
|
||||
hpLoss: 50,
|
||||
weights: {
|
||||
hacking: 0.2,
|
||||
strength: 0.15,
|
||||
defense: 0.15,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
desc:
|
||||
"AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be " +
|
||||
"a supporter of Synthoid rights. He must be removed.\n\n" +
|
||||
`The goal of ${BladeBlackOpName.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating ` +
|
||||
"evidence or information against Watataki that will cause him to be removed from his position at AeroCorp. " +
|
||||
"Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced " +
|
||||
"security measures in the world.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationX]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationX,
|
||||
n: 2,
|
||||
baseDifficulty: 3000,
|
||||
reqdRank: 7.5e3,
|
||||
rankGain: 75,
|
||||
rankLoss: 15,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"We have recently discovered an underground publication group called Samizdat. Even though most of their " +
|
||||
"publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of " +
|
||||
"their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and " +
|
||||
"other Eastern countries.\n\n" +
|
||||
"Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that " +
|
||||
`their base of operations is in ${CityName.Ishima}'s underground sewer systems. Your task is to investigate the ` +
|
||||
"sewer systems, and eliminate Samizdat. They must never publish anything again.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationTitan]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationTitan,
|
||||
n: 3,
|
||||
baseDifficulty: 4000,
|
||||
reqdRank: 10e3,
|
||||
rankGain: 100,
|
||||
rankLoss: 20,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we " +
|
||||
"know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to, " +
|
||||
"but the research that they could be conducting using Titan Laboratories' vast resources is potentially very " +
|
||||
"dangerous.\n\n" +
|
||||
`Your goal is to enter and destroy the Bioengineering department's facility in ${CityName.Aevum}. The task is not ` +
|
||||
"just to retire the Synthoids there, but also to destroy any information or research at the facility that is " +
|
||||
"relevant to the Synthoids and their goals.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationAres]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationAres,
|
||||
n: 4,
|
||||
baseDifficulty: 5000,
|
||||
reqdRank: 12.5e3,
|
||||
rankGain: 125,
|
||||
rankLoss: 20,
|
||||
hpLoss: 200,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.25,
|
||||
defense: 0.25,
|
||||
dexterity: 0.25,
|
||||
agility: 0.25,
|
||||
charisma: 0,
|
||||
intelligence: 0,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai " +
|
||||
"between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy " +
|
||||
"weapons. It is critical for the safety of humanity that this deal does not happen.\n\n" +
|
||||
"Your task is to intercept the deal. Leave no survivors.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationArchangel]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationArchangel,
|
||||
n: 5,
|
||||
baseDifficulty: 7500,
|
||||
reqdRank: 15e3,
|
||||
rankGain: 200,
|
||||
rankLoss: 20,
|
||||
hpLoss: 25,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.3,
|
||||
agility: 0.3,
|
||||
charisma: 0,
|
||||
intelligence: 0,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI " +
|
||||
"Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms " +
|
||||
"trafficking operation.\n\n" +
|
||||
"The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the " +
|
||||
"number of other casualties, but do what you must to complete the mission.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationJuggernaut]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationJuggernaut,
|
||||
n: 6,
|
||||
baseDifficulty: 10e3,
|
||||
reqdRank: 20e3,
|
||||
rankGain: 300,
|
||||
rankLoss: 40,
|
||||
hpLoss: 300,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.25,
|
||||
defense: 0.25,
|
||||
dexterity: 0.25,
|
||||
agility: 0.25,
|
||||
charisma: 0,
|
||||
intelligence: 0,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls " +
|
||||
"himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into " +
|
||||
`${CityName.Sector12}. We also have reason to believe they tried to break into one of Universal Energy's ` +
|
||||
"facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented " +
|
||||
"Synthoid, and have thus enlisted our help.\n\n" +
|
||||
"Your mission is to eradicate Juggernaut and his followers.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationRedDragon]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationRedDragon,
|
||||
n: 7,
|
||||
baseDifficulty: 12.5e3,
|
||||
reqdRank: 25e3,
|
||||
rankGain: 500,
|
||||
rankLoss: 50,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hacking: 0.05,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.25,
|
||||
agility: 0.25,
|
||||
charisma: 0,
|
||||
intelligence: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
`The ${FactionName.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. ` +
|
||||
"We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in" +
|
||||
"order to bolster their criminal activities.\n\n" +
|
||||
`Your task is to infiltrate and destroy the ${FactionName.Tetrads}' base of operations in Los Angeles. ` +
|
||||
"Intelligence tells us that their base houses one of their Synthoid manufacturing units.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationK]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationK,
|
||||
n: 8,
|
||||
baseDifficulty: 15e3,
|
||||
reqdRank: 30e3,
|
||||
rankGain: 750,
|
||||
rankLoss: 60,
|
||||
hpLoss: 1000,
|
||||
weights: {
|
||||
hacking: 0.05,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.25,
|
||||
agility: 0.25,
|
||||
charisma: 0,
|
||||
intelligence: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology. " +
|
||||
"This technology is supposedly capable of cloning Synthoids, not only physically but also their advanced AI " +
|
||||
"modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any " +
|
||||
"Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would " +
|
||||
"be catastrophic.\n\n" +
|
||||
"We do not have the power or jurisdiction to shut this down through legal or political means, so we must resort " +
|
||||
"to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in its " +
|
||||
"creation.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationDeckard]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationDeckard,
|
||||
n: 9,
|
||||
baseDifficulty: 20e3,
|
||||
reqdRank: 40e3,
|
||||
rankGain: 1e3,
|
||||
rankLoss: 75,
|
||||
hpLoss: 200,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.24,
|
||||
defense: 0.24,
|
||||
dexterity: 0.24,
|
||||
agility: 0.24,
|
||||
charisma: 0,
|
||||
intelligence: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Despite your success in eliminating VitaLife's new android-replicating technology in " +
|
||||
`${BladeBlackOpName.OperationK}, we've discovered that a small group of MK-VI Synthoids were able to make off with ` +
|
||||
"the schematics and design of the technology before the Operation. It is almost a certainty that these Synthoids " +
|
||||
"are some of the rogue MK-VI ones from the Synthoid Uprising.\n\n" +
|
||||
`The goal of ${BladeBlackOpName.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need to ` +
|
||||
"tell you how critical this mission is.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationTyrell]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationTyrell,
|
||||
n: 10,
|
||||
baseDifficulty: 25e3,
|
||||
reqdRank: 50e3,
|
||||
rankGain: 1.5e3,
|
||||
rankLoss: 100,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
`A week ago ${FactionName.BladeIndustries} reported a small break-in at one of their ${CityName.Aevum} ` +
|
||||
`Augmentation storage facilities. We figured out that ${FactionName.TheDarkArmy} was behind the heist, and didn't think ` +
|
||||
"any more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.\n\n" +
|
||||
"We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt " +
|
||||
`down associated ${FactionName.TheDarkArmy} members and eliminate them.`,
|
||||
}),
|
||||
[BladeBlackOpName.OperationWallace]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationWallace,
|
||||
n: 11,
|
||||
baseDifficulty: 30e3,
|
||||
reqdRank: 75e3,
|
||||
rankGain: 2e3,
|
||||
rankLoss: 150,
|
||||
hpLoss: 1500,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.24,
|
||||
defense: 0.24,
|
||||
dexterity: 0.24,
|
||||
agility: 0.24,
|
||||
charisma: 0,
|
||||
intelligence: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
`Based on information gathered from ${BladeBlackOpName.OperationTyrell}, we've discovered that ` +
|
||||
`${FactionName.TheDarkArmy} was well aware that there were Synthoids amongst their ranks. Even worse, we believe ` +
|
||||
`that ${FactionName.TheDarkArmy} is working together with other criminal organizations such as ` +
|
||||
`${FactionName.TheSyndicate} and that they are planning some sort of large-scale takeover of multiple major ` +
|
||||
`cities, most notably ${CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these criminal ` +
|
||||
"factions and are trying to stage another Synthoid uprising.\n\n" +
|
||||
"The best way to deal with this is to prevent it before it even happens. The goal of " +
|
||||
`${BladeBlackOpName.OperationWallace} is to destroy ${FactionName.TheDarkArmy} and Syndicate factions in ` +
|
||||
`${CityName.Aevum} immediately. Leave no survivors.`,
|
||||
}),
|
||||
[BladeBlackOpName.OperationShoulderOfOrion]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationShoulderOfOrion,
|
||||
n: 12,
|
||||
baseDifficulty: 35e3,
|
||||
reqdRank: 100e3,
|
||||
rankGain: 2.5e3,
|
||||
rankLoss: 500,
|
||||
hpLoss: 1500,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
desc:
|
||||
"China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using " +
|
||||
"Synthoids. We believe China is trying to establish the first off-world colonies.\n\n" +
|
||||
"The mission is to prevent this launch without instigating an international conflict. When you accept this " +
|
||||
"mission you will be officially disavowed by the NSA and the national government until after you successfully " +
|
||||
"return. In the event of failure, all of the operation's team members must not let themselves be captured alive.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationHyron]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationHyron,
|
||||
n: 13,
|
||||
baseDifficulty: 40e3,
|
||||
reqdRank: 125e3,
|
||||
rankGain: 3e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 500,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
`Our intelligence tells us that ${FactionName.FulcrumSecretTechnologies} is developing a quantum supercomputer ` +
|
||||
"using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data " +
|
||||
"and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of " +
|
||||
"organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.\n\n" +
|
||||
"I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.\n\n" +
|
||||
`The research for this project is being conducted at one of ${FactionName.FulcrumSecretTechnologies} secret ` +
|
||||
`facilities in ${CityName.Aevum}, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, ` +
|
||||
"and then find and kill the project lead.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationMorpheus]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationMorpheus,
|
||||
n: 14,
|
||||
baseDifficulty: 45e3,
|
||||
reqdRank: 150e3,
|
||||
rankGain: 4e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 100,
|
||||
weights: {
|
||||
hacking: 0.05,
|
||||
strength: 0.15,
|
||||
defense: 0.15,
|
||||
dexterity: 0.3,
|
||||
agility: 0.3,
|
||||
charisma: 0,
|
||||
intelligence: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isStealth: true,
|
||||
desc:
|
||||
"DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the " +
|
||||
"people's dreams and subconscious. They do this using broadcast transmitter towers. Based on information from our " +
|
||||
`agents and informants in ${CityName.Chongqing}, we have reason to believe that one of the broadcast towers there ` +
|
||||
"has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.\n\n" +
|
||||
"The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationIonStorm]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationIonStorm,
|
||||
n: 15,
|
||||
baseDifficulty: 50e3,
|
||||
reqdRank: 175e3,
|
||||
rankGain: 5e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 5000,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.24,
|
||||
defense: 0.24,
|
||||
dexterity: 0.24,
|
||||
agility: 0.24,
|
||||
charisma: 0,
|
||||
intelligence: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the " +
|
||||
`${CityName.Sector12} Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they ` +
|
||||
"have been stockpiling weapons, money, and other resources. This makes them dangerous.\n\n" +
|
||||
`This is a full-scale assault operation to find and retire all of these Synthoids in the ${CityName.Sector12} ` +
|
||||
"Slums.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationAnnihilus]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationAnnihilus,
|
||||
n: 16,
|
||||
baseDifficulty: 55e3,
|
||||
reqdRank: 200e3,
|
||||
rankGain: 7.5e3,
|
||||
rankLoss: 1e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.24,
|
||||
defense: 0.24,
|
||||
dexterity: 0.24,
|
||||
agility: 0.24,
|
||||
charisma: 0,
|
||||
intelligence: 0.04,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
"Our superiors have ordered us to eradicate everything and everyone in an underground facility located in " +
|
||||
`${CityName.Aevum}. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist ` +
|
||||
`organization called '${FactionName.TheCovenant}'. We have no prior intelligence about this organization, so you ` +
|
||||
"are going in blind.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationUltron]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationUltron,
|
||||
n: 17,
|
||||
baseDifficulty: 60e3,
|
||||
reqdRank: 250e3,
|
||||
rankGain: 10e3,
|
||||
rankLoss: 2e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
isKill: true,
|
||||
desc:
|
||||
`${FactionName.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a ` +
|
||||
"malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized " +
|
||||
"and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue " +
|
||||
"ones from the Uprising.\n\n" +
|
||||
`${FactionName.OmniTekIncorporated} has also told us they believe someone has triggered this malfunction in a ` +
|
||||
"large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in " +
|
||||
`${CityName.Volhaven} to form a terrorist group called Ultron.\n\n` +
|
||||
"Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making " +
|
||||
"moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).\n\n" +
|
||||
"Your task is to find and destroy Ultron.",
|
||||
}),
|
||||
[BladeBlackOpName.OperationCenturion]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationCenturion,
|
||||
n: 18,
|
||||
baseDifficulty: 70e3,
|
||||
reqdRank: 300e3,
|
||||
rankGain: 15e3,
|
||||
rankLoss: 5e3,
|
||||
hpLoss: 10e3,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
desc:
|
||||
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\n\n" +
|
||||
"Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its " +
|
||||
"advancement became our primary goal. And at the peak of human civilization technology turned into power. Global, " +
|
||||
"absolute power.\n\n" +
|
||||
"It seems that the universe is not without a sense of irony.\n\n" +
|
||||
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
|
||||
}),
|
||||
[BladeBlackOpName.OperationVindictus]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationVindictus,
|
||||
n: 19,
|
||||
baseDifficulty: 75e3,
|
||||
reqdRank: 350e3,
|
||||
rankGain: 20e3,
|
||||
rankLoss: 20e3,
|
||||
hpLoss: 20e3,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
desc:
|
||||
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\n\n" +
|
||||
"The bits are all around us. The daemons that hold the Node together can manifest themselves in many different " +
|
||||
"ways.\n\n" +
|
||||
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
|
||||
}),
|
||||
[BladeBlackOpName.OperationDaedalus]: new BlackOperation({
|
||||
name: BladeBlackOpName.OperationDaedalus,
|
||||
n: 20,
|
||||
baseDifficulty: 80e3,
|
||||
reqdRank: 400e3,
|
||||
rankGain: 40e3,
|
||||
rankLoss: 10e3,
|
||||
hpLoss: 100e3,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.75,
|
||||
},
|
||||
desc: "Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.",
|
||||
}),
|
||||
};
|
||||
|
||||
/** Array for quick lookup by blackop number */
|
||||
export const blackOpsArray = Object.values(BlackOperations).sort((a, b) => (a.n < b.n ? -1 : 1));
|
||||
// Verify that all "n" properties match the index in the array
|
||||
if (!blackOpsArray.every((blackOp, i) => blackOp.n === i)) {
|
||||
throw new Error("blackOpsArray did not initialize with correct indices");
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
import React from "react";
|
||||
import { BlackOperationName, CityName, FactionName } from "@enums";
|
||||
|
||||
interface IBlackOp {
|
||||
desc: JSX.Element;
|
||||
}
|
||||
|
||||
export const BlackOperations: Record<string, IBlackOp | undefined> = {
|
||||
[BlackOperationName.OperationTyphoon]: {
|
||||
desc: (
|
||||
<>
|
||||
Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that
|
||||
Zenyatta, along with the rest of the PMC, is a Synthoid.
|
||||
<br />
|
||||
<br />
|
||||
The goal of {BlackOperationName.OperationTyphoon} is to find and eliminate Zenyatta and RedWater by any means
|
||||
necessary. After the task is completed, the actions must be covered up from the general public.
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
[BlackOperationName.OperationZero]: {
|
||||
desc: (
|
||||
<>
|
||||
AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be a
|
||||
supporter of Synthoid rights. He must be removed.
|
||||
<br />
|
||||
<br />
|
||||
The goal of {BlackOperationName.OperationZero} is to covertly infiltrate AeroCorp and uncover any incriminating
|
||||
evidence or information against Watataki that will cause him to be removed from his position at AeroCorp.
|
||||
Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced
|
||||
security measures in the world.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationX]: {
|
||||
desc: (
|
||||
<>
|
||||
We have recently discovered an underground publication group called Samizdat. Even though most of their
|
||||
publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of
|
||||
their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and
|
||||
other Eastern countries.
|
||||
<br />
|
||||
<br />
|
||||
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
|
||||
their base of operations is in {CityName.Ishima}'s underground sewer systems. Your task is to investigate the
|
||||
sewer systems, and eliminate Samizdat. They must never publish anything again.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationTitan]: {
|
||||
desc: (
|
||||
<>
|
||||
Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we
|
||||
know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to,
|
||||
but the research that they could be conducting using Titan Laboratories' vast resources is potentially very
|
||||
dangerous.
|
||||
<br />
|
||||
<br />
|
||||
Your goal is to enter and destroy the Bioengineering department's facility in {CityName.Aevum}. The task is not
|
||||
just to retire the Synthoids there, but also to destroy any information or research at the facility that is
|
||||
relevant to the Synthoids and their goals.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationAres]: {
|
||||
desc: (
|
||||
<>
|
||||
One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai
|
||||
between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy
|
||||
weapons. It is critical for the safety of humanity that this deal does not happen.
|
||||
<br />
|
||||
<br />
|
||||
Your task is to intercept the deal. Leave no survivors.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationArchangel]: {
|
||||
desc: (
|
||||
<>
|
||||
Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI
|
||||
Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms
|
||||
trafficking operation.
|
||||
<br />
|
||||
<br />
|
||||
The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the
|
||||
number of other casualties, but do what you must to complete the mission.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationJuggernaut]: {
|
||||
desc: (
|
||||
<>
|
||||
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
|
||||
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into{" "}
|
||||
{CityName.Sector12}. We also have reason to believe they tried to break into one of Universal Energy's
|
||||
facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented
|
||||
Synthoid, and have thus enlisted our help.
|
||||
<br />
|
||||
<br />
|
||||
Your mission is to eradicate Juggernaut and his followers.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationRedDragon]: {
|
||||
desc: (
|
||||
<>
|
||||
The {FactionName.Tetrads} criminal organization is suspected of reverse-engineering the MK-VI Synthoid design.
|
||||
We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in
|
||||
order to bolster their criminal activities.
|
||||
<br />
|
||||
<br />
|
||||
Your task is to infiltrate and destroy the {FactionName.Tetrads}' base of operations in Los Angeles.
|
||||
Intelligence tells us that their base houses one of their Synthoid manufacturing units.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationK]: {
|
||||
desc: (
|
||||
<>
|
||||
CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology.
|
||||
This technology is supposedly capable of cloning Synthoids, not only physically but also their advanced AI
|
||||
modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any
|
||||
Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would
|
||||
be catastrophic.
|
||||
<br />
|
||||
<br />
|
||||
We do not have the power or jurisdiction to shut this down through legal or political means, so we must resort
|
||||
to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in its
|
||||
creation.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationDeckard]: {
|
||||
desc: (
|
||||
<>
|
||||
Despite your success in eliminating VitaLife's new android-replicating technology in{" "}
|
||||
{BlackOperationName.OperationK}, we've discovered that a small group of MK-VI Synthoids were able to make off
|
||||
with the schematics and design of the technology before the Operation. It is almost a certainty that these
|
||||
Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.
|
||||
<br />
|
||||
<br />
|
||||
The goal of {BlackOperationName.OperationDeckard} is to hunt down these Synthoids and retire them. I don't need
|
||||
to tell you how critical this mission is.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationTyrell]: {
|
||||
desc: (
|
||||
<>
|
||||
A week ago {FactionName.BladeIndustries} reported a small break-in at one of their {CityName.Aevum} Augmentation
|
||||
storage facilities. We figured out that {FactionName.TheDarkArmy} was behind the heist, and didn't think any
|
||||
more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.
|
||||
<br />
|
||||
<br />
|
||||
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
|
||||
down associated {FactionName.TheDarkArmy} members and eliminate them.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationWallace]: {
|
||||
desc: (
|
||||
<>
|
||||
Based on information gathered from {BlackOperationName.OperationTyrell}, we've discovered that{" "}
|
||||
{FactionName.TheDarkArmy} was well aware that there were Synthoids amongst their ranks. Even worse, we believe
|
||||
that {FactionName.TheDarkArmy} is working together with other criminal organizations such as{" "}
|
||||
{FactionName.TheSyndicate} and that they are planning some sort of large-scale takeover of multiple major
|
||||
cities, most notably {CityName.Aevum}. We suspect that Synthoids have infiltrated the ranks of these criminal
|
||||
factions and are trying to stage another Synthoid uprising.
|
||||
<br />
|
||||
<br />
|
||||
The best way to deal with this is to prevent it before it even happens. The goal of{" "}
|
||||
{BlackOperationName.OperationWallace} is to destroy {FactionName.TheDarkArmy} and Syndicate factions in{" "}
|
||||
{CityName.Aevum} immediately. Leave no survivors.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationShoulderOfOrion]: {
|
||||
desc: (
|
||||
<>
|
||||
China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using
|
||||
Synthoids. We believe China is trying to establish the first off-world colonies.
|
||||
<br />
|
||||
<br />
|
||||
The mission is to prevent this launch without instigating an international conflict. When you accept this
|
||||
mission you will be officially disavowed by the NSA and the national government until after you successfully
|
||||
return. In the event of failure, all of the operation's team members must not let themselves be captured alive.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationHyron]: {
|
||||
desc: (
|
||||
<>
|
||||
Our intelligence tells us that {FactionName.FulcrumSecretTechnologies} is developing a quantum supercomputer
|
||||
using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data
|
||||
and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of
|
||||
organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.
|
||||
<br />
|
||||
<br />
|
||||
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
|
||||
<br />
|
||||
<br />
|
||||
The research for this project is being conducted at one of {FactionName.FulcrumSecretTechnologies} secret
|
||||
facilities in {CityName.Aevum}, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work,
|
||||
and then find and kill the project lead.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationMorpheus]: {
|
||||
desc: (
|
||||
<>
|
||||
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
|
||||
people's dreams and subconscious. They do this using broadcast transmitter towers. Based on information from our
|
||||
agents and informants in {CityName.Chongqing}, we have reason to believe that one of the broadcast towers there
|
||||
has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
|
||||
<br />
|
||||
<br />
|
||||
The mission is to destroy this broadcast tower. Speed and stealth are of the utmost importance for this.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationIonStorm]: {
|
||||
desc: (
|
||||
<>
|
||||
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the{" "}
|
||||
{CityName.Sector12} Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they
|
||||
have been stockpiling weapons, money, and other resources. This makes them dangerous.
|
||||
<br />
|
||||
<br />
|
||||
This is a full-scale assault operation to find and retire all of these Synthoids in the {CityName.Sector12}{" "}
|
||||
Slums.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationAnnihilus]: {
|
||||
desc: (
|
||||
<>
|
||||
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in{" "}
|
||||
{CityName.Aevum}. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist
|
||||
organization called '{FactionName.TheCovenant}'. We have no prior intelligence about this organization, so you
|
||||
are going in blind.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationUltron]: {
|
||||
desc: (
|
||||
<>
|
||||
{FactionName.OmniTekIncorporated}, the original designer and manufacturer of Synthoids, has notified us of a
|
||||
malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized
|
||||
and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue
|
||||
ones from the Uprising.
|
||||
<br />
|
||||
<br />
|
||||
{FactionName.OmniTekIncorporated} has also told us they believe someone has triggered this malfunction in a
|
||||
large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in{" "}
|
||||
{CityName.Volhaven} to form a terrorist group called Ultron.
|
||||
<br />
|
||||
<br />
|
||||
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
|
||||
moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).
|
||||
<br />
|
||||
<br />
|
||||
Your task is to find and destroy Ultron.
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationCenturion]: {
|
||||
desc: (
|
||||
<>
|
||||
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
|
||||
<br />
|
||||
<br />
|
||||
Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its
|
||||
advancement became our primary goal. And at the peak of human civilization technology turned into power. Global,
|
||||
absolute power.
|
||||
<br />
|
||||
<br />
|
||||
It seems that the universe is not without a sense of irony.
|
||||
<br />
|
||||
<br />
|
||||
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationVindictus]: {
|
||||
desc: (
|
||||
<>
|
||||
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
|
||||
<br />
|
||||
<br />
|
||||
The bits are all around us. The daemons that hold the Node together can manifest themselves in many different
|
||||
ways.
|
||||
<br />
|
||||
<br />
|
||||
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
|
||||
</>
|
||||
),
|
||||
},
|
||||
[BlackOperationName.OperationDaedalus]: {
|
||||
desc: <> Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.</>,
|
||||
},
|
||||
};
|
121
src/Bladeburner/data/Contracts.ts
Normal file
121
src/Bladeburner/data/Contracts.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { BladeContractName } from "@enums";
|
||||
import { Contract } from "../Actions/Contract";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { assertLoadingType } from "../../utils/TypeAssertion";
|
||||
|
||||
export function createContracts(): Record<BladeContractName, Contract> {
|
||||
return {
|
||||
[BladeContractName.tracking]: new Contract({
|
||||
name: BladeContractName.tracking,
|
||||
desc:
|
||||
"Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT " +
|
||||
"engage. Stealth is of the utmost importance.\n\n" +
|
||||
"Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever " +
|
||||
"city you are currently in.",
|
||||
baseDifficulty: 125,
|
||||
difficultyFac: 1.02,
|
||||
rewardFac: 1.041,
|
||||
rankGain: 0.3,
|
||||
hpLoss: 0.5,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.05,
|
||||
defense: 0.05,
|
||||
dexterity: 0.35,
|
||||
agility: 0.35,
|
||||
charisma: 0.1,
|
||||
intelligence: 0.05,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.91,
|
||||
defense: 0.91,
|
||||
dexterity: 0.91,
|
||||
agility: 0.91,
|
||||
charisma: 0.9,
|
||||
intelligence: 1,
|
||||
},
|
||||
isStealth: true,
|
||||
growthFunction: () => getRandomInt(5, 75) / 10,
|
||||
minCount: 25,
|
||||
}),
|
||||
[BladeContractName.bountyHunter]: new Contract({
|
||||
name: BladeContractName.bountyHunter,
|
||||
desc:
|
||||
"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.\n\n" +
|
||||
"Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also " +
|
||||
"increase its chaos level.",
|
||||
baseDifficulty: 250,
|
||||
difficultyFac: 1.04,
|
||||
rewardFac: 1.085,
|
||||
rankGain: 0.9,
|
||||
hpLoss: 1,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.15,
|
||||
defense: 0.15,
|
||||
dexterity: 0.25,
|
||||
agility: 0.25,
|
||||
charisma: 0.1,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.91,
|
||||
defense: 0.91,
|
||||
dexterity: 0.91,
|
||||
agility: 0.91,
|
||||
charisma: 0.8,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isKill: true,
|
||||
growthFunction: () => getRandomInt(5, 75) / 10,
|
||||
minCount: 5,
|
||||
}),
|
||||
[BladeContractName.retirement]: new Contract({
|
||||
name: BladeContractName.retirement,
|
||||
desc:
|
||||
"Hunt down and retire (kill) rogue Synthoids.\n\n" +
|
||||
"Successfully completing a Retirement contract will lower the population in your current city, and will also " +
|
||||
"increase its chaos level.",
|
||||
baseDifficulty: 200,
|
||||
difficultyFac: 1.03,
|
||||
rewardFac: 1.065,
|
||||
rankGain: 0.6,
|
||||
hpLoss: 1,
|
||||
weights: {
|
||||
hacking: 0,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0.1,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0,
|
||||
strength: 0.91,
|
||||
defense: 0.91,
|
||||
dexterity: 0.91,
|
||||
agility: 0.91,
|
||||
charisma: 0.8,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isKill: true,
|
||||
growthFunction: () => getRandomInt(5, 75) / 10,
|
||||
minCount: 5,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function loadContractsData(data: unknown, contracts: Record<BladeContractName, Contract>) {
|
||||
// loading data as "unknown" and typechecking it down is probably not necessary
|
||||
// but this will prevent crashes even with malformed savedata
|
||||
if (!data || typeof data !== "object") return;
|
||||
assertLoadingType<Record<BladeContractName, unknown>>(data);
|
||||
for (const contractName of Object.values(BladeContractName)) {
|
||||
const loadedContract = data[contractName];
|
||||
if (!(loadedContract instanceof Contract)) continue;
|
||||
contracts[contractName].loadData(loadedContract);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
interface IContract {
|
||||
desc: JSX.Element;
|
||||
}
|
||||
|
||||
export const Contracts: Record<string, IContract | undefined> = {
|
||||
Tracking: {
|
||||
desc: (
|
||||
<>
|
||||
Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT
|
||||
engage. Stealth is of the utmost importance.
|
||||
<br />
|
||||
<br />
|
||||
Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever
|
||||
city you are currently in.
|
||||
</>
|
||||
),
|
||||
},
|
||||
"Bounty Hunter": {
|
||||
desc: (
|
||||
<>
|
||||
Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
|
||||
<br />
|
||||
<br />
|
||||
Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also
|
||||
increase its chaos level.
|
||||
</>
|
||||
),
|
||||
},
|
||||
Retirement: {
|
||||
desc: (
|
||||
<>
|
||||
Hunt down and retire (kill) rogue Synthoids.
|
||||
<br />
|
||||
<br />
|
||||
Successfully completing a Retirement contract will lower the population in your current city, and will also
|
||||
increase its chaos level.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
58
src/Bladeburner/data/GeneralActions.ts
Normal file
58
src/Bladeburner/data/GeneralActions.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { BladeGeneralActionName } from "@enums";
|
||||
import { GeneralAction } from "../Actions/GeneralAction";
|
||||
import { BladeburnerConstants } from "./Constants";
|
||||
|
||||
export const GeneralActions: Record<BladeGeneralActionName, GeneralAction> = {
|
||||
[BladeGeneralActionName.training]: new GeneralAction({
|
||||
name: BladeGeneralActionName.training,
|
||||
getActionTime: () => 30,
|
||||
desc:
|
||||
"Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for " +
|
||||
"all combat stats and also increases your max stamina.",
|
||||
}),
|
||||
[BladeGeneralActionName.fieldAnalysis]: new GeneralAction({
|
||||
name: BladeGeneralActionName.fieldAnalysis,
|
||||
getActionTime: () => 30,
|
||||
desc:
|
||||
"Mine and analyze Synthoid-related data. This improves the Bladeburner unit's intelligence on Synthoid locations " +
|
||||
"and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the " +
|
||||
"current city.\n\n" +
|
||||
"Does NOT require stamina.",
|
||||
}),
|
||||
[BladeGeneralActionName.recruitment]: new GeneralAction({
|
||||
name: BladeGeneralActionName.recruitment,
|
||||
getActionTime: function (bladeburner, person) {
|
||||
const effCharisma = bladeburner.getEffectiveSkillLevel(person, "charisma");
|
||||
const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90;
|
||||
return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor));
|
||||
},
|
||||
getSuccessChance: function (bladeburner, person) {
|
||||
return Math.pow(person.skills.charisma, 0.45) / (bladeburner.teamSize - bladeburner.sleeveSize + 1);
|
||||
},
|
||||
desc:
|
||||
"Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.\n\n" +
|
||||
"Does NOT require stamina.",
|
||||
}),
|
||||
[BladeGeneralActionName.diplomacy]: new GeneralAction({
|
||||
name: BladeGeneralActionName.diplomacy,
|
||||
getActionTime: () => 60,
|
||||
desc:
|
||||
"Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in " +
|
||||
"your current city.\n\n" +
|
||||
"Does NOT require stamina.",
|
||||
}),
|
||||
[BladeGeneralActionName.hyperbolicRegen]: new GeneralAction({
|
||||
name: BladeGeneralActionName.hyperbolicRegen,
|
||||
getActionTime: () => 60,
|
||||
desc:
|
||||
"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your " +
|
||||
"wounds and slightly increase your stamina.",
|
||||
}),
|
||||
[BladeGeneralActionName.inciteViolence]: new GeneralAction({
|
||||
name: BladeGeneralActionName.inciteViolence,
|
||||
getActionTime: () => 60,
|
||||
desc:
|
||||
"Purposefully stir trouble in the synthoid community in order to gain a political edge. This will generate " +
|
||||
"additional contracts and operations, at the cost of increased Chaos.",
|
||||
}),
|
||||
};
|
@ -1,97 +0,0 @@
|
||||
import React from "react";
|
||||
import { newWorkStats, WorkStats } from "../../Work/WorkStats";
|
||||
|
||||
interface IGeneral {
|
||||
desc: JSX.Element;
|
||||
exp: WorkStats;
|
||||
}
|
||||
|
||||
export const GeneralActions: Record<string, IGeneral | undefined> = {
|
||||
Training: {
|
||||
desc: (
|
||||
<>
|
||||
Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for
|
||||
all combat stats and also increases your max stamina.
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats({
|
||||
strExp: 30,
|
||||
defExp: 30,
|
||||
dexExp: 30,
|
||||
agiExp: 30,
|
||||
}),
|
||||
},
|
||||
|
||||
"Field Analysis": {
|
||||
desc: (
|
||||
<>
|
||||
Mine and analyze Synthoid-related data. This improves the Bladeburner unit's intelligence on Synthoid locations
|
||||
and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the
|
||||
current city.
|
||||
<br />
|
||||
<br />
|
||||
Does NOT require stamina.
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats({
|
||||
hackExp: 20,
|
||||
chaExp: 20,
|
||||
}),
|
||||
},
|
||||
|
||||
Recruitment: {
|
||||
desc: (
|
||||
<>
|
||||
Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.
|
||||
<br />
|
||||
<br />
|
||||
Does NOT require stamina.
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats({
|
||||
chaExp: 120,
|
||||
}),
|
||||
},
|
||||
|
||||
Diplomacy: {
|
||||
desc: (
|
||||
<>
|
||||
Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in
|
||||
your current city.
|
||||
<br />
|
||||
<br />
|
||||
Does NOT require stamina.
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats({
|
||||
chaExp: 120,
|
||||
}),
|
||||
},
|
||||
|
||||
"Hyperbolic Regeneration Chamber": {
|
||||
desc: (
|
||||
<>
|
||||
Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your
|
||||
wounds and slightly increase your stamina.
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats(),
|
||||
},
|
||||
"Incite Violence": {
|
||||
desc: (
|
||||
<>
|
||||
Purposefully stir trouble in the synthoid community in order to gain a political edge. This will generate
|
||||
additional contracts and operations, at the cost of increased Chaos.
|
||||
</>
|
||||
),
|
||||
exp: newWorkStats({
|
||||
strExp: 10,
|
||||
defExp: 10,
|
||||
dexExp: 10,
|
||||
agiExp: 10,
|
||||
chaExp: 10,
|
||||
}),
|
||||
},
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
export const Growths: {
|
||||
[key: string]: (() => number) | undefined;
|
||||
["Tracking"]: () => number;
|
||||
["Bounty Hunter"]: () => number;
|
||||
["Retirement"]: () => number;
|
||||
["Investigation"]: () => number;
|
||||
["Undercover Operation"]: () => number;
|
||||
["Sting Operation"]: () => number;
|
||||
["Raid"]: () => number;
|
||||
["Stealth Retirement Operation"]: () => number;
|
||||
["Assassination"]: () => number;
|
||||
} = {
|
||||
Tracking: () => getRandomInt(5, 75) / 10,
|
||||
"Bounty Hunter": () => getRandomInt(5, 75) / 10,
|
||||
Retirement: () => getRandomInt(5, 75) / 10,
|
||||
Investigation: () => getRandomInt(10, 40) / 10,
|
||||
"Undercover Operation": () => getRandomInt(10, 40) / 10,
|
||||
"Sting Operation": () => getRandomInt(3, 40) / 10,
|
||||
Raid: () => getRandomInt(2, 40) / 10,
|
||||
"Stealth Retirement Operation": () => getRandomInt(1, 20) / 10,
|
||||
Assassination: () => getRandomInt(1, 20) / 10,
|
||||
};
|
220
src/Bladeburner/data/Operations.ts
Normal file
220
src/Bladeburner/data/Operations.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import { BladeOperationName } from "@enums";
|
||||
import { Operation } from "../Actions/Operation";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { LevelableActionClass } from "../Actions/LevelableAction";
|
||||
import { assertLoadingType } from "../../utils/TypeAssertion";
|
||||
|
||||
export function createOperations(): Record<BladeOperationName, Operation> {
|
||||
return {
|
||||
[BladeOperationName.investigation]: new Operation({
|
||||
name: BladeOperationName.investigation,
|
||||
desc:
|
||||
"As a field agent, investigate and identify Synthoid populations, movements, and operations.\n\n" +
|
||||
"Successful Investigation ops will increase the accuracy of your synthoid data.\n\n" +
|
||||
"You will NOT lose HP from failed Investigation ops.",
|
||||
baseDifficulty: 400,
|
||||
difficultyFac: 1.03,
|
||||
rewardFac: 1.07,
|
||||
rankGain: 2.2,
|
||||
rankLoss: 0.2,
|
||||
weights: {
|
||||
hacking: 0.25,
|
||||
strength: 0.05,
|
||||
defense: 0.05,
|
||||
dexterity: 0.2,
|
||||
agility: 0.1,
|
||||
charisma: 0.25,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.85,
|
||||
strength: 0.9,
|
||||
defense: 0.9,
|
||||
dexterity: 0.9,
|
||||
agility: 0.9,
|
||||
charisma: 0.7,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isStealth: true,
|
||||
growthFunction: () => getRandomInt(10, 40) / 10,
|
||||
maxCount: 100,
|
||||
}),
|
||||
[BladeOperationName.undercover]: new Operation({
|
||||
name: BladeOperationName.undercover,
|
||||
desc:
|
||||
"Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.\n\n" +
|
||||
"Successful Undercover ops will increase the accuracy of your synthoid data.",
|
||||
baseDifficulty: 500,
|
||||
difficultyFac: 1.04,
|
||||
rewardFac: 1.09,
|
||||
rankGain: 4.4,
|
||||
rankLoss: 0.4,
|
||||
hpLoss: 2,
|
||||
weights: {
|
||||
hacking: 0.2,
|
||||
strength: 0.05,
|
||||
defense: 0.05,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0.2,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.8,
|
||||
strength: 0.9,
|
||||
defense: 0.9,
|
||||
dexterity: 0.9,
|
||||
agility: 0.9,
|
||||
charisma: 0.7,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isStealth: true,
|
||||
growthFunction: () => getRandomInt(10, 40) / 10,
|
||||
maxCount: 100,
|
||||
}),
|
||||
[BladeOperationName.sting]: new Operation({
|
||||
name: BladeOperationName.sting,
|
||||
desc: "Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.",
|
||||
baseDifficulty: 650,
|
||||
difficultyFac: 1.04,
|
||||
rewardFac: 1.095,
|
||||
rankGain: 5.5,
|
||||
rankLoss: 0.5,
|
||||
hpLoss: 2.5,
|
||||
weights: {
|
||||
hacking: 0.25,
|
||||
strength: 0.05,
|
||||
defense: 0.05,
|
||||
dexterity: 0.25,
|
||||
agility: 0.1,
|
||||
charisma: 0.2,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.8,
|
||||
strength: 0.85,
|
||||
defense: 0.85,
|
||||
dexterity: 0.85,
|
||||
agility: 0.85,
|
||||
charisma: 0.7,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isStealth: true,
|
||||
growthFunction: () => getRandomInt(3, 40) / 10,
|
||||
}),
|
||||
[BladeOperationName.raid]: new Operation({
|
||||
name: BladeOperationName.raid,
|
||||
desc:
|
||||
"Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your " +
|
||||
"current city in order for this Operation to be successful.",
|
||||
baseDifficulty: 800,
|
||||
difficultyFac: 1.045,
|
||||
rewardFac: 1.1,
|
||||
rankGain: 55,
|
||||
rankLoss: 2.5,
|
||||
hpLoss: 50,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.2,
|
||||
defense: 0.2,
|
||||
dexterity: 0.2,
|
||||
agility: 0.2,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.7,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isKill: true,
|
||||
growthFunction: () => getRandomInt(2, 40) / 10,
|
||||
getAvailability: function (bladeburner) {
|
||||
if (bladeburner.getCurrentCity().comms < 1) return { error: "No Synthoid communities in current city" };
|
||||
return LevelableActionClass.prototype.getAvailability.call(this, bladeburner);
|
||||
},
|
||||
}),
|
||||
[BladeOperationName.stealthRetirement]: new Operation({
|
||||
name: BladeOperationName.stealthRetirement,
|
||||
desc:
|
||||
"Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any " +
|
||||
"attention. Stealth and discretion are key.",
|
||||
baseDifficulty: 1000,
|
||||
difficultyFac: 1.05,
|
||||
rewardFac: 1.11,
|
||||
rankGain: 22,
|
||||
rankLoss: 2,
|
||||
hpLoss: 10,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.1,
|
||||
defense: 0.1,
|
||||
dexterity: 0.3,
|
||||
agility: 0.3,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.7,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.9,
|
||||
},
|
||||
isStealth: true,
|
||||
isKill: true,
|
||||
growthFunction: () => getRandomInt(1, 20) / 10,
|
||||
}),
|
||||
[BladeOperationName.assassination]: new Operation({
|
||||
name: BladeOperationName.assassination,
|
||||
desc:
|
||||
"Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the " +
|
||||
"Synthoid communities.",
|
||||
baseDifficulty: 1500,
|
||||
difficultyFac: 1.06,
|
||||
rewardFac: 1.14,
|
||||
rankGain: 44,
|
||||
rankLoss: 4,
|
||||
hpLoss: 5,
|
||||
weights: {
|
||||
hacking: 0.1,
|
||||
strength: 0.1,
|
||||
defense: 0.1,
|
||||
dexterity: 0.3,
|
||||
agility: 0.3,
|
||||
charisma: 0,
|
||||
intelligence: 0.1,
|
||||
},
|
||||
decays: {
|
||||
hacking: 0.6,
|
||||
strength: 0.8,
|
||||
defense: 0.8,
|
||||
dexterity: 0.8,
|
||||
agility: 0.8,
|
||||
charisma: 0,
|
||||
intelligence: 0.8,
|
||||
},
|
||||
isStealth: true,
|
||||
isKill: true,
|
||||
growthFunction: () => getRandomInt(1, 20) / 10,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function loadOperationsData(data: unknown, operations: Record<BladeOperationName, Operation>) {
|
||||
// loading data as "unknown" and typechecking it down is probably not necessary
|
||||
// but this will prevent crashes even with malformed savedata
|
||||
if (!data || typeof data !== "object") return;
|
||||
assertLoadingType<Record<BladeOperationName, unknown>>(data);
|
||||
for (const operationName of Object.values(BladeOperationName)) {
|
||||
const loadedOperation = data[operationName];
|
||||
if (!(loadedOperation instanceof Operation)) continue;
|
||||
operations[operationName].loadData(loadedOperation);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
interface IOperation {
|
||||
desc: JSX.Element;
|
||||
}
|
||||
|
||||
export const Operations: Record<string, IOperation | undefined> = {
|
||||
Investigation: {
|
||||
desc: (
|
||||
<>
|
||||
As a field agent, investigate and identify Synthoid populations, movements, and operations.
|
||||
<br />
|
||||
<br />
|
||||
Successful Investigation ops will increase the accuracy of your synthoid data.
|
||||
<br />
|
||||
<br />
|
||||
You will NOT lose HP from failed Investigation ops.
|
||||
</>
|
||||
),
|
||||
},
|
||||
"Undercover Operation": {
|
||||
desc: (
|
||||
<>
|
||||
Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.
|
||||
<br />
|
||||
<br />
|
||||
Successful Undercover ops will increase the accuracy of your synthoid data.
|
||||
</>
|
||||
),
|
||||
},
|
||||
"Sting Operation": {
|
||||
desc: <>Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.</>,
|
||||
},
|
||||
Raid: {
|
||||
desc: (
|
||||
<>
|
||||
Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your
|
||||
current city in order for this Operation to be successful.
|
||||
</>
|
||||
),
|
||||
},
|
||||
"Stealth Retirement Operation": {
|
||||
desc: (
|
||||
<>
|
||||
Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any
|
||||
attention. Stealth and discretion are key.
|
||||
</>
|
||||
),
|
||||
},
|
||||
Assassination: {
|
||||
desc: (
|
||||
<>
|
||||
Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the
|
||||
Synthoid communities.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
export const SkillNames = {
|
||||
BladesIntuition: "Blade's Intuition",
|
||||
Cloak: "Cloak",
|
||||
Marksman: "Marksman",
|
||||
WeaponProficiency: "Weapon Proficiency",
|
||||
ShortCircuit: "Short-Circuit",
|
||||
DigitalObserver: "Digital Observer",
|
||||
Tracer: "Tracer",
|
||||
Overclock: "Overclock",
|
||||
Reaper: "Reaper",
|
||||
EvasiveSystem: "Evasive System",
|
||||
Datamancer: "Datamancer",
|
||||
CybersEdge: "Cyber's Edge",
|
||||
HandsOfMidas: "Hands of Midas",
|
||||
Hyperdrive: "Hyperdrive",
|
||||
};
|
@ -1,78 +1,77 @@
|
||||
import { Skill } from "./Skill";
|
||||
import { SkillNames } from "./data/SkillNames";
|
||||
import { BladeMultName, BladeSkillName } from "@enums";
|
||||
import { Skill } from "../Skill";
|
||||
|
||||
export const Skills: Record<string, Skill> = {};
|
||||
|
||||
(function () {
|
||||
Skills[SkillNames.BladesIntuition] = new Skill({
|
||||
name: SkillNames.BladesIntuition,
|
||||
export const Skills: Record<BladeSkillName, Skill> = {
|
||||
[BladeSkillName.bladesIntuition]: new Skill({
|
||||
name: BladeSkillName.bladesIntuition,
|
||||
desc: "Each level of this skill increases your success chance for all Contracts, Operations, and BlackOps by 3%",
|
||||
baseCost: 3,
|
||||
costInc: 2.1,
|
||||
successChanceAll: 3,
|
||||
});
|
||||
Skills[SkillNames.Cloak] = new Skill({
|
||||
name: SkillNames.Cloak,
|
||||
mults: { [BladeMultName.successChanceAll]: 3 },
|
||||
}),
|
||||
[BladeSkillName.cloak]: new Skill({
|
||||
name: BladeSkillName.cloak,
|
||||
desc:
|
||||
"Each level of this skill increases your " +
|
||||
"success chance in stealth-related Contracts, Operations, and BlackOps by 5.5%",
|
||||
baseCost: 2,
|
||||
costInc: 1.1,
|
||||
successChanceStealth: 5.5,
|
||||
});
|
||||
Skills[SkillNames.ShortCircuit] = new Skill({
|
||||
name: SkillNames.ShortCircuit,
|
||||
mults: { [BladeMultName.successChanceStealth]: 5.5 },
|
||||
}),
|
||||
[BladeSkillName.shortCircuit]: new Skill({
|
||||
name: BladeSkillName.shortCircuit,
|
||||
desc:
|
||||
"Each level of this skill increases your success chance " +
|
||||
"in Contracts, Operations, and BlackOps that involve retirement by 5.5%",
|
||||
baseCost: 2,
|
||||
costInc: 2.1,
|
||||
successChanceKill: 5.5,
|
||||
});
|
||||
Skills[SkillNames.DigitalObserver] = new Skill({
|
||||
name: SkillNames.DigitalObserver,
|
||||
mults: { [BladeMultName.successChanceKill]: 5.5 },
|
||||
}),
|
||||
[BladeSkillName.digitalObserver]: new Skill({
|
||||
name: BladeSkillName.digitalObserver,
|
||||
desc: "Each level of this skill increases your success chance in all Operations and BlackOps by 4%",
|
||||
baseCost: 2,
|
||||
costInc: 2.1,
|
||||
successChanceOperation: 4,
|
||||
});
|
||||
Skills[SkillNames.Tracer] = new Skill({
|
||||
name: SkillNames.Tracer,
|
||||
mults: { [BladeMultName.successChanceOperation]: 4 },
|
||||
}),
|
||||
[BladeSkillName.tracer]: new Skill({
|
||||
name: BladeSkillName.tracer,
|
||||
desc: "Each level of this skill increases your success chance in all Contracts by 4%",
|
||||
baseCost: 2,
|
||||
costInc: 2.1,
|
||||
successChanceContract: 4,
|
||||
});
|
||||
Skills[SkillNames.Overclock] = new Skill({
|
||||
name: SkillNames.Overclock,
|
||||
mults: { [BladeMultName.successChanceContract]: 4 },
|
||||
}),
|
||||
[BladeSkillName.overclock]: new Skill({
|
||||
name: BladeSkillName.overclock,
|
||||
desc:
|
||||
"Each level of this skill decreases the time it takes " +
|
||||
"to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 90)",
|
||||
baseCost: 3,
|
||||
costInc: 1.4,
|
||||
maxLvl: 90,
|
||||
actionTime: 1,
|
||||
});
|
||||
Skills[SkillNames.Reaper] = new Skill({
|
||||
name: SkillNames.Reaper,
|
||||
mults: { [BladeMultName.actionTime]: -1 },
|
||||
}),
|
||||
[BladeSkillName.reaper]: new Skill({
|
||||
name: BladeSkillName.reaper,
|
||||
desc: "Each level of this skill increases your effective combat stats for Bladeburner actions by 2%",
|
||||
baseCost: 2,
|
||||
costInc: 2.1,
|
||||
effStr: 2,
|
||||
effDef: 2,
|
||||
effDex: 2,
|
||||
effAgi: 2,
|
||||
});
|
||||
Skills[SkillNames.EvasiveSystem] = new Skill({
|
||||
name: SkillNames.EvasiveSystem,
|
||||
mults: {
|
||||
[BladeMultName.effStr]: 2,
|
||||
[BladeMultName.effDef]: 2,
|
||||
[BladeMultName.effDex]: 2,
|
||||
[BladeMultName.effAgi]: 2,
|
||||
},
|
||||
}),
|
||||
[BladeSkillName.evasiveSystem]: new Skill({
|
||||
name: BladeSkillName.evasiveSystem,
|
||||
desc: "Each level of this skill increases your effective dexterity and agility for Bladeburner actions by 4%",
|
||||
baseCost: 2,
|
||||
costInc: 2.1,
|
||||
effDex: 4,
|
||||
effAgi: 4,
|
||||
});
|
||||
Skills[SkillNames.Datamancer] = new Skill({
|
||||
name: SkillNames.Datamancer,
|
||||
mults: { [BladeMultName.effDex]: 4, [BladeMultName.effAgi]: 4 },
|
||||
}),
|
||||
[BladeSkillName.datamancer]: new Skill({
|
||||
name: BladeSkillName.datamancer,
|
||||
desc:
|
||||
"Each level of this skill increases your effectiveness in " +
|
||||
"synthoid population analysis and investigation by 5%. " +
|
||||
@ -80,27 +79,27 @@ export const Skills: Record<string, Skill> = {};
|
||||
"the accuracy of your synthoid population/community estimates.",
|
||||
baseCost: 3,
|
||||
costInc: 1,
|
||||
successChanceEstimate: 5,
|
||||
});
|
||||
Skills[SkillNames.CybersEdge] = new Skill({
|
||||
name: SkillNames.CybersEdge,
|
||||
mults: { [BladeMultName.successChanceEstimate]: 5 },
|
||||
}),
|
||||
[BladeSkillName.cybersEdge]: new Skill({
|
||||
name: BladeSkillName.cybersEdge,
|
||||
desc: "Each level of this skill increases your max stamina by 2%",
|
||||
baseCost: 1,
|
||||
costInc: 3,
|
||||
stamina: 2,
|
||||
});
|
||||
Skills[SkillNames.HandsOfMidas] = new Skill({
|
||||
name: SkillNames.HandsOfMidas,
|
||||
mults: { [BladeMultName.stamina]: 2 },
|
||||
}),
|
||||
[BladeSkillName.handsOfMidas]: new Skill({
|
||||
name: BladeSkillName.handsOfMidas,
|
||||
desc: "Each level of this skill increases the amount of money you receive from Contracts by 10%",
|
||||
baseCost: 2,
|
||||
costInc: 2.5,
|
||||
money: 10,
|
||||
});
|
||||
Skills[SkillNames.Hyperdrive] = new Skill({
|
||||
name: SkillNames.Hyperdrive,
|
||||
mults: { [BladeMultName.money]: 10 },
|
||||
}),
|
||||
[BladeSkillName.hyperdrive]: new Skill({
|
||||
name: BladeSkillName.hyperdrive,
|
||||
desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 10%",
|
||||
baseCost: 1,
|
||||
costInc: 2.5,
|
||||
expGain: 10,
|
||||
});
|
||||
})();
|
||||
mults: { [BladeMultName.expGain]: 10 },
|
||||
}),
|
||||
};
|
@ -1,23 +1,21 @@
|
||||
import React from "react";
|
||||
import { Action } from "../Action";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { LevelableAction } from "../Types";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import React from "react";
|
||||
import { Box, IconButton, Tooltip, Typography } from "@mui/material";
|
||||
import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
|
||||
interface IProps {
|
||||
action: Action;
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
|
||||
interface ActionLevelProps {
|
||||
action: LevelableAction;
|
||||
isActive: boolean;
|
||||
bladeburner: Bladeburner;
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
export function ActionLevel({ action, isActive, bladeburner, rerender }: IProps): React.ReactElement {
|
||||
export function ActionLevel({ action, isActive, bladeburner, rerender }: ActionLevelProps): React.ReactElement {
|
||||
const canIncrease = action.level < action.maxLevel;
|
||||
const canDecrease = action.level > 1;
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React from "react";
|
||||
import { Box, Tab, Tabs } from "@mui/material";
|
||||
|
||||
import { GeneralActionPage } from "./GeneralActionPage";
|
||||
import { ContractPage } from "./ContractPage";
|
||||
import { OperationPage } from "./OperationPage";
|
||||
@ -6,15 +8,11 @@ import { BlackOpPage } from "./BlackOpPage";
|
||||
import { SkillPage } from "./SkillPage";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function AllPages(props: IProps): React.ReactElement {
|
||||
export function AllPages({ bladeburner }: IProps): React.ReactElement {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
function handleChange(event: React.SyntheticEvent, tab: number): void {
|
||||
@ -31,11 +29,11 @@ export function AllPages(props: IProps): React.ReactElement {
|
||||
<Tab label="Skills" />
|
||||
</Tabs>
|
||||
<Box sx={{ p: 1 }}>
|
||||
{value === 0 && <GeneralActionPage bladeburner={props.bladeburner} />}
|
||||
{value === 1 && <ContractPage bladeburner={props.bladeburner} />}
|
||||
{value === 2 && <OperationPage bladeburner={props.bladeburner} />}
|
||||
{value === 3 && <BlackOpPage bladeburner={props.bladeburner} />}
|
||||
{value === 4 && <SkillPage bladeburner={props.bladeburner} />}
|
||||
{value === 0 && <GeneralActionPage bladeburner={bladeburner} />}
|
||||
{value === 1 && <ContractPage bladeburner={bladeburner} />}
|
||||
{value === 2 && <OperationPage bladeburner={bladeburner} />}
|
||||
{value === 3 && <BlackOpPage bladeburner={bladeburner} />}
|
||||
{value === 4 && <SkillPage bladeburner={bladeburner} />}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
@ -1,26 +1,24 @@
|
||||
import React from "react";
|
||||
import { Action } from "../Action";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import type { LevelableAction } from "../Types";
|
||||
|
||||
interface IProps {
|
||||
action: Action;
|
||||
import React from "react";
|
||||
import { Box, Switch, Tooltip, Typography } from "@mui/material";
|
||||
|
||||
interface AutoLevelProps {
|
||||
action: LevelableAction;
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
export function Autolevel(props: IProps): React.ReactElement {
|
||||
export function Autolevel({ action, rerender }: AutoLevelProps): React.ReactElement {
|
||||
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
props.action.autoLevel = event.target.checked;
|
||||
props.rerender();
|
||||
action.autoLevel = event.target.checked;
|
||||
rerender();
|
||||
}
|
||||
return (
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<Tooltip title={<Typography>Automatically increase operation level when possible</Typography>}>
|
||||
<Typography> Autolevel:</Typography>
|
||||
</Tooltip>
|
||||
<Switch checked={props.action.autoLevel} onChange={onAutolevel} />
|
||||
<Switch checked={action.autoLevel} onChange={onAutolevel} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,93 +1,79 @@
|
||||
import React from "react";
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { formatNumberNoSuffix } from "../../ui/formatNumber";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||
import { TeamSizeButton } from "./TeamSizeButton";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { BlackOperation } from "../BlackOperation";
|
||||
import { BlackOperations } from "../data/BlackOperations";
|
||||
import { Player } from "@player";
|
||||
import { BlackOperation } from "../Actions/BlackOperation";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
import { StartButton } from "./StartButton";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
interface IProps {
|
||||
interface BlackOpElemProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: BlackOperation;
|
||||
blackOp: BlackOperation;
|
||||
}
|
||||
|
||||
export function BlackOpElem(props: IProps): React.ReactElement {
|
||||
export function BlackOpElem({ bladeburner, blackOp }: BlackOpElemProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const isCompleted = props.bladeburner.blackops[props.action.name] != null;
|
||||
const isCompleted = bladeburner.numBlackOpsComplete > blackOp.n;
|
||||
if (isCompleted) {
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
<Typography>{props.action.name} (COMPLETED)</Typography>
|
||||
<Typography>{blackOp.name} (COMPLETED)</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const isActive =
|
||||
props.bladeburner.action.type === ActionTypes.BlackOperation && props.action.name === props.bladeburner.action.name;
|
||||
const actionTime = props.action.getActionTime(props.bladeburner, Player);
|
||||
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
|
||||
const isActive = bladeburner.action?.name === blackOp.name;
|
||||
const actionTime = blackOp.getActionTime(bladeburner, Player);
|
||||
const hasReqdRank = bladeburner.rank >= blackOp.reqdRank;
|
||||
const computedActionTimeCurrent = Math.min(
|
||||
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
|
||||
props.bladeburner.actionTimeToComplete,
|
||||
bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
|
||||
bladeburner.actionTimeToComplete,
|
||||
);
|
||||
|
||||
const actionData = BlackOperations[props.action.name];
|
||||
if (actionData === undefined) {
|
||||
throw new Error(`Cannot find data for ${props.action.name}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
{isActive ? (
|
||||
<>
|
||||
<>
|
||||
<CopyableText value={props.action.name} />
|
||||
<CopyableText value={blackOp.name} />
|
||||
<Typography>
|
||||
(IN PROGRESS - {formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
|
||||
{formatNumberNoSuffix(props.bladeburner.actionTimeToComplete, 0)})
|
||||
{formatNumberNoSuffix(bladeburner.actionTimeToComplete, 0)})
|
||||
</Typography>
|
||||
<Typography>
|
||||
{createProgressBarText({
|
||||
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
|
||||
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
|
||||
})}
|
||||
</Typography>
|
||||
</>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CopyableText value={props.action.name} />
|
||||
<CopyableText value={blackOp.name} />
|
||||
|
||||
<StartButton
|
||||
bladeburner={props.bladeburner}
|
||||
type={ActionTypes.BlackOperation}
|
||||
name={props.action.name}
|
||||
rerender={rerender}
|
||||
/>
|
||||
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
|
||||
<StartButton bladeburner={bladeburner} action={blackOp} rerender={rerender} />
|
||||
<TeamSizeButton action={blackOp} bladeburner={bladeburner} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<Typography>{actionData.desc}</Typography>
|
||||
<Typography whiteSpace={"pre-wrap"}>{blackOp.desc}</Typography>
|
||||
<br />
|
||||
<br />
|
||||
<Typography color={hasReqdRank ? "primary" : "error"}>
|
||||
Required Rank: {formatNumberNoSuffix(props.action.reqdRank, 0)}
|
||||
Required Rank: {formatNumberNoSuffix(blackOp.reqdRank, 0)}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
<SuccessChance action={props.action} bladeburner={props.bladeburner} />
|
||||
<SuccessChance action={blackOp} bladeburner={bladeburner} />
|
||||
<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
|
||||
</Typography>
|
||||
|
@ -1,39 +1,20 @@
|
||||
import React from "react";
|
||||
import { BlackOperations } from "../BlackOperations";
|
||||
import { BlackOperation } from "../BlackOperation";
|
||||
import { blackOpsArray } from "../data/BlackOperations";
|
||||
import { BlackOperation } from "../Actions/BlackOperation";
|
||||
import { BlackOpElem } from "./BlackOpElem";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
interface IProps {
|
||||
interface BlackOpListProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function BlackOpList(props: IProps): React.ReactElement {
|
||||
let blackops: BlackOperation[] = [];
|
||||
for (const blackopName of Object.keys(BlackOperations)) {
|
||||
if (Object.hasOwn(BlackOperations, blackopName)) {
|
||||
blackops.push(BlackOperations[blackopName]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function (a, b) {
|
||||
return a.reqdRank - b.reqdRank;
|
||||
});
|
||||
|
||||
blackops = blackops.filter(
|
||||
(blackop: BlackOperation, i: number) =>
|
||||
!(
|
||||
props.bladeburner.blackops[blackops[i].name] == null &&
|
||||
i !== 0 &&
|
||||
props.bladeburner.blackops[blackops[i - 1].name] == null
|
||||
),
|
||||
);
|
||||
|
||||
blackops = blackops.reverse();
|
||||
export function BlackOpList({ bladeburner }: BlackOpListProps): React.ReactElement {
|
||||
const blackOps = blackOpsArray.slice(0, bladeburner.numBlackOpsComplete + 1);
|
||||
|
||||
return (
|
||||
<>
|
||||
{blackops.map((blackop: BlackOperation) => (
|
||||
<BlackOpElem key={blackop.name} bladeburner={props.bladeburner} action={blackop} />
|
||||
{blackOps.map((blackOp: BlackOperation) => (
|
||||
<BlackOpElem key={blackOp.name} bladeburner={bladeburner} blackOp={blackOp} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -1,17 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { BlackOperationName, FactionName } from "@enums";
|
||||
import { FactionName } from "@enums";
|
||||
import { BlackOpList } from "./BlackOpList";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
import { CorruptableText } from "../../ui/React/CorruptableText";
|
||||
import { blackOpsArray } from "../data/BlackOperations";
|
||||
|
||||
interface IProps {
|
||||
interface BlackOpPageProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function BlackOpPage(props: IProps): React.ReactElement {
|
||||
export function BlackOpPage({ bladeburner }: BlackOpPageProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
@ -28,12 +29,12 @@ export function BlackOpPage(props: IProps): React.ReactElement {
|
||||
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank
|
||||
losses.
|
||||
</Typography>
|
||||
{props.bladeburner.blackops[BlackOperationName.OperationDaedalus] ? (
|
||||
{bladeburner.numBlackOpsComplete >= blackOpsArray.length ? (
|
||||
<Button sx={{ my: 1, p: 1 }} onClick={() => Router.toPage(Page.BitVerse, { flume: false, quick: false })}>
|
||||
<CorruptableText content="Destroy w0rld_d34mon" spoiler={false}></CorruptableText>
|
||||
</Button>
|
||||
) : (
|
||||
<BlackOpList bladeburner={props.bladeburner} />
|
||||
<BlackOpList bladeburner={bladeburner} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -1,10 +1,7 @@
|
||||
import React from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { Contracts } from "../data/Contracts";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { Action } from "../Action";
|
||||
import { Player } from "@player";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
@ -15,74 +12,64 @@ import { formatNumberNoSuffix, formatBigNumber } from "../../ui/formatNumber";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
import { Contract } from "../Actions/Contract";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
|
||||
interface IProps {
|
||||
interface ContractElemProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: Action;
|
||||
action: Contract;
|
||||
}
|
||||
|
||||
export function ContractElem(props: IProps): React.ReactElement {
|
||||
export function ContractElem({ bladeburner, action }: ContractElemProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const isActive =
|
||||
props.bladeburner.action.type === ActionTypes.Contract && props.action.name === props.bladeburner.action.name;
|
||||
// Temp special return
|
||||
if (!getEnumHelper("BladeContractName").isMember(action.name)) return <></>;
|
||||
const isActive = action.name === bladeburner.action?.name;
|
||||
const computedActionTimeCurrent = Math.min(
|
||||
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
|
||||
props.bladeburner.actionTimeToComplete,
|
||||
bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
|
||||
bladeburner.actionTimeToComplete,
|
||||
);
|
||||
const actionTime = props.action.getActionTime(props.bladeburner, Player);
|
||||
|
||||
const actionData = Contracts[props.action.name];
|
||||
if (actionData === undefined) {
|
||||
throw new Error(`Cannot find data for ${props.action.name}`);
|
||||
}
|
||||
const actionTime = action.getActionTime(bladeburner, Player);
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
{isActive ? (
|
||||
<>
|
||||
<Typography>
|
||||
<CopyableText value={props.action.name} /> (IN PROGRESS -{" "}
|
||||
{formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
|
||||
{formatNumberNoSuffix(props.bladeburner.actionTimeToComplete, 0)})
|
||||
</Typography>
|
||||
<CopyableText value={action.name} /> (IN PROGRESS - {formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
|
||||
{formatNumberNoSuffix(bladeburner.actionTimeToComplete, 0)})
|
||||
<Typography>
|
||||
{createProgressBarText({
|
||||
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
|
||||
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
|
||||
})}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CopyableText value={props.action.name} />
|
||||
<StartButton
|
||||
bladeburner={props.bladeburner}
|
||||
type={ActionTypes.Contract}
|
||||
name={props.action.name}
|
||||
rerender={rerender}
|
||||
/>
|
||||
<CopyableText value={action.name} />
|
||||
<StartButton bladeburner={bladeburner} action={action} rerender={rerender} />
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
|
||||
<ActionLevel action={action} bladeburner={bladeburner} isActive={isActive} rerender={rerender} />
|
||||
<br />
|
||||
<br />
|
||||
<Typography>
|
||||
{actionData.desc}
|
||||
<Typography whiteSpace={"pre-wrap"}>
|
||||
{action.desc}
|
||||
<br />
|
||||
<br />
|
||||
<SuccessChance action={props.action} bladeburner={props.bladeburner} />
|
||||
<SuccessChance action={action} bladeburner={bladeburner} />
|
||||
<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
|
||||
<br />
|
||||
Contracts remaining: {formatBigNumber(Math.floor(props.action.count))}
|
||||
Contracts remaining: {formatBigNumber(Math.floor(action.count))}
|
||||
<br />
|
||||
Successes: {formatBigNumber(props.action.successes)}
|
||||
Successes: {formatBigNumber(action.successes)}
|
||||
<br />
|
||||
Failures: {formatBigNumber(props.action.failures)}
|
||||
Failures: {formatBigNumber(action.failures)}
|
||||
</Typography>
|
||||
<br />
|
||||
<Autolevel rerender={rerender} action={props.action} />
|
||||
<Autolevel rerender={rerender} action={action} />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
import React from "react";
|
||||
import { BladeContractName } from "@enums";
|
||||
import { ContractElem } from "./ContractElem";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function ContractList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.contracts);
|
||||
const contracts = props.bladeburner.contracts;
|
||||
export function ContractList({ bladeburner }: { bladeburner: Bladeburner }): React.ReactElement {
|
||||
const names = Object.values(BladeContractName);
|
||||
return (
|
||||
<>
|
||||
{names.map((name: string) => (
|
||||
<ContractElem key={name} bladeburner={props.bladeburner} action={contracts[name]} />
|
||||
{names.map((name) => (
|
||||
<ContractElem key={name} bladeburner={bladeburner} action={bladeburner.contracts[name]} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { GeneralAction } from "../Actions/GeneralAction";
|
||||
|
||||
import React from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||
import { formatNumberNoSuffix } from "../../ui/formatNumber";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { Action } from "../Action";
|
||||
import { GeneralActions } from "../data/GeneralActions";
|
||||
import { Player } from "@player";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
|
||||
@ -16,71 +15,46 @@ import Box from "@mui/material/Box";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
|
||||
interface IProps {
|
||||
interface GeneralActionElemProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: Action;
|
||||
action: GeneralAction;
|
||||
}
|
||||
|
||||
export function GeneralActionElem(props: IProps): React.ReactElement {
|
||||
export function GeneralActionElem({ bladeburner, action }: GeneralActionElemProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const isActive = props.action.name === props.bladeburner.action.name;
|
||||
const isActive = action.name === bladeburner.action?.name;
|
||||
const computedActionTimeCurrent = Math.min(
|
||||
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
|
||||
props.bladeburner.actionTimeToComplete,
|
||||
bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
|
||||
bladeburner.actionTimeToComplete,
|
||||
);
|
||||
const actionTime = (function (): number {
|
||||
switch (props.action.name) {
|
||||
case "Training":
|
||||
case "Field Analysis":
|
||||
return 30;
|
||||
case "Diplomacy":
|
||||
case "Hyperbolic Regeneration Chamber":
|
||||
case "Incite Violence":
|
||||
return 60;
|
||||
case "Recruitment":
|
||||
return props.bladeburner.getRecruitmentTime(Player);
|
||||
}
|
||||
return -1; // dead code
|
||||
})();
|
||||
const actionTime = action.getActionTime(bladeburner, Player);
|
||||
const successChance =
|
||||
props.action.name === "Recruitment"
|
||||
? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(Player), 1))
|
||||
: -1;
|
||||
|
||||
const actionData = GeneralActions[props.action.name];
|
||||
if (actionData === undefined) {
|
||||
throw new Error(`Cannot find data for ${props.action.name}`);
|
||||
}
|
||||
action.name === "Recruitment" ? Math.max(0, Math.min(bladeburner.getRecruitmentSuccessChance(Player), 1)) : -1;
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
{isActive ? (
|
||||
<>
|
||||
<CopyableText value={props.action.name} />
|
||||
<CopyableText value={action.name} />
|
||||
<Typography>
|
||||
(IN PROGRESS - {formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
|
||||
{formatNumberNoSuffix(props.bladeburner.actionTimeToComplete, 0)})
|
||||
{formatNumberNoSuffix(bladeburner.actionTimeToComplete, 0)})
|
||||
</Typography>
|
||||
<Typography>
|
||||
{createProgressBarText({
|
||||
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
|
||||
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
|
||||
})}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<CopyableText value={props.action.name} />
|
||||
<StartButton
|
||||
bladeburner={props.bladeburner}
|
||||
type={ActionTypes[props.action.name]}
|
||||
name={props.action.name}
|
||||
rerender={rerender}
|
||||
/>
|
||||
<CopyableText value={action.name} />
|
||||
<StartButton bladeburner={bladeburner} action={action} rerender={rerender} />
|
||||
</Box>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
<Typography>{actionData.desc}</Typography>
|
||||
<Typography>{action.desc}</Typography>
|
||||
<br />
|
||||
<br />
|
||||
<Typography>
|
||||
|
@ -1,24 +1,18 @@
|
||||
import React from "react";
|
||||
import { GeneralActionElem } from "./GeneralActionElem";
|
||||
import { Action } from "../Action";
|
||||
import { GeneralActions } from "../GeneralActions";
|
||||
import { GeneralActions } from "../data/GeneralActions";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
interface IProps {
|
||||
interface GeneralActionListProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function GeneralActionList(props: IProps): React.ReactElement {
|
||||
const actions: Action[] = [];
|
||||
for (const name of Object.keys(GeneralActions)) {
|
||||
if (Object.hasOwn(GeneralActions, name)) {
|
||||
actions.push(GeneralActions[name]);
|
||||
}
|
||||
}
|
||||
export function GeneralActionList({ bladeburner }: GeneralActionListProps): React.ReactElement {
|
||||
const actions = Object.values(GeneralActions);
|
||||
return (
|
||||
<>
|
||||
{actions.map((action: Action) => (
|
||||
<GeneralActionElem key={action.name} bladeburner={props.bladeburner} action={action} />
|
||||
{actions.map((action) => (
|
||||
<GeneralActionElem key={action.name} bladeburner={bladeburner} action={action} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -1,5 +1,10 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Operation } from "../Actions/Operation";
|
||||
|
||||
import React from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
@ -7,85 +12,69 @@ import { ActionLevel } from "./ActionLevel";
|
||||
import { Autolevel } from "./Autolevel";
|
||||
import { StartButton } from "./StartButton";
|
||||
import { TeamSizeButton } from "./TeamSizeButton";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { Operation } from "../Operation";
|
||||
import { Operations } from "../data/Operations";
|
||||
import { Player } from "@player";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
import { formatNumberNoSuffix, formatBigNumber } from "../../ui/formatNumber";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
import { BladeActionType } from "@enums";
|
||||
|
||||
interface IProps {
|
||||
interface OperationElemProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: Operation;
|
||||
operation: Operation;
|
||||
}
|
||||
|
||||
export function OperationElem(props: IProps): React.ReactElement {
|
||||
export function OperationElem({ bladeburner, operation }: OperationElemProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const isActive =
|
||||
props.bladeburner.action.type === ActionTypes.Operation && props.action.name === props.bladeburner.action.name;
|
||||
bladeburner.action?.type === BladeActionType.operation && operation.name === bladeburner.action?.name;
|
||||
const computedActionTimeCurrent = Math.min(
|
||||
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
|
||||
props.bladeburner.actionTimeToComplete,
|
||||
bladeburner.actionTimeCurrent + bladeburner.actionTimeOverflow,
|
||||
bladeburner.actionTimeToComplete,
|
||||
);
|
||||
const actionTime = props.action.getActionTime(props.bladeburner, Player);
|
||||
|
||||
const actionData = Operations[props.action.name];
|
||||
if (actionData === undefined) {
|
||||
throw new Error(`Cannot find data for ${props.action.name}`);
|
||||
}
|
||||
const actionTime = operation.getActionTime(bladeburner, Player);
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
{isActive ? (
|
||||
<>
|
||||
<Typography>
|
||||
<CopyableText value={props.action.name} /> (IN PROGRESS -{" "}
|
||||
{formatNumberNoSuffix(computedActionTimeCurrent, 0)} /{" "}
|
||||
{formatNumberNoSuffix(props.bladeburner.actionTimeToComplete, 0)})
|
||||
<CopyableText value={operation.name} /> (IN PROGRESS - {formatNumberNoSuffix(computedActionTimeCurrent, 0)}{" "}
|
||||
/ {formatNumberNoSuffix(bladeburner.actionTimeToComplete, 0)})
|
||||
</Typography>
|
||||
<Typography>
|
||||
{createProgressBarText({
|
||||
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
|
||||
progress: computedActionTimeCurrent / bladeburner.actionTimeToComplete,
|
||||
})}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CopyableText value={props.action.name} />
|
||||
<StartButton
|
||||
bladeburner={props.bladeburner}
|
||||
type={ActionTypes.Operation}
|
||||
name={props.action.name}
|
||||
rerender={rerender}
|
||||
/>
|
||||
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
|
||||
<CopyableText value={operation.name} />
|
||||
<StartButton bladeburner={bladeburner} action={operation} rerender={rerender} />
|
||||
<TeamSizeButton action={operation} bladeburner={bladeburner} />
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
|
||||
<ActionLevel action={operation} bladeburner={bladeburner} isActive={isActive} rerender={rerender} />
|
||||
<br />
|
||||
<br />
|
||||
<Typography>
|
||||
{actionData.desc}
|
||||
<Typography whiteSpace={"pre-wrap"}>
|
||||
{operation.desc}
|
||||
<br />
|
||||
<br />
|
||||
<SuccessChance action={props.action} bladeburner={props.bladeburner} />
|
||||
<SuccessChance action={operation} bladeburner={bladeburner} />
|
||||
<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
|
||||
<br />
|
||||
Operations remaining: {formatBigNumber(Math.floor(props.action.count))}
|
||||
Operations remaining: {formatBigNumber(Math.floor(operation.count))}
|
||||
<br />
|
||||
Successes: {formatBigNumber(props.action.successes)}
|
||||
Successes: {formatBigNumber(operation.successes)}
|
||||
<br />
|
||||
Failures: {formatBigNumber(props.action.failures)}
|
||||
Failures: {formatBigNumber(operation.failures)}
|
||||
</Typography>
|
||||
<br />
|
||||
<Autolevel rerender={rerender} action={props.action} />
|
||||
<Autolevel rerender={rerender} action={operation} />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
import React from "react";
|
||||
import { BladeOperationName } from "@enums";
|
||||
import { OperationElem } from "./OperationElem";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function OperationList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.operations);
|
||||
const operations = props.bladeburner.operations;
|
||||
export function OperationList({ bladeburner }: { bladeburner: Bladeburner }): React.ReactElement {
|
||||
const names = Object.values(BladeOperationName);
|
||||
return (
|
||||
<>
|
||||
{names.map((name: string) => (
|
||||
<OperationElem key={name} bladeburner={props.bladeburner} action={operations[name]} />
|
||||
{names.map((name) => (
|
||||
<OperationElem key={name} bladeburner={bladeburner} operation={bladeburner.operations[name]} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
import { formatBigNumber } from "../../ui/formatNumber";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
@ -10,34 +10,29 @@ import AddIcon from "@mui/icons-material/Add";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Skill } from "../Skill";
|
||||
|
||||
interface IProps {
|
||||
interface SkillElemProps {
|
||||
skill: Skill;
|
||||
bladeburner: Bladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillElem(props: IProps): React.ReactElement {
|
||||
const skillName = props.skill.name;
|
||||
let currentLevel = 0;
|
||||
if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {
|
||||
currentLevel = props.bladeburner.skills[skillName];
|
||||
}
|
||||
const pointCost = props.skill.calculateCost(currentLevel);
|
||||
export function SkillElem({ skill, bladeburner, onUpgrade }: SkillElemProps): React.ReactElement {
|
||||
const skillName = skill.name;
|
||||
const skillLevel = bladeburner.getSkillLevel(skillName);
|
||||
const pointCost = useMemo(() => skill.calculateCost(skillLevel), [skill, skillLevel]);
|
||||
|
||||
const canLevel = props.bladeburner.skillPoints >= pointCost;
|
||||
const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;
|
||||
const canLevel = bladeburner.skillPoints >= pointCost;
|
||||
const maxLvl = skill.maxLvl ? skillLevel >= skill.maxLvl : false;
|
||||
|
||||
function onClick(): void {
|
||||
if (props.bladeburner.skillPoints < pointCost) return;
|
||||
props.bladeburner.skillPoints -= pointCost;
|
||||
props.bladeburner.upgradeSkill(props.skill);
|
||||
props.onUpgrade();
|
||||
bladeburner.upgradeSkill(skillName);
|
||||
onUpgrade();
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
<CopyableText variant="h6" color="primary" value={props.skill.name} />
|
||||
<CopyableText variant="h6" color="primary" value={skillName} />
|
||||
{!canLevel || maxLvl ? (
|
||||
<IconButton disabled>
|
||||
<CloseIcon />
|
||||
@ -48,13 +43,13 @@ export function SkillElem(props: IProps): React.ReactElement {
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
<Typography>Level: {formatBigNumber(currentLevel)}</Typography>
|
||||
<Typography>Level: {formatBigNumber(skillLevel)}</Typography>
|
||||
{maxLvl ? (
|
||||
<Typography>MAX LEVEL</Typography>
|
||||
) : (
|
||||
<Typography>Skill Points required: {formatBigNumber(pointCost)}</Typography>
|
||||
)}
|
||||
<Typography>{props.skill.desc}</Typography>
|
||||
<Typography>{skill.desc}</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { SkillElem } from "./SkillElem";
|
||||
import { Skills } from "../Skills";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: Bladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillList(props: IProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
{Object.keys(Skills).map((skill: string) => (
|
||||
<SkillElem key={skill} bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,25 +1,24 @@
|
||||
import React, { useState } from "react";
|
||||
import { SkillList } from "./SkillList";
|
||||
import React from "react";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { formatBigNumber } from "../../ui/formatNumber";
|
||||
import Typography from "@mui/material/Typography";
|
||||
interface IProps {
|
||||
import { useRerender } from "../../ui/React/hooks";
|
||||
import { SkillElem } from "./SkillElem";
|
||||
import { Skills } from "../data/Skills";
|
||||
|
||||
interface SkillPageProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function SkillPage(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const mults = props.bladeburner.skillMultipliers;
|
||||
|
||||
function valid(mult: number | undefined): boolean {
|
||||
return mult !== undefined && mult !== 1;
|
||||
}
|
||||
export function SkillPage({ bladeburner }: SkillPageProps): React.ReactElement {
|
||||
const rerender = useRerender();
|
||||
const multDisplays = bladeburner.getSkillMultsDisplay();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
<strong>Skill Points: {formatBigNumber(props.bladeburner.skillPoints)}</strong>
|
||||
<strong>Skill Points: {formatBigNumber(bladeburner.skillPoints)}</strong>
|
||||
</Typography>
|
||||
<Typography>
|
||||
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
|
||||
@ -27,53 +26,12 @@ export function SkillPage(props: IProps): React.ReactElement {
|
||||
Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different
|
||||
skills with each other is multiplicative.
|
||||
</Typography>
|
||||
{valid(mults.successChanceAll) && (
|
||||
<Typography>Total Success Chance: x{formatBigNumber(mults.successChanceAll)}</Typography>
|
||||
)}
|
||||
{valid(mults.successChanceStealth) && (
|
||||
<Typography>Stealth Success Chance: x{formatBigNumber(mults.successChanceStealth)}</Typography>
|
||||
)}
|
||||
{valid(mults.successChanceKill) && (
|
||||
<Typography>Retirement Success Chance: x{formatBigNumber(mults.successChanceKill)}</Typography>
|
||||
)}
|
||||
{valid(mults.successChanceContract) && (
|
||||
<Typography>Contract Success Chance: x{formatBigNumber(mults.successChanceContract)}</Typography>
|
||||
)}
|
||||
{valid(mults.successChanceOperation) && (
|
||||
<Typography>Operation Success Chance: x{formatBigNumber(mults.successChanceOperation)}</Typography>
|
||||
)}
|
||||
{valid(mults.successChanceEstimate) && (
|
||||
<Typography>Synthoid Data Estimate: x{formatBigNumber(mults.successChanceEstimate)}</Typography>
|
||||
)}
|
||||
{valid(mults.actionTime) && <Typography>Action Time: x{formatBigNumber(mults.actionTime)}</Typography>}
|
||||
{valid(mults.effHack) && <Typography>Hacking Skill: x{formatBigNumber(mults.effHack)}</Typography>}
|
||||
{valid(mults.effStr) && <Typography>Strength: x{formatBigNumber(mults.effStr)}</Typography>}
|
||||
{valid(mults.effDef) && <Typography>Defense: x{formatBigNumber(mults.effDef)}</Typography>}
|
||||
{valid(mults.effDex) && <Typography>Dexterity: x{formatBigNumber(mults.effDex)}</Typography>}
|
||||
{valid(mults.effAgi) && <Typography>Agility: x{formatBigNumber(mults.effAgi)}</Typography>}
|
||||
{valid(mults.effCha) && <Typography>Charisma: x{formatBigNumber(mults.effCha)}</Typography>}
|
||||
{valid(mults.effInt) && <Typography>Intelligence: x{formatBigNumber(mults.effInt)}</Typography>}
|
||||
{valid(mults.stamina) && <Typography>Stamina: x{formatBigNumber(mults.stamina)}</Typography>}
|
||||
{valid(mults.money) && <Typography>Contract Money: x{formatBigNumber(mults.money)}</Typography>}
|
||||
{valid(mults.expGain) && <Typography>Exp Gain: x{formatBigNumber(mults.expGain)}</Typography>}
|
||||
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender((old) => !old)} />
|
||||
{multDisplays.map((multDisplay, i) => (
|
||||
<Typography key={i}>{multDisplay}</Typography>
|
||||
))}
|
||||
{Object.values(Skills).map((skill) => (
|
||||
<SkillElem key={skill.name} bladeburner={bladeburner} skill={skill} onUpgrade={rerender} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
var multKeys = Object.keys(this.skillMultipliers);
|
||||
for (var i = 0; i < multKeys.length; ++i) {
|
||||
var mult = this.skillMultipliers[multKeys[i]];
|
||||
if (mult && mult !== 1) {
|
||||
mult = formatNumber(mult, 3);
|
||||
switch(multKeys[i]) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -1,47 +1,27 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Action } from "../Types";
|
||||
|
||||
import React from "react";
|
||||
import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip";
|
||||
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { BlackOperation } from "../BlackOperation";
|
||||
import { Player } from "@player";
|
||||
import Button from "@mui/material/Button";
|
||||
import { AugmentationName } from "@enums";
|
||||
import { ActionIdentifier } from "../ActionIdentifier";
|
||||
|
||||
interface IProps {
|
||||
interface StartButtonProps {
|
||||
bladeburner: Bladeburner;
|
||||
type: number;
|
||||
name: string;
|
||||
action: Action;
|
||||
rerender: () => void;
|
||||
}
|
||||
export function StartButton(props: IProps): React.ReactElement {
|
||||
const action = props.bladeburner.getActionObject(new ActionIdentifier({ name: props.name, type: props.type }));
|
||||
if (action == null) {
|
||||
throw new Error("Failed to get Operation Object for: " + props.name);
|
||||
}
|
||||
let disabled = false;
|
||||
if (action.count < 1) {
|
||||
disabled = true;
|
||||
}
|
||||
if (props.name === "Raid" && props.bladeburner.getCurrentCity().comms === 0) {
|
||||
disabled = true;
|
||||
}
|
||||
export function StartButton({ bladeburner, action, rerender }: StartButtonProps): React.ReactElement {
|
||||
const availability = action.getAvailability(bladeburner);
|
||||
const disabledReason = availability.available ? "" : availability.error;
|
||||
|
||||
if (action instanceof BlackOperation && props.bladeburner.rank < action.reqdRank) {
|
||||
disabled = true;
|
||||
}
|
||||
function onStart(): void {
|
||||
if (disabled) return;
|
||||
const action = new ActionIdentifier();
|
||||
action.type = props.type;
|
||||
action.name = props.name;
|
||||
if (!Player.hasAugmentation(AugmentationName.BladesSimulacrum, true)) Player.finishWork(true);
|
||||
props.bladeburner.startAction(action);
|
||||
props.rerender();
|
||||
if (disabledReason) return;
|
||||
bladeburner.startAction(action.id);
|
||||
rerender();
|
||||
}
|
||||
|
||||
return (
|
||||
<Button sx={{ mx: 1 }} disabled={disabled} onClick={onStart}>
|
||||
<ButtonWithTooltip disabledTooltip={disabledReason} onClick={onStart}>
|
||||
Start
|
||||
</Button>
|
||||
</ButtonWithTooltip>
|
||||
);
|
||||
}
|
||||
|
@ -10,29 +10,23 @@ import { formatNumberNoSuffix, formatPopulation, formatBigNumber } from "../../u
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
import { joinFaction } from "../../Faction/FactionHelpers";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
|
||||
import { TravelModal } from "./TravelModal";
|
||||
|
||||
interface IProps {
|
||||
interface StatsProps {
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
|
||||
export function Stats(props: IProps): React.ReactElement {
|
||||
export function Stats({ bladeburner }: StatsProps): React.ReactElement {
|
||||
const [travelOpen, setTravelOpen] = useState(false);
|
||||
useRerender(1000);
|
||||
|
||||
const inFaction = props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;
|
||||
const inFaction = bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;
|
||||
|
||||
function openFaction(): void {
|
||||
if (!inFaction) return;
|
||||
const faction = Factions[FactionName.Bladeburners];
|
||||
if (!faction.isMember) {
|
||||
joinFaction(faction);
|
||||
}
|
||||
|
||||
Router.toPage(Page.Faction, { faction });
|
||||
const success = bladeburner.joinFaction();
|
||||
if (success) Router.toPage(Page.Faction, { faction: Factions[FactionName.Bladeburners] });
|
||||
}
|
||||
|
||||
return (
|
||||
@ -49,11 +43,11 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={props.bladeburner} />
|
||||
<TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={bladeburner} />
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
<Tooltip title={<Typography>Your rank within the Bladeburner division.</Typography>}>
|
||||
<Typography>Rank: {formatBigNumber(props.bladeburner.rank)}</Typography>
|
||||
<Typography>Rank: {formatBigNumber(bladeburner.rank)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
@ -82,23 +76,23 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Stamina: {formatBigNumber(props.bladeburner.stamina)} / {formatBigNumber(props.bladeburner.maxStamina)}
|
||||
Stamina: {formatBigNumber(bladeburner.stamina)} / {formatBigNumber(bladeburner.maxStamina)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Typography>
|
||||
Stamina Penalty: {formatNumberNoSuffix((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%
|
||||
Stamina Penalty: {formatNumberNoSuffix((1 - bladeburner.calculateStaminaPenalty()) * 100, 1)}%
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Team Size: {formatNumberNoSuffix(props.bladeburner.teamSize, 0)}</Typography>
|
||||
<Typography>Team Members Lost: {formatNumberNoSuffix(props.bladeburner.teamLost, 0)}</Typography>
|
||||
<Typography>Team Size: {formatNumberNoSuffix(bladeburner.teamSize, 0)}</Typography>
|
||||
<Typography>Team Members Lost: {formatNumberNoSuffix(bladeburner.teamLost, 0)}</Typography>
|
||||
<br />
|
||||
<Typography>Num Times Hospitalized: {props.bladeburner.numHosp}</Typography>
|
||||
<Typography>Num Times Hospitalized: {bladeburner.numHosp}</Typography>
|
||||
<Typography>
|
||||
Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} />
|
||||
Money Lost From Hospitalizations: <Money money={bladeburner.moneyLost} />
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>Current City: {props.bladeburner.city}</Typography>
|
||||
<Typography>Current City: {bladeburner.city}</Typography>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
title={
|
||||
@ -108,9 +102,7 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Est. Synthoid Population: {formatPopulation(props.bladeburner.getCurrentCity().popEst)}
|
||||
</Typography>
|
||||
<Typography>Est. Synthoid Population: {formatPopulation(bladeburner.getCurrentCity().popEst)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
@ -122,9 +114,7 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>
|
||||
Synthoid Communities: {formatNumberNoSuffix(props.bladeburner.getCurrentCity().comms, 0)}
|
||||
</Typography>
|
||||
<Typography>Synthoid Communities: {formatNumberNoSuffix(bladeburner.getCurrentCity().comms, 0)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box display="flex">
|
||||
@ -136,11 +126,11 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Typography>City Chaos: {formatBigNumber(props.bladeburner.getCurrentCity().chaos)}</Typography>
|
||||
<Typography>City Chaos: {formatBigNumber(bladeburner.getCurrentCity().chaos)}</Typography>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<br />
|
||||
{(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
|
||||
{(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
|
||||
<>
|
||||
<Box display="flex">
|
||||
<Tooltip
|
||||
@ -154,7 +144,7 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
<Typography>
|
||||
Bonus time:{" "}
|
||||
{convertTimeMsToTimeElapsedString(
|
||||
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
|
||||
(bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
|
||||
)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
@ -162,7 +152,7 @@ export function Stats(props: IProps): React.ReactElement {
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<Typography>Skill Points: {formatBigNumber(props.bladeburner.skillPoints)}</Typography>
|
||||
<Typography>Skill Points: {formatBigNumber(bladeburner.skillPoints)}</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
Aug. Success Chance mult: {formatNumberNoSuffix(Player.mults.bladeburner_success_chance * 100, 1)}%
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from "react";
|
||||
import { stealthIcon } from "../data/Icons";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { Tooltip, Typography } from "@mui/material";
|
||||
|
||||
export function StealthIcon(): React.ReactElement {
|
||||
return <Tooltip title={<Typography>This action involves stealth</Typography>}>{stealthIcon}</Tooltip>;
|
||||
|
@ -1,35 +1,27 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { Action } from "../Types";
|
||||
|
||||
import React from "react";
|
||||
import { formatNumberNoSuffix } from "../../ui/formatNumber";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { formatPercent } from "../../ui/formatNumber";
|
||||
import { StealthIcon } from "./StealthIcon";
|
||||
import { KillIcon } from "./KillIcon";
|
||||
import { Action } from "../Action";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { Player } from "@player";
|
||||
|
||||
interface IProps {
|
||||
interface SuccessChanceProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: Action;
|
||||
}
|
||||
|
||||
export function SuccessChance(props: IProps): React.ReactElement {
|
||||
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner, Player);
|
||||
export function SuccessChance({ bladeburner, action }: SuccessChanceProps): React.ReactElement {
|
||||
const [minChance, maxChance] = action.getSuccessRange(bladeburner, Player);
|
||||
|
||||
let chance = <></>;
|
||||
if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) {
|
||||
chance = <>{formatNumberNoSuffix(estimatedSuccessChance[0] * 100, 1)}%</>;
|
||||
} else {
|
||||
chance = (
|
||||
<>
|
||||
{formatNumberNoSuffix(estimatedSuccessChance[0] * 100, 1)}% ~{" "}
|
||||
{formatNumberNoSuffix(estimatedSuccessChance[1] * 100, 1)}%
|
||||
</>
|
||||
);
|
||||
}
|
||||
const chance = formatPercent(minChance, 1) + (minChance === maxChance ? "" : ` ~ ${formatPercent(maxChance, 1)}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
Estimated success chance: {chance} {props.action.isStealth ? <StealthIcon /> : <></>}
|
||||
{props.action.isKill ? <KillIcon /> : <></>}
|
||||
Estimated success chance: {chance} {action.isStealth ? <StealthIcon /> : <></>}
|
||||
{action.isKill ? <KillIcon /> : <></>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,22 +1,24 @@
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { BlackOperation, Operation } from "../Actions";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Operation } from "../Operation";
|
||||
import { Bladeburner } from "../Bladeburner";
|
||||
import { TeamSizeModal } from "./TeamSizeModal";
|
||||
import { formatNumberNoSuffix } from "../../ui/formatNumber";
|
||||
import Button from "@mui/material/Button";
|
||||
interface IProps {
|
||||
action: Operation;
|
||||
|
||||
interface TeamSizeButtonProps {
|
||||
action: Operation | BlackOperation;
|
||||
bladeburner: Bladeburner;
|
||||
}
|
||||
export function TeamSizeButton(props: IProps): React.ReactElement {
|
||||
export function TeamSizeButton({ action, bladeburner }: TeamSizeButtonProps): React.ReactElement {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button disabled={props.bladeburner.teamSize === 0} onClick={() => setOpen(true)}>
|
||||
Set Team Size (Curr Size: {formatNumberNoSuffix(props.action.teamCount, 0)})
|
||||
<Button disabled={bladeburner.teamSize === 0} onClick={() => setOpen(true)}>
|
||||
Set Team Size (Curr Size: {formatNumberNoSuffix(action.teamCount, 0)})
|
||||
</Button>
|
||||
<TeamSizeModal open={open} onClose={() => setOpen(false)} action={props.action} bladeburner={props.bladeburner} />
|
||||
<TeamSizeModal open={open} onClose={() => setOpen(false)} action={action} bladeburner={bladeburner} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Action } from "../Action";
|
||||
import type { Bladeburner } from "../Bladeburner";
|
||||
import type { BlackOperation, Operation } from "../Actions";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
@ -8,7 +8,7 @@ import { Button, TextField, Typography } from "@mui/material";
|
||||
|
||||
interface TeamSizeModalProps {
|
||||
bladeburner: Bladeburner;
|
||||
action: Action;
|
||||
action: Operation | BlackOperation;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
25
src/Bladeburner/utils/loadActionIdentifier.ts
Normal file
25
src/Bladeburner/utils/loadActionIdentifier.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { ActionIdentifier } from "../Types";
|
||||
import { BladeActionType } from "@enums";
|
||||
import { assertLoadingType } from "../../utils/TypeAssertion";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
|
||||
/** Loads an action identifier
|
||||
* This is used for loading ActionIdentifier class objects from pre-2.6.1
|
||||
* Should load both the old format and the new format */
|
||||
export function loadActionIdentifier(identifier: unknown): ActionIdentifier | null {
|
||||
if (!identifier || typeof identifier !== "object") return null;
|
||||
assertLoadingType<ActionIdentifier>(identifier);
|
||||
if (getEnumHelper("BladeBlackOpName").isMember(identifier.name)) {
|
||||
return { type: BladeActionType.blackOp, name: identifier.name };
|
||||
}
|
||||
if (getEnumHelper("BladeContractName").isMember(identifier.name)) {
|
||||
return { type: BladeActionType.contract, name: identifier.name };
|
||||
}
|
||||
if (getEnumHelper("BladeOperationName").isMember(identifier.name)) {
|
||||
return { type: BladeActionType.operation, name: identifier.name };
|
||||
}
|
||||
if (getEnumHelper("BladeGeneralActionName").isMember(identifier.name)) {
|
||||
return { type: BladeActionType.general, name: identifier.name };
|
||||
}
|
||||
return null;
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts
|
||||
import { getCompaniesMetadata } from "./data/CompaniesMetadata";
|
||||
import { Company } from "./Company";
|
||||
import { Reviver, assertLoadingType } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { assertLoadingType } from "../utils/TypeAssertion";
|
||||
import { CompanyName } from "./Enums";
|
||||
import { PartialRecord, createEnumKeyedRecord } from "../Types/Record";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Member } from "src/types";
|
||||
import type { Member } from "../types";
|
||||
|
||||
export enum IndustryType {
|
||||
Water = "Water Utilities",
|
||||
|
@ -15,10 +15,10 @@ import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { Adjuster } from "./Adjuster";
|
||||
import { Player } from "@player";
|
||||
import { CityName } from "@enums";
|
||||
import { Skills as AllSkills } from "../../Bladeburner/Skills";
|
||||
import { SkillNames } from "../../Bladeburner/data/SkillNames";
|
||||
import { BladeSkillName, CityName } from "@enums";
|
||||
import { Skills as AllSkills } from "../../Bladeburner/data/Skills";
|
||||
import { Bladeburner } from "../../Bladeburner/Bladeburner";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
|
||||
const bigNumber = 1e27;
|
||||
|
||||
@ -57,24 +57,23 @@ export function BladeburnerDev({ bladeburner }: { bladeburner: Bladeburner }): R
|
||||
};
|
||||
|
||||
// Skill functions
|
||||
const [skill, setSkill] = useState(SkillNames.BladesIntuition);
|
||||
const [skillName, setSkillName] = useState(BladeSkillName.bladesIntuition);
|
||||
function setSkillDropdown(event: SelectChangeEvent): void {
|
||||
setSkill(event.target.value);
|
||||
if (!getEnumHelper("BladeSkillName").isMember(event.target.value)) return;
|
||||
setSkillName(event.target.value);
|
||||
}
|
||||
const modifySkill = (modifier: number) => (levelchange: number) => {
|
||||
if (bladeburner.skills[AllSkills[skill].name] == null) resetSkill();
|
||||
if (!isNaN(levelchange)) {
|
||||
bladeburner.skills[AllSkills[skill].name] += levelchange * modifier;
|
||||
bladeburner.setSkillLevel(skillName, bladeburner.getSkillLevel(skillName) + levelchange * modifier);
|
||||
bladeburner.updateSkillMultipliers();
|
||||
}
|
||||
};
|
||||
const addTonsOfSkill = () => {
|
||||
if (bladeburner.skills[AllSkills[skill].name] == null) resetSkill();
|
||||
bladeburner.skills[AllSkills[skill].name] += bigNumber;
|
||||
bladeburner.setSkillLevel(skillName, bladeburner.getSkillLevel(skillName) + bigNumber);
|
||||
bladeburner.updateSkillMultipliers();
|
||||
};
|
||||
const resetSkill = () => {
|
||||
bladeburner.skills[AllSkills[skill].name] = 0;
|
||||
bladeburner.setSkillLevel(skillName, 0);
|
||||
bladeburner.updateSkillMultipliers();
|
||||
};
|
||||
|
||||
@ -82,6 +81,7 @@ export function BladeburnerDev({ bladeburner }: { bladeburner: Bladeburner }): R
|
||||
const AllContracts = bladeburner.contracts;
|
||||
const [contractTarget, setContract] = useState(AllContracts.Tracking.name);
|
||||
function setContractDropdown(event: SelectChangeEvent): void {
|
||||
if (!getEnumHelper("BladeContractName").isMember(event.target.value)) return;
|
||||
setContract(event.target.value);
|
||||
}
|
||||
const modifyContractLevel = (modifier: number) => (levelchange: number) => {
|
||||
@ -117,6 +117,7 @@ export function BladeburnerDev({ bladeburner }: { bladeburner: Bladeburner }): R
|
||||
const AllOperations = bladeburner.operations;
|
||||
const [operationTarget, setOperation] = useState(AllOperations.Investigation.name);
|
||||
function setOperationDropdown(event: SelectChangeEvent): void {
|
||||
if (!getEnumHelper("BladeOperationName").isMember(event.target.value)) return;
|
||||
setOperation(event.target.value);
|
||||
}
|
||||
const modifyOperationLevel = (modifier: number) => (levelchange: number) => {
|
||||
@ -232,7 +233,7 @@ export function BladeburnerDev({ bladeburner }: { bladeburner: Bladeburner }): R
|
||||
<td align="center">
|
||||
<FormControl>
|
||||
<InputLabel id="skills-select"></InputLabel>
|
||||
<Select labelId="skills-select" id="skills-dropdown" onChange={setSkillDropdown} value={skill}>
|
||||
<Select labelId="skills-select" id="skills-dropdown" onChange={setSkillDropdown} value={skillName}>
|
||||
{Object.values(AllSkills).map((skill) => (
|
||||
<MenuItem key={skill.name} value={skill.name}>
|
||||
{skill.name}
|
||||
|
@ -20,7 +20,6 @@ import { NumberInput } from "../../ui/React/NumberInput";
|
||||
import { Hashes } from "../../ui/React/Hashes";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
import { Bladeburner } from "../../Bladeburner/Bladeburner";
|
||||
import { GangConstants } from "../../Gang/data/Constants";
|
||||
import { checkForMessagesToSend } from "../../Message/MessageHelpers";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
@ -72,7 +71,7 @@ export function General({ parentRerender }: { parentRerender: () => void }): Rea
|
||||
|
||||
// Blade functions
|
||||
const joinBladeburner = () => {
|
||||
Player.bladeburner = new Bladeburner();
|
||||
Player.startBladeburner();
|
||||
parentRerender();
|
||||
};
|
||||
const leaveBladeburner = () => {
|
||||
|
@ -3,7 +3,8 @@ import type { PlayerObject } from "../PersonObjects/Player/PlayerObject";
|
||||
import { FactionName, FactionDiscovery } from "@enums";
|
||||
import { Faction } from "./Faction";
|
||||
|
||||
import { Reviver, assertLoadingType } from "../utils/JSONReviver";
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
import { assertLoadingType } from "../utils/TypeAssertion";
|
||||
import { PartialRecord, createEnumKeyedRecord, getRecordValues } from "../Types/Record";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
|
@ -5,7 +5,7 @@ import { Truthy } from "lodash";
|
||||
import { GoColor, GoOpponent } from "@enums";
|
||||
import { Go } from "./Go";
|
||||
import { boardStateFromSimpleBoard, simpleBoardFromBoard } from "./boardAnalysis/boardAnalysis";
|
||||
import { assertLoadingType } from "../utils/JSONReviver";
|
||||
import { assertLoadingType } from "../utils/TypeAssertion";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
import { boardSizes } from "./Constants";
|
||||
import { isInteger, isNumber } from "../types";
|
||||
|
@ -32,7 +32,15 @@ import { arrayToString } from "../utils/helpers/ArrayHelpers";
|
||||
import { HacknetServer } from "../Hacknet/HacknetServer";
|
||||
import { BaseServer } from "../Server/BaseServer";
|
||||
import { RamCostConstants } from "./RamCostGenerator";
|
||||
import { isPositiveInteger, PositiveInteger, Unknownify, isPositiveNumber, PositiveNumber } from "../types";
|
||||
import {
|
||||
isPositiveInteger,
|
||||
PositiveInteger,
|
||||
Unknownify,
|
||||
isPositiveNumber,
|
||||
PositiveNumber,
|
||||
PositiveSafeInteger,
|
||||
isPositiveSafeInteger,
|
||||
} from "../types";
|
||||
import { Engine } from "../engine";
|
||||
import { resolveFilePath, FilePath } from "../Paths/FilePath";
|
||||
import { hasScriptExtension, ScriptFilePath } from "../Paths/ScriptFilePath";
|
||||
@ -45,6 +53,7 @@ export const helpers = {
|
||||
string,
|
||||
number,
|
||||
positiveInteger,
|
||||
positiveSafeInteger,
|
||||
scriptArgs,
|
||||
runOptions,
|
||||
spawnOptions,
|
||||
@ -120,6 +129,16 @@ function positiveInteger(ctx: NetscriptContext, argName: string, v: unknown): Po
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Convert provided value v for argument argName to a positive safe integer, throwing if it looks like something else. */
|
||||
function positiveSafeInteger(ctx: NetscriptContext, argName: string, v: unknown): PositiveSafeInteger {
|
||||
const n = number(ctx, argName, v);
|
||||
if (!isPositiveSafeInteger(n)) {
|
||||
throw errorMessage(ctx, `${argName} should be a positive safe integer, was ${n}`, "TYPE");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Convert provided value v for argument argName to a positive number, throwing if it looks like something else. */
|
||||
function positiveNumber(ctx: NetscriptContext, argName: string, v: unknown): PositiveNumber {
|
||||
const n = number(ctx, argName, v);
|
||||
|
@ -1,13 +1,16 @@
|
||||
import type { Bladeburner as INetscriptBladeburner } from "@nsdefs";
|
||||
import type { Action } from "../Bladeburner/Action";
|
||||
import type { Action, LevelableAction } from "../Bladeburner/Types";
|
||||
import type { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { BladeActionType, BladeContractName, BladeGeneralActionName, BladeOperationName, BladeSkillName } from "@enums";
|
||||
import { Bladeburner, BladeburnerPromise } from "../Bladeburner/Bladeburner";
|
||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||
import { BlackOperation } from "../Bladeburner/BlackOperation";
|
||||
import { helpers } from "../Netscript/NetscriptHelpers";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
import { Skills } from "../Bladeburner/data/Skills";
|
||||
import { assertString } from "../Netscript/TypeAssertion";
|
||||
import { BlackOperations, blackOpsArray } from "../Bladeburner/data/BlackOperations";
|
||||
|
||||
export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
const checkBladeburnerAccess = function (ctx: NetscriptContext): void {
|
||||
@ -25,88 +28,88 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
return bladeburner;
|
||||
};
|
||||
|
||||
const getBladeburnerActionObject = function (ctx: NetscriptContext, type: string, name: string): Action {
|
||||
function getAction(ctx: NetscriptContext, type: unknown, name: unknown): Action {
|
||||
const bladeburner = Player.bladeburner;
|
||||
assertString(ctx, "type", type);
|
||||
assertString(ctx, "name", name);
|
||||
if (bladeburner === null) throw new Error("Must have joined bladeburner");
|
||||
const actionId = bladeburner.getActionIdFromTypeAndName(type, name);
|
||||
if (!actionId) {
|
||||
throw helpers.errorMessage(ctx, `Invalid action type='${type}', name='${name}'`);
|
||||
}
|
||||
const actionObj = bladeburner.getActionObject(actionId);
|
||||
if (!actionObj) {
|
||||
throw helpers.errorMessage(ctx, `Invalid action type='${type}', name='${name}'`);
|
||||
const action = bladeburner.getActionFromTypeAndName(type, name);
|
||||
if (!action) throw helpers.errorMessage(ctx, `Invalid action type='${type}', name='${name}'`);
|
||||
return action;
|
||||
}
|
||||
|
||||
return actionObj;
|
||||
};
|
||||
function isLevelableAction(action: Action): action is LevelableAction {
|
||||
return action.type === BladeActionType.contract || action.type === BladeActionType.operation;
|
||||
}
|
||||
|
||||
function getLevelableAction(ctx: NetscriptContext, type: unknown, name: unknown): LevelableAction {
|
||||
const action = getAction(ctx, type, name);
|
||||
if (!isLevelableAction(action)) {
|
||||
throw helpers.errorMessage(
|
||||
ctx,
|
||||
`Actions of type ${action.type} are not levelable, ${ctx.functionPath} requires a levelable action`,
|
||||
);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
return {
|
||||
inBladeburner: () => () => !!Player.bladeburner,
|
||||
getContractNames: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getContractNamesNetscriptFn();
|
||||
getBladeburner(ctx);
|
||||
return Object.values(BladeContractName);
|
||||
},
|
||||
getOperationNames: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getOperationNamesNetscriptFn();
|
||||
getBladeburner(ctx);
|
||||
return Object.values(BladeOperationName);
|
||||
},
|
||||
getBlackOpNames: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getBlackOpNamesNetscriptFn();
|
||||
getBladeburner(ctx);
|
||||
// Ensures they are sent in the correct order
|
||||
return blackOpsArray.map((blackOp) => blackOp.name);
|
||||
},
|
||||
getNextBlackOp: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getNextBlackOp();
|
||||
if (bladeburner.numBlackOpsComplete >= blackOpsArray.length) return null;
|
||||
const blackOp = blackOpsArray[bladeburner.numBlackOpsComplete];
|
||||
return { name: blackOp.name, rank: blackOp.reqdRank };
|
||||
},
|
||||
getBlackOpRank: (ctx) => (_blackOpName) => {
|
||||
const blackOpName = helpers.string(ctx, "blackOpName", _blackOpName);
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, "blackops", blackOpName);
|
||||
if (!(action instanceof BlackOperation)) throw new Error("action was not a black operation");
|
||||
return action.reqdRank;
|
||||
const blackOpName = getEnumHelper("BladeBlackOpName").nsGetMember(ctx, _blackOpName);
|
||||
return BlackOperations[blackOpName].reqdRank;
|
||||
},
|
||||
getGeneralActionNames: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getGeneralActionNamesNetscriptFn();
|
||||
getBladeburner(ctx);
|
||||
return Object.values(BladeGeneralActionName);
|
||||
},
|
||||
getSkillNames: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getSkillNamesNetscriptFn();
|
||||
getBladeburner(ctx);
|
||||
return Object.values(BladeSkillName);
|
||||
},
|
||||
startAction: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
startAction: (ctx) => (type, name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
try {
|
||||
return bladeburner.startActionNetscriptFn(type, name, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
const action = getAction(ctx, type, name);
|
||||
const attempt = bladeburner.startAction(action.id);
|
||||
helpers.log(ctx, () => attempt.message);
|
||||
return !!attempt.success;
|
||||
},
|
||||
stopBladeburnerAction: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
helpers.log(ctx, () => `Stopping current Bladeburner action.`);
|
||||
return bladeburner.resetAction();
|
||||
},
|
||||
getCurrentAction: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.getTypeAndNameFromActionId(bladeburner.action);
|
||||
// Temporary bad return type to not be an API break (idle should just return null)
|
||||
if (!bladeburner.action) return { type: "Idle", name: "Idle" };
|
||||
return { ...bladeburner.action };
|
||||
},
|
||||
getActionTime: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
getActionTime: (ctx) => (type, name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
try {
|
||||
const time = bladeburner.getActionTimeNetscriptFn(Player, type, name);
|
||||
if (typeof time === "string") {
|
||||
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
|
||||
helpers.log(ctx, () => errorLogText);
|
||||
return -1;
|
||||
} else {
|
||||
return time;
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
const action = getAction(ctx, type, name);
|
||||
// return ms instead of seconds
|
||||
return action.getActionTime(bladeburner, Player) * 1000;
|
||||
},
|
||||
getActionCurrentTime: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
@ -119,92 +122,69 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
},
|
||||
getActionEstimatedSuccessChance: (ctx) => (_type, _name) => {
|
||||
getActionEstimatedSuccessChance: (ctx) => (type, name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
try {
|
||||
const chance = bladeburner.getActionEstimatedSuccessChanceNetscriptFn(Player, type, name);
|
||||
if (typeof chance === "string") {
|
||||
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
|
||||
helpers.log(ctx, () => errorLogText);
|
||||
return [-1, -1];
|
||||
} else {
|
||||
return chance;
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
const action = getAction(ctx, type, name);
|
||||
return action.getSuccessRange(bladeburner, Player);
|
||||
},
|
||||
getActionRepGain: (ctx) => (_type, _name, _level) => {
|
||||
getActionRepGain: (ctx) => (type, name, _level) => {
|
||||
checkBladeburnerAccess(ctx);
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const level = _level === undefined ? action.level : helpers.number(ctx, "level", _level);
|
||||
const rewardMultiplier = Math.pow(action.rewardFac, level - 1);
|
||||
const action = getAction(ctx, type, name);
|
||||
const level = isLevelableAction(action) ? helpers.number(ctx, "level", _level ?? action.level) : 1;
|
||||
const rewardMultiplier = isLevelableAction(action) ? Math.pow(action.rewardFac, level - 1) : 1;
|
||||
return action.rankGain * rewardMultiplier * currentNodeMults.BladeburnerRank;
|
||||
},
|
||||
getActionCountRemaining: (ctx) => (_type, _name) => {
|
||||
getActionCountRemaining: (ctx) => (type, name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
try {
|
||||
return bladeburner.getActionCountRemainingNetscriptFn(type, name, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
const action = getAction(ctx, type, name);
|
||||
switch (action.type) {
|
||||
case BladeActionType.general:
|
||||
return Infinity;
|
||||
case BladeActionType.blackOp:
|
||||
return bladeburner.numBlackOpsComplete > action.n ? 0 : 1;
|
||||
case BladeActionType.contract:
|
||||
case BladeActionType.operation:
|
||||
return action.count;
|
||||
}
|
||||
},
|
||||
getActionMaxLevel: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
getActionMaxLevel: (ctx) => (type, name) => {
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
return action.maxLevel;
|
||||
},
|
||||
getActionCurrentLevel: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
getActionCurrentLevel: (ctx) => (type, name) => {
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
return action.level;
|
||||
},
|
||||
getActionAutolevel: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
getActionAutolevel: (ctx) => (type, name) => {
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
return action.autoLevel;
|
||||
},
|
||||
getActionSuccesses: (ctx) => (_type, _name) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
getActionSuccesses: (ctx) => (type, name) => {
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
return action.successes;
|
||||
},
|
||||
setActionAutolevel:
|
||||
(ctx) =>
|
||||
(_type, _name, _autoLevel = true) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
(type, name, _autoLevel = true) => {
|
||||
const autoLevel = !!_autoLevel;
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
action.autoLevel = autoLevel;
|
||||
helpers.log(ctx, () => `Autolevel for ${action.name} has been ${autoLevel ? "enabled" : "disabled"}`);
|
||||
},
|
||||
setActionLevel:
|
||||
(ctx) =>
|
||||
(_type, _name, _level = 1) => {
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
const level = helpers.number(ctx, "level", _level);
|
||||
setActionLevel: (ctx) => (type, name, _level) => {
|
||||
const level = helpers.positiveInteger(ctx, "level", _level ?? 1);
|
||||
checkBladeburnerAccess(ctx);
|
||||
const action = getBladeburnerActionObject(ctx, type, name);
|
||||
const action = getLevelableAction(ctx, type, name);
|
||||
if (level < 1 || level > action.maxLevel) {
|
||||
throw helpers.errorMessage(ctx, `Level must be between 1 and ${action.maxLevel}, is ${level}`);
|
||||
}
|
||||
action.level = level;
|
||||
helpers.log(ctx, () => `Set level for ${action.name} to ${level}`);
|
||||
},
|
||||
getRank: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
@ -215,57 +195,57 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
return bladeburner.skillPoints;
|
||||
},
|
||||
getSkillLevel: (ctx) => (_skillName) => {
|
||||
const skillName = helpers.string(ctx, "skillName", _skillName);
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
try {
|
||||
return bladeburner.getSkillLevelNetscriptFn(skillName, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
|
||||
return bladeburner.getSkillLevel(skillName);
|
||||
},
|
||||
getSkillUpgradeCost: (ctx) => (_skillName, _count) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
|
||||
const count = helpers.positiveSafeInteger(ctx, "count", _count ?? 1);
|
||||
const currentLevel = bladeburner.getSkillLevel(skillName);
|
||||
return Skills[skillName].calculateCost(currentLevel, count);
|
||||
},
|
||||
upgradeSkill: (ctx) => (_skillName, _count) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const skillName = getEnumHelper("BladeSkillName").nsGetMember(ctx, _skillName, "skillName");
|
||||
const count = helpers.positiveSafeInteger(ctx, "count", _count ?? 1);
|
||||
const attempt = bladeburner.upgradeSkill(skillName, count);
|
||||
helpers.log(ctx, () => attempt.message);
|
||||
return !!attempt.success;
|
||||
},
|
||||
getTeamSize: (ctx) => (type, name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
if (!type && !name) return bladeburner.teamSize;
|
||||
const action = getAction(ctx, type, name);
|
||||
switch (action.type) {
|
||||
case BladeActionType.general:
|
||||
case BladeActionType.contract:
|
||||
return 0;
|
||||
case BladeActionType.blackOp:
|
||||
case BladeActionType.operation:
|
||||
return action.teamCount;
|
||||
}
|
||||
},
|
||||
getSkillUpgradeCost:
|
||||
(ctx) =>
|
||||
(_skillName, _count = 1) => {
|
||||
setTeamSize: (ctx) => (type, name, _size) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const skillName = helpers.string(ctx, "skillName", _skillName);
|
||||
const count = helpers.number(ctx, "count", _count);
|
||||
try {
|
||||
return bladeburner.getSkillUpgradeCostNetscriptFn(skillName, count, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
const action = getAction(ctx, type, name);
|
||||
const size = helpers.positiveInteger(ctx, "size", _size);
|
||||
if (size > bladeburner.teamSize) {
|
||||
helpers.log(ctx, () => `Failed to set team size due to not enough team members.`);
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
upgradeSkill:
|
||||
(ctx) =>
|
||||
(_skillName, _count = 1) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const skillName = helpers.string(ctx, "skillName", _skillName);
|
||||
const count = helpers.number(ctx, "count", _count);
|
||||
try {
|
||||
return bladeburner.upgradeSkillNetscriptFn(skillName, count, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
switch (action.type) {
|
||||
case BladeActionType.contract:
|
||||
case BladeActionType.general:
|
||||
helpers.log(ctx, () => "Only valid for Operations and Black Operations");
|
||||
return -1;
|
||||
case BladeActionType.blackOp:
|
||||
case BladeActionType.operation: {
|
||||
action.teamCount = size;
|
||||
helpers.log(ctx, () => `Set team size for ${action.name} to ${size}`);
|
||||
return size;
|
||||
}
|
||||
},
|
||||
getTeamSize: (ctx) => (_type, _name) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
try {
|
||||
return bladeburner.getTeamSizeNetscriptFn(type, name, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
},
|
||||
setTeamSize: (ctx) => (_type, _name, _size) => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
const type = helpers.string(ctx, "type", _type);
|
||||
const name = helpers.string(ctx, "name", _name);
|
||||
const size = helpers.number(ctx, "size", _size);
|
||||
try {
|
||||
return bladeburner.setTeamSizeNetscriptFn(type, name, size, ctx.workerScript);
|
||||
} catch (e: unknown) {
|
||||
throw helpers.errorMessage(ctx, String(e));
|
||||
}
|
||||
},
|
||||
getCityEstimatedPopulation: (ctx) => (_cityName) => {
|
||||
@ -299,7 +279,9 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
},
|
||||
joinBladeburnerFaction: (ctx) => () => {
|
||||
const bladeburner = getBladeburner(ctx);
|
||||
return bladeburner.joinBladeburnerFactionNetscriptFn(ctx.workerScript);
|
||||
const attempt = bladeburner.joinFaction();
|
||||
helpers.log(ctx, () => attempt.message);
|
||||
return !!attempt.success;
|
||||
},
|
||||
joinBladeburnerDivision: (ctx) => () => {
|
||||
if (Player.bitNodeN === 7 || Player.sourceFileLvl(7) > 0) {
|
||||
@ -314,7 +296,7 @@ export function NetscriptBladeburner(): InternalAPI<INetscriptBladeburner> {
|
||||
Player.skills.dexterity >= 100 &&
|
||||
Player.skills.agility >= 100
|
||||
) {
|
||||
Player.bladeburner = new Bladeburner();
|
||||
Player.startBladeburner();
|
||||
helpers.log(ctx, () => "You have been accepted into the Bladeburner division");
|
||||
|
||||
return true;
|
||||
|
@ -3,7 +3,6 @@ import type { Singularity as ISingularity, Task as ITask } from "@nsdefs";
|
||||
import { Player } from "@player";
|
||||
import {
|
||||
AugmentationName,
|
||||
BlackOperationName,
|
||||
CityName,
|
||||
FactionName,
|
||||
FactionWorkType,
|
||||
@ -57,6 +56,7 @@ import { root } from "../Paths/Directory";
|
||||
import { getRecordEntries } from "../Types/Record";
|
||||
import { JobTracks } from "../Company/data/JobTracks";
|
||||
import { ServerConstants } from "../Server/data/Constants";
|
||||
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
|
||||
|
||||
export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
const runAfterReset = function (cbScript: ScriptFilePath) {
|
||||
@ -1122,7 +1122,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
};
|
||||
const bladeburnerRequirements = () => {
|
||||
if (!Player.bladeburner) return false;
|
||||
return Player.bladeburner.blackops[BlackOperationName.OperationDaedalus];
|
||||
return Player.bladeburner.numBlackOpsComplete >= blackOpsArray.length;
|
||||
};
|
||||
|
||||
if (!hackingRequirements() && !bladeburnerRequirements()) {
|
||||
|
@ -1,17 +1,20 @@
|
||||
import type { Augmentation } from "../Augmentation/Augmentation";
|
||||
import type { Sleeve as NetscriptSleeve } from "@nsdefs";
|
||||
import type { ActionIdentifier } from "../Bladeburner/Types";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { BladeActionType } from "@enums";
|
||||
import { Augmentations } from "../Augmentation/Augmentations";
|
||||
import { findCrime } from "../Crime/CrimeHelpers";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
import { InternalAPI, NetscriptContext, setRemovedFunctions } from "../Netscript/APIWrapper";
|
||||
import { isSleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBladeburnerWork";
|
||||
import { SleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBladeburnerWork";
|
||||
import { isSleeveFactionWork } from "../PersonObjects/Sleeve/Work/SleeveFactionWork";
|
||||
import { isSleeveCompanyWork } from "../PersonObjects/Sleeve/Work/SleeveCompanyWork";
|
||||
import { helpers } from "../Netscript/NetscriptHelpers";
|
||||
import { getAugCost } from "../Augmentation/AugmentationHelpers";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
import { SleeveWorkType } from "../PersonObjects/Sleeve/Work/Work";
|
||||
|
||||
export function NetscriptSleeve(): InternalAPI<NetscriptSleeve> {
|
||||
const checkSleeveAPIAccess = function (ctx: NetscriptContext) {
|
||||
@ -233,32 +236,25 @@ export function NetscriptSleeve(): InternalAPI<NetscriptSleeve> {
|
||||
setToBladeburnerAction: (ctx) => (_sleeveNumber, _action, _contract?) => {
|
||||
const sleeveNumber = helpers.number(ctx, "sleeveNumber", _sleeveNumber);
|
||||
const action = helpers.string(ctx, "action", _action);
|
||||
let contract: string;
|
||||
if (typeof _contract === "undefined") {
|
||||
contract = "------";
|
||||
} else {
|
||||
contract = helpers.string(ctx, "contract", _contract);
|
||||
}
|
||||
checkSleeveAPIAccess(ctx);
|
||||
checkSleeveNumber(ctx, sleeveNumber);
|
||||
|
||||
// Cannot Take on Contracts if another sleeve is performing that action
|
||||
if (action === "Take on contracts") {
|
||||
const contract = getEnumHelper("BladeContractName").nsGetMember(ctx, _contract);
|
||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||
if (i === sleeveNumber) {
|
||||
continue;
|
||||
}
|
||||
const other = Player.sleeves[i];
|
||||
if (isSleeveBladeburnerWork(other.currentWork) && other.currentWork.actionName === contract) {
|
||||
if (i === sleeveNumber) continue;
|
||||
const otherWork = Player.sleeves[i].currentWork;
|
||||
if (otherWork?.type === SleeveWorkType.BLADEBURNER && otherWork.actionId.name === contract) {
|
||||
throw helpers.errorMessage(
|
||||
ctx,
|
||||
`Sleeve ${sleeveNumber} cannot take on contracts because Sleeve ${i} is already performing that action.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const actionId: ActionIdentifier = { type: BladeActionType.contract, name: contract };
|
||||
Player.sleeves[sleeveNumber].startWork(new SleeveBladeburnerWork({ actionId }));
|
||||
}
|
||||
|
||||
return Player.sleeves[sleeveNumber].bladeburner(action, contract);
|
||||
return Player.sleeves[sleeveNumber].bladeburner(action);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -49,7 +49,6 @@ export abstract class Person implements IPerson {
|
||||
gainIntelligenceExp = personMethods.gainIntelligenceExp;
|
||||
gainStats = personMethods.gainStats;
|
||||
regenerateHp = personMethods.regenerateHp;
|
||||
queryStatFromString = personMethods.queryStatFromString;
|
||||
updateSkillLevels = personMethods.updateSkillLevels;
|
||||
hasAugmentation = personMethods.hasAugmentation;
|
||||
calculateSkill = calculateSkill; //Class version is equal to imported version
|
||||
|
@ -114,33 +114,6 @@ export function gainStats(this: Person, retValue: WorkStats): void {
|
||||
this.gainIntelligenceExp(retValue.intExp);
|
||||
}
|
||||
|
||||
//Given a string expression like "str" or "strength", returns the given stat
|
||||
export function queryStatFromString(this: Person, str: string): number {
|
||||
const tempStr = str.toLowerCase();
|
||||
if (tempStr.includes("hack")) {
|
||||
return this.skills.hacking;
|
||||
}
|
||||
if (tempStr.includes("str")) {
|
||||
return this.skills.strength;
|
||||
}
|
||||
if (tempStr.includes("def")) {
|
||||
return this.skills.defense;
|
||||
}
|
||||
if (tempStr.includes("dex")) {
|
||||
return this.skills.dexterity;
|
||||
}
|
||||
if (tempStr.includes("agi")) {
|
||||
return this.skills.agility;
|
||||
}
|
||||
if (tempStr.includes("cha")) {
|
||||
return this.skills.charisma;
|
||||
}
|
||||
if (tempStr.includes("int")) {
|
||||
return this.skills.intelligence;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function regenerateHp(this: Person, amt: number): void {
|
||||
if (typeof amt !== "number") {
|
||||
console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
|
||||
|
@ -8,4 +8,5 @@ export function canAccessBladeburner(this: PlayerObject): boolean {
|
||||
|
||||
export function startBladeburner(this: PlayerObject): void {
|
||||
this.bladeburner = new Bladeburner();
|
||||
this.bladeburner.init();
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import type { SleeveWork } from "./Work/Work";
|
||||
import { Player } from "@player";
|
||||
import { Person } from "../Person";
|
||||
|
||||
import { Contracts } from "../../Bladeburner/data/Contracts";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import {
|
||||
ClassType,
|
||||
@ -26,12 +25,13 @@ import {
|
||||
UniversityClassType,
|
||||
CompanyName,
|
||||
FactionName,
|
||||
BladeActionType,
|
||||
BladeGeneralActionName,
|
||||
} from "@enums";
|
||||
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
|
||||
import { formatPercent } from "../../ui/formatNumber";
|
||||
import { SleeveClassWork } from "./Work/SleeveClassWork";
|
||||
import { SleeveSynchroWork } from "./Work/SleeveSynchroWork";
|
||||
import { SleeveRecoveryWork } from "./Work/SleeveRecoveryWork";
|
||||
@ -391,24 +391,44 @@ export class Sleeve extends Person implements SleevePerson {
|
||||
}
|
||||
|
||||
/** Begin a bladeburner task */
|
||||
bladeburner(action: string, contract: string): boolean {
|
||||
bladeburner(action: string, contract?: string): boolean {
|
||||
if (!Player.bladeburner) return false;
|
||||
switch (action) {
|
||||
case "Training":
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "General", name: "Training" }));
|
||||
this.startWork(
|
||||
new SleeveBladeburnerWork({
|
||||
actionId: { type: BladeActionType.general, name: BladeGeneralActionName.training },
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
case "Field analysis":
|
||||
case "Field Analysis":
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "General", name: "Field Analysis" }));
|
||||
this.startWork(
|
||||
new SleeveBladeburnerWork({
|
||||
actionId: { type: BladeActionType.general, name: BladeGeneralActionName.fieldAnalysis },
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
case "Recruitment":
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "General", name: "Recruitment" }));
|
||||
this.startWork(
|
||||
new SleeveBladeburnerWork({
|
||||
actionId: { type: BladeActionType.general, name: BladeGeneralActionName.recruitment },
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
case "Diplomacy":
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "General", name: "Diplomacy" }));
|
||||
this.startWork(
|
||||
new SleeveBladeburnerWork({
|
||||
actionId: { type: BladeActionType.general, name: BladeGeneralActionName.diplomacy },
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
case "Hyperbolic Regeneration Chamber":
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "General", name: "Hyperbolic Regeneration Chamber" }));
|
||||
this.startWork(
|
||||
new SleeveBladeburnerWork({
|
||||
actionId: { type: BladeActionType.general, name: BladeGeneralActionName.hyperbolicRegen },
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
case "Infiltrate synthoids":
|
||||
case "Infiltrate Synthoids":
|
||||
@ -418,36 +438,13 @@ export class Sleeve extends Person implements SleevePerson {
|
||||
this.startWork(new SleeveSupportWork());
|
||||
return true;
|
||||
case "Take on contracts":
|
||||
if (!Contracts[contract]) return false;
|
||||
this.startWork(new SleeveBladeburnerWork({ type: "Contracts", name: contract }));
|
||||
if (!getEnumHelper("BladeContractName").isMember(contract)) return false;
|
||||
this.startWork(new SleeveBladeburnerWork({ actionId: { type: BladeActionType.contract, name: contract } }));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
recruitmentSuccessChance(): number {
|
||||
return Math.max(0, Math.min(1, Player.bladeburner?.getRecruitmentSuccessChance(this) ?? 0));
|
||||
}
|
||||
|
||||
contractSuccessChance(type: string, name: string): string {
|
||||
const bb = Player.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 `${formatPercent(chances[0])} - ${formatPercent(chances[1])}`;
|
||||
}
|
||||
}
|
||||
|
||||
takeDamage(amt: number): boolean {
|
||||
if (typeof amt !== "number") {
|
||||
console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
|
||||
|
@ -1,39 +1,40 @@
|
||||
import type { Sleeve } from "../Sleeve";
|
||||
import type { ActionIdentifier } from "../../../Bladeburner/Types";
|
||||
import type { PromisePair } from "../../../Types/Promises";
|
||||
import { Player } from "@player";
|
||||
import { BladeActionType, BladeGeneralActionName } from "@enums";
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
|
||||
import { Sleeve } from "../Sleeve";
|
||||
import { applySleeveGains, SleeveWorkClass, SleeveWorkType } from "./Work";
|
||||
import { CONSTANTS } from "../../../Constants";
|
||||
import { GeneralActions } from "../../../Bladeburner/data/GeneralActions";
|
||||
import { scaleWorkStats } from "../../../Work/WorkStats";
|
||||
import { getKeyList } from "../../../utils/helpers/getKeyList";
|
||||
import { loadActionIdentifier } from "../../../Bladeburner/utils/loadActionIdentifier";
|
||||
import { invalidWork } from "../../../Work/InvalidWork";
|
||||
|
||||
interface SleeveBladeburnerWorkParams {
|
||||
type: "General" | "Contracts";
|
||||
name: string;
|
||||
actionId: ActionIdentifier & { type: BladeActionType.general | BladeActionType.contract };
|
||||
}
|
||||
|
||||
export const isSleeveBladeburnerWork = (w: SleeveWorkClass | null): w is SleeveBladeburnerWork =>
|
||||
w !== null && w.type === SleeveWorkType.BLADEBURNER;
|
||||
w?.type === SleeveWorkType.BLADEBURNER;
|
||||
|
||||
export class SleeveBladeburnerWork extends SleeveWorkClass {
|
||||
type: SleeveWorkType.BLADEBURNER = SleeveWorkType.BLADEBURNER;
|
||||
tasksCompleted = 0;
|
||||
cyclesWorked = 0;
|
||||
actionType: "General" | "Contracts";
|
||||
actionName: string;
|
||||
actionId: ActionIdentifier & { type: BladeActionType.general | BladeActionType.contract };
|
||||
nextCompletionPair: PromisePair<void> = { promise: null, resolve: null };
|
||||
|
||||
constructor(params?: SleeveBladeburnerWorkParams) {
|
||||
super();
|
||||
this.actionType = params?.type ?? "General";
|
||||
this.actionName = params?.name ?? "Field Analysis";
|
||||
this.actionId = params?.actionId ?? { type: BladeActionType.general, name: BladeGeneralActionName.fieldAnalysis };
|
||||
}
|
||||
|
||||
cyclesNeeded(sleeve: Sleeve): number {
|
||||
const ret = Player.bladeburner?.getActionTimeNetscriptFn(sleeve, this.actionType, this.actionName);
|
||||
if (!ret || typeof ret === "string") throw new Error(`Error querying ${this.actionName} time`);
|
||||
return ret / CONSTANTS.MilliPerCycle;
|
||||
if (!Player.bladeburner) return Infinity;
|
||||
const action = Player.bladeburner.getActionObject(this.actionId);
|
||||
const timeInMs = action.getActionTime(Player.bladeburner, sleeve) * 1000;
|
||||
return timeInMs / CONSTANTS.MilliPerCycle;
|
||||
}
|
||||
|
||||
finish() {
|
||||
@ -47,30 +48,19 @@ export class SleeveBladeburnerWork extends SleeveWorkClass {
|
||||
process(sleeve: Sleeve, cycles: number) {
|
||||
if (!Player.bladeburner) return sleeve.stopWork();
|
||||
this.cyclesWorked += cycles;
|
||||
const actionIdent = Player.bladeburner.getActionIdFromTypeAndName(this.actionType, this.actionName);
|
||||
if (!actionIdent) throw new Error(`Error getting ${this.actionName} action`);
|
||||
if (this.actionType === "Contracts") {
|
||||
const action = Player.bladeburner.getActionObject(actionIdent);
|
||||
if (!action) throw new Error(`Error getting ${this.actionName} action object`);
|
||||
if (this.actionId.type === BladeActionType.contract) {
|
||||
const action = Player.bladeburner.getActionObject(this.actionId);
|
||||
if (action.count < 1) return sleeve.stopWork();
|
||||
}
|
||||
|
||||
while (this.cyclesWorked >= this.cyclesNeeded(sleeve)) {
|
||||
if (this.actionType === "Contracts") {
|
||||
const action = Player.bladeburner.getActionObject(actionIdent);
|
||||
if (!action) throw new Error(`Error getting ${this.actionName} action object`);
|
||||
if (this.actionId.type === BladeActionType.contract) {
|
||||
const action = Player.bladeburner.getActionObject(this.actionId);
|
||||
if (action.count < 1) return sleeve.stopWork();
|
||||
}
|
||||
const retValue = Player.bladeburner.completeAction(sleeve, actionIdent, false);
|
||||
if (this.actionType === "General") {
|
||||
const exp = GeneralActions[this.actionName]?.exp;
|
||||
if (!exp) throw new Error(`Somehow there was no exp for action ${this.actionType} ${this.actionName}`);
|
||||
applySleeveGains(sleeve, scaleWorkStats(exp, sleeve.shockBonus(), false));
|
||||
}
|
||||
|
||||
if (this.actionType === "Contracts") {
|
||||
const retValue = Player.bladeburner.completeAction(sleeve, this.actionId, false);
|
||||
applySleeveGains(sleeve, scaleWorkStats(retValue, sleeve.shockBonus(), false));
|
||||
}
|
||||
|
||||
this.tasksCompleted++;
|
||||
this.cyclesWorked -= this.cyclesNeeded(sleeve);
|
||||
// Resolve and reset nextCompletion promise
|
||||
@ -86,8 +76,8 @@ export class SleeveBladeburnerWork extends SleeveWorkClass {
|
||||
APICopy(sleeve: Sleeve) {
|
||||
return {
|
||||
type: SleeveWorkType.BLADEBURNER as const,
|
||||
actionType: this.actionType,
|
||||
actionName: this.actionName,
|
||||
actionType: this.actionId.type,
|
||||
actionName: this.actionId.name,
|
||||
tasksCompleted: this.tasksCompleted,
|
||||
cyclesWorked: this.cyclesWorked,
|
||||
cyclesNeeded: this.cyclesNeeded(sleeve),
|
||||
@ -104,6 +94,9 @@ export class SleeveBladeburnerWork extends SleeveWorkClass {
|
||||
|
||||
/** Initializes a BladeburnerWork object from a JSON save state. */
|
||||
static fromJSON(value: IReviverValue): SleeveBladeburnerWork {
|
||||
const actionId = loadActionIdentifier(value.data?.actionId);
|
||||
if (!actionId) return invalidWork();
|
||||
value.data.actionId = actionId;
|
||||
return Generic_fromJSON(SleeveBladeburnerWork, value.data, SleeveBladeburnerWork.savedKeys);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ function getWorkDescription(sleeve: Sleeve, progress: number): string {
|
||||
return "This sleeve is currently set to synchronize with the original consciousness. This causes the Sleeve's synchronization to increase.";
|
||||
case SleeveWorkType.BLADEBURNER:
|
||||
return (
|
||||
`This sleeve is currently attempting to perform ${work.actionName}.\n\nTasks Completed: ${formatInt(
|
||||
`This sleeve is currently attempting to perform ${work.actionId.name}.\n\nTasks Completed: ${formatInt(
|
||||
work.tasksCompleted,
|
||||
)}\n \n` + `Progress: ${formatPercent(progress)}`
|
||||
);
|
||||
|
@ -1,14 +1,20 @@
|
||||
import type { Sleeve } from "../Sleeve";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Sleeve } from "../Sleeve";
|
||||
import { MenuItem, Select, SelectChangeEvent } from "@mui/material";
|
||||
|
||||
import { Player } from "@player";
|
||||
import {
|
||||
BladeActionType,
|
||||
BladeContractName,
|
||||
CityName,
|
||||
FactionName,
|
||||
FactionWorkType,
|
||||
GymType,
|
||||
LocationName,
|
||||
} from "@enums";
|
||||
import { Crimes } from "../../../Crime/Crimes";
|
||||
import { CityName, FactionName, FactionWorkType, GymType, LocationName } from "@enums";
|
||||
import { Factions } from "../../../Faction/Factions";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
||||
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
||||
import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
|
||||
import { getEnumHelper } from "../../../utils/EnumHelper";
|
||||
import { SleeveWorkType } from "../Work/Work";
|
||||
|
||||
@ -51,7 +57,7 @@ function possibleJobs(sleeve: Sleeve): string[] {
|
||||
if (sleeve === otherSleeve) {
|
||||
continue;
|
||||
}
|
||||
if (isSleeveCompanyWork(otherSleeve.currentWork)) {
|
||||
if (otherSleeve.currentWork?.type === SleeveWorkType.COMPANY) {
|
||||
forbiddenCompanies.push(otherSleeve.currentWork.companyName);
|
||||
}
|
||||
}
|
||||
@ -70,7 +76,7 @@ function possibleFactions(sleeve: Sleeve): string[] {
|
||||
if (sleeve === otherSleeve) {
|
||||
continue;
|
||||
}
|
||||
if (isSleeveFactionWork(otherSleeve.currentWork)) {
|
||||
if (otherSleeve.currentWork?.type === SleeveWorkType.FACTION) {
|
||||
forbiddenFactions.push(otherSleeve.currentWork.factionName);
|
||||
}
|
||||
}
|
||||
@ -90,24 +96,24 @@ function possibleFactions(sleeve: Sleeve): string[] {
|
||||
});
|
||||
}
|
||||
|
||||
function possibleContracts(sleeve: Sleeve): string[] {
|
||||
function possibleContracts(sleeve: Sleeve): BladeContractName[] | ["------"] {
|
||||
const bb = Player.bladeburner;
|
||||
if (bb === null) {
|
||||
return ["------"];
|
||||
}
|
||||
let contracts = bb.getContractNamesNetscriptFn();
|
||||
let contracts = Object.values(BladeContractName);
|
||||
for (const otherSleeve of Player.sleeves) {
|
||||
if (sleeve === otherSleeve) {
|
||||
continue;
|
||||
}
|
||||
if (isSleeveBladeburnerWork(otherSleeve.currentWork) && otherSleeve.currentWork.actionType === "Contracts") {
|
||||
if (
|
||||
otherSleeve.currentWork?.type === SleeveWorkType.BLADEBURNER &&
|
||||
otherSleeve.currentWork.actionId.type === BladeActionType.contract
|
||||
) {
|
||||
const w = otherSleeve.currentWork;
|
||||
contracts = contracts.filter((x) => x != w.actionName);
|
||||
contracts = contracts.filter((x) => x != w.actionId.name);
|
||||
}
|
||||
}
|
||||
if (contracts.length === 0) {
|
||||
return ["------"];
|
||||
}
|
||||
return contracts;
|
||||
}
|
||||
|
||||
@ -256,10 +262,10 @@ function getABC(sleeve: Sleeve): [string, string, string] {
|
||||
return ["Work for Faction", work.factionName, workNames[work.factionWorkType] ?? ""];
|
||||
}
|
||||
case SleeveWorkType.BLADEBURNER:
|
||||
if (work.actionType === "Contracts") {
|
||||
return ["Perform Bladeburner Actions", "Take on contracts", work.actionName];
|
||||
if (work.actionId.type === BladeActionType.contract) {
|
||||
return ["Perform Bladeburner Actions", "Take on contracts", work.actionId.name];
|
||||
}
|
||||
return ["Perform Bladeburner Actions", work.actionName, "------"];
|
||||
return ["Perform Bladeburner Actions", work.actionId.name, "------"];
|
||||
case SleeveWorkType.CLASS: {
|
||||
if (!work.isGym()) return ["Take University Course", work.classType, work.location];
|
||||
const gymNames: Record<GymType, string> = {
|
||||
|
@ -131,7 +131,7 @@ export function prestigeAugmentation(): void {
|
||||
|
||||
// Cancel Bladeburner action
|
||||
if (Player.bladeburner) {
|
||||
Player.bladeburner.prestige();
|
||||
Player.bladeburner.prestigeAugmentation();
|
||||
}
|
||||
|
||||
// BitNode 8: Ghost of Wall Street
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { BaseServer } from "src/Server/BaseServer";
|
||||
import type { BaseServer } from "../Server/BaseServer";
|
||||
|
||||
export class RFAMessage {
|
||||
jsonrpc = "2.0"; // Transmits version of JSON-RPC. Compliance maybe allows some funky interaction with external tools?
|
||||
|
@ -1,33 +1,26 @@
|
||||
import type { ActionIdentifier } from "../../Bladeburner/Types";
|
||||
|
||||
// Root React Component for the Corporation UI
|
||||
import React, { useMemo, useState, useEffect, ReactNode } from "react";
|
||||
|
||||
import { Box, Button, IconButton, Table, TableBody, TableCell, TableRow, Tooltip, Typography } from "@mui/material";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||
import { Theme, useTheme } from "@mui/material/styles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { formatHp, formatMoney, formatSkill } from "../formatNumber";
|
||||
import { Reputation } from "./Reputation";
|
||||
import { KillScriptsModal } from "./KillScriptsModal";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import ClearAllIcon from "@mui/icons-material/ClearAll";
|
||||
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { Router } from "../GameRoot";
|
||||
import { Page } from "../Router";
|
||||
import { Player } from "@player";
|
||||
import { StatsProgressOverviewCell } from "./StatsProgressBar";
|
||||
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
import { Box, Tooltip } from "@mui/material";
|
||||
|
||||
import { isClassWork } from "../../Work/ClassWork";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { isCreateProgramWork } from "../../Work/CreateProgramWork";
|
||||
@ -36,7 +29,6 @@ import { isFactionWork } from "../../Work/FactionWork";
|
||||
import { ReputationRate } from "./ReputationRate";
|
||||
import { isCompanyWork } from "../../Work/CompanyWork";
|
||||
import { isCrimeWork } from "../../Work/CrimeWork";
|
||||
import { ActionIdentifier } from "../../Bladeburner/ActionIdentifier";
|
||||
import { Skills } from "../../PersonObjects/Skills";
|
||||
import { calculateSkillProgress } from "../../PersonObjects/formulas/skill";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
@ -240,10 +232,9 @@ export function CharacterOverview({ parentOpen, save, killScripts }: OverviewPro
|
||||
);
|
||||
}
|
||||
|
||||
function ActionText(props: { action: ActionIdentifier }): React.ReactElement {
|
||||
function ActionText({ action }: { action: ActionIdentifier }): React.ReactElement {
|
||||
const bladeburner = Player.bladeburner;
|
||||
if (!bladeburner) return <></>;
|
||||
const action = bladeburner.getTypeAndNameFromActionId(props.action);
|
||||
return (
|
||||
<Typography>
|
||||
{action.type}: {action.name}
|
||||
@ -262,9 +253,7 @@ function BladeburnerText(): React.ReactElement {
|
||||
const action = Player.bladeburner?.action;
|
||||
return useMemo(
|
||||
() =>
|
||||
//Action type 1 is Idle, see ActionTypes.ts
|
||||
//TODO 2.3: Revamp typing in bladeburner
|
||||
!action || action.type === 1 ? (
|
||||
!action ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
|
@ -1,17 +1,16 @@
|
||||
/* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */
|
||||
import type { Unknownify } from "../types";
|
||||
|
||||
import { ObjectValidator, validateObject } from "./Validator";
|
||||
import { JSONMap, JSONSet } from "../Types/Jsonable";
|
||||
import { loadActionIdentifier } from "../Bladeburner/utils/loadActionIdentifier";
|
||||
|
||||
type JsonableClass = (new () => { toJSON: () => IReviverValue }) & {
|
||||
fromJSON: (value: IReviverValue) => any;
|
||||
validationData?: ObjectValidator<any>;
|
||||
};
|
||||
|
||||
export interface IReviverValue {
|
||||
export interface IReviverValue<T = any> {
|
||||
ctor: string;
|
||||
data: any;
|
||||
data: T;
|
||||
}
|
||||
function isReviverValue(value: unknown): value is IReviverValue {
|
||||
return (
|
||||
@ -37,6 +36,8 @@ export function Reviver(_key: string, value: unknown): any {
|
||||
case "Faction": // Reviver removed in v2.6.1
|
||||
console.warn(`Legacy load type ${value.ctor} converted to expected format while loading.`);
|
||||
return value.data;
|
||||
case "ActionIdentifier": // No longer a class as of v2.6.1
|
||||
return loadActionIdentifier(value.data);
|
||||
}
|
||||
// Missing constructor with no special handling. Throw error.
|
||||
throw new Error(`Could not locate constructor named ${value.ctor}. If the save data is valid, this is a bug.`);
|
||||
@ -102,7 +103,3 @@ export function Generic_fromJSON<T extends Record<string, any>>(
|
||||
for (const [key, val] of Object.entries(data) as [keyof T, T[keyof T]][]) obj[key] = val;
|
||||
return obj;
|
||||
}
|
||||
|
||||
// This function is empty because Unknownify<T> is a typesafe assertion on any object with no runtime checks needed.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
export function assertLoadingType<T extends object>(val: object): asserts val is Unknownify<T> {}
|
||||
|
4
src/utils/TypeAssertion.ts
Normal file
4
src/utils/TypeAssertion.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import type { Unknownify } from "../types";
|
||||
// This function is empty because Unknownify<T> is a typesafe assertion on any object with no runtime checks needed.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
export function assertLoadingType<T extends object>(val: object): asserts val is Unknownify<T> {}
|
@ -2,14 +2,22 @@ import { CONSTANTS } from "../../Constants";
|
||||
/**
|
||||
* Clamps the value on a lower and an upper bound
|
||||
* @param {number} value Value to clamp
|
||||
* @param {number} min Lower bound, defaults to Number.MIN_VALUE
|
||||
* @param {number} min Lower bound, defaults to negative Number.MAX_VALUE
|
||||
* @param {number} max Upper bound, defaults to Number.MAX_VALUE
|
||||
* @returns {number} Clamped value
|
||||
*/
|
||||
export function clampNumber(value: number, min = Number.MIN_VALUE, max = Number.MAX_VALUE) {
|
||||
export function clampNumber(value: number, min = -Number.MAX_VALUE, max = Number.MAX_VALUE) {
|
||||
if (isNaN(value)) {
|
||||
if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampNumber()");
|
||||
return min;
|
||||
}
|
||||
return Math.max(Math.min(value, max), min);
|
||||
}
|
||||
|
||||
export function clampInteger(value: number, min = -Number.MAX_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
|
||||
if (isNaN(value)) {
|
||||
if (CONSTANTS.isDevBranch) throw new Error("NaN passed into clampInteger()");
|
||||
return min;
|
||||
}
|
||||
return Math.round(Math.max(Math.min(value, max), min));
|
||||
}
|
||||
|
@ -58,34 +58,15 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"bladeburner": {
|
||||
"ctor": "Bladeburner",
|
||||
"data": {
|
||||
"action": {
|
||||
"ctor": "ActionIdentifier",
|
||||
"data": {
|
||||
"name": "",
|
||||
"type": 1,
|
||||
},
|
||||
},
|
||||
"action": null,
|
||||
"actionTimeCurrent": 0,
|
||||
"actionTimeOverflow": 0,
|
||||
"actionTimeToComplete": 0,
|
||||
"automateActionHigh": {
|
||||
"ctor": "ActionIdentifier",
|
||||
"data": {
|
||||
"name": "",
|
||||
"type": 1,
|
||||
},
|
||||
},
|
||||
"automateActionLow": {
|
||||
"ctor": "ActionIdentifier",
|
||||
"data": {
|
||||
"name": "",
|
||||
"type": 1,
|
||||
},
|
||||
},
|
||||
"automateActionHigh": null,
|
||||
"automateActionLow": null,
|
||||
"automateEnabled": false,
|
||||
"automateThreshHigh": 0,
|
||||
"automateThreshLow": 0,
|
||||
"blackops": {},
|
||||
"cities": {
|
||||
"Aevum": {
|
||||
"ctor": "City",
|
||||
@ -159,124 +140,36 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"ctor": "Contract",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 266.2162162162162,
|
||||
"count": 117,
|
||||
"decays": {
|
||||
"agi": 0.91,
|
||||
"cha": 0.8,
|
||||
"def": 0.91,
|
||||
"dex": 0.91,
|
||||
"hack": 0,
|
||||
"int": 0.9,
|
||||
"str": 0.91,
|
||||
},
|
||||
"difficultyFac": 1.04,
|
||||
"failures": 0,
|
||||
"hpLoss": 1,
|
||||
"hpLost": 0,
|
||||
"isKill": true,
|
||||
"isStealth": false,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Bounty Hunter",
|
||||
"rankGain": 0.9,
|
||||
"rankLoss": 0,
|
||||
"rewardFac": 1.085,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.25,
|
||||
"cha": 0.1,
|
||||
"def": 0.15,
|
||||
"dex": 0.25,
|
||||
"hack": 0,
|
||||
"int": 0.1,
|
||||
"str": 0.15,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Retirement": {
|
||||
"ctor": "Contract",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 216.21621621621622,
|
||||
"count": 129,
|
||||
"decays": {
|
||||
"agi": 0.91,
|
||||
"cha": 0.8,
|
||||
"def": 0.91,
|
||||
"dex": 0.91,
|
||||
"hack": 0,
|
||||
"int": 0.9,
|
||||
"str": 0.91,
|
||||
},
|
||||
"difficultyFac": 1.03,
|
||||
"count": 125,
|
||||
"failures": 0,
|
||||
"hpLoss": 1,
|
||||
"hpLost": 0,
|
||||
"isKill": true,
|
||||
"isStealth": false,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Retirement",
|
||||
"rankGain": 0.6,
|
||||
"rankLoss": 0,
|
||||
"rewardFac": 1.065,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.2,
|
||||
"cha": 0.1,
|
||||
"def": 0.2,
|
||||
"dex": 0.2,
|
||||
"hack": 0,
|
||||
"int": 0.1,
|
||||
"str": 0.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Tracking": {
|
||||
"ctor": "Contract",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 131.0810810810811,
|
||||
"count": 111,
|
||||
"decays": {
|
||||
"agi": 0.91,
|
||||
"cha": 0.9,
|
||||
"def": 0.91,
|
||||
"dex": 0.91,
|
||||
"hack": 0,
|
||||
"int": 1,
|
||||
"str": 0.91,
|
||||
},
|
||||
"difficultyFac": 1.02,
|
||||
"count": 115,
|
||||
"failures": 0,
|
||||
"hpLoss": 0.5,
|
||||
"hpLost": 0,
|
||||
"isKill": false,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Tracking",
|
||||
"rankGain": 0.3,
|
||||
"rankLoss": 0,
|
||||
"rewardFac": 1.041,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.35,
|
||||
"cha": 0.1,
|
||||
"def": 0.05,
|
||||
"dex": 0.35,
|
||||
"hack": 0,
|
||||
"int": 0.05,
|
||||
"str": 0.05,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"hpLost": 0,
|
||||
"logging": {
|
||||
"blackops": true,
|
||||
"contracts": true,
|
||||
@ -287,276 +180,84 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"maxRank": 2000,
|
||||
"maxStamina": 1,
|
||||
"moneyLost": 0,
|
||||
"numBlackOpsComplete": 0,
|
||||
"numHosp": 0,
|
||||
"operations": {
|
||||
"Assassination": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 1467.5675675675675,
|
||||
"count": 51,
|
||||
"decays": {
|
||||
"agi": 0.8,
|
||||
"cha": 0,
|
||||
"def": 0.8,
|
||||
"dex": 0.8,
|
||||
"hack": 0.6,
|
||||
"int": 0.8,
|
||||
"str": 0.8,
|
||||
},
|
||||
"difficultyFac": 1.06,
|
||||
"count": 23,
|
||||
"failures": 0,
|
||||
"hpLoss": 5,
|
||||
"hpLost": 0,
|
||||
"isKill": true,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Assassination",
|
||||
"rankGain": 44,
|
||||
"rankLoss": 4,
|
||||
"reqdRank": 50000,
|
||||
"rewardFac": 1.14,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.3,
|
||||
"cha": 0,
|
||||
"def": 0.1,
|
||||
"dex": 0.3,
|
||||
"hack": 0.1,
|
||||
"int": 0.1,
|
||||
"str": 0.1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Investigation": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 438.9189189189189,
|
||||
"count": 94,
|
||||
"decays": {
|
||||
"agi": 0.9,
|
||||
"cha": 0.7,
|
||||
"def": 0.9,
|
||||
"dex": 0.9,
|
||||
"hack": 0.85,
|
||||
"int": 0.9,
|
||||
"str": 0.9,
|
||||
},
|
||||
"difficultyFac": 1.03,
|
||||
"count": 88,
|
||||
"failures": 0,
|
||||
"hpLoss": 0,
|
||||
"hpLost": 0,
|
||||
"isKill": false,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Investigation",
|
||||
"rankGain": 2.2,
|
||||
"rankLoss": 0.2,
|
||||
"reqdRank": 25,
|
||||
"rewardFac": 1.07,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.1,
|
||||
"cha": 0.25,
|
||||
"def": 0.05,
|
||||
"dex": 0.2,
|
||||
"hack": 0.25,
|
||||
"int": 0.1,
|
||||
"str": 0.05,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Raid": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 756.7567567567568,
|
||||
"count": 27,
|
||||
"decays": {
|
||||
"agi": 0.8,
|
||||
"cha": 0,
|
||||
"def": 0.8,
|
||||
"dex": 0.8,
|
||||
"hack": 0.7,
|
||||
"int": 0.9,
|
||||
"str": 0.8,
|
||||
},
|
||||
"difficultyFac": 1.045,
|
||||
"count": 7,
|
||||
"failures": 0,
|
||||
"hpLoss": 50,
|
||||
"hpLost": 0,
|
||||
"isKill": true,
|
||||
"isStealth": false,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Raid",
|
||||
"rankGain": 55,
|
||||
"rankLoss": 2.5,
|
||||
"reqdRank": 3000,
|
||||
"rewardFac": 1.1,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.2,
|
||||
"cha": 0,
|
||||
"def": 0.2,
|
||||
"dex": 0.2,
|
||||
"hack": 0.1,
|
||||
"int": 0.1,
|
||||
"str": 0.2,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Stealth Retirement Operation": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 962.1621621621622,
|
||||
"count": 39,
|
||||
"decays": {
|
||||
"agi": 0.8,
|
||||
"cha": 0,
|
||||
"def": 0.8,
|
||||
"dex": 0.8,
|
||||
"hack": 0.7,
|
||||
"int": 0.9,
|
||||
"str": 0.8,
|
||||
},
|
||||
"difficultyFac": 1.05,
|
||||
"count": 15,
|
||||
"failures": 0,
|
||||
"hpLoss": 10,
|
||||
"hpLost": 0,
|
||||
"isKill": true,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Stealth Retirement Operation",
|
||||
"rankGain": 22,
|
||||
"rankLoss": 2,
|
||||
"reqdRank": 20000,
|
||||
"rewardFac": 1.11,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.3,
|
||||
"cha": 0,
|
||||
"def": 0.1,
|
||||
"dex": 0.3,
|
||||
"hack": 0.1,
|
||||
"int": 0.1,
|
||||
"str": 0.1,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Sting Operation": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 604.3243243243244,
|
||||
"count": 15,
|
||||
"decays": {
|
||||
"agi": 0.85,
|
||||
"cha": 0.7,
|
||||
"def": 0.85,
|
||||
"dex": 0.85,
|
||||
"hack": 0.8,
|
||||
"int": 0.9,
|
||||
"str": 0.85,
|
||||
},
|
||||
"difficultyFac": 1.04,
|
||||
"count": 148,
|
||||
"failures": 0,
|
||||
"hpLoss": 2.5,
|
||||
"hpLost": 0,
|
||||
"isKill": false,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Sting Operation",
|
||||
"rankGain": 5.5,
|
||||
"rankLoss": 0.5,
|
||||
"reqdRank": 500,
|
||||
"rewardFac": 1.095,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.1,
|
||||
"cha": 0.2,
|
||||
"def": 0.05,
|
||||
"dex": 0.25,
|
||||
"hack": 0.25,
|
||||
"int": 0.1,
|
||||
"str": 0.05,
|
||||
},
|
||||
},
|
||||
},
|
||||
"Undercover Operation": {
|
||||
"ctor": "Operation",
|
||||
"data": {
|
||||
"autoLevel": true,
|
||||
"baseDifficulty": 456.7567567567568,
|
||||
"count": 2,
|
||||
"decays": {
|
||||
"agi": 0.9,
|
||||
"cha": 0.7,
|
||||
"def": 0.9,
|
||||
"dex": 0.9,
|
||||
"hack": 0.8,
|
||||
"int": 0.9,
|
||||
"str": 0.9,
|
||||
},
|
||||
"difficultyFac": 1.04,
|
||||
"count": 94,
|
||||
"failures": 0,
|
||||
"hpLoss": 2,
|
||||
"hpLost": 0,
|
||||
"isKill": false,
|
||||
"isStealth": true,
|
||||
"level": 1,
|
||||
"maxLevel": 1,
|
||||
"name": "Undercover Operation",
|
||||
"rankGain": 4.4,
|
||||
"rankLoss": 0.4,
|
||||
"reqdRank": 100,
|
||||
"rewardFac": 1.09,
|
||||
"successes": 0,
|
||||
"teamCount": 0,
|
||||
"weights": {
|
||||
"agi": 0.2,
|
||||
"cha": 0.2,
|
||||
"def": 0.05,
|
||||
"dex": 0.2,
|
||||
"hack": 0.2,
|
||||
"int": 0.1,
|
||||
"str": 0.05,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"randomEventCounter": 303,
|
||||
"rank": 2000,
|
||||
"skillMultipliers": {
|
||||
"actionTime": 1,
|
||||
"effAgi": 1,
|
||||
"effCha": 1,
|
||||
"effDef": 1,
|
||||
"effDex": 1,
|
||||
"effHack": 1,
|
||||
"effInt": 1,
|
||||
"effStr": 1,
|
||||
"expGain": 1,
|
||||
"money": 1,
|
||||
"stamina": 1,
|
||||
"successChanceAll": 1,
|
||||
"successChanceContract": 1,
|
||||
"successChanceEstimate": 1,
|
||||
"successChanceKill": 1,
|
||||
"successChanceOperation": 1,
|
||||
"successChanceStealth": 1,
|
||||
},
|
||||
"skillPoints": 666,
|
||||
"skills": {},
|
||||
"sleeveSize": 0,
|
||||
|
Loading…
Reference in New Issue
Block a user