diff --git a/markdown/bitburner.gang.getrecruitsavailable.md b/markdown/bitburner.gang.getrecruitsavailable.md new file mode 100644 index 000000000..3eae56c34 --- /dev/null +++ b/markdown/bitburner.gang.getrecruitsavailable.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Gang](./bitburner.gang.md) > [getRecruitsAvailable](./bitburner.gang.getrecruitsavailable.md) + +## Gang.getRecruitsAvailable() method + +Check how many gang members you can currently recruit. + +**Signature:** + +```typescript +getRecruitsAvailable(): number; +``` +**Returns:** + +number + +Number indicating how many members can be recruited, considering current reputation and gang size. + +## Remarks + +RAM cost: 1 GB + diff --git a/markdown/bitburner.gang.md b/markdown/bitburner.gang.md index 9f825ef5a..77c09e7fe 100644 --- a/markdown/bitburner.gang.md +++ b/markdown/bitburner.gang.md @@ -34,11 +34,14 @@ If you are not in BitNode-2, then you must have Source-File 2 in order to use th | [getMemberInformation(name)](./bitburner.gang.getmemberinformation.md) | Get information about a specific gang member. | | [getMemberNames()](./bitburner.gang.getmembernames.md) | List all gang members. | | [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about the other gangs. | +| [getRecruitsAvailable()](./bitburner.gang.getrecruitsavailable.md) | Check how many gang members you can currently recruit. | | [getTaskNames()](./bitburner.gang.gettasknames.md) | List member task names. | | [getTaskStats(name)](./bitburner.gang.gettaskstats.md) | Get stats of a task. | | [inGang()](./bitburner.gang.ingang.md) | Check if you're in a gang. | | [purchaseEquipment(memberName, equipName)](./bitburner.gang.purchaseequipment.md) | Purchase an equipment for a gang member. | | [recruitMember(name)](./bitburner.gang.recruitmember.md) | Recruit a new gang member. | +| [renameMember(memberName, newName)](./bitburner.gang.renamemember.md) | Rename a Gang member to a new unique name. | +| [respectForNextRecruit()](./bitburner.gang.respectfornextrecruit.md) | Check the amount of Respect needed for your next gang recruit. | | [setMemberTask(memberName, taskName)](./bitburner.gang.setmembertask.md) | Set gang member to task. | | [setTerritoryWarfare(engage)](./bitburner.gang.setterritorywarfare.md) | Enable/Disable territory warfare. | diff --git a/markdown/bitburner.gang.renamemember.md b/markdown/bitburner.gang.renamemember.md new file mode 100644 index 000000000..a43597a5f --- /dev/null +++ b/markdown/bitburner.gang.renamemember.md @@ -0,0 +1,33 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Gang](./bitburner.gang.md) > [renameMember](./bitburner.gang.renamemember.md) + +## Gang.renameMember() method + +Rename a Gang member to a new unique name. + +**Signature:** + +```typescript +renameMember(memberName: string, newName: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| memberName | string | Name of the member to change. | +| newName | string | New name for that gang member. | + +**Returns:** + +boolean + +True if successful, and false if not. + +## Remarks + +RAM cost: 0 GB + +Rename a Gang Member if none already has the new name. + diff --git a/markdown/bitburner.gang.respectfornextrecruit.md b/markdown/bitburner.gang.respectfornextrecruit.md new file mode 100644 index 000000000..9da8d4a08 --- /dev/null +++ b/markdown/bitburner.gang.respectfornextrecruit.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Gang](./bitburner.gang.md) > [respectForNextRecruit](./bitburner.gang.respectfornextrecruit.md) + +## Gang.respectForNextRecruit() method + +Check the amount of Respect needed for your next gang recruit. + +**Signature:** + +```typescript +respectForNextRecruit(): number; +``` +**Returns:** + +number + +The static number value of Respect needed for the next recruit, with consideration to your current gang size. Returns `Infinity` if you have reached the gang size limit. + +## Remarks + +RAM cost: 1 GB + diff --git a/markdown/bitburner.ganggeninfo.md b/markdown/bitburner.ganggeninfo.md index 0d116966d..95b53c90d 100644 --- a/markdown/bitburner.ganggeninfo.md +++ b/markdown/bitburner.ganggeninfo.md @@ -21,6 +21,7 @@ interface GangGenInfo | [moneyGainRate](./bitburner.ganggeninfo.moneygainrate.md) | | number | Money earned per game cycle | | [power](./bitburner.ganggeninfo.power.md) | | number | Gang's power for territory warfare | | [respect](./bitburner.ganggeninfo.respect.md) | | number | Gang's respect | +| [respectForNextRecruit](./bitburner.ganggeninfo.respectfornextrecruit.md) | | number | Amount of Respect needed for next gang recruit, if possible | | [respectGainRate](./bitburner.ganggeninfo.respectgainrate.md) | | number | Respect earned per game cycle | | [territory](./bitburner.ganggeninfo.territory.md) | | number | Amount of territory held | | [territoryClashChance](./bitburner.ganggeninfo.territoryclashchance.md) | | number | Clash chance | diff --git a/markdown/bitburner.ganggeninfo.respectfornextrecruit.md b/markdown/bitburner.ganggeninfo.respectfornextrecruit.md new file mode 100644 index 000000000..793644faa --- /dev/null +++ b/markdown/bitburner.ganggeninfo.respectfornextrecruit.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [GangGenInfo](./bitburner.ganggeninfo.md) > [respectForNextRecruit](./bitburner.ganggeninfo.respectfornextrecruit.md) + +## GangGenInfo.respectForNextRecruit property + +Amount of Respect needed for next gang recruit, if possible + +**Signature:** + +```typescript +respectForNextRecruit: number; +``` diff --git a/src/BitNode/BitNode.tsx b/src/BitNode/BitNode.tsx index 6543f02b5..ef6efe7f8 100644 --- a/src/BitNode/BitNode.tsx +++ b/src/BitNode/BitNode.tsx @@ -73,16 +73,16 @@ export function initBitNodes() {
Certain Factions ({FactionName.SlumSnakes}, {FactionName.Tetrads}, {FactionName.TheSyndicate},{" "} {FactionName.TheDarkArmy}, {FactionName.SpeakersForTheDead}, {FactionName.NiteSec}, {FactionName.TheBlackHand} - ) give the player the ability to form and manage their own gangs. These gangs will earn the player money and - reputation with the corresponding Faction + ) give the player the ability to form and manage their own gang, which can earn the player money and reputation + with the corresponding Faction. Gangs offer more Augmentations than Factions, and in BitNode-2 offer a way to + destroy the BitNode.
- Every Augmentation in the game will be available through the Factions listed above

Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes once your karma - decreases to a certain value. It also increases the player's crime success rate, crime money, and charisma - multipliers by: + decreases to a certain value. It also increases your crime success rate, crime money, and charisma multipliers + by:

