DEVMENU: Add tools to set stat level and queue augmentations of faction (#2118)

This commit is contained in:
catloversg
2025-05-09 16:32:53 +07:00
committed by GitHub
parent c633384de1
commit 81e1c26e7b
6 changed files with 331 additions and 254 deletions

View File

@@ -38,11 +38,11 @@ export function AchievementsDev(): React.ReactElement {
setPlayerAchievements(Player.achievements.map((m) => m.ID));
}
function disableEngine(): void {
function disableEngineCheck(): void {
Engine.Counters.achievementsCounter = Number.MAX_VALUE;
}
function enableEngine(): void {
function enableEngineCheck(): void {
Engine.Counters.achievementsCounter = 0;
}
@@ -63,8 +63,8 @@ export function AchievementsDev(): React.ReactElement {
<ButtonGroup>
<Button onClick={grantAllAchievements}>Grant All</Button>
<Button onClick={clearAchievements}>Clear</Button>
<Button onClick={disableEngine}>Disable Engine</Button>
<Button onClick={enableEngine}>Enable Engine</Button>
<Button onClick={disableEngineCheck}>Disable Engine Check</Button>
<Button onClick={enableEngineCheck}>Enable Engine Check</Button>
</ButtonGroup>
</td>
</tr>

View File

@@ -14,14 +14,18 @@ interface IProps {
subtract: (x: number) => void;
tons: () => void;
reset: () => void;
useDownArrowIcon?: boolean;
}
export function Adjuster(props: IProps): React.ReactElement {
const [value, setValue] = useState<number | string>("");
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if (event.target.value === "") setValue("");
else setValue(parseFloat(event.target.value));
if (event.target.value === "") {
setValue("");
return;
}
setValue(Number.parseFloat(event.target.value));
}
const { label, placeholder, add, subtract, reset, tons } = props;
@@ -36,9 +40,9 @@ export function Adjuster(props: IProps): React.ReactElement {
InputProps={{
startAdornment: (
<>
<Tooltip title="Add a lot">
<Tooltip title={props.useDownArrowIcon ? "Subtract a lot" : "Add a lot"}>
<IconButton onClick={tons} size="large">
<DoubleArrowIcon style={{ transform: "rotate(-90deg)" }} />
<DoubleArrowIcon style={{ transform: `rotate(${props.useDownArrowIcon ? 90 : -90}deg)` }} />
</IconButton>
</Tooltip>
<Tooltip title="Add">
@@ -50,7 +54,7 @@ export function Adjuster(props: IProps): React.ReactElement {
),
endAdornment: (
<>
<Tooltip title="Remove">
<Tooltip title="Subtract">
<IconButton onClick={() => subtract(typeof value !== "string" ? value : 0)} size="large">
<RemoveIcon />
</IconButton>

View File

@@ -15,16 +15,21 @@ import {
} from "@mui/material";
import ReplyAllIcon from "@mui/icons-material/ReplyAll";
import ReplyIcon from "@mui/icons-material/Reply";
import { AugmentationName } from "@enums";
import { AugmentationName, FactionName } from "@enums";
import { Factions } from "../../Faction/Factions";
import { FactionChooser } from "./FactionChooser";
import { getFactionAugmentationsFiltered } from "../../Faction/FactionHelpers";
export function AugmentationsDev(): React.ReactElement {
const [augmentation, setAugmentation] = React.useState<AugmentationName | null>(null);
const [selectedFaction, setSelectedFaction] = React.useState(Factions[FactionName.Illuminati]);
function queueAug(): void {
if (!augmentation) {
return;
}
if (Player.hasAugmentation(augmentation)) {
// NFG can be queued again to increase its level.
if (Player.hasAugmentation(augmentation) && augmentation !== AugmentationName.NeuroFluxGovernor) {
return;
}
Player.queueAugmentation(augmentation);
@@ -41,6 +46,21 @@ export function AugmentationsDev(): React.ReactElement {
setAugmentation(null);
}
function queueAllAugsOfFaction(): void {
for (const augName of getFactionAugmentationsFiltered(selectedFaction)) {
/**
* Skip NFG. This tool is usually used when testing the situation in which the player installs all augmentations
* from a specific faction. If we use this tool n times, we also get n levels of NFG, which may not be what we
* want to test.
*/
if (Player.hasAugmentation(augName) || augName === AugmentationName.NeuroFluxGovernor) {
continue;
}
Player.queueAugmentation(augName);
}
setAugmentation(null);
}
function clearAugs(): void {
Player.augmentations = [];
}
@@ -50,7 +70,9 @@ export function AugmentationsDev(): React.ReactElement {
}
const options = Object.values(AugmentationName).filter(
(augmentationName) => !Player.hasAugmentation(augmentationName),
(augmentationName) =>
// NFG is always eligible.
!Player.hasAugmentation(augmentationName) || augmentationName === AugmentationName.NeuroFluxGovernor,
);
return (
@@ -86,6 +108,14 @@ export function AugmentationsDev(): React.ReactElement {
</Tooltip>
</Box>
<Button onClick={clearQueuedAugs}>Clear queued augmentations</Button>
<Box display="flex" marginTop="8px">
<Tooltip title="Queue all augmentations offered by faction, except NFG">
<Button onClick={queueAllAugsOfFaction}>
<ReplyAllIcon />
</Button>
</Tooltip>
<FactionChooser faction={selectedFaction} onChange={setSelectedFaction} style={{ marginLeft: "16px" }} />
</Box>
</AccordionDetails>
</Accordion>
);

View File

@@ -0,0 +1,35 @@
import React from "react";
import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField";
import type { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { getRecordValues } from "../../Types/Record";
import { getEnumHelper } from "../../utils/EnumHelper";
export function FactionChooser({
faction,
onChange,
style,
}: {
faction: Faction;
onChange: (f: Faction) => void;
style?: React.CSSProperties;
}): React.ReactElement {
const factions = React.useMemo(() => {
return getRecordValues(Factions).map((faction) => faction.name);
}, []);
return (
<Autocomplete
style={{ width: "350px", ...style }}
options={factions}
value={faction.name}
renderInput={(params) => <TextField {...params} style={{ height: "100%" }} />}
onChange={(_, factionName) => {
if (!factionName || !getEnumHelper("FactionName").isMember(factionName)) {
return;
}
onChange(Factions[factionName]);
}}
></Autocomplete>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useState } from "react";
import {
Accordion,
AccordionSummary,
@@ -10,8 +10,6 @@ import {
RadioGroup,
Radio,
Box,
Autocomplete,
TextField,
} from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
@@ -28,14 +26,12 @@ import { getRecordValues } from "../../Types/Record";
import { getEnumHelper } from "../../utils/EnumHelper";
import { useRerender } from "../../ui/React/hooks";
import { MaxFavor } from "../../Faction/formulas/favor";
import { FactionChooser } from "./FactionChooser";
const largeAmountOfReputation = 1e12;
export function FactionsDev(): React.ReactElement {
const [selectedFaction, setSelectedFaction] = useState(Factions[FactionName.Illuminati]);
const factions = useMemo(() => {
return getRecordValues(Factions).map((faction) => faction.name);
}, []);
const rerender = useRerender();
function receiveInvite(): void {
@@ -146,18 +142,11 @@ export function FactionsDev(): React.ReactElement {
<ReplyIcon />
</Button>
</Tooltip>
<Autocomplete
style={{ marginLeft: "8px", width: "350px" }}
options={factions}
value={selectedFaction.name}
renderInput={(params) => <TextField {...params} style={{ height: "100%" }} />}
onChange={(_, factionName) => {
if (!factionName || !getEnumHelper("FactionName").isMember(factionName)) {
return;
}
setSelectedFaction(Factions[factionName]);
}}
></Autocomplete>
<FactionChooser
faction={selectedFaction}
onChange={setSelectedFaction}
style={{ marginLeft: "8px" }}
/>
</Box>
</td>
</tr>

View File

@@ -7,130 +7,218 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import { Adjuster } from "./Adjuster";
import { Player } from "@player";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { calculateExp } from "../../PersonObjects/formulas/skill";
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
const bigNumber = 1e27;
export function StatsDev(): React.ReactElement {
function modifyExp(stat: string, modifier: number) {
return function (exp: number) {
switch (stat) {
case "hacking":
if (exp) {
Player.gainHackingExp(exp * modifier);
}
break;
case "strength":
if (exp) {
Player.gainStrengthExp(exp * modifier);
}
break;
case "defense":
if (exp) {
Player.gainDefenseExp(exp * modifier);
}
break;
case "dexterity":
if (exp) {
Player.gainDexterityExp(exp * modifier);
}
break;
case "agility":
if (exp) {
Player.gainAgilityExp(exp * modifier);
}
break;
case "charisma":
if (exp) {
Player.gainCharismaExp(exp * modifier);
}
break;
case "intelligence":
if (exp) {
Player.gainIntelligenceExp(exp * modifier);
}
break;
}
Player.updateSkillLevels();
};
}
function modifyKarma(modifier: number) {
return function (amt: number) {
Player.karma += amt * modifier;
};
}
function tonsOfExp(): void {
Player.gainHackingExp(bigNumber);
Player.gainStrengthExp(bigNumber);
Player.gainDefenseExp(bigNumber);
Player.gainDexterityExp(bigNumber);
Player.gainAgilityExp(bigNumber);
Player.gainCharismaExp(bigNumber);
Player.gainIntelligenceExp(bigNumber);
Player.updateSkillLevels();
}
function resetAllExp(): void {
Player.exp.hacking = 0;
Player.exp.strength = 0;
Player.exp.defense = 0;
Player.exp.dexterity = 0;
Player.exp.agility = 0;
Player.exp.charisma = 0;
Player.exp.intelligence = 0;
Player.updateSkillLevels();
}
function resetExperience(stat: string): () => void {
return function () {
switch (stat) {
case "hacking":
Player.exp.hacking = 0;
break;
case "strength":
Player.exp.strength = 0;
break;
case "defense":
Player.exp.defense = 0;
break;
case "dexterity":
Player.exp.dexterity = 0;
break;
case "agility":
Player.exp.agility = 0;
break;
case "charisma":
Player.exp.charisma = 0;
break;
case "intelligence":
Player.exp.intelligence = 0;
break;
}
Player.updateSkillLevels();
};
}
function resetKarma(): () => void {
return function () {
Player.karma = 0;
};
}
function enableIntelligence(): void {
if (Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
Player.updateSkillLevels();
function modifyExp(stat: string, modifier: number) {
return function (exp: number) {
if (!Number.isFinite(exp) || exp < 0) {
dialogBoxCreate(`Exp cannot be ${exp}.`);
return;
}
}
switch (stat) {
case "Hacking":
Player.gainHackingExp(exp * modifier);
break;
case "Strength":
Player.gainStrengthExp(exp * modifier);
break;
case "Defense":
Player.gainDefenseExp(exp * modifier);
break;
case "Dexterity":
Player.gainDexterityExp(exp * modifier);
break;
case "Agility":
Player.gainAgilityExp(exp * modifier);
break;
case "Charisma":
Player.gainCharismaExp(exp * modifier);
break;
case "Intelligence":
Player.gainIntelligenceExp(exp * modifier);
break;
}
Player.updateSkillLevels();
};
}
function disableIntelligence(): void {
Player.exp.intelligence = 0;
Player.skills.intelligence = 0;
function setStatLevel(stat: string, level: number): void {
if (!Number.isFinite(level) || level < 1) {
dialogBoxCreate(`Invalid level.`);
return;
}
switch (stat) {
case "Hacking":
Player.exp.hacking = calculateExp(level, Player.mults.hacking * currentNodeMults.HackingLevelMultiplier);
break;
case "Strength":
Player.exp.strength = calculateExp(level, Player.mults.strength * currentNodeMults.StrengthLevelMultiplier);
break;
case "Defense":
Player.exp.defense = calculateExp(level, Player.mults.defense * currentNodeMults.DefenseLevelMultiplier);
break;
case "Dexterity":
Player.exp.dexterity = calculateExp(level, Player.mults.dexterity * currentNodeMults.DexterityLevelMultiplier);
break;
case "Agility":
Player.exp.agility = calculateExp(level, Player.mults.agility * currentNodeMults.AgilityLevelMultiplier);
break;
case "Charisma":
Player.exp.charisma = calculateExp(level, Player.mults.charisma * currentNodeMults.CharismaLevelMultiplier);
break;
case "Intelligence":
Player.exp.intelligence = 0;
Player.gainIntelligenceExp(calculateExp(level, 1));
break;
}
Player.updateSkillLevels();
}
function modifyKarma(modifier: number) {
return function (amt: number) {
Player.karma += amt * modifier;
};
}
function tonsOfExp(): void {
Player.gainHackingExp(bigNumber);
Player.gainStrengthExp(bigNumber);
Player.gainDefenseExp(bigNumber);
Player.gainDexterityExp(bigNumber);
Player.gainAgilityExp(bigNumber);
Player.gainCharismaExp(bigNumber);
Player.gainIntelligenceExp(bigNumber);
Player.updateSkillLevels();
}
function resetAllExp(): void {
Player.exp.hacking = 0;
Player.exp.strength = 0;
Player.exp.defense = 0;
Player.exp.dexterity = 0;
Player.exp.agility = 0;
Player.exp.charisma = 0;
Player.exp.intelligence = 0;
Player.updateSkillLevels();
}
function resetExperience(stat: string): () => void {
return function () {
switch (stat) {
case "Hacking":
Player.exp.hacking = 0;
break;
case "Strength":
Player.exp.strength = 0;
break;
case "Defense":
Player.exp.defense = 0;
break;
case "Dexterity":
Player.exp.dexterity = 0;
break;
case "Agility":
Player.exp.agility = 0;
break;
case "Charisma":
Player.exp.charisma = 0;
break;
case "Intelligence":
Player.exp.intelligence = 0;
break;
}
Player.updateSkillLevels();
};
}
function resetKarma(): () => void {
return function () {
Player.karma = 0;
};
}
function enableIntelligence(): void {
if (Player.skills.intelligence === 0) {
Player.skills.intelligence = 1;
Player.updateSkillLevels();
}
}
function disableIntelligence(): void {
Player.exp.intelligence = 0;
Player.skills.intelligence = 0;
Player.updateSkillLevels();
}
function StatRow({ stat }: { stat: string }): React.ReactElement {
const [level, setLevel] = React.useState<number | string>("");
return (
<tr>
<td>
<Typography>{stat}:</Typography>
</td>
<td>
<Adjuster
label={"exp"}
placeholder={"exp"}
tons={() => modifyExp(stat, 1)(bigNumber)}
add={modifyExp(stat, 1)}
subtract={modifyExp(stat, -1)}
reset={resetExperience(stat)}
/>
</td>
<td>
<TextField
label={"Level"}
value={level}
onChange={(event) => {
if (event.target.value === "") {
setLevel("");
return;
}
setLevel(Number.parseFloat(event.target.value));
}}
placeholder={"Level"}
type="number"
InputProps={{
// Without startAdornment, label and placeholder are only shown when TextField is focused
startAdornment: <></>,
endAdornment: (
<Button onClick={() => setStatLevel(stat, typeof level !== "string" ? level : 0)}>Set</Button>
),
}}
style={{ marginLeft: "20px" }}
/>
</td>
{stat === "Intelligence" && (
<>
<td>
<Button onClick={enableIntelligence} style={{ marginTop: "15px" }}>
Enable
</Button>
</td>
<td>
<Button onClick={disableIntelligence} style={{ marginTop: "15px" }}>
Disable
</Button>
</td>
</>
)}
</tr>
);
}
export function StatsDev(): React.ReactElement {
const [levelOfNormalStats, setLevelOfNormalStats] = React.useState<number | string>("");
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
@@ -148,118 +236,48 @@ export function StatsDev(): React.ReactElement {
<Button onClick={tonsOfExp}>Tons of exp</Button>
<Button onClick={resetAllExp}>Reset</Button>
</td>
</tr>
<tr>
<td>
<Typography>Hacking:</Typography>
</td>
<td>
<Adjuster
label="hacking"
placeholder="exp"
tons={() => modifyExp("hacking", 1)(bigNumber)}
add={modifyExp("hacking", 1)}
subtract={modifyExp("hacking", -1)}
reset={resetExperience("hacking")}
<TextField
label={"Level"}
value={levelOfNormalStats}
onChange={(event) => {
if (event.target.value === "") {
setLevelOfNormalStats("");
return;
}
setLevelOfNormalStats(Number.parseFloat(event.target.value));
}}
placeholder={"Level"}
type="number"
InputProps={{
// Without startAdornment, label and placeholder are only shown when TextField is focused
startAdornment: <></>,
endAdornment: (
<Tooltip title="Set all, except Intelligence and Karma">
<Button
onClick={() => {
const level = typeof levelOfNormalStats !== "string" ? levelOfNormalStats : 0;
setStatLevel("Hacking", level);
setStatLevel("Strength", level);
setStatLevel("Defense", level);
setStatLevel("Dexterity", level);
setStatLevel("Agility", level);
setStatLevel("Charisma", level);
}}
style={{ width: "100px" }}
>
Set all
</Button>
</Tooltip>
),
}}
style={{ marginLeft: "20px", width: "300px" }}
/>
</td>
</tr>
<tr>
<td>
<Typography>Strength:</Typography>
</td>
<td>
<Adjuster
label="strength"
placeholder="exp"
tons={() => modifyExp("strength", 1)(bigNumber)}
add={modifyExp("strength", 1)}
subtract={modifyExp("strength", -1)}
reset={resetExperience("strength")}
/>
</td>
</tr>
<tr>
<td>
<Typography>Defense:</Typography>
</td>
<td>
<Adjuster
label="defense"
placeholder="exp"
tons={() => modifyExp("defense", 1)(bigNumber)}
add={modifyExp("defense", 1)}
subtract={modifyExp("defense", -1)}
reset={resetExperience("defense")}
/>
</td>
</tr>
<tr>
<td>
<Typography>Dexterity:</Typography>
</td>
<td>
<Adjuster
label="dexterity"
placeholder="exp"
tons={() => modifyExp("dexterity", 1)(bigNumber)}
add={modifyExp("dexterity", 1)}
subtract={modifyExp("dexterity", -1)}
reset={resetExperience("dexterity")}
/>
</td>
</tr>
<tr>
<td>
<Typography>Agility:</Typography>
</td>
<td>
<Adjuster
label="agility"
placeholder="exp"
tons={() => modifyExp("agility", 1)(bigNumber)}
add={modifyExp("agility", 1)}
subtract={modifyExp("agility", -1)}
reset={resetExperience("agility")}
/>
</td>
</tr>
<tr>
<td>
<Typography>Charisma:</Typography>
</td>
<td>
<Adjuster
label="charisma"
placeholder="exp"
tons={() => modifyExp("charisma", 1)(bigNumber)}
add={modifyExp("charisma", 1)}
subtract={modifyExp("charisma", -1)}
reset={resetExperience("charisma")}
/>
</td>
</tr>
<tr>
<td>
<Typography>Intelligence:</Typography>
</td>
<td>
<Adjuster
label="intelligence"
placeholder="exp"
tons={() => modifyExp("intelligence", 1)(bigNumber)}
add={modifyExp("intelligence", 1)}
subtract={modifyExp("intelligence", -1)}
reset={resetExperience("intelligence")}
/>
</td>
<td>
<Button onClick={enableIntelligence}>Enable</Button>
</td>
<td>
<Button onClick={disableIntelligence}>Disable</Button>
</td>
</tr>
{["Hacking", "Strength", "Defense", "Dexterity", "Agility", "Charisma", "Intelligence"].map((value) => (
<StatRow key={value} stat={value} />
))}
<tr>
<td>
<Typography>Karma:</Typography>
@@ -268,10 +286,11 @@ export function StatsDev(): React.ReactElement {
<Adjuster
label="karma"
placeholder="amt"
tons={() => modifyExp("intelligence", 1)(100000)}
tons={() => modifyKarma(1)(-54000)}
add={modifyKarma(1)}
subtract={modifyKarma(-1)}
reset={resetKarma()}
useDownArrowIcon={true}
/>
</td>
</tr>