mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 15:43:49 +01:00
allbuild commit c8440ef2
This commit is contained in:
parent
c8440ef268
commit
07c7f0641a
@ -21,6 +21,14 @@ module.exports = {
|
|||||||
plugins: ["@typescript-eslint"],
|
plugins: ["@typescript-eslint"],
|
||||||
extends: ["plugin:@typescript-eslint/recommended"],
|
extends: ["plugin:@typescript-eslint/recommended"],
|
||||||
rules: {
|
rules: {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^__",
|
||||||
|
varsIgnorePattern: "^__",
|
||||||
|
caughtErrorsIgnorePattern: "^__",
|
||||||
|
},
|
||||||
|
],
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
},
|
},
|
||||||
|
4
dist/main.bundle.js
vendored
4
dist/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/main.bundle.js.map
vendored
2
dist/main.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
42
dist/vendor.bundle.js
vendored
42
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vendor.bundle.js.map
vendored
2
dist/vendor.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
@ -26,7 +26,7 @@ v2.0.0 - 2022-07-19 Work rework
|
|||||||
* Company faction require 400k rep to join (from 200k)
|
* Company faction require 400k rep to join (from 200k)
|
||||||
* Backdooring company server reduces faction requirement to 300k.
|
* Backdooring company server reduces faction requirement to 300k.
|
||||||
* All work generally no longer keep track of cumulative gains like exp and reputation since it's applied instantly.
|
* All work generally no longer keep track of cumulative gains like exp and reputation since it's applied instantly.
|
||||||
* getPlayer returns way less fields but does return the new 'currentWork' field, some fields are moved around.
|
* getPlayer returns way less fields but does return the new 'currentWork' field, some fields are moved around.
|
||||||
|
|
||||||
API breaks
|
API breaks
|
||||||
|
|
||||||
|
@ -71,6 +71,8 @@ getPlayer
|
|||||||
hp have been moved to the hp struct
|
hp have been moved to the hp struct
|
||||||
For example: getPlayer().max_hp => getPlayer().hp.max or hp.current
|
For example: getPlayer().max_hp => getPlayer().hp.max or hp.current
|
||||||
|
|
||||||
|
hasWseAccount, hasTixApiAccess, has4SData, has4SDataTixApi have been removed and replaced with similar stock functions
|
||||||
|
|
||||||
workForCompany
|
workForCompany
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ export const CONSTANTS: {
|
|||||||
LatestUpdate: string;
|
LatestUpdate: string;
|
||||||
} = {
|
} = {
|
||||||
VersionString: "2.0.0",
|
VersionString: "2.0.0",
|
||||||
VersionNumber: 22,
|
VersionNumber: 23,
|
||||||
|
|
||||||
// Speed (in ms) at which the main loop is updated
|
// Speed (in ms) at which the main loop is updated
|
||||||
_idleSpeed: 200,
|
_idleSpeed: 200,
|
||||||
@ -269,6 +269,7 @@ v2.0.0 - 2022-07-19 Work rework
|
|||||||
* stock.buy and stock.sell were renamed to stock.buyStock and stock.sellStock because 'buy' and 'sell'
|
* stock.buy and stock.sell were renamed to stock.buyStock and stock.sellStock because 'buy' and 'sell'
|
||||||
are very common tokens.
|
are very common tokens.
|
||||||
* corporation.bribe no longer allows to give shares as bribe.
|
* corporation.bribe no longer allows to give shares as bribe.
|
||||||
|
* hasWseAccount, hasTixApiAccess, has4SData, has4SDataTixApi have been removed and replaced with similar stock functions.
|
||||||
|
|
||||||
Netscript
|
Netscript
|
||||||
|
|
||||||
|
@ -126,6 +126,10 @@ const hacknet = {
|
|||||||
|
|
||||||
// Stock API
|
// Stock API
|
||||||
const stock = {
|
const stock = {
|
||||||
|
hasWSEAccount: 0.05,
|
||||||
|
hasTIXAPIAccess: 0.05,
|
||||||
|
has4SData: 0.05,
|
||||||
|
has4SDataTIXAPI: 0.05,
|
||||||
getSymbols: RamCostConstants.ScriptGetStockRamCost,
|
getSymbols: RamCostConstants.ScriptGetStockRamCost,
|
||||||
getPrice: RamCostConstants.ScriptGetStockRamCost,
|
getPrice: RamCostConstants.ScriptGetStockRamCost,
|
||||||
getAskPrice: RamCostConstants.ScriptGetStockRamCost,
|
getAskPrice: RamCostConstants.ScriptGetStockRamCost,
|
||||||
|
@ -585,31 +585,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
},
|
},
|
||||||
player(funcName: string, p: unknown): IPlayer {
|
player(funcName: string, p: unknown): IPlayer {
|
||||||
const fakePlayer = {
|
const fakePlayer = {
|
||||||
hacking: undefined,
|
|
||||||
hp: undefined,
|
hp: undefined,
|
||||||
max_hp: undefined,
|
|
||||||
strength: undefined,
|
|
||||||
defense: undefined,
|
|
||||||
dexterity: undefined,
|
|
||||||
agility: undefined,
|
|
||||||
charisma: undefined,
|
|
||||||
intelligence: undefined,
|
|
||||||
hacking_exp: undefined,
|
|
||||||
strength_exp: undefined,
|
|
||||||
defense_exp: undefined,
|
|
||||||
dexterity_exp: undefined,
|
|
||||||
agility_exp: undefined,
|
|
||||||
charisma_exp: undefined,
|
|
||||||
hacking_chance_mult: undefined,
|
|
||||||
mults: undefined,
|
mults: undefined,
|
||||||
numPeopleKilled: undefined,
|
numPeopleKilled: undefined,
|
||||||
money: undefined,
|
money: undefined,
|
||||||
city: undefined,
|
city: undefined,
|
||||||
location: undefined,
|
location: undefined,
|
||||||
hasWseAccount: undefined,
|
|
||||||
hasTixApiAccess: undefined,
|
|
||||||
has4SData: undefined,
|
|
||||||
has4SDataTixApi: undefined,
|
|
||||||
bitNodeN: undefined,
|
bitNodeN: undefined,
|
||||||
totalPlaytime: undefined,
|
totalPlaytime: undefined,
|
||||||
playtimeSinceLastAug: undefined,
|
playtimeSinceLastAug: undefined,
|
||||||
@ -2446,10 +2427,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
money: Player.money,
|
money: Player.money,
|
||||||
city: Player.city,
|
city: Player.city,
|
||||||
location: Player.location,
|
location: Player.location,
|
||||||
hasWseAccount: Player.hasWseAccount,
|
|
||||||
hasTixApiAccess: Player.hasTixApiAccess,
|
|
||||||
has4SData: Player.has4SData,
|
|
||||||
has4SDataTixApi: Player.has4SDataTixApi,
|
|
||||||
bitNodeN: Player.bitNodeN,
|
bitNodeN: Player.bitNodeN,
|
||||||
totalPlaytime: Player.totalPlaytime,
|
totalPlaytime: Player.totalPlaytime,
|
||||||
playtimeSinceLastAug: Player.playtimeSinceLastAug,
|
playtimeSinceLastAug: Player.playtimeSinceLastAug,
|
||||||
|
@ -39,6 +39,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
hasWSEAccount: () => (): boolean => {
|
||||||
|
return player.hasWseAccount;
|
||||||
|
},
|
||||||
|
hasTIXAPIAccess: () => (): boolean => {
|
||||||
|
return player.hasTixApiAccess;
|
||||||
|
},
|
||||||
|
has4SData: () => (): boolean => {
|
||||||
|
return player.has4SData;
|
||||||
|
},
|
||||||
|
has4SDataTIXAPI: () => (): boolean => {
|
||||||
|
return player.has4SDataTixApi;
|
||||||
|
},
|
||||||
getSymbols: (ctx: NetscriptContext) => (): string[] => {
|
getSymbols: (ctx: NetscriptContext) => (): string[] => {
|
||||||
checkTixApiAccess(ctx);
|
checkTixApiAccess(ctx);
|
||||||
return Object.values(StockSymbols);
|
return Object.values(StockSymbols);
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
import { IPlayer } from "../IPlayer";
|
import { IPlayer } from "../IPlayer";
|
||||||
import { Person } from "../Person";
|
import { Person } from "../Person";
|
||||||
import { ITaskTracker, createTaskTracker } from "../ITaskTracker";
|
|
||||||
|
|
||||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ export class Sleeve extends Person {
|
|||||||
/**
|
/**
|
||||||
* Synchronization. Number between 0 and 100
|
* Synchronization. Number between 0 and 100
|
||||||
* When experience is earned by sleeve, both the player and the sleeve get
|
* When experience is earned by sleeve, both the player and the sleeve get
|
||||||
* sync% of the experience earned. Other sleeves get sync^2% of exp
|
* sync% of the experience earned.
|
||||||
*/
|
*/
|
||||||
sync = 1;
|
sync = 1;
|
||||||
|
|
||||||
@ -83,6 +82,7 @@ export class Sleeve extends Person {
|
|||||||
shockBonus(): number {
|
shockBonus(): number {
|
||||||
return this.shock / 100;
|
return this.shock / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
syncBonus(): number {
|
syncBonus(): number {
|
||||||
return this.sync / 100;
|
return this.sync / 100;
|
||||||
}
|
}
|
||||||
@ -110,123 +110,6 @@ export class Sleeve extends Person {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to stop the current task
|
|
||||||
*/
|
|
||||||
finishTask(p: IPlayer): void {
|
|
||||||
this.stopWork(p);
|
|
||||||
|
|
||||||
this.resetTaskStatus(p);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Earn experience for any stats (supports multiple)
|
|
||||||
* This function also handles experience propogating to Player and other sleeves
|
|
||||||
*/
|
|
||||||
gainExperience(p: IPlayer, exp: ITaskTracker, numCycles = 1, fromOtherSleeve = false): ITaskTracker {
|
|
||||||
// If the experience is coming from another sleeve, it is not multiplied by anything.
|
|
||||||
// Also the player does not earn anything
|
|
||||||
if (fromOtherSleeve) {
|
|
||||||
if (exp.hack > 0) {
|
|
||||||
this.exp.hacking += exp.hack;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.str > 0) {
|
|
||||||
this.exp.strength += exp.str;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.def > 0) {
|
|
||||||
this.exp.defense += exp.def;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.dex > 0) {
|
|
||||||
this.exp.dexterity += exp.dex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.agi > 0) {
|
|
||||||
this.exp.agility += exp.agi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp.cha > 0) {
|
|
||||||
this.exp.charisma += exp.cha;
|
|
||||||
}
|
|
||||||
|
|
||||||
return createTaskTracker();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Experience is first multiplied by shock. Then 'synchronization'
|
|
||||||
// is accounted for
|
|
||||||
|
|
||||||
const multFac = (this.shock / 100) * (this.sync / 100) * numCycles;
|
|
||||||
const pHackExp = exp.hack * multFac;
|
|
||||||
const pStrExp = exp.str * multFac;
|
|
||||||
const pDefExp = exp.def * multFac;
|
|
||||||
const pDexExp = exp.dex * multFac;
|
|
||||||
const pAgiExp = exp.agi * multFac;
|
|
||||||
const pChaExp = exp.cha * multFac;
|
|
||||||
const pIntExp = exp.int * multFac;
|
|
||||||
|
|
||||||
// Experience is gained by both this sleeve and player
|
|
||||||
if (pHackExp > 0) {
|
|
||||||
this.gainHackingExp(pHackExp);
|
|
||||||
p.gainHackingExp(pHackExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pStrExp > 0) {
|
|
||||||
this.gainStrengthExp(pStrExp);
|
|
||||||
p.gainStrengthExp(pStrExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pDefExp > 0) {
|
|
||||||
this.gainDefenseExp(pDefExp);
|
|
||||||
p.gainDefenseExp(pDefExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pDexExp > 0) {
|
|
||||||
this.gainDexterityExp(pDexExp);
|
|
||||||
p.gainDexterityExp(pDexExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pAgiExp > 0) {
|
|
||||||
this.gainAgilityExp(pAgiExp);
|
|
||||||
p.gainAgilityExp(pAgiExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pChaExp > 0) {
|
|
||||||
this.gainCharismaExp(pChaExp);
|
|
||||||
p.gainCharismaExp(pChaExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pIntExp > 0) {
|
|
||||||
this.gainIntelligenceExp(pIntExp);
|
|
||||||
p.gainIntelligenceExp(pIntExp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the experience to be gained by other sleeves
|
|
||||||
return {
|
|
||||||
hack: pHackExp * (this.sync / 100),
|
|
||||||
str: pStrExp * (this.sync / 100),
|
|
||||||
def: pDefExp * (this.sync / 100),
|
|
||||||
dex: pDexExp * (this.sync / 100),
|
|
||||||
agi: pAgiExp * (this.sync / 100),
|
|
||||||
cha: pChaExp * (this.sync / 100),
|
|
||||||
int: pIntExp * (this.sync / 100),
|
|
||||||
money: exp.money,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Earn money for player
|
|
||||||
*/
|
|
||||||
// gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void {
|
|
||||||
// const gain: number = task.money * numCycles;
|
|
||||||
// this.earningsForTask.money += gain;
|
|
||||||
// this.earningsForPlayer.money += gain;
|
|
||||||
// p.gainMoney(gain, "sleeves");
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cost of upgrading this sleeve's memory by a certain amount
|
* Returns the cost of upgrading this sleeve's memory by a certain amount
|
||||||
*/
|
*/
|
||||||
@ -252,41 +135,6 @@ export class Sleeve extends Person {
|
|||||||
return currCost * baseCost;
|
return currCost * baseCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets reputation gain for the current task
|
|
||||||
* Only applicable when working for company or faction
|
|
||||||
*/
|
|
||||||
// getRepGain(p: IPlayer): number {
|
|
||||||
// if (this.currentTask === SleeveTaskType.Company) {
|
|
||||||
// const companyName: string = this.currentTaskLocation;
|
|
||||||
// const company: Company | null = Companies[companyName];
|
|
||||||
// if (company == null) {
|
|
||||||
// console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`);
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
|
|
||||||
// if (companyPosition == null) {
|
|
||||||
// console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`);
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const jobPerformance: number = companyPosition.calculateJobPerformance(
|
|
||||||
// this.skills.hacking,
|
|
||||||
// this.skills.strength,
|
|
||||||
// this.skills.defense,
|
|
||||||
// this.skills.dexterity,
|
|
||||||
// this.skills.agility,
|
|
||||||
// this.skills.charisma,
|
|
||||||
// );
|
|
||||||
// const favorMult = 1 + company.favor / 100;
|
|
||||||
|
|
||||||
// return jobPerformance * this.mults.company_rep * favorMult;
|
|
||||||
// } else {
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
installAugmentation(aug: Augmentation): void {
|
installAugmentation(aug: Augmentation): void {
|
||||||
this.exp.hacking = 0;
|
this.exp.hacking = 0;
|
||||||
this.exp.strength = 0;
|
this.exp.strength = 0;
|
||||||
@ -312,7 +160,7 @@ export class Sleeve extends Person {
|
|||||||
this.exp.charisma = 0;
|
this.exp.charisma = 0;
|
||||||
|
|
||||||
// Reset task-related stuff
|
// Reset task-related stuff
|
||||||
this.resetTaskStatus(p);
|
this.stopWork(p);
|
||||||
this.shockRecovery(p);
|
this.shockRecovery(p);
|
||||||
|
|
||||||
// Reset augs and multipliers
|
// Reset augs and multipliers
|
||||||
@ -342,27 +190,10 @@ export class Sleeve extends Person {
|
|||||||
|
|
||||||
let cyclesUsed = this.storedCycles;
|
let cyclesUsed = this.storedCycles;
|
||||||
cyclesUsed = Math.min(cyclesUsed, 15);
|
cyclesUsed = Math.min(cyclesUsed, 15);
|
||||||
if (this.currentWork) {
|
|
||||||
this.currentWork.process(p, this, cyclesUsed);
|
|
||||||
this.storedCycles -= cyclesUsed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shock gradually goes towards 100
|
|
||||||
this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);
|
this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);
|
||||||
|
if (!this.currentWork) return;
|
||||||
this.updateStatLevels();
|
this.currentWork.process(p, this, cyclesUsed);
|
||||||
|
|
||||||
this.storedCycles -= cyclesUsed;
|
this.storedCycles -= cyclesUsed;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets all parameters used to keep information about the current task
|
|
||||||
*/
|
|
||||||
resetTaskStatus(p: IPlayer): void {
|
|
||||||
this.stopWork(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shockRecovery(p: IPlayer): boolean {
|
shockRecovery(p: IPlayer): boolean {
|
||||||
@ -645,58 +476,6 @@ export class Sleeve extends Person {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// contractGainRates(p: IPlayer, type: string, name: string): void {
|
|
||||||
// const bb = p.bladeburner;
|
|
||||||
// if (bb === null) {
|
|
||||||
// const errorLogText = `bladeburner is null`;
|
|
||||||
// console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const actionIdent = bb.getActionIdFromTypeAndName(type, name);
|
|
||||||
// if (actionIdent === null) {
|
|
||||||
// const errorLogText = `Invalid action: type='${type}' name='${name}'`;
|
|
||||||
// console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
|
|
||||||
// this.resetTaskStatus(p);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const action = bb.getActionObject(actionIdent);
|
|
||||||
// if (action === null) {
|
|
||||||
// const errorLogText = `Invalid action: type='${type}' name='${name}'`;
|
|
||||||
// console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`);
|
|
||||||
// this.resetTaskStatus(p);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const retValue = bb.getActionStats(action, true);
|
|
||||||
// this.gainRatesForTask.hack = retValue.hack;
|
|
||||||
// this.gainRatesForTask.str = retValue.str;
|
|
||||||
// this.gainRatesForTask.def = retValue.def;
|
|
||||||
// this.gainRatesForTask.dex = retValue.dex;
|
|
||||||
// this.gainRatesForTask.agi = retValue.agi;
|
|
||||||
// this.gainRatesForTask.cha = retValue.cha;
|
|
||||||
// const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1);
|
|
||||||
// this.gainRatesForTask.money =
|
|
||||||
// BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money;
|
|
||||||
// }
|
|
||||||
|
|
||||||
getBladeburnerActionTime(p: IPlayer, type: string, name: string): number {
|
|
||||||
//Maybe find workerscript and use original
|
|
||||||
const bb = p.bladeburner;
|
|
||||||
if (bb === null) {
|
|
||||||
const errorLogText = `bladeburner is null`;
|
|
||||||
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = bb.getActionTimeNetscriptFn(this, type, name);
|
|
||||||
if (typeof time === "string") {
|
|
||||||
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
|
|
||||||
console.error(`Function: sleeves.getBladeburnerActionTime; Message: '${errorLogText}'`);
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
takeDamage(amt: number): boolean {
|
takeDamage(amt: number): boolean {
|
||||||
if (typeof amt !== "number") {
|
if (typeof amt !== "number") {
|
||||||
console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
|
console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
|
||||||
|
@ -4,7 +4,7 @@ import { Sleeve } from "../Sleeve";
|
|||||||
import { applySleeveGains, Work, WorkType } from "./Work";
|
import { applySleeveGains, Work, WorkType } from "./Work";
|
||||||
import { CONSTANTS } from "../../../Constants";
|
import { CONSTANTS } from "../../../Constants";
|
||||||
import { GeneralActions } from "../../../Bladeburner/data/GeneralActions";
|
import { GeneralActions } from "../../../Bladeburner/data/GeneralActions";
|
||||||
import { applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats";
|
import { WorkStats } from "../../../Work/WorkStats";
|
||||||
|
|
||||||
interface SleeveBladeburnerWorkParams {
|
interface SleeveBladeburnerWorkParams {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -5,7 +5,7 @@ import { ClassType } from "../../../Work/ClassWork";
|
|||||||
import { LocationName } from "../../../Locations/data/LocationNames";
|
import { LocationName } from "../../../Locations/data/LocationNames";
|
||||||
import { calculateClassEarnings } from "../../../Work/formulas/Class";
|
import { calculateClassEarnings } from "../../../Work/formulas/Class";
|
||||||
import { Sleeve } from "../Sleeve";
|
import { Sleeve } from "../Sleeve";
|
||||||
import { applyWorkStats, applyWorkStatsExp, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
||||||
|
|
||||||
export const isSleeveClassWork = (w: Work | null): w is SleeveClassWork => w !== null && w.type === WorkType.CLASS;
|
export const isSleeveClassWork = (w: Work | null): w is SleeveClassWork => w !== null && w.type === WorkType.CLASS;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { LocationName } from "../../../Locations/data/LocationNames";
|
|||||||
import { Companies } from "../../../Company/Companies";
|
import { Companies } from "../../../Company/Companies";
|
||||||
import { Company } from "../../../Company/Company";
|
import { Company } from "../../../Company/Company";
|
||||||
import { calculateCompanyWorkStats } from "../../../Work/formulas/Company";
|
import { calculateCompanyWorkStats } from "../../../Work/formulas/Company";
|
||||||
import { applyWorkStats, applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats";
|
import { WorkStats } from "../../../Work/WorkStats";
|
||||||
import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing";
|
import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing";
|
||||||
|
|
||||||
interface SleeveCompanyWorkParams {
|
interface SleeveCompanyWorkParams {
|
||||||
|
@ -5,7 +5,7 @@ import { applySleeveGains, Work, WorkType } from "./Work";
|
|||||||
import { CrimeType } from "../../../utils/WorkType";
|
import { CrimeType } from "../../../utils/WorkType";
|
||||||
import { Crimes } from "../../../Crime/Crimes";
|
import { Crimes } from "../../../Crime/Crimes";
|
||||||
import { Crime } from "../../../Crime/Crime";
|
import { Crime } from "../../../Crime/Crime";
|
||||||
import { applyWorkStats, applyWorkStatsExp, newWorkStats, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
import { newWorkStats, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
||||||
import { CONSTANTS } from "../../../Constants";
|
import { CONSTANTS } from "../../../Constants";
|
||||||
|
|
||||||
export const isSleeveCrimeWork = (w: Work | null): w is SleeveCrimeWork => w !== null && w.type === WorkType.CRIME;
|
export const isSleeveCrimeWork = (w: Work | null): w is SleeveCrimeWork => w !== null && w.type === WorkType.CRIME;
|
||||||
|
@ -6,13 +6,13 @@ import { FactionWorkType } from "../../../Work/data/FactionWorkType";
|
|||||||
import { FactionNames } from "../../../Faction/data/FactionNames";
|
import { FactionNames } from "../../../Faction/data/FactionNames";
|
||||||
import { Factions } from "../../../Faction/Factions";
|
import { Factions } from "../../../Faction/Factions";
|
||||||
import { calculateFactionExp } from "../../../Work/formulas/Faction";
|
import { calculateFactionExp } from "../../../Work/formulas/Faction";
|
||||||
import { applyWorkStatsExp, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
|
||||||
import { Faction } from "../../../Faction/Faction";
|
import { Faction } from "../../../Faction/Faction";
|
||||||
import {
|
import {
|
||||||
getFactionFieldWorkRepGain,
|
getFactionFieldWorkRepGain,
|
||||||
getFactionSecurityWorkRepGain,
|
getFactionSecurityWorkRepGain,
|
||||||
getHackingWorkRepGain,
|
getHackingWorkRepGain,
|
||||||
} from "../../../PersonObjects/formulas/reputation";
|
} from "../../../PersonObjects/formulas/reputation";
|
||||||
|
import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
|
||||||
|
|
||||||
interface SleeveFactionWorkParams {
|
interface SleeveFactionWorkParams {
|
||||||
factionWorkType: FactionWorkType;
|
factionWorkType: FactionWorkType;
|
||||||
|
@ -21,7 +21,7 @@ export abstract class Work {
|
|||||||
abstract process(player: IPlayer, sleeve: Sleeve, cycles: number): number;
|
abstract process(player: IPlayer, sleeve: Sleeve, cycles: number): number;
|
||||||
abstract APICopy(): Record<string, unknown>;
|
abstract APICopy(): Record<string, unknown>;
|
||||||
abstract toJSON(): IReviverValue;
|
abstract toJSON(): IReviverValue;
|
||||||
finish(_player: IPlayer): void {
|
finish(__player: IPlayer): void {
|
||||||
/* left for children to implement */
|
/* left for children to implement */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
|
|||||||
const [abc, setABC] = useState(["------", "------", "------"]);
|
const [abc, setABC] = useState(["------", "------", "------"]);
|
||||||
|
|
||||||
function setTask(): void {
|
function setTask(): void {
|
||||||
props.sleeve.resetTaskStatus(player); // sets to idle
|
|
||||||
switch (abc[0]) {
|
switch (abc[0]) {
|
||||||
case "------":
|
case "------":
|
||||||
break;
|
break;
|
||||||
|
@ -12,7 +12,6 @@ import { ReputationRate } from "../../../ui/React/ReputationRate";
|
|||||||
import { use } from "../../../ui/Context";
|
import { use } from "../../../ui/Context";
|
||||||
|
|
||||||
import { Sleeve } from "../Sleeve";
|
import { Sleeve } from "../Sleeve";
|
||||||
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
|
|
||||||
import { isSleeveClassWork } from "../Work/SleeveClassWork";
|
import { isSleeveClassWork } from "../Work/SleeveClassWork";
|
||||||
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
||||||
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
||||||
|
@ -11,6 +11,14 @@ import { FactionNames } from "../../../Faction/data/FactionNames";
|
|||||||
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
|
||||||
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
|
||||||
import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
|
import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
|
||||||
|
import { isSleeveRecoveryWork } from "../Work/SleeveRecoveryWork";
|
||||||
|
import { isSleeveSynchroWork } from "../Work/SleeveSynchroWork";
|
||||||
|
import { isSleeveClassWork } from "../Work/SleeveClassWork";
|
||||||
|
import { isSleeveInfiltrateWork } from "../Work/SleeveInfiltrateWork";
|
||||||
|
import { isSleeveSupportWork } from "../Work/SleeveSupportWork";
|
||||||
|
import { ClassType } from "../../../Work/ClassWork";
|
||||||
|
import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
|
||||||
|
import { FactionWorkType } from "../../../Work/data/FactionWorkType";
|
||||||
|
|
||||||
const universitySelectorOptions: string[] = [
|
const universitySelectorOptions: string[] = [
|
||||||
"Study Computer Science",
|
"Study Computer Science",
|
||||||
@ -243,37 +251,88 @@ const canDo: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getABC(sleeve: Sleeve): [string, string, string] {
|
function getABC(sleeve: Sleeve): [string, string, string] {
|
||||||
return ["------", "------", "------"];
|
const w = sleeve.currentWork;
|
||||||
|
if (w === null) {
|
||||||
|
return ["------", "------", "------"];
|
||||||
|
}
|
||||||
|
|
||||||
// switch (sleeve.currentTask) {
|
if (isSleeveCompanyWork(w)) {
|
||||||
// case SleeveTaskType.Idle:
|
return ["Work for Company", w.companyName, "------"];
|
||||||
// case SleeveTaskType.Company:
|
}
|
||||||
// case SleeveTaskType.Faction: {
|
if (isSleeveFactionWork(w)) {
|
||||||
// }
|
let workType = "";
|
||||||
// case SleeveTaskType.Crime:
|
switch (w.factionWorkType) {
|
||||||
// return ["Commit Crime", sleeve.crimeType, "------"];
|
case FactionWorkType.HACKING:
|
||||||
// case SleeveTaskType.Class:
|
workType = "Hacking Contracts";
|
||||||
// case SleeveTaskType.Gym: {
|
break;
|
||||||
// switch (sleeve.gymStatType) {
|
case FactionWorkType.FIELD:
|
||||||
// case "none":
|
workType = "Field Work";
|
||||||
// return ["Idle", "------", "------"];
|
break;
|
||||||
// case "str":
|
case FactionWorkType.SECURITY:
|
||||||
// return ["Workout at Gym", "Train Strength", sleeve.currentTaskLocation];
|
workType = "Security Work";
|
||||||
// case "def":
|
break;
|
||||||
// return ["Workout at Gym", "Train Defense", sleeve.currentTaskLocation];
|
}
|
||||||
// case "dex":
|
return ["Work for Faction", w.factionName, workType];
|
||||||
// return ["Workout at Gym", "Train Dexterity", sleeve.currentTaskLocation];
|
}
|
||||||
// case "agi":
|
if (isSleeveBladeburnerWork(w)) {
|
||||||
// return ["Workout at Gym", "Train Agility", sleeve.currentTaskLocation];
|
if (w.actionType === "Contracts") {
|
||||||
// }
|
return ["Perform Bladeburner Actions", "Take on contracts", w.actionName];
|
||||||
// }
|
}
|
||||||
// case SleeveTaskType.Bladeburner:
|
switch (w.actionName) {
|
||||||
// return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
|
case "Field Analysis":
|
||||||
// case SleeveTaskType.Recovery:
|
return ["Perform Bladeburner Actions", "Field Analysis", "------"];
|
||||||
// return ["Shock Recovery", "------", "------"];
|
case "Diplomacy":
|
||||||
// case SleeveTaskType.Synchro:
|
return ["Perform Bladeburner Actions", "Diplomacy", "------"];
|
||||||
// return ["Synchronize", "------", "------"];
|
case "Recruitment":
|
||||||
// }
|
return ["Perform Bladeburner Actions", "Recruitment", "------"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSleeveClassWork(w)) {
|
||||||
|
switch (w.classType) {
|
||||||
|
case ClassType.StudyComputerScience:
|
||||||
|
return ["Take University Course", "Study Computer Science", w.location];
|
||||||
|
case ClassType.DataStructures:
|
||||||
|
return ["Take University Course", "Data Structures", w.location];
|
||||||
|
case ClassType.Networks:
|
||||||
|
return ["Take University Course", "Networks", w.location];
|
||||||
|
case ClassType.Algorithms:
|
||||||
|
return ["Take University Course", "Algorithms", w.location];
|
||||||
|
case ClassType.Management:
|
||||||
|
return ["Take University Course", "Management", w.location];
|
||||||
|
case ClassType.Leadership:
|
||||||
|
return ["Take University Course", "Leadership", w.location];
|
||||||
|
case ClassType.GymStrength:
|
||||||
|
return ["Workout at Gym", "Train Strength", w.location];
|
||||||
|
case ClassType.GymDefense:
|
||||||
|
return ["Workout at Gym", "Train Defense", w.location];
|
||||||
|
case ClassType.GymDexterity:
|
||||||
|
return ["Workout at Gym", "Train Dexterity", w.location];
|
||||||
|
case ClassType.GymAgility:
|
||||||
|
return ["Workout at Gym", "Train Agility", w.location];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isSleeveCrimeWork(w)) {
|
||||||
|
return [
|
||||||
|
"Commit Crime",
|
||||||
|
Object.values(Crimes).find((crime) => crime.type === w.crimeType)?.name ?? "Shoplift",
|
||||||
|
"------",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (isSleeveSupportWork(w)) {
|
||||||
|
return ["Perform Bladeburner Actions", "Support main sleeve", "------"];
|
||||||
|
}
|
||||||
|
if (isSleeveInfiltrateWork(w)) {
|
||||||
|
return ["Perform Bladeburner Actions", "Infiltrate synthoids", "------"];
|
||||||
|
}
|
||||||
|
if (isSleeveRecoveryWork(w)) {
|
||||||
|
return ["Shock Recovery", "------", "------"];
|
||||||
|
}
|
||||||
|
if (isSleeveSynchroWork(w)) {
|
||||||
|
return ["Synchronize", "------", "------"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["------", "------", "------"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TaskSelector(props: IProps): React.ReactElement {
|
export function TaskSelector(props: IProps): React.ReactElement {
|
||||||
|
@ -26,7 +26,7 @@ export function TravelModal(props: IProps): React.ReactElement {
|
|||||||
}
|
}
|
||||||
props.sleeve.city = city as CityName;
|
props.sleeve.city = city as CityName;
|
||||||
player.loseMoney(CONSTANTS.TravelCost, "sleeve");
|
player.loseMoney(CONSTANTS.TravelCost, "sleeve");
|
||||||
props.sleeve.resetTaskStatus(player);
|
props.sleeve.stopWork(player);
|
||||||
props.rerender();
|
props.rerender();
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}
|
}
|
||||||
|
@ -480,6 +480,9 @@ function evaluateVersionCompatibility(ver: string | number): void {
|
|||||||
anyPlayer.exp.intelligence = anyPlayer.intelligence_exp;
|
anyPlayer.exp.intelligence = anyPlayer.intelligence_exp;
|
||||||
v2APIBreak();
|
v2APIBreak();
|
||||||
}
|
}
|
||||||
|
if (ver < 23) {
|
||||||
|
anyPlayer.currentWork = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
24
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -60,10 +60,6 @@ interface Player {
|
|||||||
money: number;
|
money: number;
|
||||||
city: string;
|
city: string;
|
||||||
location: string;
|
location: string;
|
||||||
hasWseAccount: boolean;
|
|
||||||
hasTixApiAccess: boolean;
|
|
||||||
has4SData: boolean;
|
|
||||||
has4SDataTixApi: boolean;
|
|
||||||
bitNodeN: number;
|
bitNodeN: number;
|
||||||
totalPlaytime: number;
|
totalPlaytime: number;
|
||||||
playtimeSinceLastAug: number;
|
playtimeSinceLastAug: number;
|
||||||
@ -1101,6 +1097,26 @@ export interface NetscriptPort {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface TIX {
|
export interface TIX {
|
||||||
|
/**
|
||||||
|
* Returns true if the player has access to a WSE Account
|
||||||
|
* @remarks RAM cost: 0.05 GB
|
||||||
|
*/
|
||||||
|
hasWSEAccount(): boolean;
|
||||||
|
/**
|
||||||
|
* Returns true if the player has access to the TIX API
|
||||||
|
* @remarks RAM cost: 0.05 GB
|
||||||
|
*/
|
||||||
|
hasTIXAPIAccess(): boolean;
|
||||||
|
/**
|
||||||
|
* Returns true if the player has access to the 4S Data
|
||||||
|
* @remarks RAM cost: 0.05 GB
|
||||||
|
*/
|
||||||
|
has4SData(): boolean;
|
||||||
|
/**
|
||||||
|
* Returns true if the player has access to the 4SData TIX API
|
||||||
|
* @remarks RAM cost: 0.05 GB
|
||||||
|
*/
|
||||||
|
has4SDataTIXAPI(): boolean;
|
||||||
/**
|
/**
|
||||||
* Returns an array of the symbols of the tradable stocks
|
* Returns an array of the symbols of the tradable stocks
|
||||||
*
|
*
|
||||||
|
@ -6,7 +6,7 @@ import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluenci
|
|||||||
import { LocationName } from "../Locations/data/LocationNames";
|
import { LocationName } from "../Locations/data/LocationNames";
|
||||||
import { calculateCompanyWorkStats } from "./formulas/Company";
|
import { calculateCompanyWorkStats } from "./formulas/Company";
|
||||||
import { Companies } from "../Company/Companies";
|
import { Companies } from "../Company/Companies";
|
||||||
import { applyWorkStats, WorkStats } from "./WorkStats";
|
import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
|
||||||
import { Company } from "../Company/Company";
|
import { Company } from "../Company/Company";
|
||||||
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
||||||
import { Reputation } from "../ui/React/Reputation";
|
import { Reputation } from "../ui/React/Reputation";
|
||||||
@ -38,7 +38,7 @@ export class CompanyWork extends Work {
|
|||||||
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
|
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
|
||||||
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
|
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
|
||||||
}
|
}
|
||||||
return calculateCompanyWorkStats(player, player, this.getCompany());
|
return scaleWorkStats(calculateCompanyWorkStats(player, player, this.getCompany()), focusBonus);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(player: IPlayer, cycles: number): boolean {
|
process(player: IPlayer, cycles: number): boolean {
|
||||||
|
@ -4,7 +4,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
|
|||||||
import { WorkStats } from "../WorkStats";
|
import { WorkStats } from "../WorkStats";
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
import { CONSTANTS } from "../../Constants";
|
import { CONSTANTS } from "../../Constants";
|
||||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
|
||||||
import { IPerson } from "src/PersonObjects/IPerson";
|
import { IPerson } from "src/PersonObjects/IPerson";
|
||||||
|
|
||||||
export const calculateCompanyWorkStats = (player: IPlayer, worker: IPerson, company: Company): WorkStats => {
|
export const calculateCompanyWorkStats = (player: IPlayer, worker: IPerson, company: Company): WorkStats => {
|
||||||
|
@ -33,7 +33,6 @@ import { Settings } from "./Settings/Settings";
|
|||||||
import { ThemeEvents } from "./Themes/ui/Theme";
|
import { ThemeEvents } from "./Themes/ui/Theme";
|
||||||
import { initSymbolToStockMap, processStockPrices } from "./StockMarket/StockMarket";
|
import { initSymbolToStockMap, processStockPrices } from "./StockMarket/StockMarket";
|
||||||
import { Terminal } from "./Terminal";
|
import { Terminal } from "./Terminal";
|
||||||
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
|
|
||||||
|
|
||||||
import { Money } from "./ui/React/Money";
|
import { Money } from "./ui/React/Money";
|
||||||
import { Hashes } from "./ui/React/Hashes";
|
import { Hashes } from "./ui/React/Hashes";
|
||||||
@ -121,20 +120,7 @@ const Engine: {
|
|||||||
|
|
||||||
// Sleeves
|
// Sleeves
|
||||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||||
if (Player.sleeves[i] instanceof Sleeve) {
|
Player.sleeves[i].process(Player, numCycles);
|
||||||
const expForOtherSleeves = Player.sleeves[i].process(Player, numCycles);
|
|
||||||
|
|
||||||
// This sleeve earns experience for other sleeves
|
|
||||||
if (expForOtherSleeves == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let j = 0; j < Player.sleeves.length; ++j) {
|
|
||||||
if (j === i) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCycles, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counters
|
// Counters
|
||||||
@ -359,20 +345,7 @@ const Engine: {
|
|||||||
|
|
||||||
// Sleeves offline progress
|
// Sleeves offline progress
|
||||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||||
if (Player.sleeves[i] instanceof Sleeve) {
|
Player.sleeves[i].process(Player, numCyclesOffline);
|
||||||
const expForOtherSleeves = Player.sleeves[i].process(Player, numCyclesOffline);
|
|
||||||
|
|
||||||
// This sleeve earns experience for other sleeves
|
|
||||||
if (expForOtherSleeves == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let j = 0; j < Player.sleeves.length; ++j) {
|
|
||||||
if (j === i) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCyclesOffline, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total playtime
|
// Update total playtime
|
||||||
|
@ -102,6 +102,10 @@ const getPlayerFields = [
|
|||||||
"intelligence_exp",
|
"intelligence_exp",
|
||||||
"hp",
|
"hp",
|
||||||
"max_hp",
|
"max_hp",
|
||||||
|
"hasWseAccount",
|
||||||
|
"hasTixApiAccess",
|
||||||
|
"has4SData",
|
||||||
|
"has4SDataTixApi",
|
||||||
];
|
];
|
||||||
|
|
||||||
const mults = [
|
const mults = [
|
||||||
|
Loading…
Reference in New Issue
Block a user