mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-07 16:17:49 +02:00
UI: Show hints of Gang mechanic in pre-endgame (#2723)
This commit is contained in:
@@ -40,6 +40,8 @@ import { CONSTANTS } from "../Constants";
|
||||
import { BladeburnerConstants } from "../Bladeburner/data/Constants";
|
||||
import type { PlayerObject } from "../PersonObjects/Player/PlayerObject";
|
||||
import { CovenantCampaign } from "./ui/CovenantCampaign";
|
||||
import { GangCampaign } from "./ui/GangCampaign";
|
||||
import { GangConstants } from "../Gang/data/Constants";
|
||||
|
||||
interface FactionInfoParams {
|
||||
infoText?: JSX.Element;
|
||||
@@ -812,3 +814,7 @@ export const FactionInfos: Record<FactionName, FactionInfo> = {
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
for (const factionName of GangConstants.Names) {
|
||||
FactionInfos[factionName].campaign = () => <GangCampaign factionName={factionName} />;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import { KEY } from "../../utils/KeyboardEventKey";
|
||||
import { FactionName } from "@enums";
|
||||
import { canCreateGang } from "../../Gang/helpers";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
@@ -33,6 +35,11 @@ export function CreateGangModal(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
function createGang(): void {
|
||||
const checkResult = canCreateGang(props.facName);
|
||||
if (!checkResult.success) {
|
||||
dialogBoxCreate(checkResult.message);
|
||||
return;
|
||||
}
|
||||
Player.startGang(props.facName, isHacking());
|
||||
props.onClose();
|
||||
Router.toPage(Page.Gang);
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Player } from "@player";
|
||||
import { Typography, Button } from "@mui/material";
|
||||
|
||||
import { FactionWorkType } from "@enums";
|
||||
import { GangButton } from "./GangButton";
|
||||
import { FactionWork } from "../../Work/FactionWork";
|
||||
import { useCycleRerender } from "../../ui/React/hooks";
|
||||
import { favorNeededToDonate } from "../formulas/donation";
|
||||
@@ -111,7 +110,6 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
|
||||
{faction.name}
|
||||
</Typography>
|
||||
<Info faction={faction} factionInfo={factionInfo} />
|
||||
<GangButton faction={faction} />
|
||||
{!isPlayersGang && (
|
||||
<>
|
||||
{factionInfo.offersWork() && (
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Button, Typography, Box, Paper, Tooltip } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { GangConstants } from "../../Gang/data/Constants";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Page } from "../../ui/Router";
|
||||
import { Player } from "@player";
|
||||
import { Faction } from "../Faction";
|
||||
import { CreateGangModal } from "./CreateGangModal";
|
||||
|
||||
interface IProps {
|
||||
faction: Faction;
|
||||
}
|
||||
|
||||
export function GangButton({ faction }: IProps): React.ReactElement {
|
||||
const [gangOpen, setGangOpen] = useState(false);
|
||||
|
||||
if (
|
||||
!GangConstants.Names.includes(faction.name) || // not even a gang
|
||||
!Player.isAwareOfGang() || // doesn't know about gang
|
||||
(Player.gang && Player.getGangName() !== faction.name) // already in another gang
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
let data = {
|
||||
enabled: false,
|
||||
title: "",
|
||||
tooltip: "" as string | React.ReactElement,
|
||||
description: "",
|
||||
};
|
||||
|
||||
if (Player.gang) {
|
||||
data = {
|
||||
enabled: true,
|
||||
title: "Manage Gang",
|
||||
tooltip: "",
|
||||
description: "Manage a gang for this Faction. Gangs will earn you money and faction reputation",
|
||||
};
|
||||
} else {
|
||||
const checkResult = Player.canAccessGang();
|
||||
data = {
|
||||
enabled: checkResult.success,
|
||||
title: "Create Gang",
|
||||
tooltip: !checkResult.success ? (
|
||||
<Typography>Unlocked when reaching {GangConstants.GangKarmaRequirement} karma</Typography>
|
||||
) : (
|
||||
""
|
||||
),
|
||||
description: "Create a gang for this Faction. Gangs will earn you money and faction reputation",
|
||||
};
|
||||
}
|
||||
|
||||
const manageGang = (): void => {
|
||||
// If player already has a gang, just go to the gang UI
|
||||
if (Player.inGang()) {
|
||||
return Router.toPage(Page.Gang);
|
||||
}
|
||||
|
||||
setGangOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
<Tooltip title={data.tooltip}>
|
||||
<span>
|
||||
<Button onClick={manageGang} disabled={!data.enabled}>
|
||||
{data.title}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Typography>{data.description}</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<CreateGangModal facName={faction.name} open={gangOpen} onClose={() => setGangOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Player } from "@player";
|
||||
import React, { useState } from "react";
|
||||
import { knowAboutBitverse } from "../../BitNode/BitNodeUtils";
|
||||
import { GangConstants } from "../../Gang/data/Constants";
|
||||
import { Router } from "../../ui/GameRoot";
|
||||
import { Modal } from "../../ui/React/Modal";
|
||||
import { Page } from "../../ui/Router";
|
||||
import { FactionName } from "../Enums";
|
||||
import { CreateGangModal } from "./CreateGangModal";
|
||||
import { Option } from "./Option";
|
||||
|
||||
function GangIncompleteCampaign() {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Option
|
||||
buttonText={"Execute the formation plan"}
|
||||
infoText={
|
||||
"The tension between our faction and its rivals has been rising. The leader plans to form a specialized " +
|
||||
"group under your command to strengthen our position by improving our standing and expanding our resources."
|
||||
}
|
||||
onClick={() => setOpen(true)}
|
||||
></Option>
|
||||
<Modal open={open} onClose={() => setOpen(false)}>
|
||||
<Typography component="div">
|
||||
Each time you attempt to execute the plan, it is abruptly interrupted for reasons no one can explain. You
|
||||
receive the same distorted message every time:
|
||||
<br />
|
||||
<br />
|
||||
#@)($*&@__Y0U__^%$#@&*()__HAV3__(&@#*$%(@
|
||||
<br />
|
||||
()@#*$%(__N0T__@&$#)@*(__S33N__)(*@#&$)(
|
||||
<br />
|
||||
@&*($#@&__TH3__#@A&#@*)(@$#@)*
|
||||
<br />
|
||||
%$#@&()@__TRU1H__()*@#$&()@#$
|
||||
</Typography>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function GangCampaign({ factionName }: { factionName: FactionName }) {
|
||||
const [gangOpen, setGangOpen] = useState(false);
|
||||
|
||||
if (!GangConstants.Names.includes(factionName)) {
|
||||
throw new Error(`Cannot create gang with ${factionName}`);
|
||||
}
|
||||
if (!knowAboutBitverse()) {
|
||||
return <GangIncompleteCampaign />;
|
||||
}
|
||||
|
||||
const data = {
|
||||
enabled: false,
|
||||
title: "",
|
||||
tooltip: "" as string | React.ReactElement,
|
||||
description: "",
|
||||
};
|
||||
|
||||
if (Player.gang) {
|
||||
if (Player.getGangName() !== factionName) {
|
||||
data.enabled = false;
|
||||
data.title = "Create Gang";
|
||||
data.tooltip = "You already created a gang with another faction";
|
||||
} else {
|
||||
data.enabled = true;
|
||||
data.title = "Manage Gang";
|
||||
data.description = "Manage a gang for this Faction. Gangs will earn you money and faction reputation";
|
||||
}
|
||||
} else {
|
||||
const checkResult = Player.canAccessGang();
|
||||
data.enabled = checkResult.success;
|
||||
data.title = "Create Gang";
|
||||
data.tooltip = !checkResult.success ? checkResult.message : "";
|
||||
data.description = "Create a gang for this Faction. Gangs will earn you money and faction reputation";
|
||||
}
|
||||
|
||||
const manageGang = (): void => {
|
||||
// If player already has a gang, just go to the gang UI
|
||||
if (Player.inGang()) {
|
||||
return Router.toPage(Page.Gang);
|
||||
}
|
||||
|
||||
setGangOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Paper sx={{ my: 1, p: 1 }}>
|
||||
<Tooltip title={data.tooltip}>
|
||||
<span>
|
||||
<Button onClick={manageGang} disabled={!data.enabled}>
|
||||
{data.title}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Typography>{data.description}</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
<CreateGangModal facName={factionName} open={gangOpen} onClose={() => setGangOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export const GangConstants = {
|
||||
FactionName.SpeakersForTheDead,
|
||||
FactionName.NiteSec,
|
||||
FactionName.TheBlackHand,
|
||||
] as string[],
|
||||
] as FactionName[],
|
||||
GangKarmaRequirement: -54000,
|
||||
/** Normal number of game cycles processed at once (2 seconds) */
|
||||
minCyclesToProcess: 2000 / CONSTANTS.MilliPerCycle,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Result } from "@nsdefs";
|
||||
import { Player } from "@player";
|
||||
import { FactionName } from "../Enums";
|
||||
import { GangConstants } from "./data/Constants";
|
||||
|
||||
export function canCreateGang(faction: FactionName): Result {
|
||||
if (Player.gang) {
|
||||
return { success: false, message: "You already have a gang." };
|
||||
}
|
||||
const checkResult = Player.canAccessGang();
|
||||
if (!checkResult.success) {
|
||||
return { success: false, message: checkResult.message };
|
||||
}
|
||||
if (!GangConstants.Names.includes(faction)) {
|
||||
return {
|
||||
success: false,
|
||||
message: `${faction} does not allow creating a gang. You can only do that with ${GangConstants.Names.join(
|
||||
", ",
|
||||
)}.`,
|
||||
};
|
||||
}
|
||||
if (!Player.factions.includes(faction)) {
|
||||
return { success: false, message: `You are not a member of ${faction}.` };
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
@@ -7,13 +7,13 @@ import { type InternalAPI, type NetscriptContext, setRemovedFunctions } from "..
|
||||
import { GangPromise, RecruitmentResult } from "../Gang/Gang";
|
||||
import { Player } from "@player";
|
||||
import { FactionName } from "@enums";
|
||||
import { GangConstants } from "../Gang/data/Constants";
|
||||
import { AllGangs } from "../Gang/AllGangs";
|
||||
import { GangMemberTasks } from "../Gang/GangMemberTasks";
|
||||
import { GangMemberUpgrades } from "../Gang/GangMemberUpgrades";
|
||||
import { helpers } from "../Netscript/NetscriptHelpers";
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { canCreateGang } from "../Gang/helpers";
|
||||
|
||||
export function NetscriptGang(): InternalAPI<IGang> {
|
||||
/** Functions as an API check and also returns the gang object */
|
||||
@@ -40,26 +40,11 @@ export function NetscriptGang(): InternalAPI<IGang> {
|
||||
const gangFunctions: InternalAPI<IGang> = {
|
||||
createGang: (ctx) => (_faction) => {
|
||||
const faction = getEnumHelper("FactionName").nsGetMember(ctx, _faction);
|
||||
if (Player.gang) {
|
||||
return false;
|
||||
}
|
||||
const checkResult = Player.canAccessGang();
|
||||
const checkResult = canCreateGang(faction);
|
||||
if (!checkResult.success) {
|
||||
helpers.log(ctx, () => checkResult.message);
|
||||
return false;
|
||||
}
|
||||
if (!GangConstants.Names.includes(faction)) {
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`${faction} does not allow creating a gang. You can only do that with ${GangConstants.Names.join(", ")}.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!Player.factions.includes(faction)) {
|
||||
helpers.log(ctx, () => `You are not a member of ${faction}.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isHacking = faction === FactionName.NiteSec || faction === FactionName.TheBlackHand;
|
||||
Player.startGang(faction, isHacking);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { FactionName } from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { Gang } from "../../../src/Gang/Gang";
|
||||
import { AllGangs } from "../../../src/Gang/AllGangs";
|
||||
import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
|
||||
import { getNS, getWorkerScriptAndNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
|
||||
import { GangConstants } from "../../../src/Gang/data/Constants";
|
||||
import { joinFaction } from "../../../src/Faction/FactionHelpers";
|
||||
import { Factions } from "../../../src/Faction/Factions";
|
||||
|
||||
beforeAll(() => {
|
||||
initGameEnvironment();
|
||||
@@ -10,12 +12,16 @@ beforeAll(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
// Give the player a gang so gang API is accessible
|
||||
Player.gang = new Gang(FactionName.SlumSnakes, false);
|
||||
Player.sourceFiles.set(2, 3);
|
||||
});
|
||||
|
||||
describe("ns.gang.getAllGangInformation", () => {
|
||||
it("should return territory and power info for all gangs including the player's", () => {
|
||||
beforeEach(() => {
|
||||
// Give the player a gang so gang API is accessible
|
||||
Player.startGang(FactionName.SlumSnakes, false);
|
||||
});
|
||||
|
||||
test("should return territory and power info for all gangs including the player's", () => {
|
||||
const ns = getNS();
|
||||
const info = ns.gang.getAllGangInformation();
|
||||
const gangNames = Object.keys(info);
|
||||
@@ -35,7 +41,7 @@ describe("ns.gang.getAllGangInformation", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should return copies, not references to the original AllGangs data", () => {
|
||||
test("should return copies, not references to the original AllGangs data", () => {
|
||||
const ns = getNS();
|
||||
const info = ns.gang.getAllGangInformation();
|
||||
|
||||
@@ -44,3 +50,55 @@ describe("ns.gang.getAllGangInformation", () => {
|
||||
expect(AllGangs[FactionName.SlumSnakes].power).not.toBe(999999);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createGang", () => {
|
||||
test("Success", () => {
|
||||
const ns = getNS();
|
||||
Player.karma = GangConstants.GangKarmaRequirement;
|
||||
joinFaction(Factions[FactionName.SlumSnakes]);
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(true);
|
||||
});
|
||||
describe("Failure", () => {
|
||||
test("Already have a gang", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
Player.karma = GangConstants.GangKarmaRequirement;
|
||||
joinFaction(Factions[FactionName.SlumSnakes]);
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(true);
|
||||
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("You already have a gang");
|
||||
expect(ns.gang.createGang(FactionName.Tetrads)).toBe(false);
|
||||
expect(ws.scriptRef.logs[1]).toMatch("You already have a gang");
|
||||
});
|
||||
test("Disabled by advanced options", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
Player.bitNodeOptions.disableGang = true;
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("Gang is disabled by advanced options");
|
||||
});
|
||||
test("Not have Source-File 2", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
Player.sourceFiles.set(2, 0);
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("You do not have Source-File 2");
|
||||
});
|
||||
test("Not enough karma", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("Your karma must be less than or equal to");
|
||||
});
|
||||
test("Invalid gang faction", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
Player.karma = GangConstants.GangKarmaRequirement;
|
||||
joinFaction(Factions[FactionName.SlumSnakes]);
|
||||
expect(ns.gang.createGang(FactionName.Illuminati)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("does not allow creating a gang");
|
||||
});
|
||||
test("Not a faction member", () => {
|
||||
const { ws, ns } = getWorkerScriptAndNS();
|
||||
Player.karma = GangConstants.GangKarmaRequirement;
|
||||
expect(ns.gang.createGang(FactionName.SlumSnakes)).toBe(false);
|
||||
expect(ws.scriptRef.logs[0]).toMatch("You are not a member of");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user