mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-18 23:38:35 +02:00
Compare commits
5 Commits
c21d1f44b2
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95af138c39 | ||
|
|
f5bbc26495 | ||
|
|
f8ec7f4294 | ||
|
|
c06c6590c9 | ||
|
|
45bce6e45e |
@@ -4,19 +4,10 @@ import { AchievementList } from "./AchievementList";
|
|||||||
import { achievements } from "./Achievements";
|
import { achievements } from "./Achievements";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { makeStyles } from "tss-react/mui";
|
|
||||||
|
|
||||||
const useStyles = makeStyles()({
|
|
||||||
root: {
|
|
||||||
width: 50,
|
|
||||||
userSelect: "none",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function AchievementsRoot(): JSX.Element {
|
export function AchievementsRoot(): JSX.Element {
|
||||||
const { classes } = useStyles();
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Typography variant="h4">Achievements</Typography>
|
<Typography variant="h4">Achievements</Typography>
|
||||||
<Box mx={2}>
|
<Box mx={2}>
|
||||||
<Typography>
|
<Typography>
|
||||||
|
|||||||
@@ -44,12 +44,18 @@ export function resolveTeamCasualties(action: TeamActionWithCasualties, team: Op
|
|||||||
*/
|
*/
|
||||||
const losses =
|
const losses =
|
||||||
minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties;
|
minCasualties <= maxCasualties ? team.getTeamCasualtiesRoll(minCasualties, maxCasualties) : minCasualties;
|
||||||
team.teamSize -= losses;
|
// Calculate the new teamSize in a temporary variable and call the setter team.teamSize ONCE.
|
||||||
if (team.teamSize < team.sleeveSize) {
|
// Note that it's important to call the setter only once; otherwise, the team count of each operation won't be reset
|
||||||
team.killRandomSupportingSleeves(team.sleeveSize - team.teamSize);
|
// correctly.
|
||||||
|
// For example, if _teamSize is 9 (1 team member + 8 support sleeves) and "losses" is 9, calling the setter with
|
||||||
|
// (team.teamSize - losses) will set teamCount of ops/blackOps to 0 while it should be 8.
|
||||||
|
let newTeamSize = team.teamSize - losses;
|
||||||
|
if (newTeamSize < team.sleeveSize) {
|
||||||
|
team.killRandomSupportingSleeves(team.sleeveSize - newTeamSize);
|
||||||
// If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize.
|
// If this happens, all team members died and some sleeves took damage. In this case, teamSize = sleeveSize.
|
||||||
team.teamSize = team.sleeveSize;
|
newTeamSize = team.sleeveSize;
|
||||||
}
|
}
|
||||||
|
team.teamSize = newTeamSize;
|
||||||
team.teamLost += losses;
|
team.teamLost += losses;
|
||||||
|
|
||||||
return losses;
|
return losses;
|
||||||
|
|||||||
@@ -72,7 +72,31 @@ export class Bladeburner implements OperationTeam {
|
|||||||
skillPoints = 0;
|
skillPoints = 0;
|
||||||
totalSkillPoints = 0;
|
totalSkillPoints = 0;
|
||||||
|
|
||||||
teamSize = 0;
|
/**
|
||||||
|
* Do NOT directly read and write this field. You must use the getter/setter.
|
||||||
|
* We use _teamSize instead of a private field #teamSize to reduce the complexity of saving/loading code.
|
||||||
|
*/
|
||||||
|
_teamSize = 0;
|
||||||
|
get teamSize() {
|
||||||
|
return this._teamSize;
|
||||||
|
}
|
||||||
|
set teamSize(value: number) {
|
||||||
|
// Ensure teamSize is a non-negative integer.
|
||||||
|
let newSize = value;
|
||||||
|
if (!Number.isInteger(newSize) || newSize < 0) {
|
||||||
|
newSize = 0;
|
||||||
|
}
|
||||||
|
// Early return if there is no change.
|
||||||
|
if (this._teamSize === newSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._teamSize = newSize;
|
||||||
|
// Reduce teamCount of actions if it's greater than the team size.
|
||||||
|
for (const action of [...Object.values(this.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
action.teamCount = Math.min(action.teamCount, this._teamSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get sleeveSize() {
|
get sleeveSize() {
|
||||||
return Player.sleevesSupportingBladeburner().length;
|
return Player.sleevesSupportingBladeburner().length;
|
||||||
}
|
}
|
||||||
@@ -1488,6 +1512,10 @@ export class Bladeburner implements OperationTeam {
|
|||||||
bladeburner.maxStamina = 1;
|
bladeburner.maxStamina = 1;
|
||||||
bladeburner.calculateMaxStamina();
|
bladeburner.calculateMaxStamina();
|
||||||
}
|
}
|
||||||
|
// "_teamSize" was "teamSize" in pre-v3 versions.
|
||||||
|
if ("teamSize" in value.data && Number.isFinite(value.data.teamSize)) {
|
||||||
|
bladeburner.teamSize = value.data.teamSize as number;
|
||||||
|
}
|
||||||
return bladeburner;
|
return bladeburner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,25 +15,26 @@ interface TeamSizeModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement {
|
export function TeamSizeModal({ bladeburner, action, open, onClose }: TeamSizeModalProps): React.ReactElement {
|
||||||
const [teamSize, setTeamSize] = useState<number | undefined>();
|
const [teamSize, setTeamSize] = useState(0);
|
||||||
|
|
||||||
function confirmTeamSize(event: React.FormEvent): void {
|
function confirmTeamSize(event: React.FormEvent): void {
|
||||||
// Prevent reloading page when submitting form
|
// Prevent reloading page when submitting form
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (teamSize === undefined) return;
|
if (!Number.isInteger(teamSize) || teamSize < 0) {
|
||||||
const num = Math.round(teamSize);
|
dialogBoxCreate("Invalid value entered for number of Team Members (must be a non-negative integer)");
|
||||||
if (isNaN(num) || num < 0) {
|
return;
|
||||||
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric and non-negative)");
|
|
||||||
} else {
|
|
||||||
action.teamCount = num;
|
|
||||||
}
|
}
|
||||||
|
action.teamCount = teamSize;
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
|
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
const x = parseFloat(event.target.value);
|
const newTeamSize = Number(event.target.value);
|
||||||
if (x > bladeburner.teamSize) setTeamSize(bladeburner.teamSize);
|
if (newTeamSize > bladeburner.teamSize) {
|
||||||
else setTeamSize(x);
|
setTeamSize(bladeburner.teamSize);
|
||||||
|
} else {
|
||||||
|
setTeamSize(newTeamSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function NetworkDisplayWrapper(): React.ReactElement {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const clearSubscription = DarknetEvents.subscribe(() => updateDisplay());
|
const clearSubscription = DarknetEvents.subscribe(() => updateDisplay());
|
||||||
draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault());
|
draggableBackground.current?.addEventListener("wheel", (e) => e.preventDefault(), { passive: false });
|
||||||
scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll);
|
scrollTo(DarknetState.netViewTopScroll, DarknetState.netViewLeftScroll);
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
|
|
||||||
|
|||||||
@@ -4,37 +4,37 @@ let pidCounter = 1;
|
|||||||
|
|
||||||
/** Find and return the next available PID for a script */
|
/** Find and return the next available PID for a script */
|
||||||
export function generateNextPid(): number {
|
export function generateNextPid(): number {
|
||||||
let tempCounter = pidCounter;
|
let pidCandidate = pidCounter;
|
||||||
|
|
||||||
// Cap the number of search iterations at some arbitrary value to avoid
|
// Cap the number of search iterations at some arbitrary value to avoid
|
||||||
// infinite loops. We'll assume that players wont have 1mil+ running scripts
|
// infinite loops. We'll assume that players won't have a million running scripts.
|
||||||
let found = false;
|
for (let attemptCounter = 0; attemptCounter < 1e6; ++attemptCounter, ++pidCandidate) {
|
||||||
for (let i = 0; i < 1e6; ) {
|
// ensure the candidate PID is a safe integer
|
||||||
if (!workerScripts.has(tempCounter + i)) {
|
if (pidCandidate >= Number.MAX_SAFE_INTEGER) {
|
||||||
found = true;
|
pidCandidate = 1;
|
||||||
tempCounter = tempCounter + i;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
// ensure the PID is not in use
|
||||||
if (i === Number.MAX_SAFE_INTEGER - 1) {
|
if (workerScripts.has(pidCandidate)) {
|
||||||
i = 1;
|
continue;
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
// found a PID that's not in use
|
||||||
|
pidCounter = pidCandidate + 1;
|
||||||
|
return pidCandidate;
|
||||||
}
|
}
|
||||||
|
// ran out of attempts without finding an unused PID :-(
|
||||||
if (found) {
|
return -1;
|
||||||
pidCounter = tempCounter + 1;
|
|
||||||
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
|
|
||||||
pidCounter = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempCounter;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the PID counter to 1.
|
||||||
|
*
|
||||||
|
* Note that the list of recently finished scripts has to be
|
||||||
|
* cleared (`recentScripts.splice(0)`) when resetting the PID counter.
|
||||||
|
* Otherwise scripts which re-use a PID that is still in the
|
||||||
|
* list of recent scripts do not show up there when they finish
|
||||||
|
* (if the previous script with that PID is still in the list at that point),
|
||||||
|
* because the function `AddRecentScript` de-duplicates scripts by PID.
|
||||||
|
*/
|
||||||
export function resetPidCounter(): void {
|
export function resetPidCounter(): void {
|
||||||
pidCounter = 1;
|
pidCounter = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import { clampNumber } from "../../utils/helpers/clampNumber";
|
|||||||
* stat level. Stat-agnostic (same formula for every stat)
|
* stat level. Stat-agnostic (same formula for every stat)
|
||||||
*/
|
*/
|
||||||
export function calculateSkill(exp: number, mult = 1): number {
|
export function calculateSkill(exp: number, mult = 1): number {
|
||||||
|
// Mult can be 0 in BN12 when the player has a very high SF12 level. In this case, the skill level will never change
|
||||||
|
// from its initial value (1 for most stats, except intelligence).
|
||||||
|
if (mult === 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200));
|
const value = Math.floor(mult * (32 * Math.log(exp + 534.6) - 200));
|
||||||
return clampNumber(value, 1);
|
return clampNumber(value, 1);
|
||||||
}
|
}
|
||||||
@@ -12,7 +17,7 @@ export function calculateSkill(exp: number, mult = 1): number {
|
|||||||
export function calculateExp(skill: number, mult = 1): number {
|
export function calculateExp(skill: number, mult = 1): number {
|
||||||
const floorSkill = Math.floor(skill);
|
const floorSkill = Math.floor(skill);
|
||||||
let value = Math.exp((skill / mult + 200) / 32) - 534.6;
|
let value = Math.exp((skill / mult + 200) / 32) - 534.6;
|
||||||
if (skill === floorSkill && Number.isFinite(skill)) {
|
if (skill === floorSkill && Number.isFinite(skill) && Number.isFinite(value)) {
|
||||||
// Check for floating point rounding issues that would cause the inverse
|
// Check for floating point rounding issues that would cause the inverse
|
||||||
// operation to return the wrong result.
|
// operation to return the wrong result.
|
||||||
let calcSkill = calculateSkill(value, mult);
|
let calcSkill = calculateSkill(value, mult);
|
||||||
|
|||||||
@@ -190,6 +190,8 @@ export function prestigeAugmentation(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear recent scripts
|
||||||
|
recentScripts.splice(0);
|
||||||
resetPidCounter();
|
resetPidCounter();
|
||||||
ProgramsSeen.clear();
|
ProgramsSeen.clear();
|
||||||
InvitationsSeen.clear();
|
InvitationsSeen.clear();
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ const Engine = {
|
|||||||
messages: 150,
|
messages: 150,
|
||||||
mechanicProcess: 5, // Process Bladeburner
|
mechanicProcess: 5, // Process Bladeburner
|
||||||
contractGeneration: 3000, // Generate Coding Contracts
|
contractGeneration: 3000, // Generate Coding Contracts
|
||||||
achievementsCounter: 60, // Check if we have new achievements
|
achievementsCounter: 5, // Check if we have new achievements
|
||||||
},
|
},
|
||||||
|
|
||||||
decrementAllCounters: function (numCycles = 1) {
|
decrementAllCounters: function (numCycles = 1) {
|
||||||
@@ -215,7 +215,7 @@ const Engine = {
|
|||||||
|
|
||||||
if (Engine.Counters.achievementsCounter <= 0) {
|
if (Engine.Counters.achievementsCounter <= 0) {
|
||||||
calculateAchievements();
|
calculateAchievements();
|
||||||
Engine.Counters.achievementsCounter = 300;
|
Engine.Counters.achievementsCounter = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This **MUST** remain the last block in the function!
|
// This **MUST** remain the last block in the function!
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Player, setPlayer } from "@player";
|
import { Player, setPlayer } from "@player";
|
||||||
import { FormatsNeedToChange } from "../../../src/ui/formatNumber";
|
|
||||||
import type { ActionIdFor } from "../../../src/Bladeburner/Types";
|
import type { ActionIdFor } from "../../../src/Bladeburner/Types";
|
||||||
import type { Bladeburner } from "../../../src/Bladeburner/Bladeburner";
|
import type { Bladeburner } from "../../../src/Bladeburner/Bladeburner";
|
||||||
import { BlackOperation } from "../../../src/Bladeburner/Actions/BlackOperation";
|
import { BlackOperation } from "../../../src/Bladeburner/Actions/BlackOperation";
|
||||||
@@ -9,6 +8,10 @@ import { SleeveSupportWork } from "../../../src/PersonObjects/Sleeve/Work/Sleeve
|
|||||||
import { BladeburnerBlackOpName, BladeburnerContractName, BladeburnerOperationName } from "@enums";
|
import { BladeburnerBlackOpName, BladeburnerContractName, BladeburnerOperationName } from "@enums";
|
||||||
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
|
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
|
||||||
import { recalculateNumberOfOwnedSleeves } from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases";
|
import { recalculateNumberOfOwnedSleeves } from "../../../src/PersonObjects/Sleeve/SleeveCovenantPurchases";
|
||||||
|
import { initGameEnvironment } from "../Utilities";
|
||||||
|
import { BlackOperations } from "../../../src/Bladeburner/data/BlackOperations";
|
||||||
|
|
||||||
|
initGameEnvironment();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You may want to use hook to help with debugging
|
* You may want to use hook to help with debugging
|
||||||
@@ -27,11 +30,6 @@ describe("Bladeburner Team", () => {
|
|||||||
let inst: Bladeburner;
|
let inst: Bladeburner;
|
||||||
let action: BlackOperation | Operation;
|
let action: BlackOperation | Operation;
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
/* Initialise Formatters. Dependency of Bladeburner */
|
|
||||||
FormatsNeedToChange.emit();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setPlayer(new PlayerObject());
|
setPlayer(new PlayerObject());
|
||||||
Player.init();
|
Player.init();
|
||||||
@@ -133,6 +131,51 @@ describe("Bladeburner Team", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Check teamSize and teamCount", () => {
|
||||||
|
test("Failing actions", () => {
|
||||||
|
teamSize(10);
|
||||||
|
startAction(OP);
|
||||||
|
forceMaxCasualties();
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
action.teamCount = 10;
|
||||||
|
expect(action.teamCount).toStrictEqual(10);
|
||||||
|
}
|
||||||
|
actionFails();
|
||||||
|
expect(inst.teamSize).toStrictEqual(0);
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
expect(action.teamCount).toStrictEqual(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test("Sleeves", () => {
|
||||||
|
teamSize(1);
|
||||||
|
supportingSleeves(8);
|
||||||
|
expect(inst.teamSize).toStrictEqual(9);
|
||||||
|
startAction(OP);
|
||||||
|
forceMaxCasualties();
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
action.teamCount = 9;
|
||||||
|
expect(action.teamCount).toStrictEqual(9);
|
||||||
|
}
|
||||||
|
actionFails();
|
||||||
|
// The teamCount of all operations/black operations should be 8, not 0.
|
||||||
|
assertSleevesHaveBeenShocked();
|
||||||
|
expect(inst.teamSize).toStrictEqual(8);
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
expect(action.teamCount).toStrictEqual(8);
|
||||||
|
}
|
||||||
|
Player.sleeves[0].stopWork();
|
||||||
|
expect(inst.teamSize).toStrictEqual(7);
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
expect(action.teamCount).toStrictEqual(7);
|
||||||
|
}
|
||||||
|
Player.sleeves[0].startWork(new SleeveSupportWork());
|
||||||
|
expect(inst.teamSize).toStrictEqual(8);
|
||||||
|
for (const action of [...Object.values(inst.operations), ...Object.values(BlackOperations)]) {
|
||||||
|
expect(action.teamCount).toStrictEqual(7);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function teamSize(n: number) {
|
function teamSize(n: number) {
|
||||||
inst.teamSize = n;
|
inst.teamSize = n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"bladeburner": {
|
"bladeburner": {
|
||||||
"ctor": "Bladeburner",
|
"ctor": "Bladeburner",
|
||||||
"data": {
|
"data": {
|
||||||
|
"_teamSize": 0,
|
||||||
"action": null,
|
"action": null,
|
||||||
"actionTimeCurrent": 0,
|
"actionTimeCurrent": 0,
|
||||||
"actionTimeOverflow": 0,
|
"actionTimeOverflow": 0,
|
||||||
@@ -281,7 +282,6 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
|||||||
"staminaBonus": 0,
|
"staminaBonus": 0,
|
||||||
"storedCycles": 0,
|
"storedCycles": 0,
|
||||||
"teamLost": 0,
|
"teamLost": 0,
|
||||||
"teamSize": 0,
|
|
||||||
"totalSkillPoints": 666,
|
"totalSkillPoints": 666,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user