diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index 7d21e352f..f19459402 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -1,5 +1,3 @@ -import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; - import { AugmentationName, BlackOperationName, @@ -69,24 +67,24 @@ function bitNodeFinishedState(): boolean { return Player.bladeburner !== null && BlackOperationName.OperationDaedalus in Player.bladeburner.blackops; } -function hasAccessToSF(player: PlayerObject, bn: number): boolean { - return player.bitNodeN === bn || player.sourceFileLvl(bn) > 0; +function hasAccessToSF(bn: number): boolean { + return Player.bitNodeN === bn || Player.sourceFileLvl(bn) > 0; } -function knowsAboutBitverse(player: PlayerObject): boolean { - return player.sourceFiles.size > 0; +function knowsAboutBitverse(): boolean { + return Player.sourceFiles.size > 0; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function sfAchievement(): Achievement[] { - const achs: Achievement[] = []; - for (let i = 0; i <= 11; i++) { - for (let j = 1; j <= 3; j++) { - achs.push({ - ID: `SF${i}.${j}`, - Condition: () => Player.sourceFileLvl(i) >= j, - }); - } +function sfAchievements(): Record { + const achs: Record = {}; + for (let i = 1; i <= 12; i++) { + const ID = `SF${i}.1`; + achs[ID] = { + ...achievementData[ID], + Icon: ID, + Visible: knowsAboutBitverse, + Condition: () => Player.sourceFileLvl(i) >= 1, + }; } return achs; } @@ -158,78 +156,7 @@ export const achievements: Record = { Icon: "formulas", Condition: () => Player.getHomeComputer().programs.includes(CompletedProgramName.formulas), }, - "SF1.1": { - ...achievementData["SF1.1"], - Icon: "SF1.1", - Visible: () => hasAccessToSF(Player, 1), - Condition: () => Player.sourceFileLvl(1) >= 1, - }, - "SF2.1": { - ...achievementData["SF2.1"], - Icon: "SF2.1", - Visible: () => hasAccessToSF(Player, 2), - Condition: () => Player.sourceFileLvl(2) >= 1, - }, - "SF3.1": { - ...achievementData["SF3.1"], - Icon: "SF3.1", - Visible: () => hasAccessToSF(Player, 3), - Condition: () => Player.sourceFileLvl(3) >= 1, - }, - "SF4.1": { - ...achievementData["SF4.1"], - Icon: "SF4.1", - Visible: () => hasAccessToSF(Player, 4), - Condition: () => Player.sourceFileLvl(4) >= 1, - }, - "SF5.1": { - ...achievementData["SF5.1"], - Icon: "SF5.1", - Visible: () => hasAccessToSF(Player, 5), - Condition: () => Player.sourceFileLvl(5) >= 1, - }, - "SF6.1": { - ...achievementData["SF6.1"], - Icon: "SF6.1", - Visible: () => hasAccessToSF(Player, 6), - Condition: () => Player.sourceFileLvl(6) >= 1, - }, - "SF7.1": { - ...achievementData["SF7.1"], - Icon: "SF7.1", - Visible: () => hasAccessToSF(Player, 7), - Condition: () => Player.sourceFileLvl(7) >= 1, - }, - "SF8.1": { - ...achievementData["SF8.1"], - Icon: "SF8.1", - Visible: () => hasAccessToSF(Player, 8), - Condition: () => Player.sourceFileLvl(8) >= 1, - }, - "SF9.1": { - ...achievementData["SF9.1"], - Icon: "SF9.1", - Visible: () => hasAccessToSF(Player, 9), - Condition: () => Player.sourceFileLvl(9) >= 1, - }, - "SF10.1": { - ...achievementData["SF10.1"], - Icon: "SF10.1", - Visible: () => hasAccessToSF(Player, 10), - Condition: () => Player.sourceFileLvl(10) >= 1, - }, - "SF11.1": { - ...achievementData["SF11.1"], - Icon: "SF11.1", - Visible: () => hasAccessToSF(Player, 11), - Condition: () => Player.sourceFileLvl(11) >= 1, - }, - "SF12.1": { - ...achievementData["SF12.1"], - Icon: "SF12.1", - Visible: () => hasAccessToSF(Player, 12), - Condition: () => Player.sourceFileLvl(12) >= 1, - }, + ...sfAchievements(), MONEY_1Q: { ...achievementData.MONEY_1Q, Icon: "$1Q", @@ -410,25 +337,25 @@ export const achievements: Record = { GANG: { ...achievementData.GANG, Icon: "GANG", - Visible: () => hasAccessToSF(Player, 2), + Visible: () => hasAccessToSF(2), Condition: () => Player.gang !== null, }, FULL_GANG: { ...achievementData.FULL_GANG, Icon: "GANGMAX", - Visible: () => hasAccessToSF(Player, 2), + Visible: () => hasAccessToSF(2), Condition: () => Player.gang !== null && Player.gang.members.length === GangConstants.MaximumGangMembers, }, GANG_TERRITORY: { ...achievementData.GANG_TERRITORY, Icon: "GANG100%", - Visible: () => hasAccessToSF(Player, 2), + Visible: () => hasAccessToSF(2), Condition: () => Player.gang !== null && AllGangs[Player.gang.facName].territory >= 0.999, }, GANG_MEMBER_POWER: { ...achievementData.GANG_MEMBER_POWER, Icon: "GANG10000", - Visible: () => hasAccessToSF(Player, 2), + Visible: () => hasAccessToSF(2), Condition: () => Player.gang !== null && Player.gang.members.some( @@ -439,19 +366,19 @@ export const achievements: Record = { CORPORATION: { ...achievementData.CORPORATION, Icon: "CORP", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: () => Player.corporation !== null, }, CORPORATION_BRIBE: { ...achievementData.CORPORATION_BRIBE, Icon: "CORPLOBBY", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: () => !!Player.corporation && Player.corporation.unlocks.has(CorpUnlockName.GovernmentPartnership), }, CORPORATION_PROD_1000: { ...achievementData.CORPORATION_PROD_1000, Icon: "CORP1000", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: () => { if (!Player.corporation) return false; for (const division of Player.corporation.divisions.values()) { @@ -463,7 +390,7 @@ export const achievements: Record = { CORPORATION_EMPLOYEE_3000: { ...achievementData.CORPORATION_EMPLOYEE_3000, Icon: "CORPCITY", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: (): boolean => { if (!Player.corporation) return false; for (const division of Player.corporation.divisions.values()) { @@ -478,7 +405,7 @@ export const achievements: Record = { Icon: "CORPRE", Name: "Own the land", Description: "Expand to the Real Estate division.", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: () => { if (!Player.corporation) return false; for (const division of Player.corporation.divisions.values()) { @@ -490,19 +417,19 @@ export const achievements: Record = { INTELLIGENCE_255: { ...achievementData.INTELLIGENCE_255, Icon: "INT255", - Visible: () => hasAccessToSF(Player, 5), + Visible: () => hasAccessToSF(5), Condition: () => Player.skills.intelligence >= 255, }, BLADEBURNER_DIVISION: { ...achievementData.BLADEBURNER_DIVISION, Icon: "BLADE", - Visible: () => hasAccessToSF(Player, 6), + Visible: () => hasAccessToSF(6), Condition: () => Player.bladeburner !== null, }, BLADEBURNER_OVERCLOCK: { ...achievementData.BLADEBURNER_OVERCLOCK, Icon: "BLADEOVERCLOCK", - Visible: () => hasAccessToSF(Player, 6), + Visible: () => hasAccessToSF(6), Condition: () => Player.bladeburner !== null && Player.bladeburner.skills[SkillNames.Overclock] === Skills[SkillNames.Overclock].maxLvl, @@ -510,7 +437,7 @@ export const achievements: Record = { BLADEBURNER_UNSPENT_100000: { ...achievementData.BLADEBURNER_UNSPENT_100000, Icon: "BLADE100K", - Visible: () => hasAccessToSF(Player, 6), + Visible: () => hasAccessToSF(6), Condition: () => Player.bladeburner !== null && Player.bladeburner.skillPoints >= 100000, }, "4S": { @@ -521,21 +448,21 @@ export const achievements: Record = { FIRST_HACKNET_SERVER: { ...achievementData.FIRST_HACKNET_SERVER, Icon: "HASHNET", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: () => hasHacknetServers() && Player.hacknetNodes.length > 0, AdditionalUnlock: [achievementData.FIRST_HACKNET_NODE.ID], }, ALL_HACKNET_SERVER: { ...achievementData.ALL_HACKNET_SERVER, Icon: "HASHNETALL", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: () => hasHacknetServers() && Player.hacknetNodes.length === HacknetServerConstants.MaxServers, AdditionalUnlock: [achievementData["30_HACKNET_NODE"].ID], }, MAX_HACKNET_SERVER: { ...achievementData.MAX_HACKNET_SERVER, Icon: "HASHNETALL", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: (): boolean => { if (!hasHacknetServers()) return false; for (const h of Player.hacknetNodes) { @@ -557,14 +484,14 @@ export const achievements: Record = { HACKNET_SERVER_1B: { ...achievementData.HACKNET_SERVER_1B, Icon: "HASHNETMONEY", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: () => hasHacknetServers() && Player.moneySourceB.hacknet >= 1e9, AdditionalUnlock: [achievementData.HACKNET_NODE_10M.ID], }, MAX_CACHE: { ...achievementData.MAX_CACHE, Icon: "HASHNETCAP", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: () => hasHacknetServers() && Player.hashManager.hashes === Player.hashManager.capacity && @@ -573,13 +500,13 @@ export const achievements: Record = { SLEEVE_8: { ...achievementData.SLEEVE_8, Icon: "SLEEVE8", - Visible: () => hasAccessToSF(Player, 10), + Visible: () => hasAccessToSF(10), Condition: () => Player.sleeves.length === 8 && Player.sourceFileLvl(10) === 3, }, INDECISIVE: { ...achievementData.INDECISIVE, Icon: "1H", - Visible: () => knowsAboutBitverse(Player), + Visible: knowsAboutBitverse, Condition: (function () { let c = 0; setInterval(() => { @@ -595,13 +522,13 @@ export const achievements: Record = { FAST_BN: { ...achievementData.FAST_BN, Icon: "2DAYS", - Visible: () => knowsAboutBitverse(Player), + Visible: knowsAboutBitverse, Condition: () => bitNodeFinishedState() && Player.playtimeSinceLastBitnode < 1000 * 60 * 60 * 24 * 2, }, CHALLENGE_BN1: { ...achievementData.CHALLENGE_BN1, Icon: "BN1+", - Visible: () => knowsAboutBitverse(Player), + Visible: knowsAboutBitverse, Condition: () => Player.bitNodeN === 1 && bitNodeFinishedState() && @@ -611,37 +538,37 @@ export const achievements: Record = { CHALLENGE_BN2: { ...achievementData.CHALLENGE_BN2, Icon: "BN2+", - Visible: () => hasAccessToSF(Player, 2), + Visible: () => hasAccessToSF(2), Condition: () => Player.bitNodeN === 2 && bitNodeFinishedState() && Player.gang === null, }, CHALLENGE_BN3: { ...achievementData.CHALLENGE_BN3, Icon: "BN3+", - Visible: () => hasAccessToSF(Player, 3), + Visible: () => hasAccessToSF(3), Condition: () => Player.bitNodeN === 3 && bitNodeFinishedState() && Player.corporation === null, }, CHALLENGE_BN6: { ...achievementData.CHALLENGE_BN6, Icon: "BN6+", - Visible: () => hasAccessToSF(Player, 6), + Visible: () => hasAccessToSF(6), Condition: () => Player.bitNodeN === 6 && bitNodeFinishedState() && Player.bladeburner === null, }, CHALLENGE_BN7: { ...achievementData.CHALLENGE_BN7, Icon: "BN7+", - Visible: () => hasAccessToSF(Player, 7), + Visible: () => hasAccessToSF(7), Condition: () => Player.bitNodeN === 7 && bitNodeFinishedState() && Player.bladeburner === null, }, CHALLENGE_BN8: { ...achievementData.CHALLENGE_BN8, Icon: "BN8+", - Visible: () => hasAccessToSF(Player, 8), + Visible: () => hasAccessToSF(8), Condition: () => Player.bitNodeN === 8 && bitNodeFinishedState() && !Player.has4SData && !Player.has4SDataTixApi, }, CHALLENGE_BN9: { ...achievementData.CHALLENGE_BN9, Icon: "BN9+", - Visible: () => hasAccessToSF(Player, 9), + Visible: () => hasAccessToSF(9), Condition: () => Player.bitNodeN === 9 && bitNodeFinishedState() && @@ -651,7 +578,7 @@ export const achievements: Record = { CHALLENGE_BN10: { ...achievementData.CHALLENGE_BN10, Icon: "BN10+", - Visible: () => hasAccessToSF(Player, 10), + Visible: () => hasAccessToSF(10), Condition: () => Player.bitNodeN === 10 && bitNodeFinishedState() && @@ -669,7 +596,7 @@ export const achievements: Record = { CHALLENGE_BN12: { ...achievementData.CHALLENGE_BN12, Icon: "BN12+", - Visible: () => hasAccessToSF(Player, 12), + Visible: () => hasAccessToSF(12), Condition: () => Player.sourceFileLvl(12) >= 50, }, BYPASS: { @@ -730,7 +657,7 @@ export const achievements: Record = { CHALLENGE_BN13: { ...achievementData.CHALLENGE_BN13, Icon: "BN13+", - Visible: () => hasAccessToSF(Player, 13), + Visible: () => hasAccessToSF(13), Condition: () => Player.bitNodeN === 13 && bitNodeFinishedState() && diff --git a/src/Bladeburner/Action.tsx b/src/Bladeburner/Action.tsx index 767b2c441..839cf27c3 100644 --- a/src/Bladeburner/Action.tsx +++ b/src/Bladeburner/Action.tsx @@ -184,14 +184,12 @@ export class Action { return Math.ceil(baseTime * this.getActionTimePenalty()); } - // For actions that have teams. To be implemented by subtypes. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getTeamSuccessBonus(inst: Bladeburner): number { + // Subtypes of Action implement these differently + getTeamSuccessBonus(__inst: Bladeburner): number { return 1; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getActionTypeSkillSuccessBonus(inst: Bladeburner): number { + getActionTypeSkillSuccessBonus(__inst: Bladeburner): number { return 1; } diff --git a/src/Infiltration/ui/SlashGame.tsx b/src/Infiltration/ui/SlashGame.tsx index 6afef1755..c5347b1cc 100644 --- a/src/Infiltration/ui/SlashGame.tsx +++ b/src/Infiltration/ui/SlashGame.tsx @@ -1,5 +1,5 @@ import { Box, Paper, Typography } from "@mui/material"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { AugmentationName } from "@enums"; import { Player } from "@player"; import { KEY } from "../../utils/helpers/keyCodes"; @@ -25,11 +25,35 @@ const difficulties: { Impossible: { window: 150 }, }; -export function SlashGame({ difficulty: _difficulty, onSuccess, onFailure }: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = { window: 0 }; - interpolate(difficulties, _difficulty, difficulty); - +export function SlashGame({ difficulty, onSuccess, onFailure }: IMinigameProps): React.ReactElement { const [phase, setPhase] = useState(0); + const [hasAugment, setHasAugment] = useState(false); + const [guardingTime, setGuardingTime] = useState(0); + + useEffect(() => { + // Determine timeframes for game phase changes + const newDifficulty: Difficulty = { window: 0 }; + interpolate(difficulties, difficulty, newDifficulty); + const timePreparing = newDifficulty.window; + const timeAttacking = 250; + const timeGuarding = Math.random() * 3250 + 1500 - (timeAttacking + timePreparing); + + // Set initial game state + setPhase(0); + setGuardingTime(timeGuarding); + setHasAugment(Player.hasAugmentation(AugmentationName.MightOfAres, true)); + + // Setup timer for game phases + let id = setTimeout(() => { + setPhase(1); + id = setTimeout(() => { + setPhase(2); + id = setTimeout(() => onFailure(), timeAttacking); + }, timePreparing); + }, timeGuarding); + + return () => clearTimeout(id); + }, [difficulty, onSuccess, onFailure]); function press(this: Document, event: KeyboardEvent): void { event.preventDefault(); @@ -41,39 +65,16 @@ export function SlashGame({ difficulty: _difficulty, onSuccess, onFailure }: IMi } } - const guardingTimeRef = useRef(Math.random() * 3250 + 1500 - (250 + difficulty.window)); - - useEffect(() => { - const preparingTime = difficulty.window; - const attackingTime = 250; - - let id = window.setTimeout(() => { - setPhase(1); - id = window.setTimeout(() => { - setPhase(2); - id = window.setTimeout(() => onFailure(), attackingTime); - }, preparingTime); - }, guardingTimeRef.current); - - return () => { - clearInterval(id); - }; - }, [difficulty.window, onFailure]); - - const hasAugment = Player.hasAugmentation(AugmentationName.MightOfAres, true); return ( <> Attack when his guard is down! - - {hasAugment ? ( + {hasAugment && ( Guard will drop in... - null} ignoreAugment_WKSharmonizer noPaper /> + null} ignoreAugment_WKSharmonizer noPaper /> - ) : ( - <> )} {phase === 0 && Guarding ...} diff --git a/src/Infiltration/ui/WireCuttingGame.tsx b/src/Infiltration/ui/WireCuttingGame.tsx index d3d40725d..b39af507a 100644 --- a/src/Infiltration/ui/WireCuttingGame.tsx +++ b/src/Infiltration/ui/WireCuttingGame.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import { Box, Paper, Typography } from "@mui/material"; import { AugmentationName } from "@enums"; @@ -10,6 +10,7 @@ import { interpolate } from "./Difficulty"; import { GameTimer } from "./GameTimer"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; +import { isPositiveInteger } from "../../types"; interface Difficulty { [key: string]: number; @@ -43,7 +44,7 @@ const colorNames: Record = { }; interface Wire { - tpe: string; + wireType: string; colors: string[]; } @@ -52,55 +53,66 @@ interface Question { shouldCut: (wire: Wire, index: number) => boolean; } -export function WireCuttingGame({ onSuccess, onFailure, ...otherProps }: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = { - timer: 0, - wiresmin: 0, - wiresmax: 0, - rules: 0, - }; - interpolate(difficulties, otherProps.difficulty, difficulty); - const timer = difficulty.timer; - const wiresRef = useRef(generateWires(difficulty)); - const questionsRef = useRef(generateQuestion(wiresRef.current, difficulty)); +export function WireCuttingGame({ onSuccess, onFailure, difficulty }: IMinigameProps): React.ReactElement { + const [questions, setQuestions] = useState([]); + const [wires, setWires] = useState([]); + const [timer, setTimer] = useState(0); + const [cutWires, setCutWires] = useState([]); + const [wiresToCut, setWiresToCut] = useState(new Set()); + const [hasAugment, setHasAugment] = useState(false); - const [cutWires, setCutWires] = useState(new Array(wiresRef.current.length).fill(false)); - const hasAugment = Player.hasAugmentation(AugmentationName.KnowledgeOfApollo, true); - - // TODO: refactor, move the code from this effect to a `press` function useEffect(() => { - // check if we won - const wiresToBeCut = []; - for (let j = 0; j < wiresRef.current.length; j++) { - let shouldBeCut = false; - for (let i = 0; i < questionsRef.current.length; i++) { - shouldBeCut = shouldBeCut || questionsRef.current[i].shouldCut(wiresRef.current[j], j); - } - wiresToBeCut.push(shouldBeCut); - } - if (wiresToBeCut.every((b, i) => b === cutWires[i])) { - onSuccess(); - } - }, [cutWires, onSuccess]); + // Determine game difficulty + const gameDifficulty: Difficulty = { + timer: 0, + wiresmin: 0, + wiresmax: 0, + rules: 0, + }; + interpolate(difficulties, difficulty, gameDifficulty); - function checkWire(wireNum: number): boolean { - return questionsRef.current.some((q) => q.shouldCut(wiresRef.current[wireNum - 1], wireNum - 1)); - } + // Calculate initial game data + const gameWires = generateWires(gameDifficulty); + const gameQuestions = generateQuestion(gameWires, gameDifficulty); + const gameWiresToCut = new Set(); + gameWires.forEach((wire, index) => { + for (const question of gameQuestions) { + if (question.shouldCut(wire, index)) { + gameWiresToCut.add(index); + return; // go to next wire + } + } + }); + + // Initialize the game state + setTimer(gameDifficulty.timer); + setWires(gameWires); + setCutWires(gameWires.map((__) => false)); + setQuestions(gameQuestions); + setWiresToCut(gameWiresToCut); + setHasAugment(Player.hasAugmentation(AugmentationName.KnowledgeOfApollo, true)); + }, [difficulty]); function press(this: Document, event: KeyboardEvent): void { event.preventDefault(); const wireNum = parseInt(event.key); + if (!isPositiveInteger(wireNum) || wireNum > wires.length) return; - if (wireNum < 1 || wireNum > wiresRef.current.length || isNaN(wireNum)) return; - setCutWires((old) => { - const next = [...old]; - next[wireNum - 1] = true; - if (!checkWire(wireNum)) { - onFailure(); - } + const wireIndex = wireNum - 1; + if (cutWires[wireIndex]) return; - return next; - }); + // Check if game has been lost + if (!wiresToCut.has(wireIndex)) return onFailure(); + + // Check if game has been won + const newWiresToCut = new Set(wiresToCut); + newWiresToCut.delete(wireIndex); + if (newWiresToCut.size === 0) return onSuccess(); + + // Rerender with new state if game has not been won or lost yet + const newCutWires = cutWires.map((old, i) => (i === wireIndex ? true : old)); + setWiresToCut(newWiresToCut); + setCutWires(newCutWires); } return ( @@ -110,19 +122,19 @@ export function WireCuttingGame({ onSuccess, onFailure, ...otherProps }: IMiniga Cut the wires with the following properties! (keyboard 1 to 9) - {questionsRef.current.map((question, i) => ( + {questions.map((question, i) => ( {question.toString()} ))} - {Array.from({ length: wiresRef.current.length }).map((_, i) => { - const isCorrectWire = checkWire(i + 1); + {Array.from({ length: wires.length }).map((_, i) => { + const isCorrectWire = cutWires[i + 1] || wiresToCut.has(i + 1); const color = hasAugment && !isCorrectWire ? Settings.theme.disabled : Settings.theme.primary; return ( @@ -132,16 +144,16 @@ export function WireCuttingGame({ onSuccess, onFailure, ...otherProps }: IMiniga })} {new Array(8).fill(0).map((_, i) => ( - {wiresRef.current.map((wire, j) => { + {wires.map((wire, j) => { if ((i === 3 || i === 4) && cutWires[j]) { return ; } - const isCorrectWire = checkWire(j + 1); + const isCorrectWire = cutWires[j + 1] || wiresToCut.has(j + 1); const wireColor = hasAugment && !isCorrectWire ? Settings.theme.disabled : wire.colors[i % wire.colors.length]; return ( - |{wire.tpe}| + |{wire.wireType}| ); })} @@ -198,7 +210,7 @@ function generateWires(difficulty: Difficulty): Wire[] { wireColors.push(colors[Math.floor(Math.random() * colors.length)]); } wires.push({ - tpe: types[Math.floor(Math.random() * types.length)], + wireType: types[Math.floor(Math.random() * types.length)], colors: wireColors, }); } diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts index 269ea9057..de4cd10e1 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts @@ -36,7 +36,7 @@ import { getHospitalizationCost } from "../../Hospital/Hospital"; import { HacknetServer } from "../../Hacknet/HacknetServer"; import { formatMoney } from "../../ui/formatNumber"; -import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; +import { MoneySource, MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { SnackbarEvents } from "../../ui/React/Snackbar"; @@ -183,7 +183,7 @@ export function setMoney(this: PlayerObject, money: number): void { this.money = money; } -export function gainMoney(this: PlayerObject, money: number, source: string): void { +export function gainMoney(this: PlayerObject, money: number, source: MoneySource): void { if (isNaN(money)) { console.error("NaN passed into Player.gainMoney()"); return; @@ -193,7 +193,7 @@ export function gainMoney(this: PlayerObject, money: number, source: string): vo this.recordMoneySource(money, source); } -export function loseMoney(this: PlayerObject, money: number, source: string): void { +export function loseMoney(this: PlayerObject, money: number, source: MoneySource): void { if (isNaN(money)) { console.error("NaN passed into Player.loseMoney()"); return; @@ -211,7 +211,7 @@ export function canAfford(this: PlayerObject, cost: number): boolean { return this.money >= cost; } -export function recordMoneySource(this: PlayerObject, amt: number, source: string): void { +export function recordMoneySource(this: PlayerObject, amt: number, source: MoneySource): void { if (!(this.moneySourceA instanceof MoneySourceTracker)) { console.warn(`Player.moneySourceA was not properly initialized. Resetting`); this.moneySourceA = new MoneySourceTracker(); @@ -1101,7 +1101,6 @@ export function gainCodingContractReward( ): string { if (!reward) return `No reward for this contract`; - /* eslint-disable no-case-declarations */ switch (reward.type) { case CodingContractRewardType.FactionReputation: { if (!Factions[reward.name]) { @@ -1152,7 +1151,6 @@ export function gainCodingContractReward( return `Gained ${formatMoney(moneyGain)}`; } } - /* eslint-enable no-case-declarations */ } export function travel(this: PlayerObject, to: CityName): boolean { diff --git a/src/Programs/ui/ProgramsRoot.tsx b/src/Programs/ui/ProgramsRoot.tsx index 0a535ca8f..abb31eb20 100644 --- a/src/Programs/ui/ProgramsRoot.tsx +++ b/src/Programs/ui/ProgramsRoot.tsx @@ -1,12 +1,12 @@ import React, { useEffect } from "react"; import { find } from "lodash"; - import { Box, Typography, Button, Container, Paper } from "@mui/material"; import { Check, Lock, Create } from "@mui/icons-material"; +import { Player } from "@player"; +import { CompletedProgramName } from "@enums"; import { Router } from "../../ui/GameRoot"; import { Page } from "../../ui/Router"; -import { Player } from "@player"; import { Settings } from "../../Settings/Settings"; import { Programs } from "../Programs"; @@ -22,7 +22,7 @@ export function ProgramsRoot(): React.ReactElement { .filter((prog) => { const create = prog.create; if (create === null) return false; - if (prog.name === "b1t_flum3.exe") { + if (prog.name === CompletedProgramName.bitFlume) { return create.req(); } return true; @@ -37,8 +37,7 @@ export function ProgramsRoot(): React.ReactElement { programs.forEach((p) => { ProgramsSeen.add(p.name); }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }); const getHackingLevelRemaining = (lvl: number): number => { return Math.ceil(Math.max(lvl - (Player.skills.hacking + Player.skills.intelligence / 2), 0)); diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index 1fedcf313..7bd52d40e 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -60,7 +60,7 @@ const RotatedDoubleArrowIcon = React.forwardRef(function RotatedDoubleArrowIcon( props: { color: "primary" | "secondary" | "error" }, __ref: React.ForwardedRef, ) { - return ; + return ; }); const openedMixin = (theme: Theme): CSSObject => ({ diff --git a/src/Work/WorkStats.ts b/src/Work/WorkStats.ts index 90b78fde9..6fc621a4c 100644 --- a/src/Work/WorkStats.ts +++ b/src/Work/WorkStats.ts @@ -1,3 +1,5 @@ +import type { MoneySource } from "../utils/MoneySourceTracker"; + import { Person } from "../PersonObjects/Person"; import { Player } from "@player"; import { Multipliers } from "../PersonObjects/Multipliers"; @@ -59,7 +61,12 @@ export const scaleWorkStats = (w: WorkStats, n: number, scaleMoney = true): Work }; }; -export const applyWorkStats = (target: Person, workStats: WorkStats, cycles: number, source: string): WorkStats => { +export const applyWorkStats = ( + target: Person, + workStats: WorkStats, + cycles: number, + source: MoneySource, +): WorkStats => { const expStats = applyWorkStatsExp(target, workStats, cycles); const gains = { money: workStats.money * cycles, diff --git a/src/types.ts b/src/types.ts index d86bbc91e..78b5192c1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,7 @@ export type Integer = number & { __Integer: true }; export type PositiveNumber = number & { __Positive: true }; export type PositiveInteger = Integer & PositiveNumber; -// Numeric typechecking functions +// Numeric typechecking functions - these should be moved somewhere else export const isInteger = (n: unknown): n is Integer => Number.isInteger(n); export const isPositiveInteger = (n: unknown): n is PositiveInteger => isInteger(n) && n > 0; @@ -16,6 +16,9 @@ export type Unknownify = { /** Get the member type of either an array or an object */ export type Member = T extends (infer arrayMember)[] ? arrayMember : T[keyof T]; +//** Get the keys of an object where the values match a given type */ +export type TypedKeys = { [K in keyof Obj]-?: Obj[K] extends T ? K : never }[keyof Obj]; + /** Status object for functions that return a boolean indicating success/failure * and an optional message */ export interface IReturnStatus { diff --git a/src/ui/React/ANSIITypography.tsx b/src/ui/React/ANSIITypography.tsx index d067f3867..fab65fca1 100644 --- a/src/ui/React/ANSIITypography.tsx +++ b/src/ui/React/ANSIITypography.tsx @@ -147,7 +147,6 @@ function ansiCodeStyle(code: string | null): Record { return [codeParts.length - startIdx, "inherit"]; } const code = codeParts[startIdx + 1]; - /* eslint-disable yoda */ if (0 <= code && code < 8) { // x8 RGB return [2, COLOR_MAP_DARK[code]]; @@ -210,7 +209,6 @@ function ansiCodeStyle(code: string | null): Record { } else if (codePart === 4) { style.textDecoration = "underline"; } - /* eslint-disable yoda */ // Foreground Color (x8) else if (30 <= codePart && codePart < 38) { style.color = COLOR_MAP_BRIGHT[codePart - 30]; diff --git a/src/utils/MoneySourceTracker.ts b/src/utils/MoneySourceTracker.ts index 5b99ff6dc..4ff2609da 100644 --- a/src/utils/MoneySourceTracker.ts +++ b/src/utils/MoneySourceTracker.ts @@ -1,13 +1,10 @@ -/** - * This is an object that is used to keep track of where all of the player's - * money is coming from (or going to) - */ +import type { TypedKeys } from "../types"; + import { Generic_fromJSON, Generic_toJSON, constructorsForReviver, IReviverValue } from "./JSONReviver"; -export class MoneySourceTracker { - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: number | Function; +export type MoneySource = TypedKeys; +export class MoneySourceTracker { bladeburner = 0; casino = 0; class = 0; @@ -29,14 +26,8 @@ export class MoneySourceTracker { augmentations = 0; // Record money earned - record(amt: number, source: string): void { - const sanitizedSource = source.toLowerCase(); - if (typeof this[sanitizedSource] !== "number" && this[sanitizedSource] !== null) { - console.warn(`MoneySourceTracker.record() called with invalid source: ${source}`); - return; - } - - (this[sanitizedSource] as number) += amt; + record(amt: number, source: MoneySource): void { + this[source] += amt; this.total += amt; }