finish sleeve rework

This commit is contained in:
Olivier Gagnon 2022-07-28 02:46:34 -04:00
parent ebe953b498
commit 4549b0d467
22 changed files with 376 additions and 365 deletions

@ -95,10 +95,10 @@ Singularity
This means calls like 'ns.connect' need to be changed to 'ns.singularity.connect'
stock.buy and stock.sell
------------------------
stock.buy, stock.sell, stock.short
----------------------------------
These 2 functions were renamed to stock.buyStock and stock.sellStock because 'buy' and 'sell'
These functions were renamed to stock.buyStock, stock.sellStock, and stock.buyShort because 'buy', 'sell', and 'short'
are very common tokens that would trick the ram calculation.
corporation.bribe

@ -1452,7 +1452,7 @@ export class Bladeburner implements IBladeburner {
}
}
} catch (e: unknown) {
exceptionAlert(e);
exceptionAlert(String(e));
}
break;
}

@ -1,11 +1,13 @@
import React from "react";
import { newWorkStats, WorkStats } from "../../Work/WorkStats";
interface IContract {
interface IGeneral {
desc: JSX.Element;
exp: WorkStats;
}
export const GeneralActions: {
[key: string]: IContract | undefined;
[key: string]: IGeneral | undefined;
} = {
Training: {
desc: (
@ -14,6 +16,12 @@ export const GeneralActions: {
all combat stats and also increases your max stamina.
</>
),
exp: newWorkStats({
strExp: 30,
defExp: 30,
dexExp: 30,
agiExp: 30,
}),
},
"Field Analysis": {
@ -27,6 +35,10 @@ export const GeneralActions: {
Does NOT require stamina.
</>
),
exp: newWorkStats({
hackExp: 20,
chaExp: 20,
}),
},
Recruitment: {
@ -38,6 +50,9 @@ export const GeneralActions: {
Does NOT require stamina.
</>
),
exp: newWorkStats({
chaExp: 120,
}),
},
Diplomacy: {
@ -50,6 +65,9 @@ export const GeneralActions: {
Does NOT require stamina.
</>
),
exp: newWorkStats({
chaExp: 120,
}),
},
"Hyperbolic Regeneration Chamber": {
@ -61,6 +79,7 @@ export const GeneralActions: {
<br />
</>
),
exp: newWorkStats(),
},
"Incite Violence": {
desc: (
@ -69,5 +88,12 @@ export const GeneralActions: {
additional contracts and operations, at the cost of increased Chaos.
</>
),
exp: newWorkStats({
strExp: 10,
defExp: 10,
dexExp: 10,
agiExp: 10,
chaExp: 10,
}),
},
};

@ -6,7 +6,7 @@ import { IMap } from "../types";
import { CrimeType } from "../utils/WorkType";
export const Crimes: IMap<Crime> = {
Shoplift: new Crime("Shoplift", CrimeType.Shoplift, 2e3, 15e3, 1 / 20, 0.1, {
Shoplift: new Crime("Shoplift", CrimeType.SHOPLIFT, 2e3, 15e3, 1 / 20, 0.1, {
dexterity_success_weight: 1,
agility_success_weight: 1,
@ -14,7 +14,7 @@ export const Crimes: IMap<Crime> = {
agility_exp: 2,
}),
RobStore: new Crime("Rob Store", CrimeType.RobStore, 60e3, 400e3, 1 / 5, 0.5, {
RobStore: new Crime("Rob Store", CrimeType.ROB_STORE, 60e3, 400e3, 1 / 5, 0.5, {
hacking_exp: 30,
dexterity_exp: 45,
agility_exp: 45,
@ -26,7 +26,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Mug: new Crime("Mug", CrimeType.Mug, 4e3, 36e3, 1 / 5, 0.25, {
Mug: new Crime("Mug", CrimeType.MUG, 4e3, 36e3, 1 / 5, 0.25, {
strength_exp: 3,
defense_exp: 3,
dexterity_exp: 3,
@ -38,7 +38,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 0.5,
}),
Larceny: new Crime("Larceny", CrimeType.Larceny, 90e3, 800e3, 1 / 3, 1.5, {
Larceny: new Crime("Larceny", CrimeType.LARCENY, 90e3, 800e3, 1 / 3, 1.5, {
hacking_exp: 45,
dexterity_exp: 60,
agility_exp: 60,
@ -50,7 +50,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
DealDrugs: new Crime("Deal Drugs", CrimeType.Drugs, 10e3, 120e3, 1, 0.5, {
DealDrugs: new Crime("Deal Drugs", CrimeType.DRUGS, 10e3, 120e3, 1, 0.5, {
dexterity_exp: 5,
agility_exp: 5,
charisma_exp: 10,
@ -60,7 +60,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1,
}),
BondForgery: new Crime("Bond Forgery", CrimeType.BondForgery, 300e3, 4.5e6, 1 / 2, 0.1, {
BondForgery: new Crime("Bond Forgery", CrimeType.BOND_FORGERY, 300e3, 4.5e6, 1 / 2, 0.1, {
hacking_exp: 100,
dexterity_exp: 150,
charisma_exp: 15,
@ -71,7 +71,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
TraffickArms: new Crime("Traffick Arms", CrimeType.TraffickArms, 40e3, 600e3, 2, 1, {
TraffickArms: new Crime("Traffick Arms", CrimeType.TRAFFIC_ARMS, 40e3, 600e3, 2, 1, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
@ -85,7 +85,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1,
}),
Homicide: new Crime("Homicide", CrimeType.Homicide, 3e3, 45e3, 1, 3, {
Homicide: new Crime("Homicide", CrimeType.HOMICIDE, 3e3, 45e3, 1, 3, {
strength_exp: 2,
defense_exp: 2,
dexterity_exp: 2,
@ -99,7 +99,7 @@ export const Crimes: IMap<Crime> = {
kills: 1,
}),
GrandTheftAuto: new Crime("Grand Theft Auto", CrimeType.GrandTheftAuto, 80e3, 1.6e6, 8, 5, {
GrandTheftAuto: new Crime("Grand Theft Auto", CrimeType.GRAND_THEFT_AUTO, 80e3, 1.6e6, 8, 5, {
strength_exp: 20,
defense_exp: 20,
dexterity_exp: 20,
@ -115,7 +115,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Kidnap: new Crime("Kidnap", CrimeType.Kidnap, 120e3, 3.6e6, 5, 6, {
Kidnap: new Crime("Kidnap", CrimeType.KIDNAP, 120e3, 3.6e6, 5, 6, {
strength_exp: 80,
defense_exp: 80,
dexterity_exp: 80,
@ -130,7 +130,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain,
}),
Assassination: new Crime("Assassination", CrimeType.Assassination, 300e3, 12e6, 8, 10, {
Assassination: new Crime("Assassination", CrimeType.ASSASSINATION, 300e3, 12e6, 8, 10, {
strength_exp: 300,
defense_exp: 300,
dexterity_exp: 300,
@ -145,7 +145,7 @@ export const Crimes: IMap<Crime> = {
kills: 1,
}),
Heist: new Crime("Heist", CrimeType.Heist, 600e3, 120e6, 18, 15, {
Heist: new Crime("Heist", CrimeType.HEIST, 600e3, 120e6, 18, 15, {
hacking_exp: 450,
strength_exp: 450,
defense_exp: 450,

@ -136,7 +136,7 @@ const stock = {
getSaleGain: RamCostConstants.ScriptGetStockRamCost,
buyStock: RamCostConstants.ScriptBuySellStockRamCost,
sellStock: RamCostConstants.ScriptBuySellStockRamCost,
short: RamCostConstants.ScriptBuySellStockRamCost,
buyShort: RamCostConstants.ScriptBuySellStockRamCost,
sellShort: RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: RamCostConstants.ScriptBuySellStockRamCost,

@ -180,20 +180,14 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
},
getTask:
(ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveTask => {
(_sleeveNumber: unknown): SleeveTask | null => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber];
return {
task: SleeveTaskType[sl.currentTask],
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
className: sl.className,
};
if (sl.currentWork === null) return null;
return sl.currentWork.APICopy();
},
getInformation:
(ctx: NetscriptContext) =>

@ -165,7 +165,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res ? stock.getBidPrice() : 0;
},
short:
buyShort:
(ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol);

@ -35,7 +35,7 @@ import { LocationName } from "../../Locations/data/LocationNames";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../utils/JSONReviver";
import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { capitalizeFirstLetter, capitalizeEachWord } from "../../utils/StringHelperFunctions";
import { capitalizeFirstLetter } from "../../utils/StringHelperFunctions";
import { FactionWorkType } from "../../Work/data/FactionWorkType";
import { Work } from "./Work/Work";
import { SleeveClassWork } from "./Work/SleeveClassWork";
@ -44,8 +44,10 @@ import { SleeveSynchroWork } from "./Work/SleeveSynchroWork";
import { SleeveRecoveryWork } from "./Work/SleeveRecoveryWork";
import { SleeveFactionWork } from "./Work/SleeveFactionWork";
import { SleeveCompanyWork } from "./Work/SleeveCompanyWork";
import { SleeveBladeburnerGeneralWork } from "./Work/SleeveBladeburnerGeneralActionWork";
import { SleeveInfiltrateWork } from "./Work/SleeveInfiltrateWork";
import { SleeveSupportWork } from "./Work/SleeveSupportWork";
import { SleeveBladeburnerWork } from "./Work/SleeveBladeburnerWork";
import { SleeveCrimeWork } from "./Work/SleeveCrimeWork";
export class Sleeve extends Person {
currentWork: Work | null = null;
@ -166,6 +168,16 @@ export class Sleeve extends Person {
return this.sync / 100;
}
startWork(player: IPlayer, w: Work): void {
if (this.currentWork) this.currentWork.finish(player);
this.currentWork = w;
}
stopWork(player: IPlayer): void {
if (this.currentWork) this.currentWork.finish(player);
this.currentWork = null;
}
/**
* Commit crimes
*/
@ -175,26 +187,13 @@ export class Sleeve extends Person {
return false;
}
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
this.gainRatesForTask.hack = crime.hacking_exp * this.mults.hacking_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.str = crime.strength_exp * this.mults.strength_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.def = crime.defense_exp * this.mults.defense_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.dex = crime.dexterity_exp * this.mults.dexterity_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.agi = crime.agility_exp * this.mults.agility_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.cha = crime.charisma_exp * this.mults.charisma_exp * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.int = crime.intelligence_exp;
this.gainRatesForTask.money = crime.money * this.mults.crime_money * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money);
this.crimeType = crime.name;
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
this.startWork(p, new SleeveCrimeWork(crime.type));
return true;
}
@ -202,89 +201,7 @@ export class Sleeve extends Person {
* Called to stop the current task
*/
finishTask(p: IPlayer): void {
this.currentWork = null;
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType);
if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus(p);
return;
}
if (Math.random() < crime.successRate(this)) {
// Success
const successGainRates: ITaskTracker = createTaskTracker();
const keysForIteration: (keyof ITaskTracker)[] = Object.keys(successGainRates) as (keyof ITaskTracker)[];
for (let i = 0; i < keysForIteration.length; ++i) {
const key = keysForIteration[i];
successGainRates[key] = this.gainRatesForTask[key] * 2;
}
this.gainExperience(p, successGainRates);
this.gainMoney(p, this.gainRatesForTask);
p.karma -= crime.karma * (this.sync / 100);
} else {
this.gainExperience(p, this.gainRatesForTask);
}
// Do not reset task to IDLE
this.currentTaskTime = 0;
return;
}
} else if (this.currentTask === SleeveTaskType.Bladeburner) {
if (this.currentTaskMaxTime === 0) {
this.currentTaskTime = 0;
return;
}
// For bladeburner, all experience and money is gained at the end
const bb = p.bladeburner;
if (bb === null) {
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return;
}
if (this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.bbAction === "Infiltrate synthoids") {
bb.infiltrateSynthoidCommunities(p);
this.currentTaskTime = 0;
return;
}
let type: string;
let name: string;
if (this.bbAction === "Take on contracts") {
type = "Contracts";
name = this.bbContract;
} else {
type = "General";
name = this.bbAction;
}
const actionIdent = bb.getActionIdFromTypeAndName(type, name);
if (actionIdent === null) {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return;
}
const action = bb.getActionObject(actionIdent);
if ((action?.count ?? 0) > 0) {
const bbRetValue = bb.completeAction(p, this, actionIdent, false);
if (bbRetValue) {
this.gainExperience(p, bbRetValue);
this.gainMoney(p, bbRetValue);
// Do not reset task to IDLE
this.currentTaskTime = 0;
return;
}
}
}
}
this.stopWork(p);
this.resetTaskStatus(p);
@ -557,30 +474,6 @@ export class Sleeve extends Person {
// Shock gradually goes towards 100
this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);
switch (this.currentTask) {
case SleeveTaskType.Company: {
this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
const company: Company = Companies[this.currentTaskLocation];
if (!(company instanceof Company)) {
console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`);
break;
}
company.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) {
this.finishTask(p);
} else {
this.finishTask(p);
}
}
this.updateStatLevels();
this.storedCycles -= cyclesUsed;
@ -592,7 +485,7 @@ export class Sleeve extends Person {
* Resets all parameters used to keep information about the current task
*/
resetTaskStatus(p: IPlayer): void {
this.currentWork = null;
this.stopWork(p);
if (this.bbAction == "Support main sleeve") {
p.bladeburner?.sleeveSupport(false);
}
@ -616,7 +509,7 @@ export class Sleeve extends Person {
} else {
this.resetTaskStatus(p);
}
this.currentWork = new SleeveRecoveryWork();
this.startWork(p, new SleeveRecoveryWork());
return true;
}
@ -626,7 +519,7 @@ export class Sleeve extends Person {
} else {
this.resetTaskStatus(p);
}
this.currentWork = new SleeveSynchroWork();
this.startWork(p, new SleeveSynchroWork());
return true;
}
@ -686,10 +579,13 @@ export class Sleeve extends Person {
}
if (!classType) return false;
this.currentWork = new SleeveClassWork({
this.startWork(
p,
new SleeveClassWork({
classType: classType,
location: loc,
});
}),
);
return true;
}
@ -747,7 +643,7 @@ export class Sleeve extends Person {
if (company == null) return false;
if (companyPosition == null) return false;
this.currentWork = new SleeveCompanyWork({ companyName: companyName });
this.startWork(p, new SleeveCompanyWork({ companyName: companyName }));
return true;
}
@ -786,10 +682,13 @@ export class Sleeve extends Person {
return false;
}
this.currentWork = new SleeveFactionWork({
this.startWork(
p,
new SleeveFactionWork({
factionWorkType: factionWorkType,
factionName: faction.name,
});
}),
);
return true;
}
@ -856,10 +755,13 @@ export class Sleeve extends Person {
// if stat is still equals its default value, then validation has failed.
if (!classType) return false;
this.currentWork = new SleeveClassWork({
this.startWork(
p,
new SleeveClassWork({
classType: classType,
location: loc,
});
}),
);
return true;
}
@ -883,7 +785,7 @@ export class Sleeve extends Person {
this.gainRatesForTask.money = 0;
this.currentTaskLocation = "";
let time = 0;
const time = 0;
this.bbContract = "------";
switch (action) {
@ -891,7 +793,7 @@ export class Sleeve extends Person {
// time = this.getBladeburnerActionTime(p, "General", action);
// this.gainRatesForTask.hack = 20 * this.mults.hacking_exp;
// this.gainRatesForTask.cha = 20 * this.mults.charisma_exp;
this.currentWork = new SleeveBladeburnerGeneralWork("Field analysis");
this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Field Analysis" }));
return true;
case "Recruitment":
// time = this.getBladeburnerActionTime(p, "General", action);
@ -900,25 +802,26 @@ export class Sleeve extends Person {
// this.currentTaskLocation = `(Success Rate: ${numeralWrapper.formatPercentage(
// this.recruitmentSuccessChance(p),
// )})`;
this.currentWork = new SleeveBladeburnerGeneralWork("Recruitment");
break;
this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Recruitment" }));
return true;
case "Diplomacy":
// time = this.getBladeburnerActionTime(p, "General", action);
this.currentWork = new SleeveBladeburnerGeneralWork("Diplomacy");
break;
this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Diplomacy" }));
return true;
case "Infiltrate synthoids":
this.currentWork = new SleeveInfiltrateWork();
break;
this.startWork(p, new SleeveInfiltrateWork());
return true;
case "Support main sleeve":
p.bladeburner?.sleeveSupport(true);
time = 0;
break;
this.startWork(p, new SleeveSupportWork(p));
return true;
case "Take on contracts":
time = this.getBladeburnerActionTime(p, "Contracts", contract);
this.contractGainRates(p, "Contracts", contract);
this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract);
this.bbContract = capitalizeEachWord(contract.toLowerCase());
break;
this.startWork(p, new SleeveBladeburnerWork({ type: "Contracts", name: contract }));
return true;
// time = this.getBladeburnerActionTime(p, "Contracts", contract);
// this.contractGainRates(p, "Contracts", contract);
// this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract);
// this.bbContract = capitalizeEachWord(contract.toLowerCase());
// break;
}
this.bbAction = capitalizeFirstLetter(action.toLowerCase());

@ -1,59 +0,0 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants";
export const isSleeveBladeburnerGeneralWork = (w: Work | null): w is SleeveBladeburnerGeneralWork =>
w !== null && w.type === WorkType.BLADEBURNER_GENERAL;
export class SleeveBladeburnerGeneralWork extends Work {
cyclesWorked = 0;
action: string;
constructor(action?: string) {
super(WorkType.BLADEBURNER_GENERAL);
this.action = action ?? "Field analysis";
}
cyclesNeeded(player: IPlayer, sleeve: Sleeve): number {
const ret = player.bladeburner?.getActionTimeNetscriptFn(sleeve, "General", this.action);
if (!ret || typeof ret === "string") throw new Error(`Error querying ${this.action} time`);
return ret / CONSTANTS._idleSpeed;
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
if (!player.bladeburner) throw new Error("sleeve doing blade work without being a member");
this.cyclesWorked += cycles;
while (this.cyclesWorked > this.cyclesNeeded(player, sleeve)) {
const actionIdent = player.bladeburner.getActionIdFromTypeAndName("General", this.action);
if (!actionIdent) throw new Error(`Error getting ${this.action} action`);
player.bladeburner.completeAction(player, sleeve, actionIdent, false);
this.cyclesWorked -= this.cyclesNeeded(player, sleeve);
}
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
action: this.action,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveBladeburnerGeneralWork", this);
}
/**
* Initiatizes a BladeburnerWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveBladeburnerGeneralWork {
return Generic_fromJSON(SleeveBladeburnerGeneralWork, value.data);
}
}
Reviver.constructors.SleeveBladeburnerGeneralWork = SleeveBladeburnerGeneralWork;

@ -0,0 +1,75 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants";
import { GeneralActions } from "../../../Bladeburner/data/GeneralActions";
import { applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats";
import { Contracts } from "src/Bladeburner/data/Contracts";
interface SleeveBladeburnerWorkParams {
type: string;
name: string;
}
export const isSleeveBladeburnerWork = (w: Work | null): w is SleeveBladeburnerWork =>
w !== null && w.type === WorkType.BLADEBURNER;
export class SleeveBladeburnerWork extends Work {
cyclesWorked = 0;
actionType: string;
actionName: string;
constructor(params?: SleeveBladeburnerWorkParams) {
super(WorkType.BLADEBURNER);
this.actionType = params?.type ?? "General";
this.actionName = params?.name ?? "Field analysis";
}
cyclesNeeded(player: IPlayer, 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._idleSpeed;
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
if (!player.bladeburner) throw new Error("sleeve doing blade work without being a member");
this.cyclesWorked += cycles;
while (this.cyclesWorked > this.cyclesNeeded(player, sleeve)) {
const actionIdent = player.bladeburner.getActionIdFromTypeAndName(this.actionType, this.actionName);
if (!actionIdent) throw new Error(`Error getting ${this.actionName} action`);
player.bladeburner.completeAction(player, sleeve, actionIdent, false);
let exp: WorkStats | undefined;
if (this.actionType === "General") {
exp = GeneralActions[this.actionName]?.exp;
if (!exp) throw new Error(`Somehow there was no exp for action ${this.actionType} ${this.actionName}`);
applyWorkStatsExp(sleeve, exp, 1);
}
this.cyclesWorked -= this.cyclesNeeded(player, sleeve);
}
return 0;
}
APICopy(): Record<string, unknown> {
return {
actionType: this.actionType,
actionName: this.actionName,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveBladeburnerWork", this);
}
/**
* Initiatizes a BladeburnerWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveBladeburnerWork {
return Generic_fromJSON(SleeveBladeburnerWork, value.data);
}
}
Reviver.constructors.SleeveBladeburnerWork = SleeveBladeburnerWork;

@ -0,0 +1,80 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { CrimeType } from "../../../utils/WorkType";
import { Crimes } from "../../../Crime/Crimes";
import { Crime } from "../../../Crime/Crime";
import { applyWorkStats, newWorkStats, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
import { CONSTANTS } from "../../../Constants";
export const isSleeveCrimeWork = (w: Work | null): w is SleeveCrimeWork => w !== null && w.type === WorkType.CRIME;
export class SleeveCrimeWork extends Work {
crimeType: CrimeType;
cyclesWorked = 0;
constructor(crimeType?: CrimeType) {
super(WorkType.CRIME);
this.crimeType = crimeType ?? CrimeType.SHOPLIFT;
}
getCrime(): Crime {
const crime = Object.values(Crimes).find((crime) => crime.type === this.crimeType);
if (!crime) throw new Error("crime should not be undefined");
return crime;
}
getExp(): WorkStats {
const crime = this.getCrime();
return newWorkStats({
money: crime.money,
hackExp: crime.hacking_exp,
strExp: crime.strength_exp,
defExp: crime.defense_exp,
dexExp: crime.dexterity_exp,
agiExp: crime.agility_exp,
chaExp: crime.charisma_exp,
intExp: crime.intelligence_exp,
});
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
this.cyclesWorked += cycles;
const crime = this.getCrime();
const gains = this.getExp();
if (this.cyclesWorked >= crime.time / CONSTANTS._idleSpeed) {
if (Math.random() < crime.successRate(sleeve)) {
applyWorkStats(player, sleeve, gains, 1, "sleeves");
player.karma -= crime.karma * sleeve.syncBonus();
} else {
applyWorkStats(player, sleeve, scaleWorkStats(gains, 0.25), 1, "sleeves");
}
this.cyclesWorked -= crime.time / CONSTANTS._idleSpeed;
}
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveCrimeWork", this);
}
/**
* Initiatizes a RecoveryWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveCrimeWork {
return Generic_fromJSON(SleeveCrimeWork, value.data);
}
}
Reviver.constructors.SleeveCrimeWork = SleeveCrimeWork;

@ -55,7 +55,7 @@ export class SleeveFactionWork extends Work {
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
if (player.gang) {
if (this.factionName === player.gang.facName) {
sleeve.currentWork = null;
sleeve.stopWork(player);
return 0;
}
}

@ -13,7 +13,7 @@ export class SleeveRecoveryWork extends Work {
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
sleeve.shock = Math.min(100, sleeve.shock + 0.0002 * cycles);
if (sleeve.shock >= 100) sleeve.currentWork = null;
if (sleeve.shock >= 100) sleeve.stopWork(player);
return 0;
}

@ -0,0 +1,43 @@
import { IPlayer } from "../../../PersonObjects/IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Work, WorkType } from "./Work";
export const isSleeveSupportWork = (w: Work | null): w is SleeveSupportWork =>
w !== null && w.type === WorkType.SUPPORT;
export class SleeveSupportWork extends Work {
constructor(player?: IPlayer) {
super(WorkType.SUPPORT);
if (player) player.bladeburner?.sleeveSupport(true);
}
process(): number {
return 0;
}
finish(player: IPlayer): void {
player.bladeburner?.sleeveSupport(false);
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveSupportWork", this);
}
/**
* Initiatizes a BladeburnerWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveSupportWork {
return Generic_fromJSON(SleeveSupportWork, value.data);
}
}
Reviver.constructors.SleeveSupportWork = SleeveSupportWork;

@ -13,7 +13,7 @@ export class SleeveSynchroWork extends Work {
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
sleeve.sync = Math.min(100, sleeve.sync + player.getIntelligenceBonus(0.5) * 0.0002 * cycles);
if (sleeve.sync >= 100) sleeve.currentWork = null;
if (sleeve.sync >= 100) sleeve.stopWork(player);
return 0;
}

@ -12,6 +12,9 @@ export abstract class Work {
abstract process(player: IPlayer, sleeve: Sleeve, cycles: number): number;
abstract APICopy(): Record<string, unknown>;
abstract toJSON(): IReviverValue;
finish(_player: IPlayer): void {
/* left for children to implement */
}
}
export enum WorkType {
@ -21,8 +24,7 @@ export enum WorkType {
CLASS = "CLASS",
RECOVERY = "RECOVERY",
SYNCHRO = "SYNCHRO",
BLADEBURNER_GENERAL = "BLADEBURNER_GENERAL",
BLADEBURNER = "BLADEBURNER",
INFILTRATE = "INFILTRATE",
BLADEBURNER_SUPPORT = "SUPPORT",
BLADEBURNER_CONTRACTS = "CONTRACTS",
SUPPORT = "SUPPORT",
}

@ -19,8 +19,10 @@ import { isSleeveSynchroWork } from "../Work/SleeveSynchroWork";
import { isSleeveRecoveryWork } from "../Work/SleeveRecoveryWork";
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
import { isSleeveBladeburnerGeneralWork } from "../Work/SleeveBladeburnerGeneralActionWork";
import { isSleeveInfiltrateWork } from "../Work/SleeveInfiltrateWork";
import { isSleeveSupportWork } from "../Work/SleeveSupportWork";
import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
interface IProps {
sleeve: Sleeve;
@ -71,42 +73,17 @@ export function SleeveElem(props: IProps): React.ReactElement {
props.rerender();
}
let desc = <></>;
switch (props.sleeve.currentTask) {
case SleeveTaskType.Idle:
desc = <>This sleeve is currently idle</>;
break;
case SleeveTaskType.Crime: {
const crime = Object.values(Crimes).find((crime) => crime.name === props.sleeve.crimeType);
if (!crime) throw new Error("crime should not be undefined");
let desc = <>This sleeve is currently idle</>;
if (isSleeveCrimeWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
const crime = w.getCrime();
desc = (
<>
This sleeve is currently attempting to {crime.type} (Success Rate:{" "}
{numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}).
</>
);
break;
}
case SleeveTaskType.Class:
desc = <>This sleeve is currently studying/taking a course at {props.sleeve.currentTaskLocation}.</>;
break;
case SleeveTaskType.Bladeburner: {
let message = "";
if (props.sleeve.bbContract !== "------") {
message = ` - ${props.sleeve.bbContract} (Success Rate: ${props.sleeve.currentTaskLocation})`;
} else if (props.sleeve.currentTaskLocation !== "") {
message = props.sleeve.currentTaskLocation;
}
desc = (
<>
This sleeve is currently attempting to {props.sleeve.bbAction}. {message}
</>
);
break;
}
default:
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`);
}
if (isSleeveClassWork(props.sleeve.currentWork)) {
@ -153,11 +130,11 @@ export function SleeveElem(props: IProps): React.ReactElement {
desc = <>This sleeve is currently working your job at {props.sleeve.currentWork.companyName}.</>;
}
if (isSleeveBladeburnerGeneralWork(props.sleeve.currentWork)) {
if (isSleeveBladeburnerWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
desc = (
<>
This sleeve is currently attempting to perform {w.action}. (
This sleeve is currently attempting to perform {w.actionName}. (
{((100 * w.cyclesWorked) / w.cyclesNeeded(player, props.sleeve)).toFixed(2)}%)
</>
);
@ -173,6 +150,10 @@ export function SleeveElem(props: IProps): React.ReactElement {
);
}
if (isSleeveSupportWork(props.sleeve.currentWork)) {
desc = <>This sleeve is currently supporting you in your bladeburner activities.</>;
}
return (
<>
<Paper sx={{ p: 1, display: "grid", gridTemplateColumns: "1fr 1fr", width: "auto", gap: 1 }}>

@ -16,6 +16,7 @@ import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { isSleeveClassWork } from "../Work/SleeveClassWork";
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
interface IProps {
sleeve: Sleeve;
@ -97,35 +98,17 @@ export function EarningsElement(props: IProps): React.ReactElement {
const player = use.Player();
let data: (string | JSX.Element)[][] = [];
if (props.sleeve.currentTask === SleeveTaskType.Crime) {
if (isSleeveCrimeWork(props.sleeve.currentWork)) {
const gains = props.sleeve.currentWork.getExp();
data = [
[
`Money`,
<>
<Money money={parseFloat(props.sleeve.currentTaskLocation)} /> (on success)
</>,
],
[`Hacking Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack)} (2x on success)`],
[`Strength Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.str)} (2x on success)`],
[`Defense Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.def)} (2x on success)`],
[`Dexterity Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.dex)} (2x on success)`],
[`Agility Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi)} (2x on success)`],
[`Charisma Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha)} (2x on success)`],
[`Money:`, <Money money={5 * gains.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * gains.hackExp)}`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * gains.strExp)}`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * gains.defExp)}`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * gains.dexExp)}`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * gains.agiExp)}`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * gains.chaExp)}`],
];
} else {
data = [
[`Money:`, <MoneyRate money={5 * props.sleeve.gainRatesForTask.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / sec`],
];
if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = props.sleeve.getRepGain(player);
data.push([`Reputation:`, <ReputationRate reputation={5 * repGain} />]);
}
}
if (isSleeveClassWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve);

@ -1016,36 +1016,13 @@ export interface SleeveInformation {
tor: boolean;
/** Sleeve multipliers */
mult: CharacterMult;
/** Time spent on the current task in milliseconds */
timeWorked: number;
/** Earnings synchronized to other sleeves */
earningsForSleeves: SleeveWorkGains;
/** Earnings synchronized to the player */
earningsForPlayer: SleeveWorkGains;
/** Earnings for this sleeve */
earningsForTask: SleeveWorkGains;
/** Faction or company reputation gained for the current task */
workRepGain: number;
}
/**
* Object representing a sleeve current task.
* @public
*/
export interface SleeveTask {
/** Task type */
task: string;
/** Crime currently attempting, if any */
crime: string;
/** Location of the task, if any */
location: string;
/** Stat being trained at the gym, if any */
gymStatType: string;
/** Faction work type being performed, if any */
factionWorkType: string;
/** Class being taken at university, if any */
className: string;
}
export interface SleeveTask = any;
/**
* Object representing a port. A port is a serialized queue.
@ -1310,7 +1287,7 @@ export interface TIX {
* @param shares - Number of shares to short. Must be positive. Will be rounded to nearest integer.
* @returns The stock price at which each share was purchased, otherwise 0 if the shares weren't purchased.
*/
short(sym: string, shares: number): number;
buyShort(sym: string, shares: number): number;
/**
* Sell short stock.

@ -26,29 +26,29 @@ enum newCrimeType {
const convertCrimeType = (crimeType: CrimeType): newCrimeType => {
switch (crimeType) {
case CrimeType.Shoplift:
case CrimeType.SHOPLIFT:
return newCrimeType.SHOPLIFT;
case CrimeType.RobStore:
case CrimeType.ROB_STORE:
return newCrimeType.ROBSTORE;
case CrimeType.Mug:
case CrimeType.MUG:
return newCrimeType.MUG;
case CrimeType.Larceny:
case CrimeType.LARCENY:
return newCrimeType.LARCENY;
case CrimeType.Drugs:
case CrimeType.DRUGS:
return newCrimeType.DRUGS;
case CrimeType.BondForgery:
case CrimeType.BOND_FORGERY:
return newCrimeType.BONDFORGERY;
case CrimeType.TraffickArms:
case CrimeType.TRAFFIC_ARMS:
return newCrimeType.TRAFFICKARMS;
case CrimeType.Homicide:
case CrimeType.HOMICIDE:
return newCrimeType.HOMICIDE;
case CrimeType.GrandTheftAuto:
case CrimeType.GRAND_THEFT_AUTO:
return newCrimeType.GRANDTHEFTAUTO;
case CrimeType.Kidnap:
case CrimeType.KIDNAP:
return newCrimeType.KIDNAP;
case CrimeType.Assassination:
case CrimeType.ASSASSINATION:
return newCrimeType.ASSASSINATION;
case CrimeType.Heist:
case CrimeType.HEIST:
return newCrimeType.HEIST;
}
return newCrimeType.SHOPLIFT;
@ -67,7 +67,7 @@ export class CrimeWork extends Work {
constructor(params?: CrimeWorkParams) {
super(WorkType.CRIME, params?.singularity ?? true);
this.crimeType = params?.crimeType ?? CrimeType.Shoplift;
this.crimeType = params?.crimeType ?? CrimeType.SHOPLIFT;
this.unitCompleted = 0;
}

@ -1,15 +1,15 @@
export enum CrimeType {
None = "",
Shoplift = "shoplift",
RobStore = "rob a store",
Mug = "mug someone",
Larceny = "commit larceny",
Drugs = "deal drugs",
BondForgery = "forge corporate bonds",
TraffickArms = "traffick illegal arms",
Homicide = "commit homicide",
GrandTheftAuto = "commit grand theft auto",
Kidnap = "kidnap someone for ransom",
Assassination = "assassinate a high-profile target",
Heist = "pull off the ultimate heist",
SHOPLIFT = "SHOPLIFT", //"shoplift",
ROB_STORE = "ROBSTORE", //"rob a store",
MUG = "MUG", //"mug someone",
LARCENY = "LARCENY", //"commit larceny",
DRUGS = "DRUGS", //"deal drugs",
BOND_FORGERY = "BONDFORGERY", //"forge corporate bonds",
TRAFFIC_ARMS = "TRAFFICKARMS", //"traffick illegal arms",
HOMICIDE = "HOMICIDE", //"commit homicide",
GRAND_THEFT_AUTO = "GRANDTHEFTAUTO", //"commit grand theft auto",
KIDNAP = "KIDNAP", //"kidnap someone for ransom",
ASSASSINATION = "ASSASSINATION", //"assassinate a high-profile target",
HEIST = "HEIST", //"pull off the ultimate heist",
}

@ -185,6 +185,12 @@ export const v2APIBreak = () => {
reason: "sell is a very common word so in order to avoid ram costs it was renamed ns.stock.sellStock",
offenders: [],
},
{
matchJS: /ns\.stock\.short/g,
matchScript: /stock\.short/g,
reason: "short is a very common word so in order to avoid ram costs it was renamed ns.stock.buyShort",
offenders: [],
},
{
matchJS: /ns\.corporation\.bribe/g,
matchScript: /corporation\.bribe/g,