From 43178bca6f92f64668e5d8cb40c17c33be47a904 Mon Sep 17 00:00:00 2001 From: Marcin Cichocki Date: Wed, 27 Sep 2023 23:03:40 +0200 Subject: [PATCH] add support for patch 2.0 --- src/core/daemon-set.ts | 47 +++++++++++++++++ src/core/daemons-i18n.ts | 51 +++++++++++++++++++ src/core/daemons.ts | 6 +++ src/core/ocr/fragments/base.ts | 1 + src/core/ocr/fragments/types-fragment.ts | 19 +++++-- src/core/ocr/ocr.test.ts | 12 ++++- src/core/ocr/ocr.ts | 3 ++ src/core/solver/breach-protocol.spec.ts | 7 ++- src/core/solver/breach-protocol.ts | 8 ++- src/electron/common/common.ts | 5 +- src/electron/common/options.ts | 11 ++++ src/electron/main/store/store.ts | 25 +++++++++ src/electron/renderer/common.ts | 8 +++ .../renderer/components/DaemonPriority.tsx | 36 +++++++++++-- .../renderer/components/GeneralSettings.tsx | 13 ++++- .../renderer/components/StatusBar.tsx | 7 ++- src/electron/worker/autosolver.ts | 1 + src/electron/worker/worker.ts | 2 + 18 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 src/core/daemon-set.ts diff --git a/src/core/daemon-set.ts b/src/core/daemon-set.ts new file mode 100644 index 00000000..ead81092 --- /dev/null +++ b/src/core/daemon-set.ts @@ -0,0 +1,47 @@ +import { DaemonId } from './common'; +import { + DAEMON_ADVANCED_DATAMINE, + DAEMON_BASIC_DATAMINE, + DAEMON_CAMERA_SHUTDOWN, + DAEMON_DATAMINE_COPY_MALWARE, + DAEMON_DATAMINE_CRAFTING_SPECS, + DAEMON_DATAMINE_V1, + DAEMON_DATAMINE_V2, + DAEMON_DATAMINE_V3, + DAEMON_EXPERT_DATAMINE, + DAEMON_FRIENDLY_TURRETS, + DAEMON_GAIN_ACCESS, + DAEMON_ICEPICK, + DAEMON_MASS_VULNERABILITY, + DAEMON_NEUTRALIZE_MALWARE, + DAEMON_OPTICS_JAMMER, + DAEMON_TURRET_SHUTDOWN, + DAEMON_WEAPONS_JAMMER, +} from './daemons'; + +export const LEGACY_DAEMONS = new Set([ + DAEMON_DATAMINE_V1, + DAEMON_DATAMINE_V2, + DAEMON_DATAMINE_V3, + DAEMON_ICEPICK, + DAEMON_MASS_VULNERABILITY, + DAEMON_CAMERA_SHUTDOWN, + DAEMON_FRIENDLY_TURRETS, + DAEMON_TURRET_SHUTDOWN, + DAEMON_OPTICS_JAMMER, + DAEMON_WEAPONS_JAMMER, + DAEMON_DATAMINE_COPY_MALWARE, + DAEMON_NEUTRALIZE_MALWARE, + DAEMON_GAIN_ACCESS, + DAEMON_DATAMINE_CRAFTING_SPECS, +]); + +export const DAEMONS = new Set([ + DAEMON_DATAMINE_COPY_MALWARE, + DAEMON_NEUTRALIZE_MALWARE, + DAEMON_GAIN_ACCESS, + DAEMON_DATAMINE_CRAFTING_SPECS, + DAEMON_BASIC_DATAMINE, + DAEMON_ADVANCED_DATAMINE, + DAEMON_EXPERT_DATAMINE, +]); diff --git a/src/core/daemons-i18n.ts b/src/core/daemons-i18n.ts index 7bf2e7b5..4adeb407 100644 --- a/src/core/daemons-i18n.ts +++ b/src/core/daemons-i18n.ts @@ -37,6 +37,9 @@ export const eng: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'NEUTRALIZE MALWARE', [d.DAEMON_GAIN_ACCESS]: 'GAIN ACCESS', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATAMINE: CRAFTING SPECS', + [d.DAEMON_BASIC_DATAMINE]: 'BASIC DATAMINE', + [d.DAEMON_ADVANCED_DATAMINE]: 'ADVANCED DATAMINE', + [d.DAEMON_EXPERT_DATAMINE]: 'EXPERT DATAMINE', }; export const pol: DaemonDict = { @@ -56,6 +59,9 @@ export const pol: DaemonDict = { [d.DAEMON_GAIN_ACCESS]: 'UZYSKAJ DOSTĘP', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'EKSPLORACJA DANYCH: SCHEMATY WYTWARZANIA', + [d.DAEMON_BASIC_DATAMINE]: 'EKSPLORACJA: DANE BAZOWE', + [d.DAEMON_ADVANCED_DATAMINE]: 'EKSPLORACJA: DANE SPECJALNE', + [d.DAEMON_EXPERT_DATAMINE]: 'EKSPLORACJA: DANE WRAŻLIWE', }; export const rus: DaemonDict = { @@ -73,6 +79,9 @@ export const rus: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'ОБЕЗВРЕДИТЬ ВИРУС', [d.DAEMON_GAIN_ACCESS]: 'ПОЛУЧИТЬ ДОСТУП', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ДОБЫЧА ДАННЫХ: ДОКУМЕНТАЦИЯ', + [d.DAEMON_BASIC_DATAMINE]: 'ОБЫЧНА ДОБЫЧА ДАННЫХ', + [d.DAEMON_ADVANCED_DATAMINE]: 'ПРОДВИНУТАЯ ДОБЫЧА ДАННЫХ', + [d.DAEMON_EXPERT_DATAMINE]: 'ВЫСОКОУРОВНЕВАЯ ДОБЫЧА ДАННЫХ', }; export const deu: DaemonDict = { @@ -90,6 +99,9 @@ export const deu: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'MALWARE NEUTRALISIEREN', [d.DAEMON_GAIN_ACCESS]: 'ZUGRIFF VERSCHAFFEN', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATA-MINING: SCHEMA', + [d.DAEMON_BASIC_DATAMINE]: 'EINFACHES DATA-MINING', + [d.DAEMON_ADVANCED_DATAMINE]: 'VERBESSERTES DATA-MINING', + [d.DAEMON_EXPERT_DATAMINE]: 'HERAUSRAGENDES DATA-MINING', }; export const fra: DaemonDict = { @@ -107,6 +119,9 @@ export const fra: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'NEUTRALISER LE MALWARE', [d.DAEMON_GAIN_ACCESS]: "OBTENIR L'ACCÈS", [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'DATA MINING : PLAN DE FABRICATION', + [d.DAEMON_BASIC_DATAMINE]: 'DATA MINING ÉLÉMENTAIRE', + [d.DAEMON_ADVANCED_DATAMINE]: 'DATA MINING AVANCÉ', + [d.DAEMON_EXPERT_DATAMINE]: 'DATA MINING EXPERT', }; export const por: DaemonDict = { @@ -125,6 +140,9 @@ export const por: DaemonDict = { [d.DAEMON_GAIN_ACCESS]: 'OBTER ACESSO', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'MINERAÇÃO DE DADOS: ESPECIALIZAÇÕES DE CRIAÇÃO', + [d.DAEMON_BASIC_DATAMINE]: 'MINERAÇÃO DE DADOS BÁSICA', + [d.DAEMON_ADVANCED_DATAMINE]: 'MINERAÇÃO DE DADOS AVANÇADA', + [d.DAEMON_EXPERT_DATAMINE]: 'PERÍCIA EM MINERAÇÃO DE DADOS', }; export const chi_sim: DaemonDict = { @@ -142,6 +160,9 @@ export const chi_sim: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: '清除恶意软件', [d.DAEMON_GAIN_ACCESS]: '获取权限', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '数据挖掘: 制作规范', + [d.DAEMON_BASIC_DATAMINE]: '基础数据挖掘', + [d.DAEMON_ADVANCED_DATAMINE]: '高级数据挖掘', + [d.DAEMON_EXPERT_DATAMINE]: '专家数据挖掘', }; export const spa: DaemonDict = { @@ -160,6 +181,9 @@ export const spa: DaemonDict = { [d.DAEMON_GAIN_ACCESS]: 'OBTENER ACCESO', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'MINERÍA DE DATOS: ESPECIFICACIONES DE CREACIÓN', + [d.DAEMON_BASIC_DATAMINE]: 'MINERÍA DE DATOS BÁSICA', + [d.DAEMON_ADVANCED_DATAMINE]: 'MINERÍA DE DATOS AVANZADA', + [d.DAEMON_EXPERT_DATAMINE]: 'MINERÍA DE DATOS EXPERTA', }; export const ita: DaemonDict = { @@ -178,6 +202,9 @@ export const ita: DaemonDict = { [d.DAEMON_GAIN_ACCESS]: 'OTTIENI ACCESSO', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ESTRAZIONE DATI: ISTRUZIONI DI CREAZIONE', + [d.DAEMON_BASIC_DATAMINE]: 'ESTRAZIONE DATI SEMPLICE', + [d.DAEMON_ADVANCED_DATAMINE]: 'ESTRAZIONE DATI AVANZATA', + [d.DAEMON_EXPERT_DATAMINE]: 'ESTRAZIONE DATI ESPERTO', }; export const kor: DaemonDict = { @@ -195,6 +222,9 @@ export const kor: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: '멀웨어 무력화', [d.DAEMON_GAIN_ACCESS]: '액세스 권한 획득', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '데이터마이닝: 제작 사양서', + [d.DAEMON_BASIC_DATAMINE]: '기본 데이터마이닝', + [d.DAEMON_ADVANCED_DATAMINE]: '고급 데이터마이닝', + [d.DAEMON_EXPERT_DATAMINE]: '전문 데이터마이닝', }; export const jpn: DaemonDict = { @@ -212,6 +242,9 @@ export const jpn: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'マルウェアを無力化する', [d.DAEMON_GAIN_ACCESS]: 'アクセス取得', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'データマイニング: クラフト用設計図', + [d.DAEMON_BASIC_DATAMINE]: '基礎データマイニング', + [d.DAEMON_ADVANCED_DATAMINE]: '高度データマイニング', + [d.DAEMON_EXPERT_DATAMINE]: '超高度データマイニング', }; export const tha: DaemonDict = { @@ -229,6 +262,9 @@ export const tha: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'กําจัดมัลแวร์', [d.DAEMON_GAIN_ACCESS]: 'ขอสิทธิ์เข้าถึง', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ดาต้าไมน์: สูตรการคราฟต์', + [d.DAEMON_BASIC_DATAMINE]: 'ดาต้าไมน์ขันพื่นฐาน', + [d.DAEMON_ADVANCED_DATAMINE]: 'ดาต้าไมน์ขันสูง', + [d.DAEMON_EXPERT_DATAMINE]: 'ดาต้าไมน์ขันเชียวชาญ', }; export const tur: DaemonDict = { @@ -246,6 +282,9 @@ export const tur: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'KÖTÜ AMAÇLI YAZILIMI ORTADAN KALDIR', [d.DAEMON_GAIN_ACCESS]: 'ERİŞİM SAĞLA', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'VERİ MADENİ: ÜRETİM KILAVUZLARI', + [d.DAEMON_BASIC_DATAMINE]: 'BASİT VERİ MADENİ', + [d.DAEMON_ADVANCED_DATAMINE]: 'GELİŞMİŞ VERİ MADENİ', + [d.DAEMON_EXPERT_DATAMINE]: 'UZMAN VERİ MADENİ', }; export const ara: DaemonDict = { @@ -263,6 +302,9 @@ export const ara: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'اقض على البرنامج الخبيث', [d.DAEMON_GAIN_ACCESS]: 'تمكن من الوصول', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'منجم بيانات: مواصفات التصنيع', + [d.DAEMON_BASIC_DATAMINE]: 'منجه بيانات أساسي', + [d.DAEMON_ADVANCED_DATAMINE]: 'منجم بيانات متقدم', + [d.DAEMON_EXPERT_DATAMINE]: 'منجم بيانات نخبوي', }; export const chi_tra: DaemonDict = { @@ -280,6 +322,9 @@ export const chi_tra: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: '制止惡意軟體', [d.DAEMON_GAIN_ACCESS]: '取得存取權', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: '資料探勘 : 製作需求', + [d.DAEMON_BASIC_DATAMINE]: '基礎資料探勘', + [d.DAEMON_ADVANCED_DATAMINE]: '進階資料探勘', + [d.DAEMON_EXPERT_DATAMINE]: '專業資料探勘', }; export const ces: DaemonDict = { @@ -297,6 +342,9 @@ export const ces: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'ZNEŠKODNIT MALWARE', [d.DAEMON_GAIN_ACCESS]: 'ZÍSKAT PŘÍSTUP', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'TĚŽBA DAT: VÝROBNÍ SPECIFIKACE', + [d.DAEMON_BASIC_DATAMINE]: 'ZÁKLADNÍ TĚŽBA DAT', + [d.DAEMON_ADVANCED_DATAMINE]: 'POKROČILÁ TĚŽBA DAT', + [d.DAEMON_EXPERT_DATAMINE]: 'EXPERTNÍ TĚŽBA DAT', }; export const hun: DaemonDict = { @@ -315,6 +363,9 @@ export const hun: DaemonDict = { [d.DAEMON_NEUTRALIZE_MALWARE]: 'ROSSZINDULATÚ SZOFTVER HATÁSTALANÍTÁSA', [d.DAEMON_GAIN_ACCESS]: 'HOZZÁFÉRÉS MEGSZERZÉSE', [d.DAEMON_DATAMINE_CRAFTING_SPECS]: 'ADATBÁNYÁSZAT: TÁRGYKÉSZÍTÉSI ÁBRA', + [d.DAEMON_BASIC_DATAMINE]: 'ALAP ADATBÁNYÁSZAT', + [d.DAEMON_ADVANCED_DATAMINE]: 'HALADÓ ADATBÁNYÁSZAT', + [d.DAEMON_EXPERT_DATAMINE]: 'SZAKÉRTŐ ADATBÁNYÁSZAT', }; export const daemonsI18n: Record = { diff --git a/src/core/daemons.ts b/src/core/daemons.ts index 7efe50f5..d0cb8204 100644 --- a/src/core/daemons.ts +++ b/src/core/daemons.ts @@ -16,3 +16,9 @@ export const DAEMON_DATAMINE_COPY_MALWARE = 'DAEMON_DATAMINE_COPY_MALWARE'; export const DAEMON_NEUTRALIZE_MALWARE = 'DAEMON_NEUTRALIZE_MALWARE'; export const DAEMON_GAIN_ACCESS = 'DAEMON_GAIN_ACCESS'; export const DAEMON_DATAMINE_CRAFTING_SPECS = 'DAEMON_DATAMINE_CRAFTING_SPECS'; +// Patch 2.0 daemons. +export const DAEMON_BASIC_DATAMINE = 'DAEMON_BASIC_DATAMINE'; +export const DAEMON_ADVANCED_DATAMINE = 'DAEMON_ADVANCED_DATAMINE'; +export const DAEMON_EXPERT_DATAMINE = 'DAEMON_EXPERT_DATAMINE'; +// This daemon is only used inside breach protocol tutorial. +// export const DAEMON_EXTRACT_EURODOLLARS = 'DAEMON_EXTRACT_EURODOLLARS'; diff --git a/src/core/ocr/fragments/base.ts b/src/core/ocr/fragments/base.ts index 3ec3fd52..5ef55bbd 100644 --- a/src/core/ocr/fragments/base.ts +++ b/src/core/ocr/fragments/base.ts @@ -43,6 +43,7 @@ export type BreachProtocolFragmentResults = [ export interface BreachProtocolFragmentOptions extends FragmentOptions { recognizer?: BreachProtocolRecognizer; + patch: '1.x' | '2.x'; } export abstract class BreachProtocolFragment< diff --git a/src/core/ocr/fragments/types-fragment.ts b/src/core/ocr/fragments/types-fragment.ts index ea4abb0d..cb352695 100644 --- a/src/core/ocr/fragments/types-fragment.ts +++ b/src/core/ocr/fragments/types-fragment.ts @@ -1,10 +1,11 @@ import { Point, similarity, unique } from '@/common'; import { - DaemonId, DAEMONS_SIZE_MAX, DAEMONS_SIZE_MIN, + DaemonId, TypesRawData, } from '../../common'; +import { DAEMONS, LEGACY_DAEMONS } from '../../daemon-set'; import { DAEMON_DATAMINE_V1, DAEMON_DATAMINE_V2, @@ -92,9 +93,14 @@ export class BreachProtocolTypesFragment< BreachProtocolTypesFragment.daemonDictLang ) { const { lang } = this.options.recognizer; - const entries = Object.entries(daemonsI18n[lang]).map( - ([k, v]: [DaemonId, string]) => [v, k] as const - ); + const daemons = this.options.patch === '1.x' ? LEGACY_DAEMONS : DAEMONS; + const daemonDictEntries = Object.entries(daemonsI18n[lang]) as [ + DaemonId, + string + ][]; + const entries = daemonDictEntries + .filter(([daemonId]) => daemons.has(daemonId)) + .map(([daemonId, translation]) => [translation, daemonId] as const); BreachProtocolTypesFragment.daemonDict = Object.fromEntries(entries); BreachProtocolTypesFragment.daemonDictLang = lang; @@ -146,7 +152,10 @@ export class BreachProtocolTypesFragment< const keys = Object.keys(BreachProtocolTypesFragment.daemonDict); return lines.map((t) => { - if (BreachProtocolTypesFragment.edgeCases.has(t)) { + if ( + this.options.patch === '1.x' && + BreachProtocolTypesFragment.edgeCases.has(t) + ) { return BreachProtocolTypesFragment.edgeCases.get(t); } diff --git a/src/core/ocr/ocr.test.ts b/src/core/ocr/ocr.test.ts index 91a99adf..dfd13bcd 100644 --- a/src/core/ocr/ocr.test.ts +++ b/src/core/ocr/ocr.test.ts @@ -136,13 +136,16 @@ describe('raw data validation', () => { it('should pass it if data is valid', () => { const gridFragment = new BreachProtocolGridFragment(container, { recognizer, + patch: '1.x', }); const daemonsFragment = new BreachProtocolDaemonsFragment(container, { recognizer, extendedDaemonsAndTypesRecognitionRange: false, + patch: '1.x', }); const bufferSizeFragment = new BreachProtocolBufferSizeFragment(container, { extendedBufferSizeRecognitionRange: false, + patch: '1.x', }); expect(gridFragment.getStatus(grid)).toBe(valid); @@ -151,7 +154,10 @@ describe('raw data validation', () => { }); it('should throw an error if grid is invalid', () => { - const fragment = new BreachProtocolGridFragment(container, { recognizer }); + const fragment = new BreachProtocolGridFragment(container, { + recognizer, + patch: '1.x', + }); const invalidGrids = [ grid.map((s, i) => (i === 5 ? '57' : s)), grid.map((s, i) => (i === 9 ? 'test' : s)), @@ -169,6 +175,7 @@ describe('raw data validation', () => { const fragment = new BreachProtocolDaemonsFragment(container, { recognizer, extendedDaemonsAndTypesRecognitionRange: false, + patch: '1.x', }); const invalidDaemons = [ daemons.map(() => ['B7']), @@ -185,6 +192,7 @@ describe('raw data validation', () => { it('should throw an error if buffer size is invalid', () => { const fragment = new BreachProtocolBufferSizeFragment(container, { extendedBufferSizeRecognitionRange: false, + patch: '1.x', }); const invalidBufferSizes = [NaN, 3, 10, 2 * Math.PI] as BufferSize[]; @@ -246,11 +254,13 @@ async function recognizeRegistryEntry( settings?.extendedBufferSizeRecognitionRange ?? false; const trimStrategy = new BreachProtocolBufferSizeTrimFragment(container, { extendedBufferSizeRecognitionRange, + patch: '1.x', }); const recognizer = new WasmBreachProtocolRecognizer(null); return Promise.all([ breachProtocolOCR(container, recognizer, { + patch: '1.x', thresholdGridAuto: true, thresholdTypesAuto: true, thresholdDaemonsAuto: true, diff --git a/src/core/ocr/ocr.ts b/src/core/ocr/ocr.ts index 8b1ce272..5135d566 100644 --- a/src/core/ocr/ocr.ts +++ b/src/core/ocr/ocr.ts @@ -29,6 +29,7 @@ export interface BreachProtocolOCROptions extends FragmentOptions { skipTypesFragment?: boolean; useFixedBufferSize?: boolean; fixedBufferSize?: number; + patch: '1.x' | '2.x'; } type FragmentCtor = new ( @@ -84,6 +85,7 @@ class BreachProtocolFragmentFactory { filterRecognizerResults, extendedDaemonsAndTypesRecognitionRange, extendedBufferSizeRecognitionRange, + patch, } = options; return { @@ -91,6 +93,7 @@ class BreachProtocolFragmentFactory { extendedBufferSizeRecognitionRange, extendedDaemonsAndTypesRecognitionRange, filterRecognizerResults, + patch, }; } } diff --git a/src/core/solver/breach-protocol.spec.ts b/src/core/solver/breach-protocol.spec.ts index a913ff66..077cd5d7 100644 --- a/src/core/solver/breach-protocol.spec.ts +++ b/src/core/solver/breach-protocol.spec.ts @@ -32,7 +32,11 @@ const testData = (data as BreachProtocolRawData[]).concat( describe('Breach protocol solve', () => { const hierarchyProvider = new IndexHierarchyProvider(); - const options: BreachProtocolOptions = { strategy: 'bfs', hierarchyProvider }; + const options: BreachProtocolOptions = { + strategy: 'bfs', + hierarchyProvider, + patch: '1.x', + }; it('should resolve 3 base cases', () => { // prettier-ignore @@ -70,6 +74,7 @@ describe('Breach protocol solve', () => { const game = new BreachProtocol(rawData, { strategy, hierarchyProvider, + patch: '1.x', }); const result = game.solve(); diff --git a/src/core/solver/breach-protocol.ts b/src/core/solver/breach-protocol.ts index 454e40f1..8da3b9d6 100644 --- a/src/core/solver/breach-protocol.ts +++ b/src/core/solver/breach-protocol.ts @@ -55,7 +55,10 @@ export class BreachProtocolResult implements Serializable { const { bufferSize, daemons } = this.game.rawData; // BP will exit automatically when all of the buffer has been used. - const willExit = this.path.length === bufferSize; + const willExit = + this.game.options.patch === '1.x' + ? this.path.length === bufferSize + : false; // Get daemons that were not used in resolved sequence. // There is no point of finding shortest daemon, @@ -123,6 +126,7 @@ export interface BreachProtocolOptions extends Pick { strategy: BreachProtocolStrategy; hierarchyProvider: HierarchyProvider; + patch: '1.x' | '2.x'; } export type BreachProtocolStrategy = 'dfs' | 'bfs'; @@ -161,7 +165,7 @@ export class BreachProtocol { constructor( public readonly rawData: BreachProtocolRawData, - private readonly options: BreachProtocolOptions + public readonly options: BreachProtocolOptions ) {} solveForSequence(sequence: Sequence) { diff --git a/src/electron/common/common.ts b/src/electron/common/common.ts index eca56729..81be0f07 100644 --- a/src/electron/common/common.ts +++ b/src/electron/common/common.ts @@ -61,7 +61,7 @@ export const KEY_BINDS = [ 'keyBindAnalyze', ] as const; -export type BreachProtocolKeyBinds = typeof KEY_BINDS[number]; +export type BreachProtocolKeyBinds = (typeof KEY_BINDS)[number]; export type KeyBindsConfig = Record; export const COMMANDS = [ @@ -76,7 +76,7 @@ export const COMMANDS = [ 'worker:analyze', ] as const; -export type BreachProtocolCommands = typeof COMMANDS[number]; +export type BreachProtocolCommands = (typeof COMMANDS)[number]; export interface AppSettings extends RobotSettings, @@ -86,6 +86,7 @@ export interface AppSettings ResolverSettings, Required, Exclude { + patch: '1.x' | '2.x'; historySize: number; preserveSourceOnSuccess: boolean; checkForUpdates: boolean; diff --git a/src/electron/common/options.ts b/src/electron/common/options.ts index 428c0e7e..6f98cebf 100644 --- a/src/electron/common/options.ts +++ b/src/electron/common/options.ts @@ -8,12 +8,15 @@ import { VK_ESCAPE, } from '@/common'; import { + DAEMON_ADVANCED_DATAMINE, + DAEMON_BASIC_DATAMINE, DAEMON_CAMERA_SHUTDOWN, DAEMON_DATAMINE_COPY_MALWARE, DAEMON_DATAMINE_CRAFTING_SPECS, DAEMON_DATAMINE_V1, DAEMON_DATAMINE_V2, DAEMON_DATAMINE_V3, + DAEMON_EXPERT_DATAMINE, DAEMON_FRIENDLY_TURRETS, DAEMON_GAIN_ACCESS, DAEMON_ICEPICK, @@ -331,6 +334,9 @@ export const options: BreachProtocolOption[] = [ DAEMON_DATAMINE_V3, DAEMON_DATAMINE_V2, DAEMON_DATAMINE_V1, + DAEMON_EXPERT_DATAMINE, + DAEMON_ADVANCED_DATAMINE, + DAEMON_BASIC_DATAMINE, ], }, { @@ -339,6 +345,11 @@ export const options: BreachProtocolOption[] = [ 'Determines if sequences should be emitted immediately, or should they be grouped by permutation of daemons. Grouped sequences are sorted by raw path length.', defaultValue: false, }, + { + id: 'patch', + description: 'Installed patch of Cyberpunk 2077.', + defaultValue: '2.x', + }, ]; function optionsToObject(cb: (option: BreachProtocolOption) => T) { diff --git a/src/electron/main/store/store.ts b/src/electron/main/store/store.ts index c991a260..b0986c85 100644 --- a/src/electron/main/store/store.ts +++ b/src/electron/main/store/store.ts @@ -19,6 +19,11 @@ import ElectronStore from 'electron-store'; import { ensureDirSync, remove, removeSync } from 'fs-extra'; import { join } from 'path'; import { appReducer } from './reducer'; +import { + DAEMON_ADVANCED_DATAMINE, + DAEMON_BASIC_DATAMINE, + DAEMON_EXPERT_DATAMINE, +} from '@/core'; type Middleware = (action: Action) => void; @@ -82,6 +87,26 @@ export class Store { }); } }, + '>=2.9.0': (store) => { + const daemons = store.get('daemonPriority'); + const newDaemons = []; + + if (!daemons.includes(DAEMON_EXPERT_DATAMINE)) { + newDaemons.push(DAEMON_EXPERT_DATAMINE); + } + + if (!daemons.includes(DAEMON_ADVANCED_DATAMINE)) { + newDaemons.push(DAEMON_ADVANCED_DATAMINE); + } + + if (!daemons.includes(DAEMON_BASIC_DATAMINE)) { + newDaemons.push(DAEMON_BASIC_DATAMINE); + } + + if (newDaemons.length > 0) { + store.set('daemonPriority', [...daemons, ...newDaemons]); + } + }, }, }); diff --git a/src/electron/renderer/common.ts b/src/electron/renderer/common.ts index 1b95cb57..d5cc9ae5 100644 --- a/src/electron/renderer/common.ts +++ b/src/electron/renderer/common.ts @@ -90,6 +90,14 @@ export function getDisplayName(display: ScreenshotDisplayOutput) { return `${display.name} (${display.width}x${display.height})`; } +export function getPatchName(patch: '1.x' | '2.x') { + if (patch === '1.x') { + return '1.63'; + } + + return '2.0'; +} + export function createRootElement(id: string) { const root = document.createElement('div'); root.id = id; diff --git a/src/electron/renderer/components/DaemonPriority.tsx b/src/electron/renderer/components/DaemonPriority.tsx index ff2daa7e..790c7755 100644 --- a/src/electron/renderer/components/DaemonPriority.tsx +++ b/src/electron/renderer/components/DaemonPriority.tsx @@ -15,6 +15,9 @@ import { DAEMON_TURRET_SHUTDOWN, DAEMON_WEAPONS_JAMMER, eng, + DAEMON_BASIC_DATAMINE, + DAEMON_ADVANCED_DATAMINE, + DAEMON_EXPERT_DATAMINE, } from '@/core'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { flushSync } from 'react-dom'; @@ -22,7 +25,9 @@ import styled from 'styled-components'; import { FlatButton } from './Buttons'; import { Dialog } from './Dialog'; import { Col, Row, Spacer } from './Flex'; -import { useField } from './Form'; +import { useField, useForm } from './Form'; +import { AppSettings } from '@/electron/common'; +import { DAEMONS, LEGACY_DAEMONS } from '@/core/daemon-set'; const Description = styled.p` color: var(--primary); @@ -34,10 +39,22 @@ const Description = styled.p` const Daemon = styled(Row).attrs({ role: 'button', -})<{ selected: boolean }>` - color: ${(p) => (p.selected ? 'var(--background)' : 'var(--accent)')}; - background: ${(p) => (p.selected ? 'var(--accent)' : 'var(--background)')}; - border: 1px solid ${(p) => (p.selected ? 'var(--accent)' : 'var(--primary)')}; +})<{ selected: boolean; disabled: boolean }>` + color: ${(p) => + p.disabled ? 'gray' : p.selected ? 'var(--background)' : 'var(--accent)'}; + background: ${(p) => + p.disabled + ? 'darkgray' + : p.selected + ? 'var(--accent)' + : 'var(--background)'}; + border: 1px solid + ${(p) => + p.disabled + ? 'darkgray' + : p.selected + ? 'var(--accent)' + : 'var(--primary)'}; display: flex; align-items: center; justify-content: center; @@ -65,6 +82,9 @@ const commonDaemons: ReadonlySet = new Set([ DAEMON_DATAMINE_V2, DAEMON_DATAMINE_V3, DAEMON_ICEPICK, + DAEMON_BASIC_DATAMINE, + DAEMON_ADVANCED_DATAMINE, + DAEMON_EXPERT_DATAMINE, ]); const perkDaemons: ReadonlySet = new Set([ @@ -119,10 +139,15 @@ interface DaemonPriorityProps { } export const DaemonPriority = ({ isOpen, onClose }: DaemonPriorityProps) => { + const { values } = useForm(); const [selected, setSelected] = useState(null); const { value, setValue } = useField(); const didMount = useRef(false); const [dirty, setDirty] = useState(false); + const daemonSet = useMemo( + () => (values.patch === '1.x' ? LEGACY_DAEMONS : DAEMONS), + [values.patch] + ); const defaultDaemons = useMemo(() => { return value.map((id) => { const type = getDaemonType(id); @@ -239,6 +264,7 @@ export const DaemonPriority = ({ isOpen, onClose }: DaemonPriorityProps) => { {daemons.map(({ id, type }) => ( setSelected((selected) => (selected === id ? null : id)) diff --git a/src/electron/renderer/components/GeneralSettings.tsx b/src/electron/renderer/components/GeneralSettings.tsx index 6433d921..45b4c928 100644 --- a/src/electron/renderer/components/GeneralSettings.tsx +++ b/src/electron/renderer/components/GeneralSettings.tsx @@ -1,9 +1,16 @@ import { RemoveLastNHistoryEntriesAction } from '@/electron/common'; -import { nativeDialog } from '../common'; +import { getPatchName, nativeDialog } from '../common'; import { Field, Label, OnBeforeValueChange } from './Form'; import { RangeSlider } from './RangeSlider'; import { Section } from './Section'; import { Switch } from './Switch'; +import { Select, SelectOption } from './Select'; + +const patches = ['1.x', '2.x'] as const; +const patchOptions: SelectOption[] = patches.map((value) => ({ + value, + name: getPatchName(value), +})); export const GeneralSettings = ({ historySize }: { historySize: number }) => { const onBeforeHistorySizeChange: OnBeforeValueChange = async ( @@ -30,6 +37,10 @@ export const GeneralSettings = ({ historySize }: { historySize: number }) => { return (
+ + + + diff --git a/src/electron/renderer/components/StatusBar.tsx b/src/electron/renderer/components/StatusBar.tsx index fab6ef13..90dac5c2 100644 --- a/src/electron/renderer/components/StatusBar.tsx +++ b/src/electron/renderer/components/StatusBar.tsx @@ -2,7 +2,7 @@ import { ActionTypes, UpdateStatus, WorkerStatus } from '@/electron/common'; import { ProgressInfo } from 'electron-updater'; import { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { getDisplayName, useIpcEvent } from '../common'; +import { getDisplayName, getPatchName, useIpcEvent } from '../common'; import { RouterExtContext } from '../router-ext'; import { StateContext } from '../state'; import { Spacer } from './Flex'; @@ -139,7 +139,7 @@ export const StatusBar = () => { displays, status, updateStatus, - settings: { activeDisplayId }, + settings: { activeDisplayId, patch }, } = useContext(StateContext); const { navigateToSetting } = useContext(RouterExtContext); const show = useSettingsChangeListener(); @@ -152,6 +152,9 @@ export const StatusBar = () => { return ( {VERSION} + navigateToSetting('patch')}> + patch: {getPatchName(patch)} + navigateToSetting('activeDisplayId')} > diff --git a/src/electron/worker/autosolver.ts b/src/electron/worker/autosolver.ts index dbcb1048..c0b6cb4a 100644 --- a/src/electron/worker/autosolver.ts +++ b/src/electron/worker/autosolver.ts @@ -102,6 +102,7 @@ export class BreachProtocolAutosolver { this.game = new BreachProtocol(rawData, { strategy: this.settings.strategy, hierarchyProvider: this.hierarchyProvider, + patch: this.settings.patch, }); this.progress.add(BreachProtocolSolveProgress.FragmentsValid); diff --git a/src/electron/worker/worker.ts b/src/electron/worker/worker.ts index 9284b254..0a1b94e1 100644 --- a/src/electron/worker/worker.ts +++ b/src/electron/worker/worker.ts @@ -377,6 +377,7 @@ export class BreachProtocolWorker { gameLang, extendedBufferSizeRecognitionRange, extendedDaemonsAndTypesRecognitionRange, + patch, } = this.settings; const container = await SharpImageContainer.create(instance, { downscaleSource, @@ -387,6 +388,7 @@ export class BreachProtocolWorker { filterRecognizerResults, extendedBufferSizeRecognitionRange, extendedDaemonsAndTypesRecognitionRange, + patch, }; this.fragments = {