DEVMENU: Easier to add/remove sleeves (#908)

This commit is contained in:
Sphyxis 2023-12-04 12:36:00 -07:00 committed by GitHub
parent 76f0f3d6d3
commit 79b0f83b5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 30 deletions

@ -27,19 +27,23 @@ import { AchievementsDev } from "./DevMenu/ui/AchievementsDev";
import { EntropyDev } from "./DevMenu/ui/EntropyDev"; import { EntropyDev } from "./DevMenu/ui/EntropyDev";
import { Exploit } from "./Exploits/Exploit"; import { Exploit } from "./Exploits/Exploit";
import { useRerender } from "./ui/React/hooks";
export function DevMenuRoot(): React.ReactElement { export function DevMenuRoot(): React.ReactElement {
useEffect(() => { useEffect(() => {
Player.giveExploit(Exploit.YoureNotMeantToAccessThis); Player.giveExploit(Exploit.YoureNotMeantToAccessThis);
}, []); }, []);
// Pass rerender to certain subpages in case certain tabs are now valid/invalid due to changes made on those pages
// Rerender periodically in case game state changes (e.g. player starts gang or buys wse account through a script)
const rerender = useRerender(400);
return ( return (
<> <>
<Typography>Development Menu - Only meant to be used for testing/debugging</Typography> <Typography>Development Menu - Only meant to be used for testing/debugging</Typography>
<General /> <General parentRerender={rerender} />
<StatsDev /> <StatsDev />
<FactionsDev /> <FactionsDev />
<AugmentationsDev /> <AugmentationsDev />
<SourceFilesDev /> <SourceFilesDev parentRerender={rerender} />
<ProgramsDev /> <ProgramsDev />
<ServersDev /> <ServersDev />
<CompaniesDev /> <CompaniesDev />

@ -23,11 +23,10 @@ import { Page } from "../../ui/Router";
import { Bladeburner } from "../../Bladeburner/Bladeburner"; import { Bladeburner } from "../../Bladeburner/Bladeburner";
import { GangConstants } from "../../Gang/data/Constants"; import { GangConstants } from "../../Gang/data/Constants";
import { checkForMessagesToSend } from "../../Message/MessageHelpers"; import { checkForMessagesToSend } from "../../Message/MessageHelpers";
import { ThemeEvents } from "../../Themes/ui/Theme";
import { getEnumHelper } from "../../utils/EnumHelper"; import { getEnumHelper } from "../../utils/EnumHelper";
import { formatRam } from "../../ui/formatNumber"; import { formatRam } from "../../ui/formatNumber";
export function General(): React.ReactElement { export function General({ parentRerender }: { parentRerender: () => void }): React.ReactElement {
const rerender = useRerender(400); const rerender = useRerender(400);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [corporationName, setCorporationName] = useState(""); const [corporationName, setCorporationName] = useState("");
@ -64,38 +63,32 @@ export function General(): React.ReactElement {
// Corp functions // Corp functions
const createCorporation = () => { const createCorporation = () => {
Player.startCorporation(corporationName, false); Player.startCorporation(corporationName, false);
// Rerender so the corp menu option will show up immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
const destroyCorporation = () => { const destroyCorporation = () => {
Player.corporation = null; Player.corporation = null;
// Rerender so the corp menu option will be removed immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
// Blade functions // Blade functions
const joinBladeburner = () => { const joinBladeburner = () => {
Player.bladeburner = new Bladeburner(); Player.bladeburner = new Bladeburner();
// Rerender so the blade menu option will show up immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
const leaveBladeburner = () => { const leaveBladeburner = () => {
Player.bladeburner = null; Player.bladeburner = null;
// Rerender so the blade menu option will be removed immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
// Gang functions // Gang functions
const startGang = () => { const startGang = () => {
const isHacking = gangFaction === FactionName.NiteSec || gangFaction === FactionName.TheBlackHand; const isHacking = gangFaction === FactionName.NiteSec || gangFaction === FactionName.TheBlackHand;
Player.startGang(gangFaction, isHacking); Player.startGang(gangFaction, isHacking);
// Rerender so the gang menu option will show up immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
const stopGang = () => { const stopGang = () => {
Player.gang = null; Player.gang = null;
// Rerender so the gang menu option will be removed immediately on the devmenu page selection parentRerender();
ThemeEvents.emit();
}; };
const setGangFactionDropdown = (event: SelectChangeEvent) => { const setGangFactionDropdown = (event: SelectChangeEvent) => {
// Todo: Make this a more specific check when a GangName enumlike is added // Todo: Make this a more specific check when a GangName enumlike is added

@ -71,6 +71,11 @@ export function SleevesDev(): React.ReactElement {
<Button onClick={sleeveSyncClearAll}>Clear all</Button> <Button onClick={sleeveSyncClearAll}>Clear all</Button>
</td> </td>
</tr> </tr>
<tr>
<td>
<Typography>Total:</Typography>
</td>
</tr>
<tr> <tr>
<td colSpan={3}> <td colSpan={3}>
<Adjuster <Adjuster

@ -5,7 +5,9 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { Player } from "@player"; import { Player } from "@player";
import { useRerender } from "../../ui/React/hooks"; import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip";
import { MaxSleevesFromCovenant } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
// Update as additional BitNodes get implemented // Update as additional BitNodes get implemented
const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; const validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
@ -16,11 +18,11 @@ const useStyles = makeStyles({
}, },
extraInfo: { extraInfo: {
marginLeft: "0.5em", marginLeft: "0.5em",
marginRight: "0.5em",
}, },
}); });
export function SourceFilesDev(): React.ReactElement { export function SourceFilesDev({ parentRerender }: { parentRerender: () => void }): React.ReactElement {
const rerender = useRerender();
const classes = useStyles(); const classes = useStyles();
const setSF = useCallback( const setSF = useCallback(
@ -30,18 +32,34 @@ export function SourceFilesDev(): React.ReactElement {
} }
if (sfLvl === 0) { if (sfLvl === 0) {
Player.sourceFiles.delete(sfN); Player.sourceFiles.delete(sfN);
rerender(); if (sfN === 10) Sleeve.recalculateNumOwned();
parentRerender();
return; return;
} }
Player.sourceFiles.set(sfN, sfLvl); Player.sourceFiles.set(sfN, sfLvl);
rerender(); if (sfN === 10) Sleeve.recalculateNumOwned();
parentRerender();
}, },
[rerender], [parentRerender],
); );
const setAllSF = useCallback((sfLvl: number) => () => validSFN.forEach((sfN) => setSF(sfN, sfLvl)()), [setSF]); const setAllSF = useCallback((sfLvl: number) => () => validSFN.forEach((sfN) => setSF(sfN, sfLvl)()), [setSF]);
const clearExploits = () => (Player.exploits = []); const clearExploits = () => (Player.exploits = []);
const addSleeve = useCallback(() => {
if (Player.sleevesFromCovenant >= 10) return;
Player.sleevesFromCovenant += 1;
Sleeve.recalculateNumOwned();
parentRerender();
}, [parentRerender]);
const removeSleeve = useCallback(() => {
if (Player.sleevesFromCovenant <= 0) return;
Player.sleevesFromCovenant -= 1;
Sleeve.recalculateNumOwned();
parentRerender();
}, [parentRerender]);
const devLvls = [0, 1, 2, 3]; const devLvls = [0, 1, 2, 3];
const buttonRow = (sfN?: number) => { const buttonRow = (sfN?: number) => {
@ -66,6 +84,23 @@ export function SourceFilesDev(): React.ReactElement {
</Button> </Button>
))} ))}
{sfN && <Typography className={classes.extraInfo}>{`Level: ${level}`}</Typography>} {sfN && <Typography className={classes.extraInfo}>{`Level: ${level}`}</Typography>}
{sfN === 10 && (
<>
<ButtonWithTooltip
disabledTooltip={Player.sleevesFromCovenant <= 0 ? "Already at minimum" : ""}
onClick={removeSleeve}
>
-1 sleeve
</ButtonWithTooltip>
<ButtonWithTooltip
disabledTooltip={Player.sleevesFromCovenant >= MaxSleevesFromCovenant ? "Already at maximum" : ""}
onClick={addSleeve}
>
+1 sleeve
</ButtonWithTooltip>
<Typography className={classes.extraInfo}>Extra sleeves: {Player.sleevesFromCovenant}</Typography>
</>
)}
</ButtonGroup> </ButtonGroup>
</td> </td>
</tr> </tr>

@ -32,7 +32,7 @@ import { resetGangs } from "../../Gang/AllGangs";
import { Cities } from "../../Locations/Cities"; import { Cities } from "../../Locations/Cities";
import { Locations } from "../../Locations/Locations"; import { Locations } from "../../Locations/Locations";
import { Sleeve } from "../Sleeve/Sleeve"; import { Sleeve } from "../Sleeve/Sleeve";
import { isSleeveCompanyWork } from "../Sleeve/Work/SleeveCompanyWork"; import { SleeveWorkType } from "../Sleeve/Work/Work";
import { calculateSkillProgress as calculateSkillProgressF, ISkillProgress } from "../formulas/skill"; import { calculateSkillProgress as calculateSkillProgressF, ISkillProgress } from "../formulas/skill";
import { AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers"; import { AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers";
import { safelyCreateUniqueServer } from "../../Server/ServerHelpers"; import { safelyCreateUniqueServer } from "../../Server/ServerHelpers";
@ -109,11 +109,7 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.queuedAugmentations = []; this.queuedAugmentations = [];
const numSleeves = Math.min(3, this.sourceFileLvl(10) + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant; Sleeve.recalculateNumOwned();
if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
for (let i = this.sleeves.length; i < numSleeves; i++) {
this.sleeves.push(new Sleeve());
}
this.sleeves.forEach((sleeve) => (sleeve.shock <= 0 ? sleeve.synchronize() : sleeve.shockRecovery())); this.sleeves.forEach((sleeve) => (sleeve.shock <= 0 ? sleeve.synchronize() : sleeve.shockRecovery()));
@ -369,7 +365,7 @@ export function quitJob(this: PlayerObject, company: CompanyName): void {
this.finishWork(true); this.finishWork(true);
} }
for (const sleeve of this.sleeves) { for (const sleeve of this.sleeves) {
if (isSleeveCompanyWork(sleeve.currentWork) && sleeve.currentWork.companyName === company) { if (sleeve.currentWork?.type === SleeveWorkType.COMPANY && sleeve.currentWork.companyName === company) {
sleeve.stopWork(); sleeve.stopWork();
dialogBoxCreate(`You quit ${company} while one of your sleeves was working there. The sleeve is now idle.`); dialogBoxCreate(`You quit ${company} while one of your sleeves was working there. The sleeve is now idle.`);
} }

@ -464,6 +464,19 @@ export class Sleeve extends Person implements SleevePerson {
} }
} }
static recalculateNumOwned() {
const numSleeves =
Math.min(3, Player.sourceFileLvl(10) + (Player.bitNodeN === 10 ? 1 : 0)) + Player.sleevesFromCovenant;
while (Player.sleeves.length > numSleeves) {
const destroyedSleeve = Player.sleeves.pop();
// This should not happen, but avoid an infinite loop in case sleevesFromCovenent or sf10 level are somehow negative
if (!destroyedSleeve) return;
// Stop work, to prevent destroyed sleeves from continuing their tasks in the void
destroyedSleeve.stopWork();
}
while (Player.sleeves.length < numSleeves) Player.sleeves.push(new Sleeve());
}
whoAmI(): string { whoAmI(): string {
return "Sleeve"; return "Sleeve";
} }

@ -47,7 +47,7 @@ export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
if (Player.canAfford(purchaseCost())) { if (Player.canAfford(purchaseCost())) {
Player.loseMoney(purchaseCost(), "sleeves"); Player.loseMoney(purchaseCost(), "sleeves");
Player.sleevesFromCovenant += 1; Player.sleevesFromCovenant += 1;
Player.sleeves.push(new Sleeve()); Sleeve.recalculateNumOwned();
rerender(); rerender();
} else { } else {
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`); dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`);