Merge branch 'dev' of github.com:danielyxie/bitburner into improvement/purchase-augs-ui

This commit is contained in:
nickofolas 2022-04-22 16:45:15 -05:00
commit 7347aac225
37 changed files with 885 additions and 186 deletions

@ -776,6 +776,7 @@ export const achievements: IMap<Achievement> = {
// { ID: FactionNames.Bladeburners.toUpperCase(), Condition: () => Player.factions.includes(FactionNames.Bladeburners) }, // { ID: FactionNames.Bladeburners.toUpperCase(), Condition: () => Player.factions.includes(FactionNames.Bladeburners) },
// { ID: "DEEPSCANV1.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV1.name) }, // { ID: "DEEPSCANV1.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV1.name) },
// { ID: "DEEPSCANV2.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV2.name) }, // { ID: "DEEPSCANV2.EXE", Condition: () => Player.getHomeComputer().programs.includes(Programs.DeepscanV2.name) },
// { ID: "INFILTRATORS", Condition: () => Player.factions.includes(FactionNames.Infiltrators) },
// { // {
// ID: "SERVERPROFILER.EXE", // ID: "SERVERPROFILER.EXE",
// Condition: () => Player.getHomeComputer().programs.includes(Programs.ServerProfiler.name), // Condition: () => Player.getHomeComputer().programs.includes(Programs.ServerProfiler.name),

@ -8,6 +8,7 @@ import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { FactionNames } from "../Faction/data/FactionNames";
export interface IConstructorParams { export interface IConstructorParams {
info: string | JSX.Element; info: string | JSX.Element;
@ -49,6 +50,12 @@ export interface IConstructorParams {
bladeburner_stamina_gain_mult?: number; bladeburner_stamina_gain_mult?: number;
bladeburner_analysis_mult?: number; bladeburner_analysis_mult?: number;
bladeburner_success_chance_mult?: number; bladeburner_success_chance_mult?: number;
infiltration_base_rep_increase?: number;
infiltration_rep_mult?: number;
infiltration_trade_mult?: number;
infiltration_sell_mult?: number;
infiltration_timer_mult?: number;
infiltration_damage_reduction_mult?: number;
startingMoney?: number; startingMoney?: number;
programs?: string[]; programs?: string[];
@ -337,6 +344,50 @@ function generateStatsDescription(mults: IMap<number>, programs?: string[], star
<br />+{f(mults.bladeburner_success_chance_mult - 1)} Bladeburner Contracts and Operations success chance <br />+{f(mults.bladeburner_success_chance_mult - 1)} Bladeburner Contracts and Operations success chance
</> </>
); );
if (mults.infiltration_base_rep_increase)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_base_rep_increase - 1)} Infiltration {FactionNames.ShadowsOfAnarchy} Reputation
base reward
</>
);
if (mults.infiltration_rep_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_rep_mult - 1)} Infiltration {FactionNames.ShadowsOfAnarchy} Reputation reward
</>
);
if (mults.infiltration_trade_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_trade_mult - 1)} Infiltration Reputation for trading information
</>
);
if (mults.infiltration_sell_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_sell_mult - 1)} Infiltration cash reward for selling information
</>
);
if (mults.infiltration_timer_mult)
desc = (
<>
{desc}
<br />+{f(mults.infiltration_timer_mult - 1)} Infiltration time per minigame
</>
);
if (mults.infiltration_damage_reduction_mult)
desc = (
<>
{desc}
<br />
{f(mults.infiltration_damage_reduction_mult - 1)} Infiltration health lost per failed minigame
</>
);
if (startingMoney) if (startingMoney)
desc = ( desc = (
@ -390,6 +441,9 @@ export class Augmentation {
// Initial cost. Doesn't change when you purchase multiple Augmentation // Initial cost. Doesn't change when you purchase multiple Augmentation
startingCost = 0; startingCost = 0;
// Initial rep requirement. Doesn't change when you purchase multiple Augmentation
startingRepRequirement = 0;
// Factions that offer this aug. // Factions that offer this aug.
factions: string[] = []; factions: string[] = [];
@ -409,6 +463,7 @@ export class Augmentation {
this.baseRepRequirement = params.repCost; this.baseRepRequirement = params.repCost;
this.baseCost = params.moneyCost; this.baseCost = params.moneyCost;
this.startingCost = this.baseCost; this.startingCost = this.baseCost;
this.startingRepRequirement = this.baseRepRequirement;
this.factions = params.factions; this.factions = params.factions;
if (params.isSpecial) { if (params.isSpecial) {
@ -509,6 +564,25 @@ export class Augmentation {
this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult;
} }
if (params.infiltration_base_rep_increase) {
this.mults.infiltration_base_rep_increase = params.infiltration_base_rep_increase;
}
if (params.infiltration_rep_mult) {
this.mults.infiltration_rep_mult = params.infiltration_rep_mult;
}
if (params.infiltration_trade_mult) {
this.mults.infiltration_trade_mult = params.infiltration_trade_mult;
}
if (params.infiltration_sell_mult) {
this.mults.infiltration_sell_mult = params.infiltration_sell_mult;
}
if (params.infiltration_timer_mult) {
this.mults.infiltration_timer_mult = params.infiltration_timer_mult;
}
if (params.infiltration_damage_reduction_mult) {
this.mults.infiltration_damage_reduction_mult = params.infiltration_damage_reduction_mult;
}
if (params.stats === undefined) if (params.stats === undefined)
this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney); this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney);
else this.stats = params.stats; else this.stats = params.stats;

@ -3,7 +3,6 @@ import { Augmentations } from "./Augmentations";
import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation, IPlayerOwnedAugmentation } from "./PlayerOwnedAugmentation";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions"; import { Factions, factionExists } from "../Faction/Factions";
import { Player } from "../Player"; import { Player } from "../Player";
@ -17,9 +16,11 @@ import {
initBladeburnerAugmentations, initBladeburnerAugmentations,
initChurchOfTheMachineGodAugmentations, initChurchOfTheMachineGodAugmentations,
initGeneralAugmentations, initGeneralAugmentations,
initSoAAugmentations,
initNeuroFluxGovernor, initNeuroFluxGovernor,
initUnstableCircadianModulator, initUnstableCircadianModulator,
} from "./AugmentationCreator"; } from "./data/AugmentationCreator";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Router } from "../ui/GameRoot"; import { Router } from "../ui/GameRoot";
export function AddToAugmentations(aug: Augmentation): void { export function AddToAugmentations(aug: Augmentation): void {
@ -50,6 +51,7 @@ function createAugmentations(): void {
initNeuroFluxGovernor(), initNeuroFluxGovernor(),
initUnstableCircadianModulator(), initUnstableCircadianModulator(),
...initGeneralAugmentations(), ...initGeneralAugmentations(),
...initSoAAugmentations(),
...(factionExists(FactionNames.Bladeburners) ? initBladeburnerAugmentations() : []), ...(factionExists(FactionNames.Bladeburners) ? initBladeburnerAugmentations() : []),
...(factionExists(FactionNames.ChurchOfTheMachineGod) ? initChurchOfTheMachineGodAugmentations() : []), ...(factionExists(FactionNames.ChurchOfTheMachineGod) ? initChurchOfTheMachineGodAugmentations() : []),
].map(resetAugmentation); ].map(resetAugmentation);
@ -82,20 +84,36 @@ function updateNeuroFluxGovernorCosts(neuroFluxGovernorAugmentation: Augmentatio
let nextLevel = getNextNeuroFluxLevel(); let nextLevel = getNextNeuroFluxLevel();
--nextLevel; --nextLevel;
const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); const multiplier = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
neuroFluxGovernorAugmentation.baseRepRequirement *= multiplier * BitNodeMultipliers.AugmentationRepCost; neuroFluxGovernorAugmentation.baseRepRequirement =
neuroFluxGovernorAugmentation.baseCost *= multiplier * BitNodeMultipliers.AugmentationMoneyCost; neuroFluxGovernorAugmentation.startingRepRequirement * multiplier * BitNodeMultipliers.AugmentationRepCost;
neuroFluxGovernorAugmentation.baseCost =
neuroFluxGovernorAugmentation.startingCost * multiplier * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length - 1; ++i) { for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier(); neuroFluxGovernorAugmentation.baseCost *= getBaseAugmentationPriceMultiplier();
} }
} }
function updateSoACosts(soaAugmentation: Augmentation): void {
const soaAugmentationNames = initSoAAugmentations().map((augmentation) => augmentation.name);
const soaAugCount = soaAugmentationNames.filter((augmentationName) =>
Player.hasAugmentation(augmentationName),
).length;
soaAugmentation.baseCost = soaAugmentation.startingCost * Math.pow(CONSTANTS.SoACostMult, soaAugCount);
if (soaAugmentationNames.find((augmentationName) => augmentationName === soaAugmentation.name)) {
soaAugmentation.baseRepRequirement =
soaAugmentation.startingRepRequirement * Math.pow(CONSTANTS.SoARepMult, soaAugCount);
}
}
export function updateAugmentationCosts(): void { export function updateAugmentationCosts(): void {
for (const name of Object.keys(Augmentations)) { for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) { if (Augmentations.hasOwnProperty(name)) {
const augmentationToUpdate = Augmentations[name]; const augmentationToUpdate = Augmentations[name];
if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) { if (augmentationToUpdate.name === AugmentationNames.NeuroFluxGovernor) {
updateNeuroFluxGovernorCosts(augmentationToUpdate); updateNeuroFluxGovernorCosts(augmentationToUpdate);
} else if (augmentationToUpdate.factions.includes(FactionNames.ShadowsOfAnarchy)) {
updateSoACosts(augmentationToUpdate);
} else { } else {
augmentationToUpdate.baseCost = augmentationToUpdate.baseCost =
augmentationToUpdate.startingCost * augmentationToUpdate.startingCost *

@ -1,11 +1,11 @@
import { Augmentation, IConstructorParams } from "./Augmentation"; import { Augmentation, IConstructorParams } from "../Augmentation";
import { AugmentationNames } from "./data/AugmentationNames"; import { AugmentationNames } from "./AugmentationNames";
import { Player } from "../Player"; import { Player } from "../../Player";
import { Programs } from "../Programs/Programs"; import { Programs } from "../../Programs/Programs";
import { WHRNG } from "../Casino/RNG"; import { WHRNG } from "../../Casino/RNG";
import React from "react"; import React from "react";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../../Faction/data/FactionNames";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../../Constants";
function getRandomBonus(): any { function getRandomBonus(): any {
const bonuses = [ const bonuses = [
@ -95,6 +95,99 @@ function getRandomBonus(): any {
return bonuses[Math.floor(bonuses.length * randomNumber.random())]; return bonuses[Math.floor(bonuses.length * randomNumber.random())];
} }
export const initSoAAugmentations = (): Augmentation[] => [
new Augmentation({
name: AugmentationNames.WKSharmonizer,
repCost: 1e4,
moneyCost: 1e6,
info:
`A copy of the WKS harmonizer from the MIA leader of the ${FactionNames.ShadowsOfAnarchy} ` +
"injects *Γ-based cells that provides general enhancement to the body.",
stats: (
<>
This augmentation makes many aspect of infiltration easier and more productive. Such as increased timer,
rewards, reduced damage taken, etc.
</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.MightOfAres,
repCost: 1e4,
moneyCost: 1e6,
info:
"Extra-occular neurons taken from old martial art master. Injecting the user the ability to " +
"predict enemy attack before they even know it themself.",
stats: (
<>This augmentation makes the Slash minigame easier by showing you via an indictor when the slash in coming.</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.WisdomOfAthena,
repCost: 1e4,
moneyCost: 1e6,
info: "A connective brain implant to SASHA that focuses in pattern recognition and predictive templating.",
stats: <>This augmentation makes the Bracket minigame easier by removing all '[' ']'.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.ChaosOfDionysus,
repCost: 1e4,
moneyCost: 1e6,
info: "Opto-occipito implant to process visual signal before brain interpretation.",
stats: <>This augmentation makes the Backwards minigame easier by flipping the words.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.BeautyOfAphrodite,
repCost: 1e4,
moneyCost: 1e6,
info:
"Pheromone extruder injected in the thoracodorsal nerve. Emits pleasing scent guaranteed to " +
"make conversational partners more agreeable.",
stats: <>This augmentation makes the Bribe minigame easier by indicating the incorrect paths.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.TrickeryOfHermes,
repCost: 1e4,
moneyCost: 1e6,
info: "Penta-dynamo-neurovascular-valve inserted in the carpal ligament, enhances dexterity.",
stats: <>This augmentation makes the Cheat Code minigame easier by allowing the opposite character.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.FloodOfPoseidon,
repCost: 1e4,
moneyCost: 1e6,
info: "Transtinatium VVD reticulator used in optico-sterbing recognition.",
stats: <>This augmentation makes the Symbol matching minigame easier by indicating the correct choice.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.HuntOfArtemis,
repCost: 1e4,
moneyCost: 1e6,
info: "magneto-turboencabulator based on technology by Micha Eike Siemon, increases the users electro-magnetic sensitivity.",
stats: (
<>
This augmentation makes the Minesweeper minigame easier by showing the location of all mines and keeping their
position.
</>
),
factions: [FactionNames.ShadowsOfAnarchy],
}),
new Augmentation({
name: AugmentationNames.KnowledgeOfApollo,
repCost: 1e4,
moneyCost: 1e6,
info: "Neodynic retention fjengeln spoofer using -φ karmions, net positive effect on implantees delta wave.",
stats: <>This augmentation makes the Wire Cutting minigame easier by indicating the incorrect wires.</>,
factions: [FactionNames.ShadowsOfAnarchy],
}),
];
export const initGeneralAugmentations = (): Augmentation[] => [ export const initGeneralAugmentations = (): Augmentation[] => [
new Augmentation({ new Augmentation({
name: AugmentationNames.HemoRecirculator, name: AugmentationNames.HemoRecirculator,
@ -1936,7 +2029,12 @@ export function initNeuroFluxGovernor(): Augmentation {
hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus), hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus),
hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus), hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus),
work_money_mult: 1.01 + donationBonus, work_money_mult: 1.01 + donationBonus,
factions: Object.values(FactionNames), factions: Object.values(FactionNames).filter(
(factionName) =>
![FactionNames.ShadowsOfAnarchy, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes(
factionName,
),
),
}); });
} }

@ -114,6 +114,28 @@ export enum AugmentationNames {
StaneksGift2 = "Stanek's Gift - Awakening", StaneksGift2 = "Stanek's Gift - Awakening",
StaneksGift3 = "Stanek's Gift - Serenity", StaneksGift3 = "Stanek's Gift - Serenity",
/*
MightOfAres = "Might of Ares", // slash
WisdomOfAthena = "Wisdom of Athena", // bracket
TrickeryOfHermes = "Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "Beauty of Aphrodite", // bribe
ChaosOfDionysus = "Chaos of Dionysus", // reverse
FloodOfPoseidon = "Flood of Poseidon", // hex
HuntOfArtemis = "Hunt of Artemis", // mine
KnowledgeOfApollo = "Knowledge of Apollo", // wire
*/
// Infiltrators MiniGames
MightOfAres = "SoA - Might of Ares", // slash
WisdomOfAthena = "SoA - Wisdom of Athena", // bracket
TrickeryOfHermes = "SoA - Trickery of Hermes", // cheatcode
BeautyOfAphrodite = "SoA - Beauty of Aphrodite", // bribe
ChaosOfDionysus = "SoA - Chaos of Dionysus", // reverse
FloodOfPoseidon = "SoA - Flood of Poseidon", // hex
HuntOfArtemis = "SoA - Hunt of Artemis", // mine
KnowledgeOfApollo = "SoA - Knowledge of Apollo", // wire
WKSharmonizer = "SoA - phyzical WKS harmonizer",
//Wasteland Augs //Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System //PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields //PepBoyForceField Generates plasma force fields

@ -171,7 +171,6 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<> <>
{Object.values(BitNodes) {Object.values(BitNodes)
.filter((node) => { .filter((node) => {
console.log(node.desc);
return node.desc !== "COMING SOON"; return node.desc !== "COMING SOON";
}) })
.map((node) => { .map((node) => {

@ -112,6 +112,8 @@ export const CONSTANTS: {
CodingContractBaseMoneyGain: number; CodingContractBaseMoneyGain: number;
AugmentationGraftingCostMult: number; AugmentationGraftingCostMult: number;
AugmentationGraftingTimeBase: number; AugmentationGraftingTimeBase: number;
SoACostMult: number;
SoARepMult: number;
EntropyEffect: number; EntropyEffect: number;
TotalNumBitNodes: number; TotalNumBitNodes: number;
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
@ -281,6 +283,10 @@ export const CONSTANTS: {
AugmentationGraftingCostMult: 3, AugmentationGraftingCostMult: 3,
AugmentationGraftingTimeBase: 3600000, AugmentationGraftingTimeBase: 3600000,
// SoA mults
SoACostMult: 7,
SoARepMult: 1.3,
// Value raised to the number of entropy stacks, then multiplied to player multipliers // Value raised to the number of entropy stacks, then multiplied to player multipliers
EntropyEffect: 0.98, EntropyEffect: 0.98,

@ -3,7 +3,6 @@ import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Faction } from "./Faction"; import { Faction } from "./Faction";
import { Factions } from "./Factions"; import { Factions } from "./Factions";
@ -19,6 +18,7 @@ import {
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { InvitationEvent } from "./ui/InvitationModal"; import { InvitationEvent } from "./ui/InvitationModal";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { updateAugmentationCosts, getNextNeuroFluxLevel } from "../Augmentation/AugmentationHelpers";
import { SFC32RNG } from "../Casino/RNG"; import { SFC32RNG } from "../Casino/RNG";
export function inviteToFaction(faction: Faction): void { export function inviteToFaction(faction: Faction): void {
@ -104,31 +104,13 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
} else if (aug.baseCost === 0 || Player.money >= aug.baseCost) { } else if (aug.baseCost === 0 || Player.money >= aug.baseCost) {
const queuedAugmentation = new PlayerOwnedAugmentation(aug.name); const queuedAugmentation = new PlayerOwnedAugmentation(aug.name);
if (aug.name == AugmentationNames.NeuroFluxGovernor) { if (aug.name == AugmentationNames.NeuroFluxGovernor) {
queuedAugmentation.level = getNextNeurofluxLevel(); queuedAugmentation.level = getNextNeuroFluxLevel();
} }
Player.queuedAugmentations.push(queuedAugmentation); Player.queuedAugmentations.push(queuedAugmentation);
Player.loseMoney(aug.baseCost, "augmentations"); Player.loseMoney(aug.baseCost, "augmentations");
// If you just purchased Neuroflux Governor, recalculate the cost updateAugmentationCosts();
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
let nextLevel = getNextNeurofluxLevel();
--nextLevel;
const mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel);
aug.baseRepRequirement = 500 * mult * BitNodeMultipliers.AugmentationRepCost;
aug.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost;
for (let i = 0; i < Player.queuedAugmentations.length - 1; ++i) {
aug.baseCost *= CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
}
for (const name of Object.keys(Augmentations)) {
if (Augmentations.hasOwnProperty(name)) {
Augmentations[name].baseCost *=
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][Player.sourceFileLvl(11)];
}
}
if (sing) { if (sing) {
return "You purchased " + aug.name; return "You purchased " + aug.name;
@ -152,24 +134,6 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
return ""; return "";
} }
export function getNextNeurofluxLevel(): number {
// Get current Neuroflux level based on Player's augmentations
let currLevel = 0;
for (let i = 0; i < Player.augmentations.length; ++i) {
if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
currLevel = Player.augmentations[i].level;
}
}
// Account for purchased but uninstalled Augmentations
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) {
++currLevel;
}
}
return currLevel + 1;
}
export function processPassiveFactionRepGain(numCycles: number): void { export function processPassiveFactionRepGain(numCycles: number): void {
for (const name of Object.keys(Factions)) { for (const name of Object.keys(Factions)) {
if (name === Player.currentWorkFactionName) continue; if (name === Player.currentWorkFactionName) continue;

@ -3,6 +3,7 @@ import { IMap } from "../types";
import { FactionNames } from "./data/FactionNames"; import { FactionNames } from "./data/FactionNames";
import { use } from "../ui/Context"; import { use } from "../ui/Context";
import { Option } from "./ui/Option"; import { Option } from "./ui/Option";
import { Typography } from "@mui/material";
interface FactionInfoParams { interface FactionInfoParams {
infoText?: JSX.Element; infoText?: JSX.Element;
@ -511,4 +512,17 @@ export const FactionInfos: IMap<FactionInfo> = {
); );
}, },
}), }),
[FactionNames.ShadowsOfAnarchy]: new FactionInfo({
infoText: (
<>
The government is ruled by the corporations that we have allowed to consume it. To release the world from its
shackles, the gods grant us their strength.
</>
),
special: true,
keepOnInstall: true,
assignment: (): React.ReactElement => {
return <Typography>{FactionNames.ShadowsOfAnarchy} can only gain reputation by infiltrating.</Typography>;
},
}),
}; };

@ -32,4 +32,5 @@ export enum FactionNames {
CyberSec = "CyberSec", CyberSec = "CyberSec",
Bladeburners = "Bladeburners", Bladeburners = "Bladeburners",
ChurchOfTheMachineGod = "Church of the Machine God", ChurchOfTheMachineGod = "Church of the Machine God",
ShadowsOfAnarchy = "Shadows of Anarchy",
} }

@ -1,14 +1,7 @@
/** /**
* Root React Component for displaying a faction's "Purchase Augmentations" page * Root React Component for displaying a faction's "Purchase Augmentations" page
*/ */
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import React, { useState } from "react"; import React, { useState } from "react";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { Augmentations } from "../../Augmentation/Augmentations"; import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { PurchaseableAugmentation, PurchaseableAugmentations } from "../../Augmentation/ui/PurchaseableAugmentations"; import { PurchaseableAugmentation, PurchaseableAugmentations } from "../../Augmentation/ui/PurchaseableAugmentations";
@ -21,6 +14,11 @@ import { Reputation } from "../../ui/React/Reputation";
import { Faction } from "../Faction"; import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers"; import { getFactionAugmentationsFiltered, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import {Box, Button, Typography, Tooltip, TableBody, Table} from "@mui/material";
import { getGenericAugmentationPriceMultiplier } from "../../Augmentation/AugmentationHelpers";
import { FactionNames } from "../data/FactionNames";
type IProps = { type IProps = {
faction: Faction; faction: Faction;
routeToMainPage: () => void; routeToMainPage: () => void;
@ -160,6 +158,14 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
</> </>
); );
} }
const multiplierComponent =
props.faction.name !== FactionNames.ShadowsOfAnarchy ? (
<Typography>
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>
) : (
<></>
);
return ( return (
<> <>
@ -181,9 +187,7 @@ export function AugmentationsPage(props: IProps): React.ReactElement {
</Typography> </Typography>
} }
> >
<Typography> {multiplierComponent}
Price multiplier: x {numeralWrapper.formatMultiplier(getGenericAugmentationPriceMultiplier())}
</Typography>
</Tooltip> </Tooltip>
</Box> </Box>
<Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button> <Button onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)}>Sort by Cost</Button>

@ -58,6 +58,7 @@ export function Info(props: IProps): React.ReactElement {
const Assignment = props.factionInfo.assignment ?? DefaultAssignment; const Assignment = props.factionInfo.assignment ?? DefaultAssignment;
const favorGain = props.faction.getFavorGain(); const favorGain = props.faction.getFavorGain();
return ( return (
<> <>
<Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography> <Typography classes={{ root: classes.noformat }}>{props.factionInfo.infoText}</Typography>

@ -0,0 +1,170 @@
/**
* React component for displaying a single augmentation for purchase through
* the faction UI
*/
import React, { useState } from "react";
import { hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { Augmentation as AugFormat } from "../../ui/React/Augmentation";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { TableCell } from "../../ui/React/Table";
import TableRow from "@mui/material/TableRow";
import { getNextNeuroFluxLevel } from "../../Augmentation/AugmentationHelpers";
interface IReqProps {
augName: string;
p: IPlayer;
hasReq: boolean;
rep: number;
hasRep: boolean;
cost: number;
hasCost: boolean;
}
function Requirements(props: IReqProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (!props.hasReq) {
return (
<TableCell key={1} colSpan={2}>
<Typography color="error">
Requires{" "}
{aug.prereqs.map((aug, i) => (
<AugFormat key={i} name={aug} />
))}
</Typography>
</TableCell>
);
}
return (
<React.Fragment key="f">
<TableCell key={1}>
<Typography>
<Money money={props.cost} player={props.p} />
</Typography>
</TableCell>
<TableCell key={2}>
<Typography color={props.hasRep ? "primary" : "error"}>
Requires <Reputation reputation={props.rep} /> faction reputation
</Typography>
</TableCell>
</React.Fragment>
);
}
interface IProps {
augName: string;
faction: Faction;
p: IPlayer;
rerender: () => void;
owned?: boolean;
}
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
return <></>;
}
const moneyCost = aug.baseCost;
const repCost = aug.baseRepRequirement;
const hasReq = hasAugmentationPrereqs(aug);
const hasRep = props.faction.playerReputation >= repCost;
const hasCost = aug.baseCost === 0 || props.p.money > aug.baseCost;
// Determine UI properties
const color: "error" | "primary" = !hasReq || !hasRep || !hasCost ? "error" : "primary";
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeuroFluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string") {
tooltip = (
<>
<span>{aug.info}</span>
<br />
<br />
{aug.stats}
</>
);
} else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
function handleClick(): void {
if (color === "error") return;
if (!Settings.SuppressBuyAugmentationConfirmation) {
setOpen(true);
} else {
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
return (
<TableRow>
{!props.owned && (
<TableCell key={0}>
<Button onClick={handleClick} color={color}>
Buy
</Button>
<PurchaseAugmentationModal
open={open}
onClose={() => setOpen(false)}
aug={aug}
faction={props.faction}
rerender={props.rerender}
/>
</TableCell>
)}
<TableCell key={1}>
<Box display="flex">
<Tooltip title={<Typography>{tooltip}</Typography>} placement="top">
<Typography>{btnTxt}</Typography>
</Tooltip>
</Box>
</TableCell>
{!props.owned && (
<Requirements
key={2}
augName={props.augName}
p={props.p}
cost={moneyCost}
rep={repCost}
hasReq={hasReq}
hasRep={hasRep}
hasCost={hasCost}
/>
)}
</TableRow>
);
}

@ -0,0 +1,25 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { calculateSkill } from "../../PersonObjects/formulas/skill";
function calculateRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number {
const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function calculateDifficulty(player: IPlayer, startingSecurityLevel: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
return calculateRawDiff(player, totalStats, startingSecurityLevel);
}
export function calculateReward(player: IPlayer, startingSecurityLevel: number): number {
const xpMult = 10 * 60 * 15;
const total =
calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) +
calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) +
calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) +
calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) +
calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult);
return calculateRawDiff(player, total, startingSecurityLevel);
}

@ -0,0 +1,51 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { LocationsMetadata } from "../../Locations/data/LocationsMetadata";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
export function calculateSellInformationCashReward(
player: IPlayer,
reward: number,
maxLevel: number,
difficulty: number,
): number {
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
export function calculateTradeInformationRepReward(
player: IPlayer,
reward: number,
maxLevel: number,
difficulty: number,
): number {
const levelBonus = maxLevel * Math.pow(1.01, maxLevel);
return (
Math.pow(reward + 1, 2) *
Math.pow(difficulty, 3) *
3e3 *
levelBonus *
(player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.5 : 1) *
BitNodeMultipliers.InfiltrationMoney
);
}
export function calculateInfiltratorsRepReward(player: IPlayer, faction: Faction, difficulty: number): number {
const maxStartingSecurityLevel = LocationsMetadata.reduce((acc, data): number => {
const startingSecurityLevel = data.infiltrationData?.startingSecurityLevel || 0;
return acc > startingSecurityLevel ? acc : startingSecurityLevel;
}, 0);
const baseRepGain = (difficulty / maxStartingSecurityLevel) * 5000;
return baseRepGain * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 2 : 1) * (1 + faction.favor / 100);
}

@ -8,6 +8,8 @@ import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor"; import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -34,6 +36,7 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer; const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty)); const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState(""); const [guess, setGuess] = useState("");
const hasAugment = Player.hasAugmentation(AugmentationNames.ChaosOfDionysus, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
@ -48,11 +51,13 @@ export function BackwardGame(props: IMinigameProps): React.ReactElement {
<Grid container spacing={3}> <Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} /> <GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h4">Type it backward</Typography> <Typography variant="h4">Type it{!hasAugment ? " backward" : ""}</Typography>
<KeyHandler onKeyDown={press} onFailure={props.onFailure} /> <KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography style={{ transform: "scaleX(-1)" }}>{answer}</Typography> <Typography style={{ transform: hasAugment ? "none" : "scaleX(-1)", marginLeft: hasAugment ? "50%" : "none" }}>
{answer}
</Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography> <Typography>

@ -7,6 +7,8 @@ import { random } from "../utils";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor"; import { BlinkingCursor } from "./BlinkingCursor";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
interface Difficulty { interface Difficulty {
@ -31,9 +33,12 @@ const difficulties: {
function generateLeftSide(difficulty: Difficulty): string { function generateLeftSide(difficulty: Difficulty): string {
let str = ""; let str = "";
const options = [KEY.OPEN_BRACKET, KEY.LESS_THAN, KEY.OPEN_PARENTHESIS, KEY.OPEN_BRACE]; const options = [KEY.OPEN_BRACKET, KEY.LESS_THAN, KEY.OPEN_PARENTHESIS, KEY.OPEN_BRACE];
if (Player.hasAugmentation(AugmentationNames.WisdomOfAthena, true)) {
options.splice(0, 1);
}
const length = random(difficulty.min, difficulty.max); const length = random(difficulty.min, difficulty.max);
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
str += options[Math.floor(Math.random() * 4)]; str += options[Math.floor(Math.random() * options.length)];
} }
return str; return str;

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps"; import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler"; import { KeyHandler } from "./KeyHandler";
@ -6,6 +6,9 @@ import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
import { downArrowSymbol, upArrowSymbol } from "../utils"; import { downArrowSymbol, upArrowSymbol } from "../utils";
interface Difficulty { interface Difficulty {
@ -31,20 +34,54 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
interpolate(difficulties, props.difficulty, difficulty); interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer; const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty)); const [choices] = useState(makeChoices(difficulty));
const [correctIndex, setCorrectIndex] = useState(0);
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const currentChoice = choices[index];
useEffect(() => {
setCorrectIndex(choices.findIndex((choice) => positive.includes(choice)));
}, [choices]);
const defaultColor = Settings.theme.primary;
const disabledColor = Settings.theme.disabled;
let upColor = defaultColor;
let downColor = defaultColor;
let choiceColor = defaultColor;
const hasAugment = Player.hasAugmentation(AugmentationNames.BeautyOfAphrodite, true);
if (hasAugment) {
const upIndex = index + 1 >= choices.length ? 0 : index + 1;
let upDistance = correctIndex - upIndex;
if (upIndex > correctIndex) {
upDistance = choices.length - 1 - upIndex + correctIndex;
}
const downIndex = index - 1 < 0 ? choices.length - 1 : index - 1;
let downDistance = downIndex - correctIndex;
if (downIndex < correctIndex) {
downDistance = downIndex + choices.length - 1 - correctIndex;
}
const onCorrectIndex = correctIndex == index;
upColor = upDistance <= downDistance && !onCorrectIndex ? upColor : disabledColor;
downColor = upDistance >= downDistance && !onCorrectIndex ? downColor : disabledColor;
choiceColor = onCorrectIndex ? defaultColor : disabledColor;
}
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
const k = event.key; const k = event.key;
if (k === KEY.SPACE) { if (k === KEY.SPACE) {
if (positive.includes(choices[index])) props.onSuccess(); if (positive.includes(currentChoice)) props.onSuccess();
else props.onFailure(); else props.onFailure();
return; return;
} }
let newIndex = index; let newIndex = index;
if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].map((key) => key as string).includes(k)) newIndex++; if ([KEY.UP_ARROW, KEY.W, KEY.RIGHT_ARROW, KEY.D].map((k) => k as string).includes(k)) newIndex++;
if ([KEY.DOWN_ARROW, KEY.S, KEY.LEFT_ARROW, KEY.A].map((key) => key as string).includes(k)) newIndex--; if ([KEY.DOWN_ARROW, KEY.S, KEY.LEFT_ARROW, KEY.A].map((k) => k as string).includes(k)) newIndex--;
while (newIndex < 0) newIndex += choices.length; while (newIndex < 0) newIndex += choices.length;
while (newIndex > choices.length - 1) newIndex -= choices.length; while (newIndex > choices.length - 1) newIndex -= choices.length;
setIndex(newIndex); setIndex(newIndex);
@ -58,13 +95,13 @@ export function BribeGame(props: IMinigameProps): React.ReactElement {
<KeyHandler onKeyDown={press} onFailure={props.onFailure} /> <KeyHandler onKeyDown={press} onFailure={props.onFailure} />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={upColor}>
{upArrowSymbol} {upArrowSymbol}
</Typography> </Typography>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={choiceColor}>
{choices[index]} {currentChoice}
</Typography> </Typography>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={downColor}>
{downArrowSymbol} {downArrowSymbol}
</Typography> </Typography>
</Grid> </Grid>

@ -3,9 +3,19 @@ import Grid from "@mui/material/Grid";
import { IMinigameProps } from "./IMinigameProps"; import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler"; import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer"; import { GameTimer } from "./GameTimer";
import { random, getArrow, rightArrowSymbol, leftArrowSymbol, upArrowSymbol, downArrowSymbol } from "../utils"; import {
random,
getArrow,
getInverseArrow,
leftArrowSymbol,
rightArrowSymbol,
upArrowSymbol,
downArrowSymbol,
} from "../utils";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -32,10 +42,11 @@ export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
const timer = difficulty.timer; const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty)); const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const hasAugment = Player.hasAugmentation(AugmentationNames.TrickeryOfHermes, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
if (code[index] !== getArrow(event)) { if (code[index] !== getArrow(event) && (!hasAugment || code[index] !== getInverseArrow(event))) {
props.onFailure(); props.onFailure();
return; return;
} }

@ -7,6 +7,9 @@ import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils"; import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { Settings } from "../../Settings/Settings";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -33,10 +36,11 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
interpolate(difficulties, props.difficulty, difficulty); interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer; const timer = difficulty.timer;
const [grid] = useState(generatePuzzle(difficulty)); const [grid] = useState(generatePuzzle(difficulty));
const [answer] = useState(generateAnswer(grid, difficulty)); const [answers] = useState(generateAnswers(grid, difficulty));
const [index, setIndex] = useState(0); const [currentAnswerIndex, setCurrentAnswerIndex] = useState(0);
const [pos, setPos] = useState([0, 0]); const [pos, setPos] = useState([0, 0]);
const hasAugment = Player.hasAugmentation(AugmentationNames.FloodOfPoseidon, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
const move = [0, 0]; const move = [0, 0];
@ -62,13 +66,13 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
if (event.key === KEY.SPACE) { if (event.key === KEY.SPACE) {
const selected = grid[pos[1]][pos[0]]; const selected = grid[pos[1]][pos[0]];
const expected = answer[index]; const expected = answers[currentAnswerIndex];
if (selected !== expected) { if (selected !== expected) {
props.onFailure(); props.onFailure();
return; return;
} }
setIndex(index + 1); setCurrentAnswerIndex(currentAnswerIndex + 1);
if (answer.length === index + 1) props.onSuccess(); if (answers.length === currentAnswerIndex + 1) props.onSuccess();
} }
} }
@ -78,17 +82,17 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
<GameTimer millis={timer} onExpire={props.onFailure} /> <GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h4">Match the symbols!</Typography> <Typography variant="h4">Match the symbols!</Typography>
<Typography variant="h5" color="primary"> <Typography variant="h5" color={Settings.theme.primary}>
Targets:{" "} Targets:{" "}
{answer.map((a, i) => { {answers.map((a, i) => {
if (i == index) if (i == currentAnswerIndex)
return ( return (
<span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}> <span key={`${i}`} style={{ fontSize: "1em", color: "blue" }}>
{a}&nbsp; {a}&nbsp;
</span> </span>
); );
return ( return (
<span key={`${i}`} style={{ fontSize: "1em" }}> <span key={`${i}`} style={{ fontSize: "1em", color: Settings.theme.primary }}>
{a}&nbsp; {a}&nbsp;
</span> </span>
); );
@ -99,14 +103,19 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
<div key={y}> <div key={y}>
<Typography> <Typography>
{line.map((cell, x) => { {line.map((cell, x) => {
if (x == pos[0] && y == pos[1]) const isCorrectAnswer = cell === answers[currentAnswerIndex];
if (x == pos[0] && y == pos[1]) {
return ( return (
<span key={`${x}${y}`} style={{ fontSize: fontSize, color: "blue" }}> <span key={`${x}${y}`} style={{ fontSize: fontSize, color: "blue" }}>
{cell}&nbsp; {cell}&nbsp;
</span> </span>
); );
}
const optionColor = hasAugment && !isCorrectAnswer ? Settings.theme.disabled : Settings.theme.primary;
return ( return (
<span key={`${x}${y}`} style={{ fontSize: fontSize }}> <span key={`${x}${y}`} style={{ fontSize: fontSize, color: optionColor }}>
{cell}&nbsp; {cell}&nbsp;
</span> </span>
); );
@ -121,12 +130,12 @@ export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
); );
} }
function generateAnswer(grid: string[][], difficulty: Difficulty): string[] { function generateAnswers(grid: string[][], difficulty: Difficulty): string[] {
const answer = []; const answers = [];
for (let i = 0; i < Math.round(difficulty.symbols); i++) { for (let i = 0; i < Math.round(difficulty.symbols); i++) {
answer.push(grid[Math.floor(Math.random() * grid.length)][Math.floor(Math.random() * grid[0].length)]); answers.push(grid[Math.floor(Math.random() * grid.length)][Math.floor(Math.random() * grid[0].length)]);
} }
return answer; return answers;
} }
function randChar(): string { function randChar(): string {

@ -13,6 +13,7 @@ import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame"; import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory"; import { Victory } from "./Victory";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface IProps { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -91,7 +92,9 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false); pushResult(false);
// Kill the player immediately if they use automation, so // Kill the player immediately if they use automation, so
// it's clear they're not meant to // it's clear they're not meant to
const damage = options?.automated ? player.hp : props.StartingDifficulty * 3; const damage = options?.automated
? player.hp
: props.StartingDifficulty * 3 * (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 0.5 : 1);
if (player.takeDamage(damage)) { if (player.takeDamage(damage)) {
router.toCity(); router.toCity();
return; return;
@ -110,7 +113,16 @@ export function Game(props: IProps): React.ReactElement {
stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />; stageComponent = <Countdown onFinish={() => setStage(Stage.Minigame)} />;
break; break;
case Stage.Minigame: { case Stage.Minigame: {
const MiniGame = minigames[gameIds.id]; /**
*
BackwardGame,
BribeGame,
CheatCodeGame,
Cyberpunk2077Game,
MinesweeperGame,
WireCuttingGame,
*/
const MiniGame = WireCuttingGame; // minigames[gameIds.id];
stageComponent = <MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty + level / 50} />; stageComponent = <MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty + level / 50} />;
break; break;
} }

@ -3,6 +3,8 @@ import React, { useState, useEffect } from "react";
import withStyles from "@mui/styles/withStyles"; import withStyles from "@mui/styles/withStyles";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { use } from "../../ui/Context";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
const TimerProgress = withStyles((theme: Theme) => ({ const TimerProgress = withStyles((theme: Theme) => ({
root: { root: {
@ -20,14 +22,16 @@ interface IProps {
} }
export function GameTimer(props: IProps): React.ReactElement { export function GameTimer(props: IProps): React.ReactElement {
const player = use.Player();
const [v, setV] = useState(100); const [v, setV] = useState(100);
const totalMillis = (player.hasAugmentation(AugmentationNames.WKSharmonizer) ? 1.3 : 1) * props.millis;
const tick = 200; const tick = 200;
useEffect(() => { useEffect(() => {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
setV((old) => { setV((old) => {
if (old <= 0) props.onExpire(); if (old <= 0) props.onExpire();
return old - (tick / props.millis) * 100; return old - (tick / totalMillis) * 100;
}); });
}, tick); }, tick);
return () => { return () => {

@ -1,48 +1,22 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import React, { useState } from "react"; import React, { useState } from "react";
import { Intro } from "./Intro"; import { Intro } from "./Intro";
import { Game } from "./Game"; import { Game } from "./Game";
import { Location } from "../../Locations/Location"; import { Location } from "../../Locations/Location";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { calculateSkill } from "../../PersonObjects/formulas/skill"; import { calculateDifficulty, calculateReward } from "../formulas/game";
interface IProps { interface IProps {
location: Location; location: Location;
} }
function calcRawDiff(player: IPlayer, stats: number, startingDifficulty: number): number {
const difficulty = startingDifficulty - Math.pow(stats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
return calcRawDiff(player, totalStats, startingDifficulty);
}
function calcReward(player: IPlayer, startingDifficulty: number): number {
const xpMult = 10 * 60 * 15;
const total =
calculateSkill(player.strength_exp_mult * xpMult, player.strength_mult) +
calculateSkill(player.defense_exp_mult * xpMult, player.defense_mult) +
calculateSkill(player.agility_exp_mult * xpMult, player.agility_mult) +
calculateSkill(player.dexterity_exp_mult * xpMult, player.dexterity_mult) +
calculateSkill(player.charisma_exp_mult * xpMult, player.charisma_mult);
return calcRawDiff(player, total, startingDifficulty);
}
export function InfiltrationRoot(props: IProps): React.ReactElement { export function InfiltrationRoot(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const [start, setStart] = useState(false); const [start, setStart] = useState(false);
if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location."); if (props.location.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location.");
const startingDifficulty = props.location.infiltrationData.startingSecurityLevel; const startingSecurityLevel = props.location.infiltrationData.startingSecurityLevel;
const difficulty = calcDifficulty(player, startingDifficulty); const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calcReward(player, startingDifficulty); const reward = calculateReward(player, startingSecurityLevel);
console.log(`${difficulty} ${reward}`);
function cancel(): void { function cancel(): void {
router.toCity(); router.toCity();
@ -62,7 +36,7 @@ export function InfiltrationRoot(props: IProps): React.ReactElement {
return ( return (
<Game <Game
StartingDifficulty={startingDifficulty} StartingDifficulty={startingSecurityLevel}
Difficulty={difficulty} Difficulty={difficulty}
Reward={reward} Reward={reward}
MaxLevel={props.location.infiltrationData.maxClearanceLevel} MaxLevel={props.location.infiltrationData.maxClearanceLevel}

@ -7,6 +7,8 @@ import { interpolate } from "./Difficulty";
import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils"; import { downArrowSymbol, getArrow, leftArrowSymbol, rightArrowSymbol, upArrowSymbol } from "../utils";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -36,7 +38,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
const [answer, setAnswer] = useState(generateEmptyField(difficulty)); const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]); const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true); const [memoryPhase, setMemoryPhase] = useState(true);
const hasAugment = Player.hasAugmentation(AugmentationNames.HuntOfArtemis, true);
function press(this: Document, event: KeyboardEvent): void { function press(this: Document, event: KeyboardEvent): void {
event.preventDefault(); event.preventDefault();
if (memoryPhase) return; if (memoryPhase) return;
@ -94,6 +96,7 @@ export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
} else { } else {
if (x == pos[0] && y == pos[1]) return <span key={x}>[X]&nbsp;</span>; if (x == pos[0] && y == pos[1]) return <span key={x}>[X]&nbsp;</span>;
if (answer[y][x]) return <span key={x}>[.]&nbsp;</span>; if (answer[y][x]) return <span key={x}>[.]&nbsp;</span>;
if (hasAugment && minefield[y][x]) return <span key={x}>[?]&nbsp;</span>;
return <span key={x}>[&nbsp;]&nbsp;</span>; return <span key={x}>[&nbsp;]&nbsp;</span>;
} }
})} })}

@ -6,6 +6,8 @@ import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { Player } from "../../Player";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -38,6 +40,10 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
props.onSuccess(); props.onSuccess();
} }
} }
const hasAugment = Player.hasAugmentation(AugmentationNames.MightOfAres, true);
const phaseZeroTime = Math.random() * 3250 + 1500 - (250 + difficulty.window);
const phaseOneTime = 250;
const timeUntilAttacking = phaseZeroTime + phaseOneTime;
useEffect(() => { useEffect(() => {
let id = window.setTimeout(() => { let id = window.setTimeout(() => {
@ -45,8 +51,8 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
id = window.setTimeout(() => { id = window.setTimeout(() => {
setPhase(2); setPhase(2);
id = window.setTimeout(() => setPhase(0), difficulty.window); id = window.setTimeout(() => setPhase(0), difficulty.window);
}, 250); }, phaseOneTime);
}, Math.random() * 3250 + 1500 - (250 + difficulty.window)); }, phaseZeroTime);
return () => { return () => {
clearInterval(id); clearInterval(id);
}; };
@ -57,6 +63,14 @@ export function SlashGame(props: IMinigameProps): React.ReactElement {
<GameTimer millis={5000} onExpire={props.onFailure} /> <GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h4">Slash when his guard is down!</Typography> <Typography variant="h4">Slash when his guard is down!</Typography>
{hasAugment ? (
<>
<Typography variant="h4">Guard will drop in...</Typography>
<GameTimer millis={timeUntilAttacking} onExpire={props.onFailure} />
</>
) : (
<></>
)}
{phase === 0 && <Typography variant="h4">Guarding ...</Typography>} {phase === 0 && <Typography variant="h4">Guarding ...</Typography>}
{phase === 1 && <Typography variant="h4">Preparing?</Typography>} {phase === 1 && <Typography variant="h4">Preparing?</Typography>}
{phase === 2 && <Typography variant="h4">ATTACKING!</Typography>} {phase === 2 && <Typography variant="h4">ATTACKING!</Typography>}

@ -3,12 +3,19 @@ import React, { useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import { FactionNames } from "../../Faction/data/FactionNames";
import { formatNumber } from "../../utils/StringHelperFunctions";
import {
calculateInfiltratorsRepReward,
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../formulas/victory";
import { inviteToFaction } from "../../Faction/FactionHelpers";
interface IProps { interface IProps {
StartingDifficulty: number; StartingDifficulty: number;
@ -23,31 +30,25 @@ export function Victory(props: IProps): React.ReactElement {
const [faction, setFaction] = useState("none"); const [faction, setFaction] = useState("none");
function quitInfiltration(): void { function quitInfiltration(): void {
handleInfiltrators();
router.toCity(); router.toCity();
} }
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel); const soa = Factions[FactionNames.ShadowsOfAnarchy];
const repGain = calculateTradeInformationRepReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty);
const moneyGain = calculateSellInformationCashReward(player, props.Reward, props.MaxLevel, props.StartingDifficulty);
const infiltrationRepGain = calculateInfiltratorsRepReward(player, soa, props.StartingDifficulty);
const repGain = const isMemberOfInfiltrators = player.factions.includes(FactionNames.ShadowsOfAnarchy);
Math.pow(props.Reward + 1, 1.1) *
Math.pow(props.StartingDifficulty, 1.2) *
30 *
levelBonus *
BitNodeMultipliers.InfiltrationRep;
const moneyGain =
Math.pow(props.Reward + 1, 2) *
Math.pow(props.StartingDifficulty, 3) *
3e3 *
levelBonus *
BitNodeMultipliers.InfiltrationMoney;
function sell(): void { function sell(): void {
handleInfiltrators();
player.gainMoney(moneyGain, "infiltration"); player.gainMoney(moneyGain, "infiltration");
quitInfiltration(); quitInfiltration();
} }
function trade(): void { function trade(): void {
handleInfiltrators();
if (faction === "none") return; if (faction === "none") return;
Factions[faction].playerReputation += repGain; Factions[faction].playerReputation += repGain;
quitInfiltration(); quitInfiltration();
@ -57,6 +58,13 @@ export function Victory(props: IProps): React.ReactElement {
setFaction(event.target.value); setFaction(event.target.value);
} }
function handleInfiltrators(): void {
inviteToFaction(Factions[FactionNames.ShadowsOfAnarchy]);
if (isMemberOfInfiltrators) {
soa.playerReputation += infiltrationRepGain;
}
}
return ( return (
<> <>
<Grid container spacing={3}> <Grid container spacing={3}>
@ -65,7 +73,15 @@ export function Victory(props: IProps): React.ReactElement {
</Grid> </Grid>
<Grid item xs={10}> <Grid item xs={10}>
<Typography variant="h5" color="primary"> <Typography variant="h5" color="primary">
You can trade the confidential information you found for money or reputation. You{" "}
{isMemberOfInfiltrators ? (
<>
have gained {formatNumber(infiltrationRepGain, 2)} rep for {FactionNames.ShadowsOfAnarchy} and{" "}
</>
) : (
<></>
)}
can trade the confidential information you found for money or reputation.
</Typography> </Typography>
<Select value={faction} onChange={changeDropdown}> <Select value={faction} onChange={changeDropdown}>
<MenuItem key={"none"} value={"none"}> <MenuItem key={"none"} value={"none"}>

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { IMinigameProps } from "./IMinigameProps"; import { IMinigameProps } from "./IMinigameProps";
@ -7,6 +7,9 @@ import { GameTimer } from "./GameTimer";
import { random } from "../utils"; import { random } from "../utils";
import { interpolate } from "./Difficulty"; import { interpolate } from "./Difficulty";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { Settings } from "../../Settings/Settings";
interface Difficulty { interface Difficulty {
[key: string]: number; [key: string]: number;
@ -61,23 +64,13 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const [wires] = useState(generateWires(difficulty)); const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false)); const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty)); const [questions] = useState(generateQuestion(wires, difficulty));
const hasAugment = Player.hasAugmentation(AugmentationNames.KnowledgeOfApollo, true);
function checkWire(wireNum: number): boolean { function checkWire(wireNum: number): boolean {
return !questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1)); return questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1));
}
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires((old) => {
const next = [...old];
next[wireNum - 1] = true;
if (checkWire(wireNum)) {
props.onFailure();
} }
useEffect(() => {
// check if we won // check if we won
const wiresToBeCut = []; const wiresToBeCut = [];
for (let j = 0; j < wires.length; j++) { for (let j = 0; j < wires.length; j++) {
@ -87,9 +80,22 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
} }
wiresToBeCut.push(shouldBeCut); wiresToBeCut.push(shouldBeCut);
} }
if (wiresToBeCut.every((b, i) => b === next[i])) { if (wiresToBeCut.every((b, i) => b === cutWires[i])) {
props.onSuccess(); props.onSuccess();
} }
}, [cutWires]);
function press(this: Document, event: KeyboardEvent): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires((old) => {
const next = [...old];
next[wireNum - 1] = true;
if (!checkWire(wireNum)) {
props.onFailure();
}
return next; return next;
}); });
@ -104,18 +110,28 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
<Typography key={i}>{question.toString()}</Typography> <Typography key={i}>{question.toString()}</Typography>
))} ))}
<Typography> <Typography>
{new Array(wires.length).fill(0).map((_, i) => ( {new Array(wires.length).fill(0).map((_, i) => {
<span key={i}>&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;</span> const isCorrectWire = checkWire(i + 1);
))} const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary;
return (
<span key={i} style={{ color: color }}>
&nbsp;{i + 1}&nbsp;&nbsp;&nbsp;&nbsp;
</span>
);
})}
</Typography> </Typography>
{new Array(8).fill(0).map((_, i) => ( {new Array(8).fill(0).map((_, i) => (
<div key={i}> <div key={i}>
<Typography> <Typography>
{wires.map((wire, j) => { {wires.map((wire, j) => {
if ((i === 3 || i === 4) && cutWires[j]) if ((i === 3 || i === 4) && cutWires[j]) {
return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>; return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>;
}
const isCorrectWire = checkWire(j + 1);
const wireColor =
hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length];
return ( return (
<span key={j} style={{ color: wire.colors[i % wire.colors.length] }}> <span key={j} style={{ color: wireColor }}>
|{wire.tpe}|&nbsp;&nbsp;&nbsp; |{wire.tpe}|&nbsp;&nbsp;&nbsp;
</span> </span>
); );

@ -70,6 +70,10 @@ export const RamCostConstants: IMap<number> = {
ScriptStanekPlace: 5, ScriptStanekPlace: 5,
ScriptStanekFragmentAt: 2, ScriptStanekFragmentAt: 2,
ScriptStanekDeleteAt: 0.15, ScriptStanekDeleteAt: 0.15,
ScriptInfiltrationCalculateDifficulty: 2.5,
ScriptInfiltrationCalculateRewards: 2.5,
ScriptInfiltrationGetLocations: 5,
ScriptInfiltrationGetInfiltrations: 15,
ScriptStanekAcceptGift: 2, ScriptStanekAcceptGift: 2,
}; };
@ -245,6 +249,13 @@ const bladeburner: IMap<any> = {
getBonusTime: 0, getBonusTime: 0,
}; };
const infiltration: IMap<any> = {
calculateDifficulty: RamCostConstants.ScriptInfiltrationCalculateDifficulty,
calculateRewards: RamCostConstants.ScriptInfiltrationCalculateRewards,
calculateGetLocations: RamCostConstants.ScriptInfiltrationGetLocations,
calculateGetInfiltrations: RamCostConstants.ScriptInfiltrationGetInfiltrations,
};
// Coding Contract API // Coding Contract API
const codingcontract: IMap<any> = { const codingcontract: IMap<any> = {
attempt: RamCostConstants.ScriptCodingContractBaseRamCost, attempt: RamCostConstants.ScriptCodingContractBaseRamCost,
@ -314,6 +325,7 @@ export const RamCosts: IMap<any> = {
...singularity, // singularity is in namespace & toplevel ...singularity, // singularity is in namespace & toplevel
gang, gang,
bladeburner, bladeburner,
infiltration,
codingcontract, codingcontract,
sleeve, sleeve,
stanek, stanek,

@ -65,6 +65,7 @@ import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra"; import { NetscriptExtra } from "./NetscriptFunctions/Extra";
import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet"; import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { NetscriptStanek } from "./NetscriptFunctions/Stanek"; import { NetscriptStanek } from "./NetscriptFunctions/Stanek";
import { NetscriptInfiltration } from "./NetscriptFunctions/Infiltration";
import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface"; import { NetscriptUserInterface } from "./NetscriptFunctions/UserInterface";
import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner"; import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner";
import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract"; import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
@ -81,6 +82,7 @@ import {
Gang as IGang, Gang as IGang,
Bladeburner as IBladeburner, Bladeburner as IBladeburner,
Stanek as IStanek, Stanek as IStanek,
Infiltration as IInfiltration,
RunningScript as IRunningScript, RunningScript as IRunningScript,
RecentScript as IRecentScript, RecentScript as IRecentScript,
SourceFileLvl, SourceFileLvl,
@ -113,6 +115,7 @@ interface NS extends INS {
gang: IGang; gang: IGang;
bladeburner: IBladeburner; bladeburner: IBladeburner;
stanek: IStanek; stanek: IStanek;
infiltration: IInfiltration;
} }
export function NetscriptFunctions(workerScript: WorkerScript): NS { export function NetscriptFunctions(workerScript: WorkerScript): NS {
@ -525,6 +528,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper); const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript, helper); const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper);
const infiltration = wrapAPI(helper, {}, workerScript, NetscriptInfiltration(Player), "infiltration")
.infiltration as unknown as IInfiltration;
const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek") const stanek = wrapAPI(helper, {}, workerScript, NetscriptStanek(Player, workerScript, helper), "stanek")
.stanek as unknown as IStanek; .stanek as unknown as IStanek;
const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
@ -547,6 +552,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
sleeve: sleeve, sleeve: sleeve,
corporation: corporation, corporation: corporation,
stanek: stanek, stanek: stanek,
infiltration: infiltration,
ui: ui, ui: ui,
formulas: formulas, formulas: formulas,
stock: stockmarket, stock: stockmarket,

@ -0,0 +1,54 @@
import { IPlayer } from "../PersonObjects/IPlayer";
import { Infiltration as IInfiltration, InfiltrationLocation } from "../ScriptEditor/NetscriptDefinitions";
import { Location } from "../Locations/Location";
import { Locations } from "../Locations/Locations";
import { calculateDifficulty, calculateReward } from "../Infiltration/formulas/game";
import {
calculateInfiltratorsRepReward,
calculateSellInformationCashReward,
calculateTradeInformationRepReward,
} from "../Infiltration/formulas/victory";
import { FactionNames } from "../Faction/data/FactionNames";
import { Factions } from "../Faction/Factions";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { checkEnum } from "../utils/helpers/checkEnum";
import { LocationName } from "../Locations/data/LocationNames";
export function NetscriptInfiltration(player: IPlayer): InternalAPI<IInfiltration> {
const getLocationsWithInfiltrations = Object.values(Locations).filter(
(location: Location) => location.infiltrationData,
);
const calculateInfiltrationData = (ctx: NetscriptContext, locationName: string): InfiltrationLocation => {
if (!checkEnum(LocationName, locationName)) throw new Error(`Location '${locationName}' does not exists.`);
const location = Locations[locationName];
if (location === undefined) throw ctx.makeRuntimeErrorMsg(`Location '${location}' does not exists.`);
if (location.infiltrationData === undefined)
throw ctx.makeRuntimeErrorMsg(`Location '${location}' does not provide infiltrations.`);
const startingSecurityLevel = location.infiltrationData.startingSecurityLevel;
const difficulty = calculateDifficulty(player, startingSecurityLevel);
const reward = calculateReward(player, startingSecurityLevel);
const maxLevel = location.infiltrationData.maxClearanceLevel;
return {
location: location,
reward: {
tradeRep: calculateTradeInformationRepReward(player, reward, maxLevel, difficulty),
sellCash: calculateSellInformationCashReward(player, reward, maxLevel, difficulty),
SoARep: calculateInfiltratorsRepReward(player, Factions[FactionNames.ShadowsOfAnarchy], difficulty),
},
difficulty: difficulty,
};
};
return {
getPossibleLocations: () => (): string[] => {
return getLocationsWithInfiltrations.map((l) => l + "");
},
getInfiltration:
(ctx: NetscriptContext) =>
(_location: unknown): InfiltrationLocation => {
const location = ctx.helper.string("location", _location);
return calculateInfiltrationData(ctx, location);
},
};
}

@ -97,6 +97,13 @@ export abstract class Person {
bladeburner_analysis_mult = 1; bladeburner_analysis_mult = 1;
bladeburner_success_chance_mult = 1; bladeburner_success_chance_mult = 1;
infiltration_base_rep_increase = 0;
infiltration_rep_mult = 1;
infiltration_trade_mult = 1;
infiltration_sell_mult = 1;
infiltration_timer_mult = 1;
infiltration_damage_reduction_mult = 1;
/** /**
* Augmentations * Augmentations
*/ */
@ -195,6 +202,24 @@ export abstract class Person {
this.crime_success_mult = 1; this.crime_success_mult = 1;
this.work_money_mult = 1; this.work_money_mult = 1;
this.hacknet_node_money_mult = 1;
this.hacknet_node_purchase_cost_mult = 1;
this.hacknet_node_ram_cost_mult = 1;
this.hacknet_node_core_cost_mult = 1;
this.hacknet_node_level_cost_mult = 1;
this.bladeburner_max_stamina_mult = 1;
this.bladeburner_stamina_gain_mult = 1;
this.bladeburner_analysis_mult = 1;
this.bladeburner_success_chance_mult = 1;
this.infiltration_base_rep_increase = 0;
this.infiltration_rep_mult = 1;
this.infiltration_trade_mult = 1;
this.infiltration_sell_mult = 1;
this.infiltration_timer_mult = 1;
this.infiltration_damage_reduction_mult = 1;
} }
/** /**

@ -7,7 +7,7 @@ import { Augmentation } from "../../Augmentation/Augmentation";
import { calculateEntropy } from "../Grafting/EntropyAccumulation"; import { calculateEntropy } from "../Grafting/EntropyAccumulation";
export function hasAugmentation(this: IPlayer, aug: string | Augmentation, includeQueued = false): boolean { export function hasAugmentation(this: IPlayer, aug: string | Augmentation, ignoreQueued = false): boolean {
const augName: string = aug instanceof Augmentation ? aug.name : aug; const augName: string = aug instanceof Augmentation ? aug.name : aug;
for (const owned of this.augmentations) { for (const owned of this.augmentations) {
@ -16,7 +16,7 @@ export function hasAugmentation(this: IPlayer, aug: string | Augmentation, inclu
} }
} }
if (!includeQueued) { if (!ignoreQueued) {
for (const owned of this.queuedAugmentations) { for (const owned of this.queuedAugmentations) {
if (owned.name === augName) { if (owned.name === augName) {
return true; return true;

@ -56,7 +56,7 @@ function possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {
function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] { function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
// Array of all factions that other sleeves are working for // Array of all factions that other sleeves are working for
const forbiddenFactions = [FactionNames.Bladeburners as string]; const forbiddenFactions = [FactionNames.Bladeburners as string, FactionNames.ShadowsOfAnarchy as string];
if (player.gang) { if (player.gang) {
forbiddenFactions.push(player.gang.facName); forbiddenFactions.push(player.gang.facName);
} }

@ -24,6 +24,8 @@ import { SxProps } from "@mui/system";
import { PlayerObject } from "./PersonObjects/Player/PlayerObject"; import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
import { pushGameSaved } from "./Electron"; import { pushGameSaved } from "./Electron";
import { defaultMonacoTheme } from "./ScriptEditor/ui/themes"; import { defaultMonacoTheme } from "./ScriptEditor/ui/themes";
import { FactionNames } from "./Faction/data/FactionNames";
import { Faction } from "./Faction/Faction";
/* SaveObject.js /* SaveObject.js
* Defines the object used to save/load games * Defines the object used to save/load games
@ -398,6 +400,9 @@ function evaluateVersionCompatibility(ver: string | number): void {
if (ver < 15) { if (ver < 15) {
(Settings as any).EditorTheme = { ...defaultMonacoTheme }; (Settings as any).EditorTheme = { ...defaultMonacoTheme };
} }
if (ver < 16) {
Factions[FactionNames.ShadowsOfAnarchy] = new Faction(FactionNames.ShadowsOfAnarchy);
}
} }
} }

@ -215,6 +215,9 @@ async function parseOnlyRamCalculate(
} else if (ref in workerScript.env.vars.stanek) { } else if (ref in workerScript.env.vars.stanek) {
func = workerScript.env.vars.stanek[ref]; func = workerScript.env.vars.stanek[ref];
refDetail = `stanek.${ref}`; refDetail = `stanek.${ref}`;
} else if (ref in workerScript.env.vars.infiltration) {
func = workerScript.env.vars.infiltration[ref];
refDetail = `infiltration.${ref}`;
} else if (ref in workerScript.env.vars.gang) { } else if (ref in workerScript.env.vars.gang) {
func = workerScript.env.vars.gang[ref]; func = workerScript.env.vars.gang[ref];
refDetail = `gang.${ref}`; refDetail = `gang.${ref}`;

@ -4273,6 +4273,41 @@ interface Stanek {
acceptGift(): boolean; acceptGift(): boolean;
} }
export interface InfiltrationReward {
tradeRep: number;
sellCash: number;
SoARep: number;
}
export interface InfiltrationLocation {
location: any;
reward: InfiltrationReward;
difficulty: number;
}
/**
* Infiltration API.
* @public
*/
interface Infiltration {
/**
* Get all locations that can be infiltrated.
* @remarks
* RAM cost: 5 GB
*
* @returns all locations that can be infiltrated.
*/
getPossibleLocations(): string[];
/**
* Get all infiltrations with difficulty, location and rewards.
* @remarks
* RAM cost: 15 GB
*
* @returns Infiltration data for given location.
*/
getInfiltration(location: string): InfiltrationLocation;
}
/** /**
* User Interface API. * User Interface API.
* @public * @public
@ -4422,6 +4457,11 @@ export interface NS {
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
readonly stanek: Stanek; readonly stanek: Stanek;
/**
* Namespace for infiltration functions.
* RAM cost: 0 GB
*/
readonly infiltration: Infiltration;
/** /**
* Namespace for corporation functions. * Namespace for corporation functions.
* RAM cost: 0 GB * RAM cost: 0 GB

@ -30,17 +30,17 @@ describe("Numeral formatting for positive numbers", () => {
expect(numeralWrapper.formatReallyBigNumber(987654321)).toEqual("987.654m"); expect(numeralWrapper.formatReallyBigNumber(987654321)).toEqual("987.654m");
expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual("987.654b"); expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual("987.654b");
expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual("987.654t"); expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual("987.654t");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e3)).toEqual("987.654q"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000)).toEqual("987.654q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e6)).toEqual("987.654Q"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000)).toEqual("987.654Q");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e9)).toEqual("987.654s"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000)).toEqual("987.654s");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e12)).toEqual("987.654S"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000)).toEqual("987.654S");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e15)).toEqual("987.654o"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000)).toEqual("987.654o");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e18)).toEqual("987.654n"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000)).toEqual("987.654n");
}); });
test("should format even bigger really big numbers in scientific format", () => { test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(987654321987654e21)).toEqual("9.877e+35"); expect(numeralWrapper.formatReallyBigNumber(987654321987654000000000000000000000)).toEqual("9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e22)).toEqual("9.877e+36"); expect(numeralWrapper.formatReallyBigNumber(9876543219876540000000000000000000000)).toEqual("9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(987654321987654e23)).toEqual("9.877e+37"); expect(numeralWrapper.formatReallyBigNumber(98765432198765400000000000000000000000)).toEqual("9.877e+37");
}); });
test("should format percentage", () => { test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(1234.56789)).toEqual("123456.79%"); expect(numeralWrapper.formatPercentage(1234.56789)).toEqual("123456.79%");
@ -74,17 +74,17 @@ describe("Numeral formatting for negative numbers", () => {
expect(numeralWrapper.formatReallyBigNumber(-987654321)).toEqual("-987.654m"); expect(numeralWrapper.formatReallyBigNumber(-987654321)).toEqual("-987.654m");
expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual("-987.654b"); expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual("-987.654b");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual("-987.654t"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual("-987.654t");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e3)).toEqual("-987.654q"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000)).toEqual("-987.654q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e6)).toEqual("-987.654Q"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000)).toEqual("-987.654Q");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e9)).toEqual("-987.654s"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000)).toEqual("-987.654s");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e12)).toEqual("-987.654S"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000)).toEqual("-987.654S");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e15)).toEqual("-987.654o"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000)).toEqual("-987.654o");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e18)).toEqual("-987.654n"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000)).toEqual("-987.654n");
}); });
test("should format even bigger really big numbers in scientific format", () => { test("should format even bigger really big numbers in scientific format", () => {
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e21)).toEqual("-9.877e+35"); expect(numeralWrapper.formatReallyBigNumber(-987654321987654000000000000000000000)).toEqual("-9.877e+35");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e22)).toEqual("-9.877e+36"); expect(numeralWrapper.formatReallyBigNumber(-9876543219876540000000000000000000000)).toEqual("-9.877e+36");
expect(numeralWrapper.formatReallyBigNumber(-987654321987654e23)).toEqual("-9.877e+37"); expect(numeralWrapper.formatReallyBigNumber(-98765432198765400000000000000000000000)).toEqual("-9.877e+37");
}); });
test("should format percentage", () => { test("should format percentage", () => {
expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual("-123456.79%"); expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual("-123456.79%");