From 28da81f3f12b657da43f6b973d3aedde3fe718f2 Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:44:19 +0700 Subject: [PATCH] BLADEBURNER: Add visual cues to warn player of dangerous actions and status of population, chaos (#1856) --- src/Bladeburner/Actions/Action.ts | 3 + src/Bladeburner/Bladeburner.ts | 35 +++++------ src/Bladeburner/data/Contracts.ts | 15 ++--- src/Bladeburner/data/GeneralActions.ts | 12 ++-- src/Bladeburner/data/Operations.ts | 23 +++++--- src/Bladeburner/ui/ActionHeader.tsx | 11 +++- src/Bladeburner/ui/GeneralActionElem.tsx | 8 +-- src/Bladeburner/ui/Stats.tsx | 75 +++++++++++++++++++++--- src/Bladeburner/ui/SuccessChance.tsx | 7 ++- 9 files changed, 134 insertions(+), 55 deletions(-) diff --git a/src/Bladeburner/Actions/Action.ts b/src/Bladeburner/Actions/Action.ts index 71163d810..0e61cdb82 100644 --- a/src/Bladeburner/Actions/Action.ts +++ b/src/Bladeburner/Actions/Action.ts @@ -12,6 +12,7 @@ import { clampNumber } from "../../utils/helpers/clampNumber"; export interface ActionParams { desc: string; + warning?: string; successScaling?: string; baseDifficulty?: number; rewardFac?: number; @@ -26,6 +27,7 @@ export interface ActionParams { export abstract class ActionClass { desc = ""; + warning = ""; successScaling = ""; // For LevelableActions, the base difficulty can be increased based on action level baseDifficulty = 100; @@ -63,6 +65,7 @@ export abstract class ActionClass { constructor(params: ActionParams | null = null) { if (!params) return; this.desc = params.desc; + if (params.warning) this.warning = params.warning; if (params.successScaling) this.successScaling = params.successScaling; if (params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10); diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index d3ee85820..286602309 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -829,24 +829,25 @@ export class Bladeburner implements OperationTeam { } completeContract(success: boolean, action: Contract): void { + if (!success) { + return; + } const city = this.getCurrentCity(); - if (success) { - switch (action.name) { - case BladeburnerContractName.Tracking: - // Increase estimate accuracy by a relatively small amount - city.improvePopulationEstimateByCount( - getRandomIntInclusive(100, 1e3) * this.getSkillMult(BladeburnerMultName.SuccessChanceEstimate), - ); - break; - case BladeburnerContractName.BountyHunter: - city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); - city.changeChaosByCount(0.02); - break; - case BladeburnerContractName.Retirement: - city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); - city.changeChaosByCount(0.04); - break; - } + switch (action.name) { + case BladeburnerContractName.Tracking: + // Increase estimate accuracy by a relatively small amount + city.improvePopulationEstimateByCount( + getRandomIntInclusive(100, 1e3) * this.getSkillMult(BladeburnerMultName.SuccessChanceEstimate), + ); + break; + case BladeburnerContractName.BountyHunter: + city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); + city.changeChaosByCount(0.02); + break; + case BladeburnerContractName.Retirement: + city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); + city.changeChaosByCount(0.04); + break; } } diff --git a/src/Bladeburner/data/Contracts.ts b/src/Bladeburner/data/Contracts.ts index ed01af648..dcc1d0520 100644 --- a/src/Bladeburner/data/Contracts.ts +++ b/src/Bladeburner/data/Contracts.ts @@ -9,9 +9,8 @@ export function createContracts(): Record { name: BladeburnerContractName.Tracking, desc: "Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT " + - "engage. Stealth is of the utmost importance.\n\n" + - "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever " + - "city you are currently in.", + "engage. Stealth is of the utmost importance.\n" + + "Successfully completing this contract will slightly improve the Synthoid population estimate of your current city.", successScaling: "Significantly affected by Dexterity and Agility. Minor bonus from combat stats and Charisma. Unaffected by Hacking skill.", baseDifficulty: 125, @@ -44,9 +43,8 @@ export function createContracts(): Record { [BladeburnerContractName.BountyHunter]: new Contract({ name: BladeburnerContractName.BountyHunter, desc: - "Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.\n\n" + - "Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also " + - "increase its chaos level.", + "Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.\n" + + "Successfully completing this contract will decrease the Synthoid population of your current city and increase its chaos level.", successScaling: "Significantly affected by Dexterity and Agility. Minor bonus from combat stats and Charisma. Unaffected by Hacking skill.", baseDifficulty: 250, @@ -79,9 +77,8 @@ export function createContracts(): Record { [BladeburnerContractName.Retirement]: new Contract({ name: BladeburnerContractName.Retirement, desc: - "Hunt down and retire (kill) rogue Synthoids.\n\n" + - "Successfully completing a Retirement contract will lower the population in your current city, and will also " + - "increase its chaos level.", + "Hunt down and retire (kill) rogue Synthoids.\n" + + "Successfully completing this contract will decrease the Synthoid population of your current city and increase its chaos level.", successScaling: "Affected by combat stats. Minor bonus from Charisma. Unaffected by Hacking skill.", baseDifficulty: 200, difficultyFac: 1.03, diff --git a/src/Bladeburner/data/GeneralActions.ts b/src/Bladeburner/data/GeneralActions.ts index 7bb6b5914..d3b945c00 100644 --- a/src/Bladeburner/data/GeneralActions.ts +++ b/src/Bladeburner/data/GeneralActions.ts @@ -16,7 +16,7 @@ export const GeneralActions: Record desc: "Mine and analyze Synthoid-related data. This improves the Bladeburner unit's intelligence on Synthoid locations " + "and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the " + - "current city.\n\n" + + "current city.\n" + "Does NOT require stamina.", }), [BladeburnerGeneralActionName.Recruitment]: new GeneralAction({ @@ -30,7 +30,7 @@ export const GeneralActions: Record return Math.pow(person.skills.charisma, 0.45) / (bladeburner.teamSize - bladeburner.sleeveSize + 1); }, desc: - "Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.\n\n" + + "Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.\n" + "Does NOT require stamina.", successScaling: "Success chance is affected by Charisma.", }), @@ -38,8 +38,8 @@ export const GeneralActions: Record name: BladeburnerGeneralActionName.Diplomacy, getActionTime: () => 60, desc: - "Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in " + - "your current city.\n\n" + + "Improve diplomatic relations with the Synthoid population. Completing this action will reduce the chaos level of " + + "your current city.\n" + "Does NOT require stamina.", }), [BladeburnerGeneralActionName.HyperbolicRegen]: new GeneralAction({ @@ -54,6 +54,8 @@ export const GeneralActions: Record getActionTime: () => 60, desc: "Purposefully stir trouble in the synthoid community in order to gain a political edge. This will generate " + - "additional contracts and operations, at the cost of increased Chaos.", + "additional contracts and operations at the cost of increasing the chaos level of all cities.\n" + + "Does NOT require stamina.", + warning: "This action increases chaos of all cities by percentage.", }), }; diff --git a/src/Bladeburner/data/Operations.ts b/src/Bladeburner/data/Operations.ts index d11491fac..065a98d00 100644 --- a/src/Bladeburner/data/Operations.ts +++ b/src/Bladeburner/data/Operations.ts @@ -9,8 +9,8 @@ export function createOperations(): Record [BladeburnerOperationName.Investigation]: new Operation({ name: BladeburnerOperationName.Investigation, desc: - "As a field agent, investigate and identify Synthoid populations, movements, and operations.\n\n" + - "Successful Investigation ops will increase the accuracy of your synthoid data.\n\n" + + "As a field agent, investigate and identify Synthoid populations, movements, and operations.\n" + + "Successful Investigation ops will increase the accuracy of your synthoid data.\n" + "You will NOT lose HP from failed Investigation ops.", successScaling: "Significantly affected by Hacking skill and Charisma. Minor bonus from combat stats.", baseDifficulty: 400, @@ -43,7 +43,7 @@ export function createOperations(): Record [BladeburnerOperationName.Undercover]: new Operation({ name: BladeburnerOperationName.Undercover, desc: - "Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.\n\n" + + "Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.\n" + "Successful Undercover ops will increase the accuracy of your synthoid data.", successScaling: "Affected by Hacking skill, Dexterity, Agility and Charisma. Minor bonus from Defense and Strength.", @@ -77,7 +77,10 @@ export function createOperations(): Record }), [BladeburnerOperationName.Sting]: new Operation({ name: BladeburnerOperationName.Sting, - desc: "Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.", + desc: + "Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.\n" + + "Completing this operation will increase the chaos level of your current city. If you complete it successfully, it will decrease the Synthoid population of your current city.", + warning: "This action decreases population by percentage.", successScaling: "Significantly affected by Hacking skill and Dexterity. Major bonus from Charisma. Minor bonus from combat stats.", baseDifficulty: 650, @@ -111,7 +114,9 @@ export function createOperations(): Record name: BladeburnerOperationName.Raid, desc: "Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your " + - "current city in order for this Operation to be successful.", + "current city in order for this Operation to be successful.\n" + + "Completing this operation will decrease the Synthoid population of your current city and increase its chaos level.", + warning: "This action decreases population and increases chaos by percentage.", successScaling: "Affected by combat stats. Minor bonus from Hacking skill. Unaffected by Charisma.", baseDifficulty: 800, difficultyFac: 1.045, @@ -148,7 +153,9 @@ export function createOperations(): Record name: BladeburnerOperationName.StealthRetirement, desc: "Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any " + - "attention. Stealth and discretion are key.", + "attention. Stealth and discretion are key.\n" + + "Completing this operation will DECREASE the chaos level of your current city. If you complete it successfully, it will decrease the Synthoid population of your current city.", + warning: "This action decreases population by percentage.", successScaling: "Significantly affected by Dexterity and Agility. Minor bonus from combat stats and Hacking skill. Unaffected by Charisma.", baseDifficulty: 1000, @@ -183,7 +190,9 @@ export function createOperations(): Record name: BladeburnerOperationName.Assassination, desc: "Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the " + - "Synthoid communities.", + "Synthoid communities.\n" + + "Completing this operation may increase the chaos level of your current city. If you complete it successfully, it will decrease the Synthoid population of your current city.", + warning: "This action may increase chaos by percentage.", successScaling: "Significantly affected by Dexterity and Agility. Minor bonus from combat stats and Hacking skill.\n" + "Unaffected by Charisma.", diff --git a/src/Bladeburner/ui/ActionHeader.tsx b/src/Bladeburner/ui/ActionHeader.tsx index 51b87b9a7..9acba3b6e 100644 --- a/src/Bladeburner/ui/ActionHeader.tsx +++ b/src/Bladeburner/ui/ActionHeader.tsx @@ -2,7 +2,7 @@ import type { Bladeburner } from "../Bladeburner"; import type { Action } from "../Types"; import React from "react"; -import { Box, Typography } from "@mui/material"; +import { Box, Tooltip, Typography } from "@mui/material"; import { CopyableText } from "../../ui/React/CopyableText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { StartButton } from "./StartButton"; @@ -13,6 +13,8 @@ import { formatNumberNoSuffix } from "../../ui/formatNumber"; import { BlackOperation, Operation } from "../Actions"; import { BladeburnerConstants } from "../data/Constants"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; +import WarningIcon from "@mui/icons-material/Warning"; +import { Settings } from "../../Settings/Settings"; interface ActionHeaderProps { bladeburner: Bladeburner; @@ -87,6 +89,13 @@ export function ActionHeader({ bladeburner, action, rerender }: ActionHeaderProp return ( + {action.warning && ( + + + + + + )} {allowTeam && } diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index 276a085da..f93612d3a 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -2,14 +2,13 @@ import type { Bladeburner } from "../Bladeburner"; import type { GeneralAction } from "../Actions/GeneralAction"; import React from "react"; -import { formatNumberNoSuffix } from "../../ui/formatNumber"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { Player } from "@player"; import { Paper, Typography } from "@mui/material"; import { useRerender } from "../../ui/React/hooks"; import { ActionHeader } from "./ActionHeader"; import { BladeburnerGeneralActionName } from "@enums"; -import { clampNumber } from "../../utils/helpers/clampNumber"; +import { SuccessChance } from "./SuccessChance"; interface GeneralActionElemProps { bladeburner: Bladeburner; @@ -24,15 +23,14 @@ export function GeneralActionElem({ bladeburner, action }: GeneralActionElemProp
- {action.desc} + {action.desc}
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} {action.name === BladeburnerGeneralActionName.Recruitment && ( <>
- Estimated success chance:{" "} - {formatNumberNoSuffix(clampNumber(action.getSuccessChance(bladeburner, Player), 0, 1) * 100, 1)}% + )}
diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index df7419f2d..99726083c 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -13,6 +13,8 @@ import { Factions } from "../../Faction/Factions"; import { Router } from "../../ui/GameRoot"; import { Page } from "../../ui/Router"; import { TravelModal } from "./TravelModal"; +import WarningIcon from "@mui/icons-material/Warning"; +import { Settings } from "../../Settings/Settings"; interface StatsProps { bladeburner: Bladeburner; @@ -29,6 +31,35 @@ export function Stats({ bladeburner }: StatsProps): React.ReactElement { if (success) Router.toPage(Page.Faction, { faction: Factions[FactionName.Bladeburners] }); } + let populationTextColor = Settings.theme.primary; + let populationWarning: string | null = null; + /** + * The initial population is randomized between 1e9 and 1.5e9. If it drops below 1e9, the success chance is reduced. + * We use 2 thresholds: + * - 8e8: The success chance is reduced by ~15%. On average, random events usually do not reduce the population to + * this low number. + * - 1e8: The success chance is reduced by ~80%. If the population is reduced to this number, it's very likely that + * the player is performing actions that decrease the population by percentage. + */ + if (bladeburner.getCurrentCity().pop <= 1e8) { + populationTextColor = Settings.theme.error; + populationWarning = "extremely low"; + } else if (bladeburner.getCurrentCity().pop < 9e8) { + populationTextColor = Settings.theme.warning; + populationWarning = "low"; + } + + let chaosTextColor = Settings.theme.primary; + let chaosWarning: string | null = null; + // When chaos is 1e4, the success chance is reduced by ~99%. + if (bladeburner.getCurrentCity().chaos >= 1e4) { + chaosTextColor = Settings.theme.error; + chaosWarning = "extremely high"; + } else if (bladeburner.getCurrentCity().chaos >= BladeburnerConstants.ChaosThreshold) { + chaosTextColor = Settings.theme.warning; + chaosWarning = "high"; + } + return ( @@ -96,13 +127,30 @@ export function Stats({ bladeburner }: StatsProps): React.ReactElement { - This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An - accurate population count increases success rate estimates. + + + This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An + accurate population estimate increases success rate estimates. + +
+ + You should be careful with actions that decrease Synthoid population by percentage. Those actions can + kill a large number of Synthoids in a short amount of time. Low population count decreases the success + chance of most actions. If the population count is too low, you will need to move to another city. + + {populationWarning && ( + <> +
+ The intelligence agency notifies us that Synthoid population is {populationWarning}. + + )}
} > - Est. Synthoid Population: {formatPopulation(bladeburner.getCurrentCity().popEst)} + + Est. Synthoid Population: {formatPopulation(bladeburner.getCurrentCity().popEst)} + {populationWarning && } +
@@ -120,13 +168,24 @@ export function Stats({ bladeburner }: StatsProps): React.ReactElement { - The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a - chaos level can make contracts and operations harder. + + + Tensions and conflicts between humans and Synthoids increase the city's chaos level. High chaos level + makes contracts and operations harder. + + {chaosWarning && ( + <> +
+ Chaos level is {chaosWarning}. + + )}
} > - City Chaos: {formatBigNumber(bladeburner.getCurrentCity().chaos)} + + City Chaos: {formatBigNumber(bladeburner.getCurrentCity().chaos)} + {chaosWarning && } +

diff --git a/src/Bladeburner/ui/SuccessChance.tsx b/src/Bladeburner/ui/SuccessChance.tsx index d07ef3a2a..81058f4c6 100644 --- a/src/Bladeburner/ui/SuccessChance.tsx +++ b/src/Bladeburner/ui/SuccessChance.tsx @@ -7,6 +7,7 @@ import { Player } from "@player"; import { formatPercent } from "../../ui/formatNumber"; import { StealthIcon } from "./StealthIcon"; import { KillIcon } from "./KillIcon"; +import InfoIcon from "@mui/icons-material/Info"; import { Tooltip, Typography } from "@mui/material"; interface SuccessChanceProps { @@ -22,10 +23,10 @@ export function SuccessChance({ bladeburner, action }: SuccessChanceProps): Reac return ( <> {action.successScaling} : ""}> - + Estimated success chance: {chance} - {/* Intentional space*/}{" "} - + {action.successScaling && } + {action.isStealth ? : <>} {action.isKill ? : <>}