Level 1: 24% diff --git a/src/Documentation/doc/advanced/gang.md b/src/Documentation/doc/advanced/gang.md index c99a58077..8c1927bcc 100644 --- a/src/Documentation/doc/advanced/gang.md +++ b/src/Documentation/doc/advanced/gang.md @@ -1,5 +1,35 @@ -# Gang +# Gangs -Managing a gang can be very rewarding. By rising above all other gangs you get access to almost all augmentations in the game. +In the wake of crisis and war, Gang activity surged. Stronger than ever in a lawless world - Enhanced with fantastic technology, no longer held back by ethics and morals, 'free from the shadows'.... -PLACEHOLDER +Seen by most of the population as nihilistic, murderous and vile, occassional rumors suggest Gangs sometimes involve themselves with vigilanteism, hacktivism, perhaps even plotting against The Enders, seeking to destroy a world they cannot save. + +## Starting and Recruiting + +Outside of [BitNode-2](bitnodes.md) gangs require much more crime and heartbreak to create, but can still be a great help. Creating a Gang in other [BitNodes](bitnodes.md) will offer more [Augmentations](../basic/augmentations.md) than other [Factions](factions.md), but they will not be a way to destroy the [BitNode](bitnodes.md) alone. + +After creating a gang, you will be able to start recruiting, adding members to your gang as you gain Respect. While in a BitNode, your gang and gang member stats will not reset if you install augmentations. + +## Respect + +Earned as your gang members complete tasks, Respect affects your gang's productivity, including your Faction Reputation (needed to buy augmentations from your Gang Faction), and the number of recruits you can have. An individual gang member's Respect is lost or reset if they Ascend, or are killed in a Territory Warfare clash. + +## Ascending + +When experienced enough, gang members are offered Ascension, a permanent boost to their stat multipliers at the cost of resetting their base stats and equipment to 0, and reducing your Gang Reputation by the same amount as that member had earned since they last Ascended. + +## Equipping and Managing + +Buying Equipment for a gang member will give them a stat boost until they Ascend or are killed, at which point most equipment will reset. + +Augmentations you install on gang members (in the Gang Equipment subpage) do not reset when they Ascend. + +Active gang members earn stats, respect and money based on their current stats, their equipment, and the effects of Ascending. + +## Wanted, Territory and Clashes + +Your gang's "Wanted Level" can make tasks much less productive, and is affected by the tasks assigned to gang members. "Ethical Hacking" or "Vigilante Justice" tasks can lower Wanted Level. + +"Territory Warfare" is a special task that builds Power for your gang. If "Territory Warfare" is enabled [see the Territory subpage of your Gang page], members have a chance to win or lose territory by clashing with other gangs. The % of Territory you control affects most aspects of your gang productivity. + +Note that gang members can die during clashes, even if your gang wins. diff --git a/src/Faction/ui/CreateGangModal.tsx b/src/Faction/ui/CreateGangModal.tsx index 454424acc..dd2eb0430 100644 --- a/src/Faction/ui/CreateGangModal.tsx +++ b/src/Faction/ui/CreateGangModal.tsx @@ -17,13 +17,15 @@ interface IProps { /** React Component for the popup used to create a new gang. */ export function CreateGangModal(props: IProps): React.ReactElement { const combatGangText = - "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " + - "Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " + + props.facName + + " is a COMBAT gang and its members will have different tasks than in HACKING gangs. " + + "Compared to hacking gangs, progression with a combat gang can be more difficult as territory management " + "is more important. However, well-managed combat gangs can progress faster than hacking ones."; const hackingGangText = - "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " + - "Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " + + props.facName + + " is a HACKING gang and its members will have different tasks than in COMBAT gangs. " + + "Compared to combat gangs, progression with a hacking gang is slower but more straightforward as territory warfare " + "is not as important."; function isHacking(): boolean { @@ -46,19 +48,19 @@ export function CreateGangModal(props: IProps): React.ReactElement { Would you like to create a new Gang with {props.facName}?

- Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It - also resets your reputation with this faction. + This will prevent you from creating a Gang with any other Faction until the BitNode is destroyed or abandoned. + It will also reset your reputation with {props.facName}.

{isHacking() ? hackingGangText : combatGangText}

- Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each - of these Factions have all Augmentations available. + Other than hacking vs combat and name, there are no differences between gangs. + ); } diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index c5570b604..80aa8face 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -228,13 +228,28 @@ export function FactionsRoot(): React.ReactElement { )} + {Player.inGang() && ( + + Your Gang + + )} + {Player.inGang() && ( + + + + )} Your Factions {allJoinedFactions.length > 0 ? ( allJoinedFactions.map((facName) => { - if (!Object.hasOwn(Factions, facName)) return null; + if (!Object.hasOwn(Factions, facName) || Player.getGangName() === facName) return null; return ; }) ) : ( diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts index a5823f7ae..649e4ecb0 100644 --- a/src/Gang/Gang.ts +++ b/src/Gang/Gang.ts @@ -291,16 +291,25 @@ export class Gang { canRecruitMember(): boolean { if (this.members.length >= GangConstants.MaximumGangMembers) return false; - return this.respect >= this.getRespectNeededToRecruitMember(); + return this.respect >= this.respectForNextRecruit(); } - getRespectNeededToRecruitMember(): number { - // First N gang members are free (can be recruited at 0 respect) - const numFreeMembers = 3; - if (this.members.length < numFreeMembers) return 0; + /** @returns The respect threshold needed for the next member recruitment. Infinity if already at or above max members. */ + respectForNextRecruit(): number { + if (this.members.length < GangConstants.numFreeMembers) return 0; + if (this.members.length >= GangConstants.MaximumGangMembers) { + return Infinity; + } + const exponent = this.members.length - GangConstants.numFreeMembers + 1; + return Math.pow(GangConstants.recruitThresholdBase, exponent); + } - const i = this.members.length - (numFreeMembers - 1); - return Math.pow(5, i); + getRecruitsAvailable(): number { + const numFreeMembers = 3; + const recruitCostBase = 5; + if (this.members.length < numFreeMembers && this.respect < Math.pow(recruitCostBase, numFreeMembers)) + return numFreeMembers - this.members.length; // if the max possible is less than freeMembers + return Math.floor(Math.log(this.respect) / Math.log(recruitCostBase)) + numFreeMembers - this.members.length; //else } recruitMember(name: string): boolean { diff --git a/src/Gang/data/Constants.ts b/src/Gang/data/Constants.ts index b975dda12..95a557bbd 100644 --- a/src/Gang/data/Constants.ts +++ b/src/Gang/data/Constants.ts @@ -2,6 +2,10 @@ import { CONSTANTS } from "../../Constants"; import { FactionName } from "@enums"; export const GangConstants = { + /** Number of members that can be recruited with 0 respect. */ + numFreeMembers: 3, + /** Exponential base used in determining the respect threshold for recruiting a new member. */ + recruitThresholdBase: 5, // Respect is divided by this to get rep gain GangRespectToReputationRatio: 75, MaximumGangMembers: 12, diff --git a/src/Gang/data/tasks.ts b/src/Gang/data/tasks.ts index acdeeed2a..c37324479 100644 --- a/src/Gang/data/tasks.ts +++ b/src/Gang/data/tasks.ts @@ -379,7 +379,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ params: { chaWeight: 100, difficulty: 8 }, }, { - desc: "Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.", + desc: "Members assigned to this task increase your gang's power and will fight for territory if 'Territory Warfare' is engaged. Note that gang members can be killed while assigned this task.", isCombat: true, isHacking: true, name: "Territory Warfare", diff --git a/src/Gang/ui/AscensionModal.tsx b/src/Gang/ui/AscensionModal.tsx index ed69f1fc2..eb5bf20a5 100644 --- a/src/Gang/ui/AscensionModal.tsx +++ b/src/Gang/ui/AscensionModal.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; @@ -8,6 +8,7 @@ import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { Modal } from "../../ui/React/Modal"; import { useGang } from "./Context"; import { useRerender } from "../../ui/React/hooks"; +import { Player } from "@player"; type AscensionModalProps = { open: boolean; @@ -20,21 +21,30 @@ type AscensionModalProps = { * React Component for the content of the popup before the player confirms the * ascension of a gang member. */ -export function AscensionModal(props: AscensionModalProps): React.ReactElement { +export function AscensionModal({ open, onClose, member, onAscend }: AscensionModalProps): React.ReactElement { const gang = useGang(); useRerender(1000); + //Cleanup if modal is closed for other reasons, ie. ns.gang.ascendMember() + useEffect(() => onClose, [onClose]); + function confirm(): void { - props.onAscend(); - const res = gang.ascendMember(props.member); + onAscend(); + const res = gang.ascendMember(member); dialogBoxCreate( <> - You ascended {props.member.name}!
+ {member.name} ascended!
- Your gang lost {formatRespect(res.respect)} respect. + {res.respect > 0 && ( +
+
+ Your gang, {Player.gang?.facName}, lost {formatRespect(res.respect)} respect. +
+
+ )}
+ {member.name} gained the following stat multipliers for ascending:
- {props.member.name} gained the following stat multipliers for ascending:
Hacking: x{formatPreciseMultiplier(res.hack)}
@@ -50,26 +60,31 @@ export function AscensionModal(props: AscensionModalProps): React.ReactElement {
, ); - props.onClose(); + onClose(); } // const ascendBenefits = props.member.getAscensionResults(); - const preAscend = props.member.getCurrentAscensionMults(); - const postAscend = props.member.getAscensionMultsAfterAscend(); + const preAscend = member.getCurrentAscensionMults(); + const postAscend = member.getAscensionMultsAfterAscend(); return ( - + Are you sure you want to ascend this member? They will lose all of
their non-Augmentation upgrades and their stats will reset back to 1.
+ {member.earnedRespect > 0 && ( +
+
+ Furthermore, your gang will lose {formatRespect(member.earnedRespect)} respect. +
+
+ )}
- Furthermore, your gang will lose {formatRespect(props.member.earnedRespect)} respect + In return, {member.name} will gain the following permanent boost to stat multipliers:

- In return, they will gain the following permanent boost to stat multipliers: -
Hacking: x{formatPreciseMultiplier(preAscend.hack)} => x{formatPreciseMultiplier(postAscend.hack)}
Strength: x{formatPreciseMultiplier(preAscend.str)} => x{formatPreciseMultiplier(postAscend.str)} @@ -82,8 +97,10 @@ export function AscensionModal(props: AscensionModalProps): React.ReactElement {
Charisma: x{formatPreciseMultiplier(preAscend.cha)} => x{formatPreciseMultiplier(postAscend.cha)}
+
+
); } diff --git a/src/Gang/ui/EquipmentsSubpage.tsx b/src/Gang/ui/EquipmentsSubpage.tsx index aa59b1773..fd57e174e 100644 --- a/src/Gang/ui/EquipmentsSubpage.tsx +++ b/src/Gang/ui/EquipmentsSubpage.tsx @@ -248,8 +248,8 @@ export function EquipmentsSubpage(): React.ReactElement { - You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power - leads to more discounts. + A discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to + more discounts. } > diff --git a/src/Gang/ui/GangMemberCardContent.tsx b/src/Gang/ui/GangMemberCardContent.tsx index 806a447c6..d5edc4464 100644 --- a/src/Gang/ui/GangMemberCardContent.tsx +++ b/src/Gang/ui/GangMemberCardContent.tsx @@ -2,7 +2,7 @@ * React Component for the content of the accordion of gang members on the * management subpage. */ -import React, { useState } from "react"; +import React from "react"; import { GangMemberStats } from "./GangMemberStats"; import { TaskSelector } from "./TaskSelector"; import { AscensionModal } from "./AscensionModal"; @@ -13,51 +13,47 @@ import HelpIcon from "@mui/icons-material/Help"; import { GangMember } from "../GangMember"; import { StaticModal } from "../../ui/React/StaticModal"; +import { useBoolean, useRerender } from "../../ui/React/hooks"; interface IProps { member: GangMember; } export function GangMemberCardContent(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const [helpOpen, setHelpOpen] = useState(false); - const [ascendOpen, setAscendOpen] = useState(false); + const rerender = useRerender(); + const [helpOpen, { on: openHelpModal, off: closeHelpModal }] = useBoolean(false); + const [ascendOpen, { on: openAscensionModal, off: closeAscensionModal }] = useBoolean(false); return ( <> {props.member.canAscend() && ( - - setAscendOpen(false)} - member={props.member} - onAscend={() => setRerender((old) => !old)} - /> - - setHelpOpen(false)}> + - Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their + Ascending a Gang Member resets that member's progress and stats in exchange for a permanent boost to their stat multipliers.

- The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp - they have. + The stat boost a Gang Member gains upon ascension is based on the amount of exp they have, and will be + shown before you choose to ascend them.

- Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect - equal to the total respect earned by the member. + Upon ascension, they will lose all of their non-Augmentation Equipment and your gang will lose respect + equal to the total respect earned by that member.
)} - setRerender((old) => !old)} member={props.member} /> + ); diff --git a/src/Gang/ui/ManagementSubpage.tsx b/src/Gang/ui/ManagementSubpage.tsx index 9dc2f5175..f623221a6 100644 --- a/src/Gang/ui/ManagementSubpage.tsx +++ b/src/Gang/ui/ManagementSubpage.tsx @@ -2,33 +2,54 @@ import React from "react"; import { GangStats } from "./GangStats"; import { GangMemberList } from "./GangMemberList"; import { useGang } from "./Context"; -import Typography from "@mui/material/Typography"; +import { Typography, FormControlLabel, Switch, Tooltip } from "@mui/material"; /** React Component for the subpage that manages gang members, the main page. */ export function ManagementSubpage(): React.ReactElement { const gang = useGang(); return ( <> + + {gang.facName} (your Gang) + - This page is used to manage your gang members and get an overview of your gang's stats. +
+ If a gang member is not earning much money or respect, the task you assigned might be too difficult. Consider + assigning an easier task, or training them. Tasks closer to the top of the dropdown list are generally easier. + Alternatively, low production might be a sign that your wanted level is too high. Consider doing{" "} + {gang.isHackingGang ? "Ethical Hacking or " : ""} + Vigilante Justice to lower your wanted level.

- If a gang member is not earning much money or respect, the task that you have assigned to that member might be - too difficult. Consider training that member's stats or choosing an easier task. The tasks closer to the top of - the dropdown list are generally easier. Alternatively, the gang member's low production might be due to the fact - that your wanted level is too high. Consider assigning a few members to the ' - {gang.isHackingGang ? "Ethical Hacking" : "Vigilante Justice"}' task to lower your wanted level. + Installing Augmentations does NOT reset progress with your Gang. Furthermore, after installing Augmentations, + you will automatically join whatever Faction you created your gang with.

- Installing Augmentations does NOT reset your progress with your Gang. Furthermore, after installing - Augmentations, you will automatically be a member of whatever Faction you created your gang with. -
-
- You can also manage your gang programmatically through Netscript using the Gang API + You can also manage your gang programmatically through Netscript using the Gang API.


+ (gang.territoryWarfareEngaged = event.target.checked)} + /> + } + label={ + + Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance + to gradually decrease until it reaches 0%. See the "Territory" subpage for more information. + + } + > + Engage in Territory Warfare + + } + /> ); diff --git a/src/Gang/ui/RecruitButton.tsx b/src/Gang/ui/RecruitButton.tsx index ddf7b69e0..98658e0d0 100644 --- a/src/Gang/ui/RecruitButton.tsx +++ b/src/Gang/ui/RecruitButton.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import { RecruitModal } from "./RecruitModal"; -import { GangConstants } from "../data/Constants"; import { formatRespect } from "../../ui/formatNumber"; import { useGang } from "./Context"; import Typography from "@mui/material/Typography"; @@ -15,24 +14,29 @@ interface IProps { export function RecruitButton(props: IProps): React.ReactElement { const gang = useGang(); const [open, setOpen] = useState(false); - if (gang.members.length >= GangConstants.MaximumGangMembers) { - return <>; - } + const recruitsAvailable = gang.getRecruitsAvailable(); if (!gang.canRecruitMember()) { - const respect = gang.getRespectNeededToRecruitMember(); + const respectNeeded = gang.respectForNextRecruit(); return ( - {formatRespect(respect)} respect needed to recruit next member + {respectNeeded === Infinity ? ( + Maximum gang members already recruited + ) : ( + {formatRespect(respectNeeded)} respect needed to recruit next member + )} ); } return ( <> - + + + Can recruit {recruitsAvailable} more gang member{recruitsAvailable === 1 ? "" : "s"} + setOpen(false)} onRecruit={props.onRecruit} /> diff --git a/src/Gang/ui/RecruitModal.tsx b/src/Gang/ui/RecruitModal.tsx index c1150320f..0a1092ef9 100644 --- a/src/Gang/ui/RecruitModal.tsx +++ b/src/Gang/ui/RecruitModal.tsx @@ -23,12 +23,13 @@ export function RecruitModal(props: IRecruitPopupProps): React.ReactElement { if (disabled) return; // At this point, the only way this can fail is if you already // have a gang member with the same name - if (!gang.recruitMember(name)) { + if (!gang.recruitMember(name) && name !== "") { dialogBoxCreate("You already have a gang member with this name!"); return; } props.onRecruit(); + setName(""); props.onClose(); } @@ -42,7 +43,7 @@ export function RecruitModal(props: IRecruitPopupProps): React.ReactElement { return ( - Enter a name for your new Gang member: + Enter a name for your new Gang Member:
diff --git a/src/Gang/ui/TerritoryInfoModal.tsx b/src/Gang/ui/TerritoryInfoModal.tsx index fc8a1850b..d98cfb2ad 100644 --- a/src/Gang/ui/TerritoryInfoModal.tsx +++ b/src/Gang/ui/TerritoryInfoModal.tsx @@ -16,15 +16,15 @@ export const TerritoryInfoModal = ({ open, onClose }: IProps): React.ReactElemen Clashing Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on - your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The - accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare' - task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also - loses a small amount of power whenever you lose a clash. + your gang's power, found in the Territory display or with methods from the Gang API. Your gang's power slowly + accumulates over time, determined by the stats of all Gang members you have assigned to the 'Territory + Warfare' task. Gang members that are not assigned to this task do not contribute to your gang's power. Your + gang also loses a small amount of power whenever you lose a clash.

NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of - whether you win or lose the clash. A gang member being killed results in both respect and power loss for your - gang. + whether you win or lose the clash. A gang member being killed results in loss of both respect and power for + your gang.

Territory diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx index 5715c35b9..ed0ce9a61 100644 --- a/src/Gang/ui/TerritorySubpage.tsx +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -65,8 +65,8 @@ export function TerritorySubpage(): React.ReactElement { - If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members - dies in a territory clash. + If this is enabled, you will receive a pop-up notifying you whenever one of your Gang Members dies in + a territory clash. } > diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 3aa95f4ab..7186cbddf 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -213,10 +213,13 @@ const gang = { createGang: RamCostConstants.GangApiBase / 4, inGang: RamCostConstants.GangApiBase / 4, getMemberNames: RamCostConstants.GangApiBase / 4, + renameMember: 0, getGangInformation: RamCostConstants.GangApiBase / 2, getOtherGangInformation: RamCostConstants.GangApiBase / 2, getMemberInformation: RamCostConstants.GangApiBase / 2, canRecruitMember: RamCostConstants.GangApiBase / 4, + getRecruitsAvailable: RamCostConstants.GangApiBase / 4, + respectForNextRecruit: RamCostConstants.GangApiBase / 4, recruitMember: RamCostConstants.GangApiBase / 2, getTaskNames: RamCostConstants.GangApiBase / 4, getTaskStats: RamCostConstants.GangApiBase / 4, diff --git a/src/NetscriptFunctions/Gang.ts b/src/NetscriptFunctions/Gang.ts index 5b9d5732a..35e91d93e 100644 --- a/src/NetscriptFunctions/Gang.ts +++ b/src/NetscriptFunctions/Gang.ts @@ -53,6 +53,32 @@ export function NetscriptGang(): InternalAPI { const gang = getGang(ctx); return gang.members.map((member) => member.name); }, + renameMember: (ctx) => (_memberName, _newName) => { + const gang = getGang(ctx); + const memberName = helpers.string(ctx, "memberName", _memberName); + const newName = helpers.string(ctx, "newName", _newName); + const member = gang.members.find((m) => m.name === memberName); + if (!memberName) { + throw helpers.makeRuntimeErrorMsg(ctx, `Invalid memberName: "" (empty string)`); + } + if (!newName) { + throw helpers.makeRuntimeErrorMsg(ctx, `Invalid newName: "" (empty string)`); + } + if (newName === memberName) { + throw helpers.makeRuntimeErrorMsg(ctx, `newName and memberName must be different, but both were: ${newName}`); + } + if (!member) { + helpers.log(ctx, () => `Failed to rename member: No member exists with memberName: ${memberName}`); + return false; + } + if (gang.members.map((m) => m.name).includes(newName)) { + helpers.log(ctx, () => `Failed to rename member: A different member already has the newName: ${newName}`); + return false; + } + member.name = newName; + helpers.log(ctx, () => `Renamed member from memberName: ${memberName} to newName: ${newName}`); + return true; + }, getGangInformation: (ctx) => () => { const gang = getGang(ctx); return { @@ -62,6 +88,7 @@ export function NetscriptGang(): InternalAPI { power: gang.getPower(), respect: gang.respect, respectGainRate: gang.respectGainRate, + respectForNextRecruit: gang.respectForNextRecruit(), territory: gang.getTerritory(), territoryClashChance: gang.territoryClashChance, territoryWarfareEngaged: gang.territoryWarfareEngaged, @@ -134,17 +161,31 @@ export function NetscriptGang(): InternalAPI { const gang = getGang(ctx); return gang.canRecruitMember(); }, + getRecruitsAvailable: (ctx) => () => { + const gang = getGang(ctx); + return gang.getRecruitsAvailable(); + }, + respectForNextRecruit: (ctx) => () => { + const gang = getGang(ctx); + return gang.respectForNextRecruit(); + }, recruitMember: (ctx) => (_memberName) => { const memberName = helpers.string(ctx, "memberName", _memberName); const gang = getGang(ctx); const recruited = gang.recruitMember(memberName); - if (recruited) { + if (memberName === "") { + ctx.workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member. Name must be provided.`); + return false; + } else if (recruited) { ctx.workerScript.log("gang.recruitMember", () => `Successfully recruited Gang Member '${memberName}'`); + return recruited; } else { - ctx.workerScript.log("gang.recruitMember", () => `Failed to recruit Gang Member '${memberName}'`); + ctx.workerScript.log( + "gang.recruitMember", + () => `Failed to recruit Gang Member '${memberName}'. Name already used.`, + ); + return recruited; } - - return recruited; }, getTaskNames: (ctx) => () => { const gang = getGang(ctx); diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 64218743d..633fe144e 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -771,6 +771,8 @@ interface GangGenInfo { respect: number; /** Respect earned per game cycle */ respectGainRate: number; + /** Amount of Respect needed for next gang recruit, if possible */ + respectForNextRecruit: number; /** Amount of territory held */ territory: number; /** Clash chance */ @@ -3397,6 +3399,18 @@ export interface Gang { */ getMemberNames(): string[]; + /** + * Rename a Gang member to a new unique name. + * @remarks + * RAM cost: 0 GB + * + * Rename a Gang Member if none already has the new name. + * @param memberName - Name of the member to change. + * @param newName - New name for that gang member. + * @returns True if successful, and false if not. + */ + renameMember(memberName: string, newName: string): boolean; + /** * Get information about your gang. * @remarks @@ -3450,6 +3464,26 @@ export interface Gang { */ canRecruitMember(): boolean; + /** + * Check how many gang members you can currently recruit. + * @remarks + * RAM cost: 1 GB + * + * @returns Number indicating how many members can be recruited, + * considering current reputation and gang size. + */ + getRecruitsAvailable(): number; + + /** + * Check the amount of Respect needed for your next gang recruit. + * @remarks + * RAM cost: 1 GB + * + * @returns The static number value of Respect needed for the next + * recruit, with consideration to your current gang size. + * Returns `Infinity` if you have reached the gang size limit. + */ + respectForNextRecruit(): number; /** * Recruit a new gang member. * @remarks