From ee3eb68bf901c54bada013fcd2603351ba4bfbd6 Mon Sep 17 00:00:00 2001 From: Razzmatazz Date: Sun, 23 Jun 2024 16:59:17 -0500 Subject: [PATCH 1/4] support multiple game modes --- scripts/cache-api-data.mjs | 18 +-- src/components/price-graph/index.js | 4 +- src/data/game-modes.json | 4 + .../achievements/do-fetch-achievements.mjs | 9 +- src/features/achievements/index.js | 15 ++- src/features/barters/do-fetch-barters.mjs | 9 +- src/features/barters/index.js | 17 ++- src/features/bosses/do-fetch-bosses.mjs | 9 +- src/features/bosses/index.js | 15 ++- src/features/crafts/do-fetch-crafts.mjs | 9 +- src/features/crafts/index.js | 17 ++- src/features/hideout/do-fetch-hideout.mjs | 121 ++++++++++-------- src/features/hideout/index.js | 16 ++- src/features/items/do-fetch-items.mjs | 13 +- src/features/items/index.js | 15 ++- src/features/maps/do-fetch-maps.mjs | 9 +- src/features/maps/index.js | 15 ++- src/features/meta/do-fetch-meta.mjs | 3 +- src/features/meta/index.js | 16 ++- src/features/quests/do-fetch-quests.mjs | 9 +- src/features/quests/index.js | 15 ++- src/features/settings/settingsSlice.js | 10 ++ src/features/traders/do-fetch-traders.mjs | 116 +++++++++-------- src/features/traders/index.js | 15 ++- src/modules/api-query.mjs | 25 +++- src/modules/flea-market-fee.mjs | 11 ++ src/modules/placeholder-data.js | 4 +- src/pages/settings/index.css | 4 +- src/pages/settings/index.js | 20 +++ src/translations/en/translation.json | 4 +- 30 files changed, 355 insertions(+), 212 deletions(-) create mode 100644 src/data/game-modes.json diff --git a/scripts/cache-api-data.mjs b/scripts/cache-api-data.mjs index 77b15756c..f1efc357b 100644 --- a/scripts/cache-api-data.mjs +++ b/scripts/cache-api-data.mjs @@ -105,14 +105,14 @@ try { const apiPromises = []; apiPromises.push(Promise.all([ - doFetchBarters('en', true).then(barters => { + doFetchBarters({prebuild: true}).then(barters => { for (const barter of barters) { barter.cached = true; } fs.writeFileSync('./src/data/barters.json', JSON.stringify(barters)); return barters; }), - doFetchCrafts('en', true).then(crafts => { + doFetchCrafts({prebuild: true}).then(crafts => { for (const craft of crafts) { craft.cached = true; } @@ -120,7 +120,7 @@ try { return crafts; }) ]).then((bartersAndCrafts) => { - return doFetchItems('en', true).then(items => { + return doFetchItems({prebuild: true}).then(items => { const filteredItems = []; for (const bartersCrafts of bartersAndCrafts) { bartersCrafts.forEach(bc => { @@ -203,11 +203,11 @@ try { }); })); - apiPromises.push(doFetchHideout('en', true).then(hideout => { + apiPromises.push(doFetchHideout({prebuild: true}).then(hideout => { fs.writeFileSync('./src/data/hideout.json', JSON.stringify(hideout)); })); - apiPromises.push(doFetchTraders('en', true).then(traders => { + apiPromises.push(doFetchTraders({prebuild: true}).then(traders => { for (const trader of traders) { delete trader.resetTime; } @@ -231,7 +231,7 @@ try { }); })); - apiPromises.push(doFetchMaps('en', true).then(maps => { + apiPromises.push(doFetchMaps({prebuild: true}).then(maps => { fs.writeFileSync('./src/data/maps_cached.json', JSON.stringify(maps)); return getMapNames(langs).then(mapResults => { @@ -250,7 +250,7 @@ try { }); })); - apiPromises.push(doFetchBosses('en', true).then(bosses => { + apiPromises.push(doFetchBosses({prebuild: true}).then(bosses => { fs.writeFileSync('./src/data/bosses.json', JSON.stringify(bosses)); return getBossNames(langs).then(bossResults => { @@ -268,11 +268,11 @@ try { }); })); - apiPromises.push(doFetchMeta('en', true).then(meta => { + apiPromises.push(doFetchMeta({prebuild: true}).then(meta => { fs.writeFileSync('./src/data/meta.json', JSON.stringify(meta)); })); - apiPromises.push(doFetchQuests('en', true).then(quests => { + apiPromises.push(doFetchQuests({prebuild: true}).then(quests => { const groupedQuestsDic = quests.reduce((acc, item) => { if (!acc[item.trader.normalizedName]) { acc[item.trader.normalizedName] = []; diff --git a/src/components/price-graph/index.js b/src/components/price-graph/index.js index d9ecf3b4f..c5f0912f3 100644 --- a/src/components/price-graph/index.js +++ b/src/components/price-graph/index.js @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; import { VictoryChart, VictoryLine, @@ -27,10 +28,11 @@ function PriceGraph({ item, itemId }) { const [filterRange, setFilterRange] = useState([0,0]); const { t } = useTranslation(); + const gameMode = useSelector((state) => state.settings.gameMode); const { status, data } = useQuery( `historical-price-${itemId}`, `{ - historicalItemPrices(id:"${itemId}"){ + historicalItemPrices(id:"${itemId}", gameMode: ${gameMode}){ price priceMin timestamp diff --git a/src/data/game-modes.json b/src/data/game-modes.json new file mode 100644 index 000000000..dd8ce18f1 --- /dev/null +++ b/src/data/game-modes.json @@ -0,0 +1,4 @@ +[ + "regular", + "pve" +] \ No newline at end of file diff --git a/src/features/achievements/do-fetch-achievements.mjs b/src/features/achievements/do-fetch-achievements.mjs index 4e8cbe875..c67423bd2 100644 --- a/src/features/achievements/do-fetch-achievements.mjs +++ b/src/features/achievements/do-fetch-achievements.mjs @@ -5,9 +5,10 @@ class AchievementsQuery extends APIQuery { super('achievements'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevAchievements { - achievements(lang: ${language}) { + achievements(lang: ${language}, gameMode: ${gameMode}) { id name description @@ -52,8 +53,8 @@ class AchievementsQuery extends APIQuery { const achievementsQuery = new AchievementsQuery(); -const doFetchAchievements = async (language, prebuild = false) => { - return achievementsQuery.run(language, prebuild); +const doFetchAchievements = async (options) => { + return achievementsQuery.run(options); }; export default doFetchAchievements; diff --git a/src/features/achievements/index.js b/src/features/achievements/index.js index 4f1640005..a670603da 100644 --- a/src/features/achievements/index.js +++ b/src/features/achievements/index.js @@ -12,9 +12,11 @@ const initialState = { error: null, }; -export const fetchAchievements = createAsyncThunk('achievements/fetchAchievements', () => - doFetchAchievements(langCode()), -); +export const fetchAchievements = createAsyncThunk('achievements/fetchAchievements', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchAchievements({language: langCode(), gameMode}); +}); const achievementsSlice = createSlice({ name: 'achievements', initialState, @@ -41,6 +43,7 @@ const achievementsSlice = createSlice({ export const achievementsReducer = achievementsSlice.reducer; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -52,10 +55,12 @@ export default function useAchievementsData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.achievements); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchAchievements()); clearRefreshInterval(); } @@ -67,7 +72,7 @@ export default function useAchievementsData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/barters/do-fetch-barters.mjs b/src/features/barters/do-fetch-barters.mjs index 1f4d2626c..71e222a2d 100644 --- a/src/features/barters/do-fetch-barters.mjs +++ b/src/features/barters/do-fetch-barters.mjs @@ -5,9 +5,10 @@ class BartersQuery extends APIQuery { super('barters'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevBarters { - barters(lang: ${language}) { + barters(lang: ${language}, gameMode: ${gameMode}) { rewardItems { item { id @@ -80,8 +81,8 @@ class BartersQuery extends APIQuery { const bartersQuery = new BartersQuery(); -const doFetchBarters = async (language, prebuild = false) => { - return bartersQuery.run(language, prebuild); +const doFetchBarters = async (options) => { + return bartersQuery.run(options); }; export default doFetchBarters; diff --git a/src/features/barters/index.js b/src/features/barters/index.js index 285ec2881..3e5efd255 100644 --- a/src/features/barters/index.js +++ b/src/features/barters/index.js @@ -16,9 +16,11 @@ const initialState = { error: null, }; -export const fetchBarters = createAsyncThunk('barters/fetchBarters', () => - doFetchBarters(langCode()) -); +export const fetchBarters = createAsyncThunk('barters/fetchBarters', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchBarters({language: langCode(), gameMode}); +}); const bartersSlice = createSlice({ name: 'barters', @@ -146,7 +148,7 @@ export const selectAllBarters = createSelector([selectBarters, selectQuests, sel }).filter(barter => barter.rewardItems.length > 0 && barter.requiredItems.length > 0); }); -let fetchedData = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -158,12 +160,13 @@ export default function useBartersData() { const dispatch = useDispatch(); const { status, error } = useSelector((state) => state.barters); const data = useSelector(selectAllBarters); + const gameMode = useSelector((state) => state.settings.gameMode); useItemsData(); useQuestsData(); useEffect(() => { - if (!fetchedData) { - fetchedData = true; + if (fetchedGameMode !== gameMode) { + fetchedGameMode = true; dispatch(fetchBarters()); clearRefreshInterval(); } @@ -175,7 +178,7 @@ export default function useBartersData() { return () => { clearRefreshInterval(); }; - }, [dispatch]); + }, [dispatch, gameMode]); return { data, status, error }; }; diff --git a/src/features/bosses/do-fetch-bosses.mjs b/src/features/bosses/do-fetch-bosses.mjs index 9033eb52d..92a0ec40b 100644 --- a/src/features/bosses/do-fetch-bosses.mjs +++ b/src/features/bosses/do-fetch-bosses.mjs @@ -5,9 +5,10 @@ class BossesQuery extends APIQuery { super('bosses'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevBosses { - bosses(lang: ${language}) { + bosses(lang: ${language}, gameMode: ${gameMode}) { name normalizedName imagePortraitLink @@ -69,8 +70,8 @@ class BossesQuery extends APIQuery { const bossesQuery = new BossesQuery(); -const doFetchBosses = async (language = 'en', prebuild = false) => { - return bossesQuery.run(language, prebuild); +const doFetchBosses = async (options) => { + return bossesQuery.run(options); }; export default doFetchBosses; diff --git a/src/features/bosses/index.js b/src/features/bosses/index.js index 5fec51bc4..0ff3b18ba 100644 --- a/src/features/bosses/index.js +++ b/src/features/bosses/index.js @@ -15,9 +15,11 @@ const initialState = { error: null, }; -export const fetchBosses = createAsyncThunk('bosses/fetchBosses', () => - doFetchBosses(langCode()), -); +export const fetchBosses = createAsyncThunk('bosses/fetchBosses', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchBosses({language: langCode(), gameMode}); +}); const bossesSlice = createSlice({ name: 'bosses', initialState, @@ -90,6 +92,7 @@ export const selectAllBosses = createSelector([selectBosses, selectMaps], (bosse }); let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -103,10 +106,12 @@ export default function useBossesData() { const data = useSelector(selectAllBosses); useMapsData(); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchBosses()); clearRefreshInterval(); } @@ -118,7 +123,7 @@ export default function useBossesData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/crafts/do-fetch-crafts.mjs b/src/features/crafts/do-fetch-crafts.mjs index 2116223af..5fc5afdaf 100644 --- a/src/features/crafts/do-fetch-crafts.mjs +++ b/src/features/crafts/do-fetch-crafts.mjs @@ -5,9 +5,10 @@ class CraftsQuery extends APIQuery { super('crafts'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevCrafts { - crafts(lang: ${language}) { + crafts(lang: ${language}, gameMode: ${gameMode}) { station { id normalizedName @@ -78,6 +79,6 @@ class CraftsQuery extends APIQuery { const craftsQuery = new CraftsQuery(); -export default async function doFetchCrafts(language, prebuild = false) { - return craftsQuery.run(language, prebuild); +export default async function doFetchCrafts(options) { + return craftsQuery.run(options); }; diff --git a/src/features/crafts/index.js b/src/features/crafts/index.js index bed2fb261..920cbd163 100644 --- a/src/features/crafts/index.js +++ b/src/features/crafts/index.js @@ -16,9 +16,11 @@ const initialState = { error: null, }; -export const fetchCrafts = createAsyncThunk('crafts/fetchCrafts', async () => - doFetchCrafts(langCode()), -); +export const fetchCrafts = createAsyncThunk('crafts/fetchCrafts', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchCrafts({language: langCode(), gameMode}); +}); const craftsSlice = createSlice({ name: 'crafts', @@ -158,7 +160,7 @@ export const selectAllCrafts = createSelector([selectCrafts, selectQuests, selec }).filter(craft => craft.rewardItems.length > 0 && craft.requiredItems.length > 0); }); -let fetchedData = false; +let fetchedGamMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -170,12 +172,13 @@ export default function useCraftsData() { const dispatch = useDispatch(); const { status, error } = useSelector((state) => state.crafts); const data = useSelector(selectAllCrafts); + const gameMode = useSelector((state) => state.settings.gameMode); useItemsData(); useQuestsData(); useEffect(() => { - if (!fetchedData) { - fetchedData = true; + if (fetchedGamMode !== gameMode) { + fetchedGamMode = gameMode; dispatch(fetchCrafts()); clearRefreshInterval(); } @@ -187,7 +190,7 @@ export default function useCraftsData() { return () => { clearRefreshInterval(); }; - }, [dispatch]); + }, [dispatch, gameMode]); return { data, status, error }; }; diff --git a/src/features/hideout/do-fetch-hideout.mjs b/src/features/hideout/do-fetch-hideout.mjs index 43c06613a..ca16d28a1 100644 --- a/src/features/hideout/do-fetch-hideout.mjs +++ b/src/features/hideout/do-fetch-hideout.mjs @@ -1,72 +1,85 @@ -import graphqlRequest from '../../modules/graphql-request.mjs'; +import APIQuery from '../../modules/api-query.mjs'; -const doFetchHideout = async (language, prebuild = false) => { - const query = `query TarkovDevHideout { - hideoutStations(lang: ${language}) { - id - name - normalizedName - imageLink - levels { +class HideoutQuery extends APIQuery { + constructor() { + super('hideout'); + } + + async query(options) { + const { language, gameMode, prebuild} = options; + const query = `query TarkovDevHideout { + hideoutStations(lang: ${language}, gameMode: ${gameMode}) { id - level - itemRequirements { - quantity - item { - name - id - iconLink + name + normalizedName + imageLink + levels { + id + level + itemRequirements { + quantity + item { + name + id + iconLink + } + } + stationLevelRequirements { + station { + id + normalizedName + } + level } - } - stationLevelRequirements { - station { - id - normalizedName + traderRequirements { + trader { + id + normalizedName + } + level } - level } - traderRequirements { - trader { - id - normalizedName - } - level + crafts { + id } } - crafts { - id - } - } - }`; + }`; - const queryData = await graphqlRequest(query); + const queryData = await this.graphqlRequest(query); - if (queryData.errors) { - if (queryData.data) { - for (const error of queryData.errors) { - let badItem = false; - if (error.path) { - badItem = queryData.data; - for (let i = 0; i < 2; i++) { - badItem = badItem[error.path[i]]; + if (queryData.errors) { + if (queryData.data) { + for (const error of queryData.errors) { + let badItem = false; + if (error.path) { + badItem = queryData.data; + for (let i = 0; i < 2; i++) { + badItem = badItem[error.path[i]]; + } + } + console.log(`Error in hideoutStations API query: ${error.message}`); + if (badItem) { + console.log(badItem) } - } - console.log(`Error in hideoutStations API query: ${error.message}`); - if (badItem) { - console.log(badItem) } } + // only throw error if this is for prebuild or data wasn't returned + if ( + prebuild || !queryData.data || + !queryData.data.hideoutStations || !queryData.data.hideoutStations.length + ) { + return Promise.reject(new Error(queryData.errors[0].message)); + } } - // only throw error if this is for prebuild or data wasn't returned - if ( - prebuild || !queryData.data || - !queryData.data.hideoutStations || !queryData.data.hideoutStations.length - ) { - return Promise.reject(new Error(queryData.errors[0].message)); - } + + return queryData.data.hideoutStations; } +} + +const hideoutQuery = new HideoutQuery(); - return queryData.data.hideoutStations; +const doFetchHideout = async (options) => { + return hideoutQuery.run(options); }; export default doFetchHideout; diff --git a/src/features/hideout/index.js b/src/features/hideout/index.js index 61679ab85..c5e502d09 100644 --- a/src/features/hideout/index.js +++ b/src/features/hideout/index.js @@ -14,10 +14,11 @@ const initialState = { error: null, }; -export const fetchHideout = createAsyncThunk( - 'hideout/fetchHideout', - async () => doFetchHideout(langCode()) -); +export const fetchHideout = createAsyncThunk('hideout/fetchHideout', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchHideout({language: langCode(), gameMode}); +}); const hideoutSlice = createSlice({ name: 'hideout', @@ -47,6 +48,7 @@ export const hideoutReducer = hideoutSlice.reducer; export const selectAllHideoutModules = (state) => state.hideout.data; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -58,10 +60,12 @@ export default function useHideoutData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.hideout); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchHideout()); clearRefreshInterval(); } @@ -73,7 +77,7 @@ export default function useHideoutData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/items/do-fetch-items.mjs b/src/features/items/do-fetch-items.mjs index 666df286c..4fd17e646 100644 --- a/src/features/items/do-fetch-items.mjs +++ b/src/features/items/do-fetch-items.mjs @@ -8,11 +8,12 @@ class ItemsQuery extends APIQuery { super('items'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const itemLimit = 2000; const QueryBody = offset => { return `query TarkovDevItems { - items(lang: ${language}, limit: ${itemLimit}, offset: ${offset}) { + items(lang: ${language}, gameMode: ${gameMode}, limit: ${itemLimit}, offset: ${offset}) { id bsgCategoryId categories { @@ -350,7 +351,7 @@ class ItemsQuery extends APIQuery { }; //console.time('items query'); const [itemData, miscData, itemGrids] = await Promise.all([ - new Promise(async resolve => { + new Promise(async (resolve, reject) => { let offset = 0; const retrievedItems = { data: { @@ -359,7 +360,7 @@ class ItemsQuery extends APIQuery { errors: [], }; while (true) { - const itemBatch = await this.graphqlRequest(QueryBody(offset)); + const itemBatch = await this.graphqlRequest(QueryBody(offset)).catch(reject); if (itemBatch.errors) { retrievedItems.errors.concat(itemBatch.errors); } @@ -661,8 +662,8 @@ class ItemsQuery extends APIQuery { const itemsQuery = new ItemsQuery(); -const doFetchItems = async (language, prebuild = false) => { - return itemsQuery.run(language, prebuild); +const doFetchItems = async (options) => { + return itemsQuery.run(options); }; export default doFetchItems; diff --git a/src/features/items/index.js b/src/features/items/index.js index ef1131bdf..580df87d6 100644 --- a/src/features/items/index.js +++ b/src/features/items/index.js @@ -13,9 +13,11 @@ const initialState = { error: null, }; -export const fetchItems = createAsyncThunk('items/fetchItems', () => - doFetchItems(langCode()) -); +export const fetchItems = createAsyncThunk('items/fetchItems', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchItems({language: langCode(), gameMode}); +}); const itemsSlice = createSlice({ name: 'items', initialState, @@ -53,6 +55,7 @@ export const itemsReducer = itemsSlice.reducer; export const selectAllItems = (state) => state.items.data; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -64,10 +67,12 @@ export default function useItemsData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.items); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchItems()); clearRefreshInterval(); } @@ -79,7 +84,7 @@ export default function useItemsData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/maps/do-fetch-maps.mjs b/src/features/maps/do-fetch-maps.mjs index 0bd28e064..05b08fd30 100644 --- a/src/features/maps/do-fetch-maps.mjs +++ b/src/features/maps/do-fetch-maps.mjs @@ -5,9 +5,10 @@ class MapsQuery extends APIQuery { super('maps'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevMaps { - maps(lang: ${language}) { + maps(lang: ${language}, gameMode: ${gameMode}) { id tarkovDataId name @@ -195,8 +196,8 @@ class MapsQuery extends APIQuery { const mapsQuery = new MapsQuery(); -const doFetchMaps = async (language, prebuild = false) => { - return mapsQuery.run(language, prebuild); +const doFetchMaps = async (options) => { + return mapsQuery.run(options); }; export default doFetchMaps; diff --git a/src/features/maps/index.js b/src/features/maps/index.js index 5c856377f..ff24c8060 100644 --- a/src/features/maps/index.js +++ b/src/features/maps/index.js @@ -29,9 +29,11 @@ const initialState = { error: null, }; -export const fetchMaps = createAsyncThunk('maps/fetchMaps', () => - doFetchMaps(langCode()), -); +export const fetchMaps = createAsyncThunk('maps/fetchMaps', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchMaps({language: langCode(), gameMode}); +}); const mapsSlice = createSlice({ name: 'maps', initialState, @@ -60,6 +62,7 @@ export const mapsReducer = mapsSlice.reducer; export const selectMaps = (state) => state.maps.data; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -71,10 +74,12 @@ export default function useMapsData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.maps); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchMaps()); clearRefreshInterval(); } @@ -86,7 +91,7 @@ export default function useMapsData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/meta/do-fetch-meta.mjs b/src/features/meta/do-fetch-meta.mjs index 90eed002a..7d6e6a6b1 100644 --- a/src/features/meta/do-fetch-meta.mjs +++ b/src/features/meta/do-fetch-meta.mjs @@ -5,7 +5,8 @@ class MetaQuery extends APIQuery { super('meta'); } - async query(language, prebuild = false) { + async query(options) { + const { language, prebuild } = options; const query = `query TarkovDevMeta { fleaMarket(lang: ${language}) { name diff --git a/src/features/meta/index.js b/src/features/meta/index.js index 0950e327e..c3be68764 100644 --- a/src/features/meta/index.js +++ b/src/features/meta/index.js @@ -13,9 +13,12 @@ const initialState = { error: null, }; -export const fetchMeta = createAsyncThunk('meta/fetchMeta', () => - doFetchMeta(langCode()), -); +export const fetchMeta = createAsyncThunk('meta/fetchMeta', () => (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchMeta({language: langCode(), gameMode}); +}); + const metaSlice = createSlice({ name: 'meta', initialState, @@ -44,6 +47,7 @@ export const metaReducer = metaSlice.reducer; export const selectMeta = (state) => state.meta.data; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -55,10 +59,12 @@ export default function useMetaData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.meta); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchMeta()); clearRefreshInterval(); } @@ -70,7 +76,7 @@ export default function useMetaData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/quests/do-fetch-quests.mjs b/src/features/quests/do-fetch-quests.mjs index a42446921..4b2691281 100644 --- a/src/features/quests/do-fetch-quests.mjs +++ b/src/features/quests/do-fetch-quests.mjs @@ -5,9 +5,10 @@ class QuestsQuery extends APIQuery { super('quests'); } - async query(language, prebuild = false) { + async query(options) { + const { language, gameMode, prebuild} = options; const query = `query TarkovDevTasks { - tasks(lang: ${language}) { + tasks(lang: ${language}, gameMode: ${gameMode}) { id tarkovDataId name @@ -429,8 +430,8 @@ class QuestsQuery extends APIQuery { const questsQuery = new QuestsQuery(); -const doFetchQuests = async (language, prebuild = false) => { - return questsQuery.run(language, prebuild); +const doFetchQuests = async (options) => { + return questsQuery.run(options); }; export default doFetchQuests; diff --git a/src/features/quests/index.js b/src/features/quests/index.js index 15dbae296..226eb01b4 100644 --- a/src/features/quests/index.js +++ b/src/features/quests/index.js @@ -13,9 +13,11 @@ const initialState = { error: null, }; -export const fetchQuests = createAsyncThunk('quests/fetchQuests', () => - doFetchQuests(langCode()), -); +export const fetchQuests = createAsyncThunk('quests/fetchQuests', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchQuests({language: langCode(), gameMode}); +}); const questsSlice = createSlice({ name: 'quests', initialState, @@ -109,6 +111,7 @@ export const selectQuestsWithActive = createSelector([selectQuests, selectTrader }); let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -121,10 +124,12 @@ export default function useQuestsData() { const { status, error } = useSelector((state) => state.quests); const data = useSelector(selectQuestsWithActive); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchQuests()); clearRefreshInterval(); } @@ -136,7 +141,7 @@ export default function useQuestsData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/features/settings/settingsSlice.js b/src/features/settings/settingsSlice.js index 446985c43..eb39c632a 100644 --- a/src/features/settings/settingsSlice.js +++ b/src/features/settings/settingsSlice.js @@ -114,6 +114,7 @@ const settingsSlice = createSlice({ mechanic: localStorageReadJson('mechanic', 4), ragman: localStorageReadJson('ragman', 4), jaeger: localStorageReadJson('jaeger', 4), + ref: localStorageReadJson('ref', 4), 'bitcoin-farm': localStorageReadJson('bitcoin-farm', 3), 'booze-generator': localStorageReadJson('booze-generator', 1), 'christmas-tree': localStorageReadJson('christmas-tree', 1), @@ -134,6 +135,7 @@ const settingsSlice = createSlice({ minDogtagLevel: localStorageReadJson('minDogtagLevel', 1), hideDogtagBarters: localStorageReadJson('hideDogtagBarters', false), playerPosition: localStorageReadJson('playerPosition', null), + gameMode: localStorageReadJson('gameMode', 'regular'), }, reducers: { setTarkovTrackerAPIKey: (state, action) => { @@ -184,6 +186,13 @@ const settingsSlice = createSlice({ newPosition, ); }, + setGameMode: (state, action) => { + state.gameMode = action.payload; + localStorageWriteJson( + 'gameMode', + action.payload, + ); + }, }, extraReducers: (builder) => { builder.addCase(fetchTarkovTrackerProgress.pending, (state, action) => { @@ -266,6 +275,7 @@ export const { toggleHideRemoteControl, toggleHideDogtagBarters, setPlayerPosition, + setGameMode, } = settingsSlice.actions; export default settingsSlice.reducer; diff --git a/src/features/traders/do-fetch-traders.mjs b/src/features/traders/do-fetch-traders.mjs index e6bdc97f9..9aafb47e2 100644 --- a/src/features/traders/do-fetch-traders.mjs +++ b/src/features/traders/do-fetch-traders.mjs @@ -1,62 +1,78 @@ -import graphqlRequest from '../../modules/graphql-request.mjs'; +import APIQuery from '../../modules/api-query.mjs'; -export default async function doFetchTraders(language, prebuild = false) { - const query = `query TarkovDevTraders { - traders(lang: ${language}) { - id - name - description - normalizedName - imageLink - currency { +class TradersQuery extends APIQuery { + constructor() { + super('traders'); + } + + async query(options) { + const { language, gameMode, prebuild} = options; + const query = `query TarkovDevTraders { + traders(lang: ${language}, gameMode: ${gameMode}) { id name + description normalizedName + imageLink + currency { + id + name + normalizedName + } + resetTime + discount + levels { + id + level + requiredPlayerLevel + requiredReputation + requiredCommerce + payRate + insuranceRate + repairCostMultiplier + } + barters { + id + } } - resetTime - discount - levels { - id - level - requiredPlayerLevel - requiredReputation - requiredCommerce - payRate - insuranceRate - repairCostMultiplier - } - barters { - id - } - } - }`; - - const tradersData = await graphqlRequest(query); - - if (tradersData.errors) { - if (tradersData.data) { - for (const error of tradersData.errors) { - let badItem = false; - if (error.path) { - badItem = tradersData.data; - for (let i = 0; i < 2; i++) { - badItem = badItem[error.path[i]]; + }`; + + const tradersData = await this.graphqlRequest(query); + + if (tradersData.errors) { + if (tradersData.data) { + for (const error of tradersData.errors) { + let badItem = false; + if (error.path) { + badItem = tradersData.data; + for (let i = 0; i < 2; i++) { + badItem = badItem[error.path[i]]; + } + } + console.log(`Error in traders API query: ${error.message}`); + if (badItem) { + console.log(badItem) } - } - console.log(`Error in traders API query: ${error.message}`); - if (badItem) { - console.log(badItem) } } + // only throw error if this is for prebuild or data wasn't returned + if ( + prebuild || !tradersData.data || + !tradersData.data.traders || !tradersData.data.traders.length + ) { + return Promise.reject(new Error(tradersData.errors[0].message)); + } } - // only throw error if this is for prebuild or data wasn't returned - if ( - prebuild || !tradersData.data || - !tradersData.data.traders || !tradersData.data.traders.length - ) { - return Promise.reject(new Error(tradersData.errors[0].message)); - } + + return tradersData.data.traders; } + +} - return tradersData.data.traders; +const tradersQuery = new TradersQuery(); + +const doFetchTraders = async (options) => { + return tradersQuery.run(options); }; + +export default doFetchTraders; diff --git a/src/features/traders/index.js b/src/features/traders/index.js index 8209c01e9..4b6fe9442 100644 --- a/src/features/traders/index.js +++ b/src/features/traders/index.js @@ -14,9 +14,11 @@ const initialState = { error: null, }; -export const fetchTraders = createAsyncThunk('traders/fetchTraders', () => - doFetchTraders(langCode()), -); +export const fetchTraders = createAsyncThunk('traders/fetchTraders', (arg, { getState }) => { + const state = getState(); + const gameMode = state.settings.gameMode; + return doFetchTraders({language: langCode(), gameMode}); +}); const tradersSlice = createSlice({ name: 'traders', initialState, @@ -45,6 +47,7 @@ export const tradersReducer = tradersSlice.reducer; export const selectAllTraders = (state) => state.traders.data; let fetchedLang = false; +let fetchedGameMode = false; let refreshInterval = false; const clearRefreshInterval = () => { @@ -56,10 +59,12 @@ export default function useTradersData() { const dispatch = useDispatch(); const { data, status, error } = useSelector((state) => state.traders); const lang = useLangCode(); + const gameMode = useSelector((state) => state.settings.gameMode); useEffect(() => { - if (fetchedLang !== lang) { + if (fetchedLang !== lang || fetchedGameMode !== gameMode) { fetchedLang = lang; + fetchedGameMode = gameMode; dispatch(fetchTraders()); clearRefreshInterval(); } @@ -71,7 +76,7 @@ export default function useTradersData() { return () => { clearRefreshInterval(); }; - }, [dispatch, lang,]); + }, [dispatch, lang, gameMode]); return { data, status, error }; }; diff --git a/src/modules/api-query.mjs b/src/modules/api-query.mjs index 2249aa3fe..c388e65be 100644 --- a/src/modules/api-query.mjs +++ b/src/modules/api-query.mjs @@ -1,5 +1,11 @@ import graphqlRequest from './graphql-request.mjs'; +const defaultOptions = { + language: 'en', + gameMode: 'regular', + prebuild: false, +}; + class APIQuery { constructor(queryName) { this.name = queryName; @@ -14,23 +20,28 @@ class APIQuery { return Promise.reject('Not implemented'); } - async run(language = 'en', prebuild = false) { - if (this.pendingQuery[language]) { - return this.pendingQuery[language]; + async run(options = defaultOptions) { + options = { + ...defaultOptions, + ...options, + }; + const pendingKey = options.language+options.gameMode; + if (this.pendingQuery[pendingKey]) { + return this.pendingQuery[pendingKey]; } let resolvePending, rejectPending; - this.pendingQuery[language] = new Promise((resolve, reject) => { + this.pendingQuery[pendingKey] = new Promise((resolve, reject) => { resolvePending = resolve; rejectPending = reject; }); try { - const result = await this.query(language, prebuild); + const result = await this.query(options); resolvePending(result); - this.pendingQuery[language] = false; + this.pendingQuery[pendingKey] = false; return result; } catch (error) { rejectPending(error); - this.pendingQuery[language] = false; + this.pendingQuery[pendingKey] = false; return Promise.reject(error); } } diff --git a/src/modules/flea-market-fee.mjs b/src/modules/flea-market-fee.mjs index 1ea4fbedc..8670c63df 100644 --- a/src/modules/flea-market-fee.mjs +++ b/src/modules/flea-market-fee.mjs @@ -1,4 +1,7 @@ // https://escapefromtarkov.gamepedia.com/Trading#Flea_Market +import { useCallback } from 'react'; + +import useMetaData from '../features/meta/index.js'; const localStorageReadJson = (key, defaultValue) => { try { @@ -43,3 +46,11 @@ export default function fleaMarketFee(basePrice, sellPrice, count = 1, Ti = 0.05 V0 * Ti * Math.pow(4, P0) * Q + VR * Tr * Math.pow(4, PR) * Q, ) * IC; }; + +export function useFleaMarketFee() { + const { data: meta } = useMetaData(); + const feeFunction = useCallback((basePrice, sellPrice, count = 1) => { + return fleaMarketFee(basePrice, sellPrice, count, meta.FleaMarket.sellOfferFeeRate, meta.FleaMarket.sellRequirementFeeRate); + }, [meta]); + return feeFunction; +} diff --git a/src/modules/placeholder-data.js b/src/modules/placeholder-data.js index a69a389d0..8fb1d2c15 100644 --- a/src/modules/placeholder-data.js +++ b/src/modules/placeholder-data.js @@ -1,7 +1,7 @@ import cachedBarters from '../data/barters.json'; import cachedCrafts from '../data/crafts.json'; -import cachedHideout from '../data/hideout.json' +import cachedHideout from '../data/hideout.json'; import cachedItems from '../data/items.json'; import cachedItemsLocale from '../data/items_locale.json'; @@ -17,7 +17,7 @@ import cachedBossesLocale from '../data/bosses_locale.json'; import cachedTasks from '../data/quests.json'; import cachedTasksLocale from '../data/quests_locale.json'; -import cachedTraders from '../data/traders.json' +import cachedTraders from '../data/traders.json'; import cachedTradersLocale from '../data/traders_locale.json'; export function placeholderBarters(language = 'en') { diff --git a/src/pages/settings/index.css b/src/pages/settings/index.css index f2d6ddda3..4bb24a88c 100644 --- a/src/pages/settings/index.css +++ b/src/pages/settings/index.css @@ -25,13 +25,13 @@ width: 122px; } -.language-toggle-wrapper .select__control { +.language-toggle-wrapper .select__control, .game-mode .select__control { color: #1b1919; background-color: #2d2c2e; border-color: #9a8866; } -.language-toggle-wrapper .select__single-value { +.language-toggle-wrapper .select__single-value, .game-mode .select__single-value { color: #fff; } diff --git a/src/pages/settings/index.js b/src/pages/settings/index.js index bc3bbb054..26e53b8a3 100644 --- a/src/pages/settings/index.js +++ b/src/pages/settings/index.js @@ -20,12 +20,14 @@ import { toggleTarkovTracker, toggleHideRemoteControl, toggleHideDogtagBarters, + setGameMode, // selectCompletedQuests, } from '../../features/settings/settingsSlice.js'; import useHideoutData from '../../features/hideout/index.js'; import useTradersData from '../../features/traders/index.js'; import supportedLanguages from '../../data/supported-languages.json'; +import gameModes from '../../data/game-modes.json'; import { getWipeData } from '../wipe-length/index.js'; @@ -70,6 +72,7 @@ function Settings() { (state) => state.settings.useTarkovTracker, ); const hideDogtagBarters = useSelector((state) => state.settings.hideDogtagBarters); + const gameMode = useSelector((state) => state.settings.gameMode); const refs = { 'bitcoin-farm': useRef(null), @@ -175,6 +178,23 @@ function Settings() {

{t('General')}

+