From a5c0d0c25ac8bf90ca3ca93cc5d75dd5301f8e37 Mon Sep 17 00:00:00 2001 From: nrbdev Date: Mon, 10 Feb 2025 20:00:17 -0500 Subject: [PATCH 01/12] prettier --- scripts/update-translations.js | 6 +- static/webui/script.js | 56 +++++--- static/webui/translations/en.js | 238 ++++++++++++++++---------------- static/webui/translations/ru.js | 238 ++++++++++++++++---------------- 4 files changed, 276 insertions(+), 262 deletions(-) diff --git a/scripts/update-translations.js b/scripts/update-translations.js index c26deb7f..53e26677 100644 --- a/scripts/update-translations.js +++ b/scripts/update-translations.js @@ -1,7 +1,7 @@ // Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php // Converted via ChatGPT-4o -const fs = require('fs'); +const fs = require("fs"); function extractStrings(content) { const regex = /([a-zA-Z_]+): `([^`]*)`,/g; @@ -22,7 +22,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => { const content = fs.readFileSync(`../static/webui/translations/${file}`, "utf8"); const targetStrings = extractStrings(content); const contentLines = content.split("\n"); - + const fileHandle = fs.openSync(`../static/webui/translations/${file}`, "w"); fs.writeSync(fileHandle, contentLines[0] + "\n"); @@ -40,7 +40,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => { fs.writeSync(fileHandle, line + "\n"); } }); - + fs.closeSync(fileHandle); } }); diff --git a/static/webui/script.js b/static/webui/script.js index a7afa25d..4053f028 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -108,8 +108,10 @@ single.on("route_load", function (event) { function loc(tag) { return ((window.dict ?? {})[tag] ?? tag) - .split("|DISPLAYNAME|").join(document.querySelector(".displayname").textContent) - .split("|EMAIL|").join(localStorage.getItem("email")); + .split("|DISPLAYNAME|") + .join(document.querySelector(".displayname").textContent) + .split("|EMAIL|") + .join(localStorage.getItem("email")); } function updateLocElements() { @@ -129,7 +131,7 @@ function setActiveLanguage(lang) { const webui_lang = ["en", "ru"].indexOf(lang) == -1 ? "en" : lang; const script = document.createElement("script"); script.src = "/translations/" + webui_lang + ".js"; - script.onload = function() { + script.onload = function () { updateLocElements(); resolve(window.dict); }; @@ -141,7 +143,8 @@ setActiveLanguage(localStorage.getItem("lang") ?? "en"); function setLanguage(lang) { setActiveLanguage(lang); localStorage.setItem("lang", lang); - if (window.authz) { // Not in prelogin state? + if (window.authz) { + // Not in prelogin state? fetchItemList(); updateInventory(); } @@ -151,7 +154,7 @@ let uniqueLevelCaps = {}; function fetchItemList() { window.itemListPromise = new Promise(resolve => { const req = $.get("/custom/getItemLists?lang=" + window.lang); - req.done(async (data) => { + req.done(async data => { await dictPromise; window.archonCrystalUpgrades = data.archonCrystalUpgrades; @@ -173,9 +176,13 @@ function fetchItemList() { "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: loc("code_kitgun") }, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: loc("code_kitgun") }, "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: loc("code_zaw") }, - "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { name: loc("code_moteAmp") }, + "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { + name: loc("code_moteAmp") + }, "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": { name: loc("code_amp") }, - "/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { name: loc("code_sirocco") }, + "/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { + name: loc("code_sirocco") + }, "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: loc("code_kdrive") }, // Missing in data sources "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser": { name: loc("code_legendaryCore") }, @@ -227,13 +234,10 @@ function updateInventory() { window.didInitialInventoryUpdate = true; // Populate inventory route - [ - "RegularCredits", - "PremiumCredits", - "FusionPoints", - "PrimeTokens" - ].forEach(currency => { - document.getElementById(currency + "-owned").textContent = loc("currency_owned").split("|COUNT|").join(data[currency].toLocaleString()); + ["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens"].forEach(currency => { + document.getElementById(currency + "-owned").textContent = loc("currency_owned") + .split("|COUNT|") + .join(data[currency].toLocaleString()); }); [ @@ -366,11 +370,24 @@ function updateInventory() { const td = document.createElement("td"); td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat; td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name; - td.innerHTML += " ▲ " + fingerprint.buffs.length + ""; td.innerHTML += - " ▼ " + fingerprint.curses.length + ""; + " ▲ " + + fingerprint.buffs.length + + ""; td.innerHTML += - " ⟳ " + parseInt(fingerprint.rerolls) + ""; + " ▼ " + + fingerprint.curses.length + + ""; + td.innerHTML += + " ⟳ " + + parseInt(fingerprint.rerolls) + + ""; tr.appendChild(td); } { @@ -611,10 +628,7 @@ function addMissingEquipment(categories) { } }); }); - if ( - requests.length != 0 && - window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length)) - ) { + if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) { dispatchAddItemsRequestsBatch(requests); } } diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 7cc8e996..aac5bcb5 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -1,120 +1,120 @@ dict = { - general_inventoryUpdateNote: `Note: Changes made here will only be applied in-game when the game syncs the inventory. Visiting the navigation should be the easiest way to trigger that.`, - general_addButton: `Add`, - general_bulkActions: `Bulk Actions`, - code_nonValidAuthz: `Your credentials are no longer valid.`, - code_changeNameConfirm: `What would you like to change your account name to?`, - code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`, - code_archgun: `Archgun`, - code_melee: `Melee`, - code_pistol: `Pistol`, - code_rifle: `Rifle`, - code_shotgun: `Shotgun`, - code_kitgun: `Kitgun`, - code_zaw: `Zaw`, - code_moteAmp: `Mote Amp`, - code_amp: `Amp`, - code_sirocco: `Sirocco`, - code_kDrive: `K-Drive`, - code_legendaryCore: `Legendary Core`, - code_traumaticPeculiar: `Traumatic Peculiar`, - code_badItem: `(Imposter)`, - code_maxRank: `Max Rank`, - code_rename: `Rename`, - code_renamePrompt: `Enter new custom name:`, - code_remove: `Remove`, - code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, - code_noEquipmentToRankUp: `No equipment to rank up.`, - code_succAdded: `Successfully added.`, - code_buffsNumber: `Number of buffs`, - code_cursesNumber: `Number of curses`, - code_rerollsNumber: `Number of rerolls`, - code_viewStats: `View Stats`, - code_rank: `Rank`, - code_count: `Count`, - code_focusAllUnlocked: `All focus schools are already unlocked.`, - code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, - code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, - code_succImport: `Successfully imported.`, - login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, - login_emailLabel: `Email address`, - login_passwordLabel: `Password`, - login_loginButton: `Login`, - navbar_logout: `Logout`, - navbar_renameAccount: `Rename Account`, - navbar_deleteAccount: `Delete Account`, - navbar_inventory: `Inventory`, - navbar_mods: `Mods`, - navbar_cheats: `Cheats`, - navbar_import: `Import`, - inventory_addItems: `Add Items`, - inventory_suits: `Warframes`, - inventory_longGuns: `Primary Weapons`, - inventory_pistols: `Secondary Weapons`, - inventory_melee: `Melee Weapons`, - inventory_spaceSuits: `Archwings`, - inventory_spaceGuns: `Archwing Primary Weapons`, - inventory_spaceMelee: `Archwing Melee Weapons`, - inventory_mechSuits: `Necramechs`, - inventory_sentinels: `Sentinels`, - inventory_sentinelWeapons: `Sentinel Weapons`, - inventory_operatorAmps: `Amps`, - inventory_hoverboards: `K-Drives`, - inventory_bulkAddSuits: `Add Missing Warframes`, - inventory_bulkAddWeapons: `Add Missing Weapons`, - inventory_bulkAddSpaceSuits: `Add Missing Archwings`, - inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`, - inventory_bulkAddSentinels: `Add Missing Sentinels`, - inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`, - inventory_bulkRankUpSuits: `Max Rank All Warframes`, - inventory_bulkRankUpWeapons: `Max Rank All Weapons`, - inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, - inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, - inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, - inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, - currency_RegularCredits: `Credits`, - currency_PremiumCredits: `Platinum`, - currency_FusionPoints: `Endo`, - currency_PrimeTokens: `Regal Aya`, - currency_owned: `You have |COUNT|.`, - powersuit_archonShardsLabel: `Archon Shard Slots`, - powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades`, - mods_addRiven: `Add Riven`, - mods_fingerprint: `Fingerprint`, - mods_fingerprintHelp: `Need help with the fingerprint?`, - mods_rivens: `Rivens`, - mods_mods: `Mods`, - mods_bulkAddMods: `Add Missing Mods`, - cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add |DISPLAYNAME| to administratorNames in the config.json.`, - cheats_server: `Server`, - cheats_skipTutorial: `Skip Tutorial`, - cheats_skipAllDialogue: `Skip All Dialogue`, - cheats_unlockAllScans: `Unlock All Scans`, - cheats_unlockAllMissions: `Unlock All Missions`, - cheats_unlockAllQuests: `Unlock All Quests`, - cheats_completeAllQuests: `Complete All Quests`, - cheats_infiniteCredits: `Infinite Credits`, - cheats_infinitePlatinum: `Infinite Platinum`, - cheats_infiniteEndo: `Infinite Endo`, - cheats_infiniteRegalAya: `Infinite Regal Aya`, - cheats_unlockAllShipFeatures: `Unlock All Ship Features`, - cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, - cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, - cheats_unlockAllSkins: `Unlock All Skins`, - cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, - cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`, - cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`, - cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`, - cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`, - cheats_noDailyStandingLimits: `No Daily Standing Limits`, - cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, - cheats_saveSettings: `Save Settings`, - cheats_account: `Account`, - cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, - cheats_helminthUnlockAll: `Fully Level Up Helminth`, - cheats_changeSupportedSyndicate: `Supported syndicate`, - cheats_changeButton: `Change`, - cheats_none: `None`, - import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, - import_submit: `Submit`, -} \ No newline at end of file + general_inventoryUpdateNote: `Note: Changes made here will only be applied in-game when the game syncs the inventory. Visiting the navigation should be the easiest way to trigger that.`, + general_addButton: `Add`, + general_bulkActions: `Bulk Actions`, + code_nonValidAuthz: `Your credentials are no longer valid.`, + code_changeNameConfirm: `What would you like to change your account name to?`, + code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`, + code_archgun: `Archgun`, + code_melee: `Melee`, + code_pistol: `Pistol`, + code_rifle: `Rifle`, + code_shotgun: `Shotgun`, + code_kitgun: `Kitgun`, + code_zaw: `Zaw`, + code_moteAmp: `Mote Amp`, + code_amp: `Amp`, + code_sirocco: `Sirocco`, + code_kDrive: `K-Drive`, + code_legendaryCore: `Legendary Core`, + code_traumaticPeculiar: `Traumatic Peculiar`, + code_badItem: `(Imposter)`, + code_maxRank: `Max Rank`, + code_rename: `Rename`, + code_renamePrompt: `Enter new custom name:`, + code_remove: `Remove`, + code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, + code_noEquipmentToRankUp: `No equipment to rank up.`, + code_succAdded: `Successfully added.`, + code_buffsNumber: `Number of buffs`, + code_cursesNumber: `Number of curses`, + code_rerollsNumber: `Number of rerolls`, + code_viewStats: `View Stats`, + code_rank: `Rank`, + code_count: `Count`, + code_focusAllUnlocked: `All focus schools are already unlocked.`, + code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, + code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, + code_succImport: `Successfully imported.`, + login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, + login_emailLabel: `Email address`, + login_passwordLabel: `Password`, + login_loginButton: `Login`, + navbar_logout: `Logout`, + navbar_renameAccount: `Rename Account`, + navbar_deleteAccount: `Delete Account`, + navbar_inventory: `Inventory`, + navbar_mods: `Mods`, + navbar_cheats: `Cheats`, + navbar_import: `Import`, + inventory_addItems: `Add Items`, + inventory_suits: `Warframes`, + inventory_longGuns: `Primary Weapons`, + inventory_pistols: `Secondary Weapons`, + inventory_melee: `Melee Weapons`, + inventory_spaceSuits: `Archwings`, + inventory_spaceGuns: `Archwing Primary Weapons`, + inventory_spaceMelee: `Archwing Melee Weapons`, + inventory_mechSuits: `Necramechs`, + inventory_sentinels: `Sentinels`, + inventory_sentinelWeapons: `Sentinel Weapons`, + inventory_operatorAmps: `Amps`, + inventory_hoverboards: `K-Drives`, + inventory_bulkAddSuits: `Add Missing Warframes`, + inventory_bulkAddWeapons: `Add Missing Weapons`, + inventory_bulkAddSpaceSuits: `Add Missing Archwings`, + inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`, + inventory_bulkAddSentinels: `Add Missing Sentinels`, + inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`, + inventory_bulkRankUpSuits: `Max Rank All Warframes`, + inventory_bulkRankUpWeapons: `Max Rank All Weapons`, + inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, + inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, + inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, + inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, + currency_RegularCredits: `Credits`, + currency_PremiumCredits: `Platinum`, + currency_FusionPoints: `Endo`, + currency_PrimeTokens: `Regal Aya`, + currency_owned: `You have |COUNT|.`, + powersuit_archonShardsLabel: `Archon Shard Slots`, + powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades`, + mods_addRiven: `Add Riven`, + mods_fingerprint: `Fingerprint`, + mods_fingerprintHelp: `Need help with the fingerprint?`, + mods_rivens: `Rivens`, + mods_mods: `Mods`, + mods_bulkAddMods: `Add Missing Mods`, + cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add |DISPLAYNAME| to administratorNames in the config.json.`, + cheats_server: `Server`, + cheats_skipTutorial: `Skip Tutorial`, + cheats_skipAllDialogue: `Skip All Dialogue`, + cheats_unlockAllScans: `Unlock All Scans`, + cheats_unlockAllMissions: `Unlock All Missions`, + cheats_unlockAllQuests: `Unlock All Quests`, + cheats_completeAllQuests: `Complete All Quests`, + cheats_infiniteCredits: `Infinite Credits`, + cheats_infinitePlatinum: `Infinite Platinum`, + cheats_infiniteEndo: `Infinite Endo`, + cheats_infiniteRegalAya: `Infinite Regal Aya`, + cheats_unlockAllShipFeatures: `Unlock All Ship Features`, + cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, + cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, + cheats_unlockAllSkins: `Unlock All Skins`, + cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, + cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`, + cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`, + cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`, + cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`, + cheats_noDailyStandingLimits: `No Daily Standing Limits`, + cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, + cheats_saveSettings: `Save Settings`, + cheats_account: `Account`, + cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, + cheats_helminthUnlockAll: `Fully Level Up Helminth`, + cheats_changeSupportedSyndicate: `Supported syndicate`, + cheats_changeButton: `Change`, + cheats_none: `None`, + import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, + import_submit: `Submit` +}; diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 77a8a34a..9dda0439 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -1,121 +1,121 @@ // Russian translation by AMelonInsideLemon dict = { - general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`, - general_addButton: `Добавить`, - general_bulkActions: `Массовые действия`, - code_nonValidAuthz: `Ваши данные больше не действительны.`, - code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`, - code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`, - code_archgun: `Арч-Пушка`, - code_melee: `Ближний бой`, - code_pistol: `Пистолет`, - code_rifle: `Винтовка`, - code_shotgun: `Дробовик`, - code_kitgun: `Китган`, - code_zaw: `Зо`, - code_moteAmp: `Пылинка`, - code_amp: `Усилитель`, - code_sirocco: `Сирокко`, - code_kDrive: `К-Драйв`, - code_legendaryCore: `Легендарное ядро`, - code_traumaticPeculiar: `Травмирующая Странность`, - code_badItem: `(Самозванец)`, - code_maxRank: `Максимальный ранг`, - code_rename: `Переименовать`, - code_renamePrompt: `Введите новое имя:`, - code_remove: `Удалить`, - code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, - code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, - code_succAdded: `Успешно добавлено.`, - code_buffsNumber: `Количество усилений`, - code_cursesNumber: `Количество проклятий`, - code_rerollsNumber: `Количество циклов`, - code_viewStats: `Просмотр характеристики`, - code_rank: `Ранг`, - code_count: `Количество`, - code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, - code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, - code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, - code_succImport: `Успешно импортировано.`, - login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, - login_emailLabel: `Адрес электронной почты`, - login_passwordLabel: `Пароль`, - login_loginButton: `Войти`, - navbar_logout: `Выйти`, - navbar_renameAccount: `Переименовать аккаунт`, - navbar_deleteAccount: `Удалить аккаунт`, - navbar_inventory: `Инвентарь`, - navbar_mods: `Моды`, - navbar_cheats: `Читы`, - navbar_import: `Импорт`, - inventory_addItems: `Добавить предметы`, - inventory_suits: `Варфреймы`, - inventory_longGuns: `Основное оружие`, - inventory_pistols: `Вторичное оружие`, - inventory_melee: `Оружие ближнего боя`, - inventory_spaceSuits: `Арчвинги`, - inventory_spaceGuns: `Оружие арчвинга`, - inventory_spaceMelee: `Оружие ближнего боя арчвинга`, - inventory_mechSuits: `Некрамехи`, - inventory_sentinels: `Стражи`, - inventory_sentinelWeapons: `Оружие стражей`, - inventory_operatorAmps: `Усилители`, - inventory_hoverboards: `К-Драйвы`, - inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, - inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, - inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, - inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`, - inventory_bulkAddSentinels: `Добавить отсутствующих стражей`, - inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`, - inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`, - inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`, - inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`, - inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, - inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, - inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, - currency_RegularCredits: `Кредиты`, - currency_PremiumCredits: `Платина`, - currency_FusionPoints: `Эндо`, - currency_PrimeTokens: `Королевские Айя`, - currency_owned: `У тебя есть |COUNT|.`, - powersuit_archonShardsLabel: `Ячейки осколков архонта`, - powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`, - mods_addRiven: `Добавить Мод Разлома`, - mods_fingerprint: `Отпечаток`, - mods_fingerprintHelp: `Нужна помощь с отпечатком?`, - mods_rivens: `Моды Разлома`, - mods_mods: `Моды`, - mods_bulkAddMods: `Добавить отсутствующие моды`, - cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте \"|DISPLAYNAME|\" в administratorNames в config.json.`, - cheats_server: `Сервер`, - cheats_skipTutorial: `Пропустить обучение`, - cheats_skipAllDialogue: `Пропустить все диалоги`, - cheats_unlockAllScans: `Разблокировать все сканирования`, - cheats_unlockAllMissions: `Разблокировать все миссии`, - cheats_unlockAllQuests: `Разблокировать все квесты`, - cheats_completeAllQuests: `Завершить все квесты`, - cheats_infiniteCredits: `Бесконечные кредиты`, - cheats_infinitePlatinum: `Бесконечная платина`, - cheats_infiniteEndo: `Бесконечное эндо`, - cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, - cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, - cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, - cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, - cheats_unlockAllSkins: `Разблокировать все скины`, - cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, - cheats_universalPolarityEverywhere: `Универсальная полярность везде`, - cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`, - cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, - cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`, - cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, - cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, - cheats_saveSettings: `Сохранить настройки`, - cheats_account: `Аккаунт`, - cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, - cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, - cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, - cheats_changeButton: `Изменить`, - cheats_none: `Отсутствует`, - import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, - import_submit: `Отправить`, -} \ No newline at end of file + general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`, + general_addButton: `Добавить`, + general_bulkActions: `Массовые действия`, + code_nonValidAuthz: `Ваши данные больше не действительны.`, + code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`, + code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`, + code_archgun: `Арч-Пушка`, + code_melee: `Ближний бой`, + code_pistol: `Пистолет`, + code_rifle: `Винтовка`, + code_shotgun: `Дробовик`, + code_kitgun: `Китган`, + code_zaw: `Зо`, + code_moteAmp: `Пылинка`, + code_amp: `Усилитель`, + code_sirocco: `Сирокко`, + code_kDrive: `К-Драйв`, + code_legendaryCore: `Легендарное ядро`, + code_traumaticPeculiar: `Травмирующая Странность`, + code_badItem: `(Самозванец)`, + code_maxRank: `Максимальный ранг`, + code_rename: `Переименовать`, + code_renamePrompt: `Введите новое имя:`, + code_remove: `Удалить`, + code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, + code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, + code_succAdded: `Успешно добавлено.`, + code_buffsNumber: `Количество усилений`, + code_cursesNumber: `Количество проклятий`, + code_rerollsNumber: `Количество циклов`, + code_viewStats: `Просмотр характеристики`, + code_rank: `Ранг`, + code_count: `Количество`, + code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, + code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, + code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, + code_succImport: `Успешно импортировано.`, + login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, + login_emailLabel: `Адрес электронной почты`, + login_passwordLabel: `Пароль`, + login_loginButton: `Войти`, + navbar_logout: `Выйти`, + navbar_renameAccount: `Переименовать аккаунт`, + navbar_deleteAccount: `Удалить аккаунт`, + navbar_inventory: `Инвентарь`, + navbar_mods: `Моды`, + navbar_cheats: `Читы`, + navbar_import: `Импорт`, + inventory_addItems: `Добавить предметы`, + inventory_suits: `Варфреймы`, + inventory_longGuns: `Основное оружие`, + inventory_pistols: `Вторичное оружие`, + inventory_melee: `Оружие ближнего боя`, + inventory_spaceSuits: `Арчвинги`, + inventory_spaceGuns: `Оружие арчвинга`, + inventory_spaceMelee: `Оружие ближнего боя арчвинга`, + inventory_mechSuits: `Некрамехи`, + inventory_sentinels: `Стражи`, + inventory_sentinelWeapons: `Оружие стражей`, + inventory_operatorAmps: `Усилители`, + inventory_hoverboards: `К-Драйвы`, + inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, + inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, + inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, + inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`, + inventory_bulkAddSentinels: `Добавить отсутствующих стражей`, + inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`, + inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`, + inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`, + inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`, + inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, + inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, + inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, + currency_RegularCredits: `Кредиты`, + currency_PremiumCredits: `Платина`, + currency_FusionPoints: `Эндо`, + currency_PrimeTokens: `Королевские Айя`, + currency_owned: `У тебя есть |COUNT|.`, + powersuit_archonShardsLabel: `Ячейки осколков архонта`, + powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`, + mods_addRiven: `Добавить Мод Разлома`, + mods_fingerprint: `Отпечаток`, + mods_fingerprintHelp: `Нужна помощь с отпечатком?`, + mods_rivens: `Моды Разлома`, + mods_mods: `Моды`, + mods_bulkAddMods: `Добавить отсутствующие моды`, + cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте \"|DISPLAYNAME|\" в administratorNames в config.json.`, + cheats_server: `Сервер`, + cheats_skipTutorial: `Пропустить обучение`, + cheats_skipAllDialogue: `Пропустить все диалоги`, + cheats_unlockAllScans: `Разблокировать все сканирования`, + cheats_unlockAllMissions: `Разблокировать все миссии`, + cheats_unlockAllQuests: `Разблокировать все квесты`, + cheats_completeAllQuests: `Завершить все квесты`, + cheats_infiniteCredits: `Бесконечные кредиты`, + cheats_infinitePlatinum: `Бесконечная платина`, + cheats_infiniteEndo: `Бесконечное эндо`, + cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, + cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, + cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, + cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, + cheats_unlockAllSkins: `Разблокировать все скины`, + cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, + cheats_universalPolarityEverywhere: `Универсальная полярность везде`, + cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`, + cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, + cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`, + cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, + cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, + cheats_saveSettings: `Сохранить настройки`, + cheats_account: `Аккаунт`, + cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, + cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, + cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, + cheats_changeButton: `Изменить`, + cheats_none: `Отсутствует`, + import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, + import_submit: `Отправить` +}; -- 2.47.2 From 739111392fab750195ca774aac7e3c417b52661f Mon Sep 17 00:00:00 2001 From: nrbdev Date: Thu, 20 Feb 2025 00:15:52 -0500 Subject: [PATCH 02/12] add api route --- src/routes/api.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/api.ts b/src/routes/api.ts index d42a9f82..353ccc48 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -35,6 +35,7 @@ import { gildWeaponController } from "@/src/controllers/api/gildWeaponController import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController"; import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey"; +import { giveStartingGearController } from "../controllers/api/giveStartingGear"; import { guildTechController } from "../controllers/api/guildTechController"; import { hostSessionController } from "@/src/controllers/api/hostSessionController"; import { hubController } from "@/src/controllers/api/hubController"; @@ -145,6 +146,7 @@ apiRouter.post("/gildWeapon.php", gildWeaponController); apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController); apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController); apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController); +apiRouter.post("/giveStartingGear.php", giveStartingGearController); apiRouter.post("/guildTech.php", guildTechController); apiRouter.post("/hostSession.php", hostSessionController); apiRouter.post("/infestedFoundry.php", infestedFoundryController); -- 2.47.2 From 5957e15d4b82f90995cdd42af698da4cbd8b0d62 Mon Sep 17 00:00:00 2001 From: nrbdev Date: Thu, 20 Feb 2025 04:02:15 -0500 Subject: [PATCH 03/12] feat: giveStartingGear --- src/controllers/api/giveStartingGear.ts | 208 ++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 src/controllers/api/giveStartingGear.ts diff --git a/src/controllers/api/giveStartingGear.ts b/src/controllers/api/giveStartingGear.ts new file mode 100644 index 00000000..903fae2e --- /dev/null +++ b/src/controllers/api/giveStartingGear.ts @@ -0,0 +1,208 @@ +import { config } from "@/src/services/configService"; +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { Types } from "mongoose"; +import { IOid } from "@/src/types/commonTypes"; +import { RequestHandler } from "express"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { + IDailyAffiliations, + IInventoryClient, + IPlayerSkills, + ITypeCount +} from "@/src/types/inventoryTypes/inventoryTypes"; +import { addConsumables, addItem, addKeyChainItems, addMods, getInventory } from "@/src/services/inventoryService"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { getLevelKeyRewards } from "@/src/services/itemDataService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; + +export const giveStartingGearController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + + const dataJSON = getJSONfromString(String(req.body)); + + await addKeyChainItems(inventory, { + KeyChain: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain", + ChainStage: 0 + }); + + for (const key of Object.keys(dataJSON) as Array) { + switch (key) { + // This is the only gear we receive as the rest will come from future quests + case "LongGuns": + case "Pistols": + case "Melee": + case "Suits": + // Filter out already owned items (shouldnt happen but this was mostly for testing) + const existingItems = new Set(inventory[key].map(item => item.ItemType)); + + // Adding items to inventory + inventory[key].push( + ...dataJSON[key] + .filter(item => !existingItems.has(item.ItemType)) + .map(x => ({ + _id: new Types.ObjectId(), + ItemType: x.ItemType, + XP: x.XP, + Configs: [{}, {}, {}] + })) + ); + } + } + + addConsumables(inventory, [{ ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" }]); + if (!config.unlockAllFlavourItems) { + inventory.FlavourItems.push([ + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4" } + ]); + } + addMods(inventory, [ + { + ItemCount: 1, + ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod", + LastAdded: toOid(new Types.ObjectId()) + } + ]); + + inventory.DrifterMelee.push({ + ItemType: "/Lotus/Types/Friendly/PlayerControllable/Weapons/DuviriDualSwords", + _id: new Types.ObjectId() + }); + inventory.PlayedParkourTutorial = true; + inventory.ReceivedStartingGear = true; + inventory.TrainingDate = new Date(); + inventory.QuestKeys.push({ + Progress: [{ i: true }], + unlock: false, + ItemType: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" + }); + await inventory.save(); + + res.json({}); +}; + +// Alot of stuff is not received in the request, instead of being redundant I will just omit an already created type (pain) +interface IStartingGearClient + extends Omit< + IInventoryClient, + | keyof IDailyAffiliations + | "Missions" + | "RandomUpgradesIdentified" + | "LastRegionPlayed" + | "TradesRemaining" + | "DailyFocus" + | "GiftsRemaining" + | "HasOwnedVoidProjectionsPreviously" + | "ChallengesFixVersion" + | "ChallengeProgress" + | "ReceivedStartingGear" + | "PendingRecipes" + | "PendingTrades" + | "DeathMarks" + | "WebFlags" + | "CompletedAlerts" + | "TauntHistory" + | "StoryModeChoice" + | "PeriodicMissionCompletions" + | "ActiveDojoColorResearch" + | "SentientSpawnChanceBoosters" + | "SupportedSyndicate" + | "Affiliations" + | "QualifyingInvasions" + | "FactionScores" + | "ArchwingEnabled" + | "PendingSpectreLoadouts" + | "SpectreLoadouts" + | "CompletedSyndicates" + | "FocusXP" + | "Alignment" + | "CompletedSorties" + | "LastSortieReward" + | "ActiveAvatarImageType" + | "DiscoveredMarkers" + | "CompletedJobs" + | "FocusAbility" + | "HasContributedToDojo" + | "HWIDProtectEnabled" + | "AlignmentReplay" + | "PersonalGoalProgress" + | "ThemeStyle" + | "ThemeBackground" + | "ThemeSounds" + | "BountyScore" + | "ChallengeInstanceStates" + | "LoginMilestoneRewards" + | "NodeIntrosCompleted" + | "GuildId" + | "CompletedJobChains" + | "SeasonChallengeHistory" + | "EquippedInstrument" + | "InvasionChainProgress" + | "NemesisHistory" + | "LastNemesisAllySpawnTime" + | "Settings" + | "PersonalTechProjects" + | "PlayerSkills" + | "TradeBannedUntil" + | "PlayedParkourTutorial" + | "SubscribedToEmailsPersonalized" + | "BlessingCooldown" + | "NemesisAbandonedRewards" + | "LastInventorySync" + | "NextRefill" + | "CustomMarkers" + | "ActiveLandscapeTraps" + | "EvolutionProgress" + | "RepVotes" + | "UsedDailyDeals" + | "LibraryPersonalTarget" + | "LibraryPersonalProgress" + | "CollectibleSeries" + | "LibraryAvailableDailyTaskInfo" + | "HasResetAccount" + | "PendingCoupon" + | "Harvestable" + | "DeathSquadable" + | "EndlessXP" + | "DialogueHistory" + > { + LongGuns: IStartingGearItem[]; + Melee: IStartingGearItem[]; + Pistols: IStartingGearItem[]; + Suits: IStartingGearItem[]; + XPLost?: unknown[]; + CrewShipFusionPoints?: number; + PlayerSkillGains?: IPlayerSkills[]; + // Lot's of unknown but these never receive data (at least not in this request) + StrippedItems?: unknown[]; + BonusMiscItems?: unknown[]; + EmailItems: ITypeCount[]; + OneTimePurchases?: unknown[]; + Rating?: number; + WishlistChanges?: unknown[]; + RecentVendorPurchases: (string | number)[]; + RemovedIdItems?: { ItemId: number }[]; + SongChallenges?: unknown[]; +} + +interface IStartingGearItem + extends Omit< + IEquipmentClient, + | "_id" + | "InfestationDate" + | "InfestationDays" + | "InfestationType" + | "UnlockLevel" + | "Weapon" + | "Customization" + | "RailjackImage" + | "CrewMembers" + | "Details" + > { + // Warframe sends an ItemId instead _id, it will be converted to _id before being pushed to the inventory + ItemId: IOid; + Favorite?: boolean; +} -- 2.47.2 From 19be92acd0c23d2adaa8ac72b1a189a9aa6c6083 Mon Sep 17 00:00:00 2001 From: nrbdev Date: Thu, 20 Feb 2025 04:02:15 -0500 Subject: [PATCH 04/12] feat: giveStartingGear --- src/controllers/api/giveStartingGear.ts | 207 ++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/controllers/api/giveStartingGear.ts diff --git a/src/controllers/api/giveStartingGear.ts b/src/controllers/api/giveStartingGear.ts new file mode 100644 index 00000000..061c4687 --- /dev/null +++ b/src/controllers/api/giveStartingGear.ts @@ -0,0 +1,207 @@ +import { config } from "@/src/services/configService"; +import { toOid } from "@/src/helpers/inventoryHelpers"; +import { Types } from "mongoose"; +import { IOid } from "@/src/types/commonTypes"; +import { RequestHandler } from "express"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { + IDailyAffiliations, + IInventoryClient, + IPlayerSkills, + ITypeCount +} from "@/src/types/inventoryTypes/inventoryTypes"; +import { addConsumables, addKeyChainItems, addMods, getInventory } from "@/src/services/inventoryService"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { getAccountIdForRequest } from "@/src/services/loginService"; + +export const giveStartingGearController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + + const dataJSON = getJSONfromString(String(req.body)); + + await addKeyChainItems(inventory, { + KeyChain: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain", + ChainStage: 0 + }); + + for (const key of Object.keys(dataJSON) as Array) { + switch (key) { + // This is the only gear we receive as the rest will come from future quests + case "LongGuns": + case "Pistols": + case "Melee": + case "Suits": + // Filter out already owned items (shouldnt happen but this was mostly for testing) + const existingItems = new Set(inventory[key].map(item => item.ItemType)); + + // Adding items to inventory + inventory[key].push( + ...dataJSON[key] + .filter(item => !existingItems.has(item.ItemType)) + .map(x => ({ + _id: new Types.ObjectId(), + ItemType: x.ItemType, + XP: x.XP, + Configs: [{}, {}, {}] + })) + ); + } + } + + addConsumables(inventory, [{ ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" }]); + if (!config.unlockAllFlavourItems) { + inventory.FlavourItems.push([ + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3" }, + { ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4" } + ]); + } + addMods(inventory, [ + { + ItemCount: 1, + ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod", + LastAdded: toOid(new Types.ObjectId()) + } + ]); + + inventory.DrifterMelee.push({ + ItemType: "/Lotus/Types/Friendly/PlayerControllable/Weapons/DuviriDualSwords", + _id: new Types.ObjectId() + }); + inventory.PlayedParkourTutorial = true; + inventory.ReceivedStartingGear = true; + inventory.TrainingDate = new Date(); + inventory.QuestKeys.push({ + Progress: [{ i: true }], + unlock: false, + ItemType: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" + }); + await inventory.save(); + + res.json({}); +}; + +// Alot of stuff is not received in the request, instead of being redundant I will just omit an already created type (pain) +interface IStartingGearClient + extends Omit< + IInventoryClient, + | keyof IDailyAffiliations + | "Missions" + | "RandomUpgradesIdentified" + | "LastRegionPlayed" + | "TradesRemaining" + | "DailyFocus" + | "GiftsRemaining" + | "HasOwnedVoidProjectionsPreviously" + | "ChallengesFixVersion" + | "ChallengeProgress" + | "ReceivedStartingGear" + | "PendingRecipes" + | "PendingTrades" + | "DeathMarks" + | "WebFlags" + | "CompletedAlerts" + | "TauntHistory" + | "StoryModeChoice" + | "PeriodicMissionCompletions" + | "ActiveDojoColorResearch" + | "SentientSpawnChanceBoosters" + | "SupportedSyndicate" + | "Affiliations" + | "QualifyingInvasions" + | "FactionScores" + | "ArchwingEnabled" + | "PendingSpectreLoadouts" + | "SpectreLoadouts" + | "CompletedSyndicates" + | "FocusXP" + | "Alignment" + | "CompletedSorties" + | "LastSortieReward" + | "ActiveAvatarImageType" + | "DiscoveredMarkers" + | "CompletedJobs" + | "FocusAbility" + | "HasContributedToDojo" + | "HWIDProtectEnabled" + | "AlignmentReplay" + | "PersonalGoalProgress" + | "ThemeStyle" + | "ThemeBackground" + | "ThemeSounds" + | "BountyScore" + | "ChallengeInstanceStates" + | "LoginMilestoneRewards" + | "NodeIntrosCompleted" + | "GuildId" + | "CompletedJobChains" + | "SeasonChallengeHistory" + | "EquippedInstrument" + | "InvasionChainProgress" + | "NemesisHistory" + | "LastNemesisAllySpawnTime" + | "Settings" + | "PersonalTechProjects" + | "PlayerSkills" + | "TradeBannedUntil" + | "PlayedParkourTutorial" + | "SubscribedToEmailsPersonalized" + | "BlessingCooldown" + | "NemesisAbandonedRewards" + | "LastInventorySync" + | "NextRefill" + | "CustomMarkers" + | "ActiveLandscapeTraps" + | "EvolutionProgress" + | "RepVotes" + | "UsedDailyDeals" + | "LibraryPersonalTarget" + | "LibraryPersonalProgress" + | "CollectibleSeries" + | "LibraryAvailableDailyTaskInfo" + | "HasResetAccount" + | "PendingCoupon" + | "Harvestable" + | "DeathSquadable" + | "EndlessXP" + | "DialogueHistory" + > { + LongGuns: IStartingGearItem[]; + Melee: IStartingGearItem[]; + Pistols: IStartingGearItem[]; + Suits: IStartingGearItem[]; + XPLost?: unknown[]; + CrewShipFusionPoints?: number; + PlayerSkillGains?: IPlayerSkills[]; + // Lot's of unknown but these never receive data (at least not in this request) + StrippedItems?: unknown[]; + BonusMiscItems?: unknown[]; + EmailItems: ITypeCount[]; + OneTimePurchases?: unknown[]; + Rating?: number; + WishlistChanges?: unknown[]; + RecentVendorPurchases: (string | number)[]; + RemovedIdItems?: { ItemId: number }[]; + SongChallenges?: unknown[]; +} + +interface IStartingGearItem + extends Omit< + IEquipmentClient, + | "_id" + | "InfestationDate" + | "InfestationDays" + | "InfestationType" + | "UnlockLevel" + | "Weapon" + | "Customization" + | "RailjackImage" + | "CrewMembers" + | "Details" + > { + // Warframe sends an ItemId instead _id, it will be converted to _id before being pushed to the inventory + ItemId: IOid; + Favorite?: boolean; +} -- 2.47.2 From 11486e1f765da69b587378ebd8b81b34db1e9fc1 Mon Sep 17 00:00:00 2001 From: Sainan Date: Tue, 18 Feb 2025 13:48:21 -0800 Subject: [PATCH 05/12] fix: add missing quest keys at updateQuestKey (#958) it's possible the quest key was not in already in the inventory but the quest was still available due to unlockAllQuests Closes #957 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/958 --- src/services/questService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/questService.ts b/src/services/questService.ts index e53126c5..ae7c8133 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -3,6 +3,7 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; import { HydratedDocument } from "mongoose"; +import { config } from "@/src/services/configService"; export interface IUpdateQuestRequest { QuestKeys: Omit[]; @@ -22,10 +23,10 @@ export const updateQuestKey = ( throw new Error("more than 1 quest key not supported"); } - const questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType); - + let questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType); if (questKeyIndex === -1) { - throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); + if (!config.unlockAllQuests) throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); + questKeyIndex = inventory.QuestKeys.push({ ItemType: questKeyUpdate[0].ItemType }) - 1; } inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0]; -- 2.47.2 From f00be81cc823038660bd19e92b738bf80a3b2541 Mon Sep 17 00:00:00 2001 From: Ordis <134585663+OrdisPrime@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:14:42 -0800 Subject: [PATCH 06/12] feat: move quest cheats to webui (#963) Co-authored-by: Sainan Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/963 Co-authored-by: Ordis <134585663+OrdisPrime@users.noreply.github.com> Co-committed-by: Ordis <134585663+OrdisPrime@users.noreply.github.com> --- config.json.example | 2 - src/controllers/api/inventoryController.ts | 39 +-------- .../custom/manageQuestsController.ts | 80 +++++++++++++++++++ src/routes/custom.ts | 2 + src/services/configService.ts | 2 - src/services/questService.ts | 15 +++- static/webui/index.html | 36 +++++++-- static/webui/script.js | 9 +++ static/webui/translations/en.js | 7 ++ static/webui/translations/ru.js | 7 ++ 10 files changed, 145 insertions(+), 54 deletions(-) create mode 100644 src/controllers/custom/manageQuestsController.ts diff --git a/config.json.example b/config.json.example index 660da015..2bdc035a 100644 --- a/config.json.example +++ b/config.json.example @@ -13,8 +13,6 @@ "skipAllDialogue": true, "unlockAllScans": true, "unlockAllMissions": true, - "unlockAllQuests": true, - "completeAllQuests": true, "infiniteCredits": true, "infinitePlatinum": true, "infiniteEndo": true, diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index d614b147..df7cebcf 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -9,7 +9,6 @@ import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inve import { ExportCustoms, ExportFlavour, - ExportKeys, ExportRegions, ExportResources, ExportVirtuals @@ -102,42 +101,6 @@ export const getInventoryResponse = async ( addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked"); } - if (config.unlockAllQuests) { - for (const [k, v] of Object.entries(ExportKeys)) { - if ("chainStages" in v) { - if (!inventoryResponse.QuestKeys.find(quest => quest.ItemType == k)) { - inventoryResponse.QuestKeys.push({ ItemType: k }); - } - } - } - } - if (config.completeAllQuests) { - for (const quest of inventoryResponse.QuestKeys) { - quest.unlock = true; - quest.Completed = true; - - let numStages = 1; - if (quest.ItemType in ExportKeys && "chainStages" in ExportKeys[quest.ItemType]) { - numStages = ExportKeys[quest.ItemType].chainStages!.length; - } - quest.Progress = []; - for (let i = 0; i != numStages; ++i) { - quest.Progress.push({ - c: 0, - i: false, - m: false, - b: [] - }); - } - } - - inventoryResponse.ArchwingEnabled = true; - inventoryResponse.ActiveQuest = ""; //TODO: might need to reconsider this if this does not work long term. - - // Skip "Watch The Maker" - addString(inventoryResponse.NodeIntrosCompleted, "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"); - } - if (config.unlockAllShipDecorations) { inventoryResponse.ShipDecorations = []; for (const [uniqueName, item] of Object.entries(ExportResources)) { @@ -261,7 +224,7 @@ export const getInventoryResponse = async ( return inventoryResponse; }; -const addString = (arr: string[], str: string): void => { +export const addString = (arr: string[], str: string): void => { if (!arr.find(x => x == str)) { arr.push(str); } diff --git a/src/controllers/custom/manageQuestsController.ts b/src/controllers/custom/manageQuestsController.ts new file mode 100644 index 00000000..504b4951 --- /dev/null +++ b/src/controllers/custom/manageQuestsController.ts @@ -0,0 +1,80 @@ +import { addString } from "@/src/controllers/api/inventoryController"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { addQuestKey, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService"; +import { IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; +import { logger } from "@/src/utils/logger"; +import { RequestHandler } from "express"; +import { ExportKeys } from "warframe-public-export-plus"; + +export const manageQuestsController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const operation = req.query.operation as + | "unlockAll" + | "completeAll" + | "ResetAll" + | "completeAllUnlocked" + | "updateKey"; + const questKeyUpdate = req.body as IUpdateQuestRequest["QuestKeys"]; + + const allQuestKeys: string[] = []; + for (const [k, v] of Object.entries(ExportKeys)) { + if ("chainStages" in v) { + allQuestKeys.push(k); + } + } + const inventory = await getInventory(accountId, "QuestKeys NodeIntrosCompleted"); + + switch (operation) { + case "updateKey": { + //TODO: if this is intended to be used, one needs to add a updateQuestKeyMultiple, the game does never intend to do it, so it errors for multiple keys. + updateQuestKey(inventory, questKeyUpdate); + break; + } + case "unlockAll": { + for (const questKey of allQuestKeys) { + addQuestKey(inventory, { ItemType: questKey, Completed: false, unlock: true, Progress: [] }); + } + break; + } + case "completeAll": { + logger.info("completing all quests.."); + for (const questKey of allQuestKeys) { + const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0; + const Progress = Array(chainStageTotal).fill({ c: 0, i: true, m: true, b: [] } satisfies IQuestStage); + const inventoryQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey); + if (inventoryQuestKey) { + inventoryQuestKey.Completed = true; + inventoryQuestKey.Progress = Progress; + continue; + } + addQuestKey(inventory, { ItemType: questKey, Completed: true, unlock: true, Progress: Progress }); + } + inventory.ArchwingEnabled = true; + inventory.ActiveQuest = ""; + + // Skip "Watch The Maker" + addString(inventory.NodeIntrosCompleted, "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"); + break; + } + case "ResetAll": { + logger.info("resetting all quests.."); + for (const questKey of inventory.QuestKeys) { + questKey.Completed = false; + questKey.Progress = []; + } + break; + } + case "completeAllUnlocked": { + logger.info("completing all unlocked quests.."); + for (const questKey of inventory.QuestKeys) { + //if (!questKey.unlock) { continue; } + questKey.Completed = true; + } + break; + } + } + + await inventory.save(); + res.status(200).end(); +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index ad2ea069..a69afc1c 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -16,6 +16,7 @@ import { importController } from "@/src/controllers/custom/importController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; +import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController"; const customRouter = express.Router(); @@ -32,6 +33,7 @@ customRouter.post("/addCurrency", addCurrencyController); customRouter.post("/addItems", addItemsController); customRouter.post("/addXp", addXpController); customRouter.post("/import", importController); +customRouter.post("/manageQuests", manageQuestsController); customRouter.get("/config", getConfigDataController); customRouter.post("/config", updateConfigDataController); diff --git a/src/services/configService.ts b/src/services/configService.ts index 89d81e8d..2312727b 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -39,8 +39,6 @@ interface IConfig { skipAllDialogue?: boolean; unlockAllScans?: boolean; unlockAllMissions?: boolean; - unlockAllQuests?: boolean; - completeAllQuests?: boolean; infiniteCredits?: boolean; infinitePlatinum?: boolean; infiniteEndo?: boolean; diff --git a/src/services/questService.ts b/src/services/questService.ts index ae7c8133..75316763 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -3,7 +3,6 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; import { HydratedDocument } from "mongoose"; -import { config } from "@/src/services/configService"; export interface IUpdateQuestRequest { QuestKeys: Omit[]; @@ -23,10 +22,10 @@ export const updateQuestKey = ( throw new Error("more than 1 quest key not supported"); } - let questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType); + const questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType); + if (questKeyIndex === -1) { - if (!config.unlockAllQuests) throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); - questKeyIndex = inventory.QuestKeys.push({ ItemType: questKeyUpdate[0].ItemType }) - 1; + throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); } inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0]; @@ -63,3 +62,11 @@ export const updateQuestStage = ( Object.assign(questStage, questStageUpdate); }; + +export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQuestKeyDatabase): void => { + if (inventory.QuestKeys.some(q => q.ItemType === questKey.ItemType)) { + logger.error(`quest key ${questKey.ItemType} already exists`); + return; + } + inventory.QuestKeys.push(questKey); +}; diff --git a/static/webui/index.html b/static/webui/index.html index 5273ba50..6af03e3c 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -410,6 +410,27 @@ +
+
+
+
+ + +
+
+
+
+
+
+
+ + + + +
+
+
+
@@ -436,14 +457,6 @@
-
- - -
-
- - -
@@ -524,6 +537,13 @@
+
+
+ + + + +
diff --git a/static/webui/script.js b/static/webui/script.js index 32d002c2..1c55cf4d 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1153,3 +1153,12 @@ function doAddCurrency(currency) { updateInventory(); }); } + +function doQuestUpdate(operation) { + $.post({ + url: "/custom/manageQuests?" + window.authz + "&operation=" + operation, + contentType: "application/json" + }).then(function () { + updateInventory(); + }); +} diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index aac5bcb5..70203a0f 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -45,6 +45,7 @@ dict = { navbar_deleteAccount: `Delete Account`, navbar_inventory: `Inventory`, navbar_mods: `Mods`, + navbar_quests: `Quests`, navbar_cheats: `Cheats`, navbar_import: `Import`, inventory_addItems: `Add Items`, @@ -72,6 +73,7 @@ dict = { inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, + currency_RegularCredits: `Credits`, currency_PremiumCredits: `Platinum`, currency_FusionPoints: `Endo`, @@ -115,6 +117,11 @@ dict = { cheats_changeSupportedSyndicate: `Supported syndicate`, cheats_changeButton: `Change`, cheats_none: `None`, + cheats_quests: `Quests`, + cheats_quests_UnlockAll: `Unlock All Quests`, + cheats_quests_CompleteAll: `Complete All Quests`, + cheats_quests_CompleteAllUnlocked: `Complete All Unlocked Quests`, + cheats_quests_ResetAll: `Reset All Quests`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, import_submit: `Submit` }; diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 9dda0439..a7ad7771 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -46,6 +46,7 @@ dict = { navbar_deleteAccount: `Удалить аккаунт`, navbar_inventory: `Инвентарь`, navbar_mods: `Моды`, + navbar_quests: `[UNTRANSLATED] Quests`, navbar_cheats: `Читы`, navbar_import: `Импорт`, inventory_addItems: `Добавить предметы`, @@ -73,6 +74,7 @@ dict = { inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, + currency_RegularCredits: `Кредиты`, currency_PremiumCredits: `Платина`, currency_FusionPoints: `Эндо`, @@ -116,6 +118,11 @@ dict = { cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeButton: `Изменить`, cheats_none: `Отсутствует`, + cheats_quests: `[UNTRANSLATED] Quests`, + cheats_quests_UnlockAll: `[UNTRANSLATED] Unlock All Quests`, + cheats_quests_CompleteAll: `[UNTRANSLATED] Complete All Quests`, + cheats_quests_CompleteAllUnlocked: `[UNTRANSLATED] Complete All Unlocked Quests`, + cheats_quests_ResetAll: `[UNTRANSLATED] Reset All Quests`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить` }; -- 2.47.2 From 7c2c0f9077d13d01d1bcfa3e50b27117d401dc89 Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 19 Feb 2025 12:42:21 -0800 Subject: [PATCH 07/12] fix: don't use path-based matching to add QuestKeys (#967) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/967 Co-authored-by: Sainan Co-committed-by: Sainan --- src/services/inventoryService.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 1241d61c..65ccd2d0 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -38,6 +38,7 @@ import { ExportCustoms, ExportFlavour, ExportGear, + ExportKeys, ExportRecipes, ExportResources, ExportSentinels, @@ -365,6 +366,19 @@ export const addItem = async ( } }; } + if (typeName in ExportKeys) { + // Note: "/Lotus/Types/Keys/" contains some EmailItems and ShipFeatureItems + inventory.QuestKeys.push({ ItemType: typeName }); + return { + InventoryChanges: { + QuestKeys: [ + { + ItemType: typeName + } + ] + } + }; + } // Path-based duck typing switch (typeName.substr(1).split("/")[1]) { @@ -481,18 +495,6 @@ export const addItem = async ( } break; } - case "Keys": { - inventory.QuestKeys.push({ ItemType: typeName }); - return { - InventoryChanges: { - QuestKeys: [ - { - ItemType: typeName - } - ] - } - }; - } case "NeutralCreatures": { const horseIndex = inventory.Horses.push({ ItemType: typeName }); return { -- 2.47.2 From 9768e30ac6e6104d71aa0e22244f82484540817c Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 19 Feb 2025 13:42:36 -0800 Subject: [PATCH 08/12] chore: enforce that account only owns one of 'singleton items' (#969) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/969 --- src/services/inventoryService.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 65ccd2d0..de73a2ef 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -830,6 +830,9 @@ const addCrewShip = ( typeName: string, inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { + if (inventory.CrewShips.length != 0) { + throw new Error("refusing to add CrewShip because account already has one"); + } const index = inventory.CrewShips.push({ ItemType: typeName }) - 1; inventoryChanges.CrewShips ??= []; (inventoryChanges.CrewShips as object[]).push(inventory.CrewShips[index].toJSON()); @@ -841,6 +844,9 @@ const addCrewShipHarness = ( typeName: string, inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { + if (inventory.CrewShips.length != 0) { + throw new Error("refusing to add CrewShipHarness because account already has one"); + } const index = inventory.CrewShipHarnesses.push({ ItemType: typeName }) - 1; inventoryChanges.CrewShipHarnesses ??= []; (inventoryChanges.CrewShipHarnesses as object[]).push(inventory.CrewShipHarnesses[index].toJSON()); -- 2.47.2 From 768ef27308f12a425e684beadbc6b1d7b111c0d0 Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 19 Feb 2025 13:53:21 -0800 Subject: [PATCH 09/12] chore: handle motorcycle in addItems (#970) Closes #968 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/970 --- src/services/inventoryService.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index de73a2ef..c2df25d0 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -516,6 +516,11 @@ export const addItem = async ( } }; } + case "Vehicles": + if (typeName == "/Lotus/Types/Vehicles/Motorcycle/MotorcyclePowerSuit") { + return { InventoryChanges: addMotorcycle(inventory, typeName) }; + } + break; } break; } @@ -853,6 +858,20 @@ const addCrewShipHarness = ( return inventoryChanges; }; +const addMotorcycle = ( + inventory: TInventoryDatabaseDocument, + typeName: string, + inventoryChanges: IInventoryChanges = {} +): IInventoryChanges => { + if (inventory.Motorcycles.length != 0) { + throw new Error("refusing to add Motorcycle because account already has one"); + } + const index = inventory.Motorcycles.push({ ItemType: typeName }) - 1; + inventoryChanges.Motorcycles ??= []; + (inventoryChanges.Motorcycles as object[]).push(inventory.Motorcycles[index].toJSON()); + return inventoryChanges; +}; + //TODO: wrong id is not erroring export const addGearExpByCategory = ( inventory: TInventoryDatabaseDocument, -- 2.47.2 From 19407000123b718de6d96320b7bf1e73ff0e15fb Mon Sep 17 00:00:00 2001 From: Sainan Date: Wed, 19 Feb 2025 14:07:28 -0800 Subject: [PATCH 10/12] chore: typings (#971) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/971 --- src/services/inventoryService.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index c2df25d0..f7720a35 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -840,7 +840,7 @@ const addCrewShip = ( } const index = inventory.CrewShips.push({ ItemType: typeName }) - 1; inventoryChanges.CrewShips ??= []; - (inventoryChanges.CrewShips as object[]).push(inventory.CrewShips[index].toJSON()); + (inventoryChanges.CrewShips as IEquipmentClient[]).push(inventory.CrewShips[index].toJSON()); return inventoryChanges; }; @@ -854,7 +854,9 @@ const addCrewShipHarness = ( } const index = inventory.CrewShipHarnesses.push({ ItemType: typeName }) - 1; inventoryChanges.CrewShipHarnesses ??= []; - (inventoryChanges.CrewShipHarnesses as object[]).push(inventory.CrewShipHarnesses[index].toJSON()); + (inventoryChanges.CrewShipHarnesses as IEquipmentClient[]).push( + inventory.CrewShipHarnesses[index].toJSON() + ); return inventoryChanges; }; @@ -868,7 +870,7 @@ const addMotorcycle = ( } const index = inventory.Motorcycles.push({ ItemType: typeName }) - 1; inventoryChanges.Motorcycles ??= []; - (inventoryChanges.Motorcycles as object[]).push(inventory.Motorcycles[index].toJSON()); + (inventoryChanges.Motorcycles as IEquipmentClient[]).push(inventory.Motorcycles[index].toJSON()); return inventoryChanges; }; -- 2.47.2 From b0498e36c104442f933a56ea1e0e06c06750b95d Mon Sep 17 00:00:00 2001 From: CyberVenom Date: Wed, 19 Feb 2025 14:09:02 -0800 Subject: [PATCH 11/12] fix: save settings when accepting trade policy. (#966) ![image.png](/attachments/b9954b5f-5ece-4803-b728-548ca2320fdf) Co-authored-by: Kenya-DK Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/966 Co-authored-by: CyberVenom Co-committed-by: CyberVenom --- src/controllers/api/saveSettingsController.ts | 22 +++++++++++++++++++ src/routes/api.ts | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 src/controllers/api/saveSettingsController.ts diff --git a/src/controllers/api/saveSettingsController.ts b/src/controllers/api/saveSettingsController.ts new file mode 100644 index 00000000..72bf8bfa --- /dev/null +++ b/src/controllers/api/saveSettingsController.ts @@ -0,0 +1,22 @@ +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { getInventory } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; +import { ISettings } from "../../types/inventoryTypes/inventoryTypes"; + +interface ISaveSettingsRequest { + Settings: ISettings; +} + +const saveSettingsController: RequestHandler = async (req, res): Promise => { + const accountId = await getAccountIdForRequest(req); + + const settingResults = getJSONfromString(String(req.body)); + + const inventory = await getInventory(accountId); + inventory.Settings = Object.assign(inventory.Settings, settingResults.Settings); + await inventory.save(); + res.json(inventory.Settings); +}; + +export { saveSettingsController }; diff --git a/src/routes/api.ts b/src/routes/api.ts index 353ccc48..ef353ebf 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -87,6 +87,7 @@ import { updateQuestController } from "@/src/controllers/api/updateQuestControll import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController"; import { updateThemeController } from "../controllers/api/updateThemeController"; import { upgradesController } from "@/src/controllers/api/upgradesController"; +import { saveSettingsController } from "../controllers/api/saveSettingsController"; const apiRouter = express.Router(); @@ -184,5 +185,6 @@ apiRouter.post("/updateQuest.php", updateQuestController); apiRouter.post("/updateSession.php", updateSessionPostController); apiRouter.post("/updateTheme.php", updateThemeController); apiRouter.post("/upgrades.php", upgradesController); +apiRouter.post("/saveSettings.php", saveSettingsController); export { apiRouter }; -- 2.47.2 From 2f1f0c511b60f4fb0a6187a95188394d64a1731d Mon Sep 17 00:00:00 2001 From: Ordis <134585663+OrdisPrime@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:09:47 -0800 Subject: [PATCH 12/12] fix(webui): quest cheats (#965) Completing Quests via the webui will now also award the quest's items and mails. Also fixes doubly adding key chain items. A few items will not be added, as it is currently impossible to determine the item category by path for these items. This will be fixed soon. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/965 Co-authored-by: Ordis <134585663+OrdisPrime@users.noreply.github.com> Co-committed-by: Ordis <134585663+OrdisPrime@users.noreply.github.com> --- .../giveKeyChainTriggeredItemsController.ts | 25 +---- .../giveKeyChainTriggeredMessageController.ts | 23 +--- .../custom/manageQuestsController.ts | 68 ++++++++---- src/models/inventoryModels/inventoryModel.ts | 1 + src/services/inventoryService.ts | 8 +- src/services/questService.ts | 101 ++++++++++++++++++ 6 files changed, 161 insertions(+), 65 deletions(-) diff --git a/src/controllers/api/giveKeyChainTriggeredItemsController.ts b/src/controllers/api/giveKeyChainTriggeredItemsController.ts index ef1e4700..8e391b01 100644 --- a/src/controllers/api/giveKeyChainTriggeredItemsController.ts +++ b/src/controllers/api/giveKeyChainTriggeredItemsController.ts @@ -1,34 +1,19 @@ import { RequestHandler } from "express"; -import { isEmptyObject, parseString } from "@/src/helpers/general"; +import { parseString } from "@/src/helpers/general"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { addKeyChainItems, getInventory } from "@/src/services/inventoryService"; +import { getInventory } from "@/src/services/inventoryService"; import { IGroup } from "@/src/types/loginTypes"; -import { updateQuestStage } from "@/src/services/questService"; +import { giveKeyChainItem } from "@/src/services/questService"; export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => { const accountId = parseString(req.query.accountId); const keyChainInfo = getJSONfromString((req.body as string).toString()); const inventory = await getInventory(accountId); - const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo); - - if (isEmptyObject(inventoryChanges)) { - throw new Error("inventory changes was empty after getting keychain items: should not happen"); - } - // items were added: update quest stage's i (item was given) - updateQuestStage(inventory, keyChainInfo, { i: true }); - + const inventoryChanges = giveKeyChainItem(inventory, keyChainInfo); await inventory.save(); - res.send(inventoryChanges); - //TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory - /* - some items are added or removed (not sure) to the wishlist, in that case a - WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"], - is added to the response, need to determine for which items this is the case and what purpose this has. - */ - //{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0} - //{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]} + res.send(inventoryChanges); }; export interface IKeyChainRequest { diff --git a/src/controllers/api/giveKeyChainTriggeredMessageController.ts b/src/controllers/api/giveKeyChainTriggeredMessageController.ts index 0f699d42..dec4b8a1 100644 --- a/src/controllers/api/giveKeyChainTriggeredMessageController.ts +++ b/src/controllers/api/giveKeyChainTriggeredMessageController.ts @@ -1,34 +1,15 @@ import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; -import { IMessage } from "@/src/models/inboxModel"; -import { createMessage } from "@/src/services/inboxService"; import { getInventory } from "@/src/services/inventoryService"; -import { getKeyChainMessage } from "@/src/services/itemDataService"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { updateQuestStage } from "@/src/services/questService"; +import { giveKeyChainMessage } from "@/src/services/questService"; import { RequestHandler } from "express"; export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest; - const keyChainMessage = getKeyChainMessage(keyChainInfo); - - const message = { - sndr: keyChainMessage.sender, - msg: keyChainMessage.body, - sub: keyChainMessage.title, - att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined, - countedAtt: keyChainMessage.countedAttachments.length > 0 ? keyChainMessage.countedAttachments : undefined, - icon: keyChainMessage.icon ?? "", - transmission: keyChainMessage.transmission ?? "", - highPriority: keyChainMessage.highPriority ?? false, - r: false - } satisfies IMessage; - - await createMessage(accountId, [message]); - const inventory = await getInventory(accountId, "QuestKeys"); - updateQuestStage(inventory, keyChainInfo, { m: true }); + await giveKeyChainMessage(inventory, accountId, keyChainInfo); await inventory.save(); res.send(1); diff --git a/src/controllers/custom/manageQuestsController.ts b/src/controllers/custom/manageQuestsController.ts index 504b4951..49283bf3 100644 --- a/src/controllers/custom/manageQuestsController.ts +++ b/src/controllers/custom/manageQuestsController.ts @@ -1,8 +1,7 @@ import { addString } from "@/src/controllers/api/inventoryController"; import { getInventory } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; -import { addQuestKey, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService"; -import { IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; +import { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService"; import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; import { ExportKeys } from "warframe-public-export-plus"; @@ -23,7 +22,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => { allQuestKeys.push(k); } } - const inventory = await getInventory(accountId, "QuestKeys NodeIntrosCompleted"); + const inventory = await getInventory(accountId); switch (operation) { case "updateKey": { @@ -40,21 +39,31 @@ export const manageQuestsController: RequestHandler = async (req, res) => { case "completeAll": { logger.info("completing all quests.."); for (const questKey of allQuestKeys) { - const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0; - const Progress = Array(chainStageTotal).fill({ c: 0, i: true, m: true, b: [] } satisfies IQuestStage); - const inventoryQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey); - if (inventoryQuestKey) { - inventoryQuestKey.Completed = true; - inventoryQuestKey.Progress = Progress; - continue; + try { + await completeQuest(inventory, questKey); + } catch (error) { + if (error instanceof Error) { + logger.error( + `Something went wrong completing quest ${questKey}, probably could not add some item` + ); + logger.error(error.message); + } } - addQuestKey(inventory, { ItemType: questKey, Completed: true, unlock: true, Progress: Progress }); - } - inventory.ArchwingEnabled = true; - inventory.ActiveQuest = ""; - // Skip "Watch The Maker" - addString(inventory.NodeIntrosCompleted, "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"); + //Skip "Watch The Maker" + if (questKey === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") { + addString( + inventory.NodeIntrosCompleted, + "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level" + ); + } + + if (questKey === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") { + inventory.ArchwingEnabled = true; + } + } + + inventory.ActiveQuest = ""; break; } case "ResetAll": { @@ -63,14 +72,37 @@ export const manageQuestsController: RequestHandler = async (req, res) => { questKey.Completed = false; questKey.Progress = []; } + inventory.ActiveQuest = ""; break; } case "completeAllUnlocked": { logger.info("completing all unlocked quests.."); for (const questKey of inventory.QuestKeys) { - //if (!questKey.unlock) { continue; } - questKey.Completed = true; + console.log("size of questkeys", inventory.QuestKeys.length); + try { + await completeQuest(inventory, questKey.ItemType); + } catch (error) { + if (error instanceof Error) { + logger.error( + `Something went wrong completing quest ${questKey.ItemType}, probably could not add some item` + ); + logger.error(error.message); + } + } + + //Skip "Watch The Maker" + if (questKey.ItemType === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") { + addString( + inventory.NodeIntrosCompleted, + "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level" + ); + } + + if (questKey.ItemType === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") { + inventory.ArchwingEnabled = true; + } } + inventory.ActiveQuest = ""; break; } } diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 5848600e..fe651349 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1249,6 +1249,7 @@ export type InventoryDocumentProps = { KahlLoadOuts: Types.DocumentArray; PendingRecipes: Types.DocumentArray; WeaponSkins: Types.DocumentArray; + QuestKeys: Types.DocumentArray; } & { [K in TEquipmentKey]: Types.DocumentArray }; // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index f7720a35..8780e82f 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -367,7 +367,7 @@ export const addItem = async ( }; } if (typeName in ExportKeys) { - // Note: "/Lotus/Types/Keys/" contains some EmailItems and ShipFeatureItems + // Note: "/Lotus/Types/Keys/" contains some EmailItems inventory.QuestKeys.push({ ItemType: typeName }); return { InventoryChanges: { @@ -524,9 +524,7 @@ export const addItem = async ( } break; } - const errorMessage = `unable to add item: ${typeName}`; - logger.error(errorMessage); - throw new Error(errorMessage); + throw new Error(`unable to add item: ${typeName}`); }; export const addItems = async ( @@ -1183,7 +1181,5 @@ export const addKeyChainItems = async ( combineInventoryChanges(inventoryChanges, inventoryChangesDelta.InventoryChanges); } - await addItems(inventory, nonStoreItems); - return inventoryChanges; }; diff --git a/src/services/questService.ts b/src/services/questService.ts index 75316763..e51eb70a 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -1,8 +1,14 @@ import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { isEmptyObject } from "@/src/helpers/general"; +import { IMessage } from "@/src/models/inboxModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; +import { createMessage } from "@/src/services/inboxService"; +import { addKeyChainItems } from "@/src/services/inventoryService"; +import { getKeyChainMessage } from "@/src/services/itemDataService"; import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; import { HydratedDocument } from "mongoose"; +import { ExportKeys } from "warframe-public-export-plus"; export interface IUpdateQuestRequest { QuestKeys: Omit[]; @@ -70,3 +76,98 @@ export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQu } inventory.QuestKeys.push(questKey); }; + +export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string) => { + const chainStages = ExportKeys[questKey]?.chainStages; + + if (!chainStages) { + throw new Error(`Quest ${questKey} does not contain chain stages`); + } + + const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0; + + const existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey); + + if (existingQuestKey?.Completed) { + return; + } + const Progress = Array(chainStageTotal).fill({ + c: 0, + i: false, + m: false, + b: [] + } satisfies IQuestStage); + + const completedQuestKey: IQuestKeyDatabase = { + ItemType: questKey, + Completed: true, + unlock: true, + Progress: Progress, + CompletionDate: new Date() + }; + + //overwrite current quest progress, might lead to multiple quest item rewards + if (existingQuestKey) { + existingQuestKey.overwrite(completedQuestKey); + //Object.assign(existingQuestKey, completedQuestKey); + } else { + addQuestKey(inventory, completedQuestKey); + } + + for (let i = 0; i < chainStageTotal; i++) { + if (chainStages[i].itemsToGiveWhenTriggered.length > 0) { + await giveKeyChainItem(inventory, { KeyChain: questKey, ChainStage: i }); + } + + if (chainStages[i].messageToSendWhenTriggered) { + await giveKeyChainMessage(inventory, inventory.accountOwnerId.toString(), { + KeyChain: questKey, + ChainStage: i + }); + } + } + //TODO: handle quest completions +}; + +export const giveKeyChainItem = async (inventory: TInventoryDatabaseDocument, keyChainInfo: IKeyChainRequest) => { + const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo); + + if (isEmptyObject(inventoryChanges)) { + throw new Error("inventory changes was empty after getting keychain items: should not happen"); + } + // items were added: update quest stage's i (item was given) + updateQuestStage(inventory, keyChainInfo, { i: true }); + + //TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory + /* + some items are added or removed (not sure) to the wishlist, in that case a + WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"], + is added to the response, need to determine for which items this is the case and what purpose this has. + */ + //{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0} + //{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]} +}; + +export const giveKeyChainMessage = async ( + inventory: TInventoryDatabaseDocument, + accountId: string, + keyChainInfo: IKeyChainRequest +) => { + const keyChainMessage = getKeyChainMessage(keyChainInfo); + + const message = { + sndr: keyChainMessage.sender, + msg: keyChainMessage.body, + sub: keyChainMessage.title, + att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined, + countedAtt: keyChainMessage.countedAttachments.length > 0 ? keyChainMessage.countedAttachments : undefined, + icon: keyChainMessage.icon ?? "", + transmission: keyChainMessage.transmission ?? "", + highPriority: keyChainMessage.highPriority ?? false, + r: false + } satisfies IMessage; + + await createMessage(accountId, [message]); + + updateQuestStage(inventory, keyChainInfo, { m: true }); +}; -- 2.47.2