From 6dd9b42f4026d41065eb00b281e41be2eeab2f4f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 06:36:47 -0700 Subject: [PATCH 01/24] feat(webui): update inventory when in-game changes are made (#2239) A bit of a rough initial implementation, but already works pretty well. Closes #2224 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2239 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/missionInventoryUpdateController.ts | 3 +++ src/controllers/api/nameWeaponController.ts | 2 ++ src/controllers/api/purchaseController.ts | 2 ++ src/controllers/api/renamePetController.ts | 2 ++ src/controllers/api/sellController.ts | 2 ++ src/services/webService.ts | 1 + static/webui/script.js | 3 +++ 7 files changed, 15 insertions(+) diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 3b5009c2..93f8033c 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -7,6 +7,7 @@ import { generateRewardSeed, getInventory } from "@/src/services/inventoryServic import { getInventoryResponse } from "./inventoryController"; import { logger } from "@/src/utils/logger"; import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes"; +import { sendWsBroadcastTo } from "@/src/services/webService"; /* **** INPUT **** @@ -76,6 +77,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) InventoryJson: JSON.stringify(inventoryResponse), MissionRewards: [] }); + sendWsBroadcastTo(account._id.toString(), { update_inventory: true }); return; } @@ -106,6 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) AffiliationMods, ConquestCompletedMissionsCount } satisfies IMissionInventoryUpdateResponse); + sendWsBroadcastTo(account._id.toString(), { update_inventory: true }); }; /* diff --git a/src/controllers/api/nameWeaponController.ts b/src/controllers/api/nameWeaponController.ts index 5d1011be..8d378feb 100644 --- a/src/controllers/api/nameWeaponController.ts +++ b/src/controllers/api/nameWeaponController.ts @@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; +import { sendWsBroadcastTo } from "@/src/services/webService"; interface INameWeaponRequest { ItemName: string; @@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => { res.json({ InventoryChanges: currencyChanges }); + sendWsBroadcastTo(accountId, { update_inventory: true }); }; diff --git a/src/controllers/api/purchaseController.ts b/src/controllers/api/purchaseController.ts index 4b35e0c5..ba314845 100644 --- a/src/controllers/api/purchaseController.ts +++ b/src/controllers/api/purchaseController.ts @@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { IPurchaseRequest } from "@/src/types/purchaseTypes"; import { handlePurchase } from "@/src/services/purchaseService"; import { getInventory } from "@/src/services/inventoryService"; +import { sendWsBroadcastTo } from "@/src/services/webService"; export const purchaseController: RequestHandler = async (req, res) => { const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest; @@ -11,4 +12,5 @@ export const purchaseController: RequestHandler = async (req, res) => { const response = await handlePurchase(purchaseRequest, inventory); await inventory.save(); res.json(response); + sendWsBroadcastTo(accountId, { update_inventory: true }); }; diff --git a/src/controllers/api/renamePetController.ts b/src/controllers/api/renamePetController.ts index 61212641..40b4ec37 100644 --- a/src/controllers/api/renamePetController.ts +++ b/src/controllers/api/renamePetController.ts @@ -1,6 +1,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; +import { sendWsBroadcastTo } from "@/src/services/webService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; @@ -22,6 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => { ...data, inventoryChanges: inventoryChanges }); + sendWsBroadcastTo(accountId, { update_inventory: true }); }; interface IRenamePetRequest { diff --git a/src/controllers/api/sellController.ts b/src/controllers/api/sellController.ts index fdfb3a82..ff5c8f42 100644 --- a/src/controllers/api/sellController.ts +++ b/src/controllers/api/sellController.ts @@ -15,6 +15,7 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; +import { sendWsBroadcastTo } from "@/src/services/webService"; export const sellController: RequestHandler = async (req, res) => { const payload = JSON.parse(String(req.body)) as ISellRequest; @@ -279,6 +280,7 @@ export const sellController: RequestHandler = async (req, res) => { res.json({ inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" }); + sendWsBroadcastTo(accountId, { update_inventory: true }); }; interface ISellRequest { diff --git a/src/services/webService.ts b/src/services/webService.ts index 92578c61..25db1d69 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -125,6 +125,7 @@ interface IWsMsgToClient { isRegister: boolean; }; logged_out?: boolean; + update_inventory?: boolean; } const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { diff --git a/static/webui/script.js b/static/webui/script.js index 44f7ade0..1f1e86dd 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -79,6 +79,9 @@ function openWebSocket() { if ("logged_out" in msg) { sendAuth(); } + if ("update_inventory" in msg) { + updateInventory(); + } }; window.ws.onclose = function () { ws_is_open = false; From bf12f90c8899ab0324841c0684573c18f54232b3 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 06:37:17 -0700 Subject: [PATCH 02/24] chore: replace unlockAllMissions config with an account cheats button (#2241) This way, mission completion rewards are given. This is especially import for junction rewards like quest keys (Closes #2229). Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2241 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 - src/controllers/api/inventoryController.ts | 28 +------ .../custom/completeAllMissionsController.ts | 34 ++++++++ src/helpers/stringHelpers.ts | 6 ++ src/routes/custom.ts | 2 + src/services/configService.ts | 1 - src/services/missionInventoryUpdateService.ts | 2 +- static/webui/index.html | 9 +- static/webui/script.js | 84 ++++++++++--------- 9 files changed, 91 insertions(+), 76 deletions(-) create mode 100644 src/controllers/custom/completeAllMissionsController.ts diff --git a/config.json.example b/config.json.example index 9287091c..78eac7a1 100644 --- a/config.json.example +++ b/config.json.example @@ -13,7 +13,6 @@ "skipTutorial": false, "skipAllDialogue": false, "unlockAllScans": false, - "unlockAllMissions": false, "infiniteCredits": false, "infinitePlatinum": false, "infiniteEndo": false, diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index cdaf8d4d..ed4b8cff 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -6,13 +6,7 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json"; import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes"; import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes"; -import { - ExportCustoms, - ExportFlavour, - ExportRegions, - ExportResources, - ExportVirtuals -} from "warframe-public-export-plus"; +import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService"; import { addMiscItems, @@ -22,7 +16,7 @@ import { generateRewardSeed } from "@/src/services/inventoryService"; import { logger } from "@/src/utils/logger"; -import { catBreadHash } from "@/src/helpers/stringHelpers"; +import { addString, catBreadHash } from "@/src/helpers/stringHelpers"; import { Types } from "mongoose"; import { getNemesisManifest } from "@/src/helpers/nemesisHelpers"; import { getPersonalRooms } from "@/src/services/personalRoomsService"; @@ -167,18 +161,6 @@ export const getInventoryResponse = async ( } } - if (config.unlockAllMissions) { - inventoryResponse.Missions = []; - for (const tag of Object.keys(ExportRegions)) { - inventoryResponse.Missions.push({ - Completes: 1, - Tier: 1, - Tag: tag - }); - } - addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked"); - } - if (config.unlockAllShipDecorations) { inventoryResponse.ShipDecorations = []; for (const [uniqueName, item] of Object.entries(ExportResources)) { @@ -362,12 +344,6 @@ const allEudicoHeistJobs = [ "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour" ]; -const addString = (arr: string[], str: string): void => { - if (arr.indexOf(str) == -1) { - arr.push(str); - } -}; - const getExpRequiredForMr = (rank: number): number => { if (rank <= 30) { return 2500 * rank * rank; diff --git a/src/controllers/custom/completeAllMissionsController.ts b/src/controllers/custom/completeAllMissionsController.ts new file mode 100644 index 00000000..2e7ac2fb --- /dev/null +++ b/src/controllers/custom/completeAllMissionsController.ts @@ -0,0 +1,34 @@ +import { addString } from "@/src/helpers/stringHelpers"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService"; +import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; +import { IMissionReward } from "@/src/types/missionTypes"; +import { RequestHandler } from "express"; +import { ExportRegions } from "warframe-public-export-plus"; + +export const completeAllMissionsController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const MissionRewards: IMissionReward[] = []; + for (const [tag, node] of Object.entries(ExportRegions)) { + if (!inventory.Missions.find(x => x.Tag == tag)) { + inventory.Missions.push({ + Completes: 1, + Tier: 1, + Tag: tag + }); + + if (node.missionReward) { + console.log(node.missionReward); + addFixedLevelRewards(node.missionReward, inventory, MissionRewards); + } + } + } + for (const reward of MissionRewards) { + await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true); + } + addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked"); + await inventory.save(); + res.end(); +}; diff --git a/src/helpers/stringHelpers.ts b/src/helpers/stringHelpers.ts index 06d2ac28..3512c1ea 100644 --- a/src/helpers/stringHelpers.ts +++ b/src/helpers/stringHelpers.ts @@ -54,3 +54,9 @@ export const regexEscape = (str: string): string => { str = str.split("}").join("\\}"); return str; }; + +export const addString = (arr: string[], str: string): void => { + if (arr.indexOf(str) == -1) { + arr.push(str); + } +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 75eb80a4..35d89d4d 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -12,6 +12,7 @@ import { ircDroppedController } from "@/src/controllers/custom/ircDroppedControl import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController"; import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController"; import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController"; +import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController"; import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createMessageController } from "@/src/controllers/custom/createMessageController"; @@ -40,6 +41,7 @@ customRouter.get("/ircDropped", ircDroppedController); customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController); customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController); +customRouter.get("/completeAllMissions", completeAllMissionsController); customRouter.post("/createAccount", createAccountController); customRouter.post("/createMessage", createMessageController); diff --git a/src/services/configService.ts b/src/services/configService.ts index 6ef2e96f..79d76d73 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -19,7 +19,6 @@ export interface IConfig { skipTutorial?: boolean; skipAllDialogue?: boolean; unlockAllScans?: boolean; - unlockAllMissions?: boolean; infiniteCredits?: boolean; infinitePlatinum?: boolean; infiniteEndo?: boolean; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 499115f9..e903f9de 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1367,7 +1367,7 @@ export const addFixedLevelRewards = ( if (rewards.countedItems) { for (const item of rewards.countedItems) { MissionRewards.push({ - StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`, + StoreItem: toStoreItem(item.ItemType), ItemCount: item.ItemCount }); } diff --git a/static/webui/index.html b/static/webui/index.html index 1a748968..dca46e9d 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -452,9 +452,6 @@ -
- -
@@ -587,10 +584,6 @@ -
- - -
@@ -782,9 +775,11 @@
+ +
diff --git a/static/webui/script.js b/static/webui/script.js index 1f1e86dd..110dd1a8 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -114,11 +114,9 @@ function doLogin() { window.registerSubmit = false; } -function revalidateAuthz(succ_cb) { - getWebSocket().then(() => { - // We have a websocket connection, so authz should be good. - succ_cb(); - }); +async function revalidateAuthz() { + await getWebSocket(); + // We have a websocket connection, so authz should be good. } function logout() { @@ -138,7 +136,7 @@ function doLogout() { function renameAccount() { const newname = window.prompt(loc("code_changeNameConfirm")); if (newname) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { $(".displayname").text(newname); updateLocElements(); @@ -149,7 +147,7 @@ function renameAccount() { function deleteAccount() { if (window.confirm(loc("code_deleteAccountConfirm"))) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { fetch("/custom/deleteAccount?" + window.authz).then(() => { logout(); single.loadRoute("/webui/"); // Show login screen @@ -649,7 +647,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - revalidateAuthz(() => { + revalidateAuthz().then(() => { const promises = []; if (item.XP < maxXP) { promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP)); @@ -1239,7 +1237,7 @@ function doAcquireEquipment(category) { .focus(); return; } - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.post({ url: "/custom/addItems?" + window.authz, contentType: "application/json", @@ -1366,7 +1364,7 @@ function doAcquireModularEquipment(category, WeaponType) { } }); if (category == "KubrowPets") Parts.unshift(WeaponType); - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.post({ url: "/api/modularWeaponCrafting.php?" + window.authz, contentType: "application/octet-stream", @@ -1419,7 +1417,7 @@ $("input[list]").on("input", function () { }); function dispatchAddItemsRequestsBatch(requests) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.post({ url: "/custom/addItems?" + window.authz, contentType: "application/json", @@ -1463,7 +1461,7 @@ function addMissingEvolutionProgress() { } function maxRankAllEvolutions() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(data => { const requests = []; @@ -1487,7 +1485,7 @@ function maxRankAllEvolutions() { } function maxRankAllEquipment(categories) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(data => { window.itemListPromise.then(itemMap => { @@ -1561,7 +1559,7 @@ function addGearExp(category, oid, xp) { } function sendBatchGearExp(data) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/addXp?" + window.authz, contentType: "application/json", @@ -1574,7 +1572,7 @@ function sendBatchGearExp(data) { } function renameGear(category, oid, name) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { if (category == "KubrowPets") { $.post({ url: "/api/renamePet.php?" + window.authz + "&webui=1", @@ -1602,7 +1600,7 @@ function renameGear(category, oid, name) { function disposeOfGear(category, oid) { if (category == "KubrowPets") { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/releasePet.php?" + window.authz, contentType: "application/octet-stream", @@ -1624,7 +1622,7 @@ function disposeOfGear(category, oid) { Count: 0 } ]; - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/sell.php?" + window.authz, contentType: "text/plain", @@ -1646,7 +1644,7 @@ function disposeOfItems(category, type, count) { Count: count } ]; - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/sell.php?" + window.authz, contentType: "text/plain", @@ -1656,7 +1654,7 @@ function disposeOfItems(category, type, count) { } function gildEquipment(category, oid) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category, contentType: "application/octet-stream", @@ -1670,7 +1668,7 @@ function gildEquipment(category, oid) { } function maturePet(oid, revert) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/maturePet.php?" + window.authz, contentType: "application/octet-stream", @@ -1685,7 +1683,7 @@ function maturePet(oid, revert) { } function setEvolutionProgress(requests) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.post({ url: "/custom/setEvolutionProgress?" + window.authz, contentType: "application/json", @@ -1705,7 +1703,7 @@ function doAcquireMiscItems() { } const count = parseInt($("#miscitem-count").val()); if (count != 0) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/addItems?" + window.authz, contentType: "application/json", @@ -1746,7 +1744,7 @@ function doAcquireRiven() { return; } const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val(); - revalidateAuthz(() => { + revalidateAuthz().then(() => { // Add riven type to inventory $.post({ url: "/custom/addItems?" + window.authz, @@ -1793,7 +1791,7 @@ $("#addriven-fingerprint").on("input", () => { }); function setFingerprint(ItemType, ItemId, fingerprint) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/api/artifacts.php?" + window.authz, contentType: "text/plain", @@ -1821,7 +1819,7 @@ function doAcquireMod() { } const count = parseInt($("#mod-count").val()); if (count != 0) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/addItems?" + window.authz, contentType: "application/json", @@ -1898,7 +1896,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { ); } else { if ((await res.text()) == "Log-in expired") { - revalidateAuthz(() => { + revalidateAuthz().then(() => { if (single.getCurrentPath() == "/webui/cheats") { single.loadRoute("/webui/cheats"); } @@ -1915,7 +1913,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { }); function doUnlockAllFocusSchools() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(async data => { const missingFocusUpgrades = { "/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true, @@ -1966,13 +1964,13 @@ function unlockFocusSchool(upgradeType) { } function doHelminthUnlockAll() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall"); }); } function doIntrinsicsUnlockAll() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.get("/custom/unlockAllIntrinsics?" + window.authz); }); } @@ -1984,7 +1982,7 @@ function doAddAllMods() { } modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser"); - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(data => { for (const modOwned of data.RawUpgrades) { @@ -2016,7 +2014,7 @@ function doAddAllMods() { } function doRemoveUnrankedMods() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(inventory => { window.itemListPromise.then(itemMap => { @@ -2041,7 +2039,7 @@ function doRemoveUnrankedMods() { } function doAddMissingMaxRankMods() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => { updateInventory(); }); @@ -2063,7 +2061,7 @@ function doPushArchonCrystalUpgrade() { $("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus(); return; } - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.get( "/custom/pushArchonCrystalUpgrade?" + window.authz + @@ -2081,7 +2079,7 @@ function doPushArchonCrystalUpgrade() { } function doPopArchonCrystalUpgrade(type) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.get( "/custom/popArchonCrystalUpgrade?" + window.authz + @@ -2096,7 +2094,7 @@ function doPopArchonCrystalUpgrade(type) { } function doImport() { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/import?" + window.authz, contentType: "application/json", @@ -2113,7 +2111,7 @@ function doImport() { function doChangeSupportedSyndicate() { const uniqueName = document.getElementById("changeSyndicate").value; - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () { updateInventory(); }); @@ -2121,7 +2119,7 @@ function doChangeSupportedSyndicate() { } function doAddCurrency(currency) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/addCurrency?" + window.authz, contentType: "application/json", @@ -2136,7 +2134,7 @@ function doAddCurrency(currency) { } function doQuestUpdate(operation, itemType) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType, contentType: "application/json" @@ -2147,7 +2145,7 @@ function doQuestUpdate(operation, itemType) { } function doBulkQuestUpdate(operation) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/manageQuests?" + window.authz + "&operation=" + operation, contentType: "application/json" @@ -2245,7 +2243,7 @@ function handleModularSelection(category) { } function setBooster(ItemType, ExpiryDate, callback) { - revalidateAuthz(() => { + revalidateAuthz().then(() => { $.post({ url: "/custom/setBooster?" + window.authz, contentType: "application/json", @@ -2335,3 +2333,9 @@ async function doMaxPlexus() { toast(loc("code_noEquipmentToRankUp")); } } + +async function doUnlockAllMissions() { + await revalidateAuthz(); + await fetch("/custom/completeAllMissions?" + window.authz); + updateInventory(); +} From d7b9fb1ab5a5f394614d96b10418eb62ca22ec06 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 06:41:06 -0700 Subject: [PATCH 03/24] fix(webui): once awake stage 2 not consistently showing up in-game (#2244) Fixes #2040 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2244 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/custom/manageQuestsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/custom/manageQuestsController.ts b/src/controllers/custom/manageQuestsController.ts index b951c754..365fb29c 100644 --- a/src/controllers/custom/manageQuestsController.ts +++ b/src/controllers/custom/manageQuestsController.ts @@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => { await completeQuest(inventory, questKey.ItemType); } else { const progress = { - c: questManifest.chainStages![currentStage].key ? -1 : 0, + c: 0, i: false, m: false, b: [] From 558af66965d5a0757b610454e7f321ceea9901c6 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 06:41:43 -0700 Subject: [PATCH 04/24] chore: verify that httpsPort has actually been bound by us (#2243) I'm not the biggest fan of this, but it's a semi-regular problem and this should help affected users quickly discover it. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2243 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 204 +++++++++++++++++++++++++++++++++++++ package.json | 2 + src/services/webService.ts | 39 ++++++- 3 files changed, 244 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index dc6eb284..bbe6c67e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", @@ -21,6 +22,7 @@ "typescript": "^5.5", "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", + "websocket": "^1.0.35", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "ws": "^8.18.2" @@ -382,6 +384,15 @@ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", "license": "MIT" }, + "node_modules/@types/websocket": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz", + "integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/whatwg-url": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", @@ -910,6 +921,19 @@ "node": ">=16.20.1" } }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1128,6 +1152,19 @@ "node": ">= 8" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1239,6 +1276,46 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1400,6 +1477,21 @@ "node": "*" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1473,6 +1565,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1515,6 +1617,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2044,6 +2155,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2449,6 +2566,23 @@ "node": ">= 0.6" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -3239,6 +3373,12 @@ "dev": true, "license": "0BSD" }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3279,6 +3419,15 @@ "node": ">= 0.6" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -3317,6 +3466,19 @@ "punycode": "^2.1.0" } }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3351,6 +3513,38 @@ "node": ">=12" } }, + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/whatwg-url": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", @@ -3471,6 +3665,16 @@ } } }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b5ca4fbd..1ac5831f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", @@ -30,6 +31,7 @@ "typescript": "^5.5", "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", + "websocket": "^1.0.35", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "ws": "^8.18.2" diff --git a/src/services/webService.ts b/src/services/webService.ts index 25db1d69..3be29a58 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -10,6 +10,7 @@ import { Account } from "../models/loginModel"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; import { IDatabaseAccountJson } from "../types/loginTypes"; import { HydratedDocument } from "mongoose"; +import websocket from "websocket"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; @@ -44,6 +45,37 @@ export const startWebServer = (): void => { logger.info( "Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort) ); + + void runWsSelfTest("wss", httpsPort).then(ok => { + if (!ok) { + logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`); + if (process.platform == "win32") { + logger.warn( + `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess` + ); + } + } + }); + }); + }); +}; + +const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise => { + return new Promise(resolve => { + const client = new websocket.client({ tlsOptions: { rejectUnauthorized: false } }); + client.connect(`${protocol}://localhost:${port}/custom/selftest`); + client.on("connect", connection => { + connection.on("message", msg => { + if (msg.type == "utf8" && msg.utf8Data == "SpaceNinjaServer") { + resolve(true); + } + }); + connection.on("close", () => { + resolve(false); + }); + }); + client.on("connectFailed", () => { + resolve(false); }); }); }; @@ -128,7 +160,12 @@ interface IWsMsgToClient { update_inventory?: boolean; } -const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { +const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { + if (req.url == "/custom/selftest") { + ws.send("SpaceNinjaServer"); + ws.close(); + return; + } // eslint-disable-next-line @typescript-eslint/no-misused-promises ws.on("message", async msg => { const data = JSON.parse(String(msg)) as IWsMsgFromClient; From 7ca7147b78c55fa4177b723960fdcc2aa1106fb7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:45:21 +0200 Subject: [PATCH 05/24] fix(docker): install node-gyp deps to fix arm64 build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ec346634..df0a01ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:24-alpine3.21 -RUN apk add --no-cache bash jq +RUN apk add --no-cache bash jq python3 make gcc alpine-sdk COPY . /app WORKDIR /app From 84f081312ba10f2855873cddff653ac5ea44f95d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 06:55:44 -0700 Subject: [PATCH 06/24] feat: fullyStockedVendors cheat (#2246) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2246 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/services/configService.ts | 1 + src/services/serversideVendorsService.ts | 162 ++++++++++++++--------- static/webui/index.html | 4 + static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 115 insertions(+), 59 deletions(-) diff --git a/config.json.example b/config.json.example index 78eac7a1..72495c8b 100644 --- a/config.json.example +++ b/config.json.example @@ -41,6 +41,7 @@ "noVendorPurchaseLimits": false, "noDeathMarks": false, "noKimCooldowns": false, + "fullyStockedVendors": false, "syndicateMissionsRepeatable": false, "unlockAllProfitTakerStages": false, "instantFinishRivenChallenge": false, diff --git a/src/services/configService.ts b/src/services/configService.ts index 79d76d73..74b8f5b7 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -48,6 +48,7 @@ export interface IConfig { noVendorPurchaseLimits?: boolean; noDeathMarks?: boolean; noKimCooldowns?: boolean; + fullyStockedVendors?: boolean; syndicateMissionsRepeatable?: boolean; unlockAllProfitTakerStages?: boolean; instantFinishRivenChallenge?: boolean; diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index c40c3cc6..d5ea7410 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -6,6 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; +import { config } from "./configService"; interface IGeneratableVendorInfo extends Omit { cycleOffset?: number; @@ -59,20 +60,23 @@ const getCycleDuration = (manifest: IVendor): number => { return dur * unixTimesInMs.hour; }; -export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { +export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => { for (const vendorInfo of generatableVendors) { if (vendorInfo.TypeName == typeName) { - return generateVendorManifest(vendorInfo); + return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors); } } if (typeName in ExportVendors) { const manifest = ExportVendors[typeName]; - return generateVendorManifest({ - _id: { $oid: getVendorOid(typeName) }, - TypeName: typeName, - RandomSeedType: manifest.randomSeedType, - cycleDuration: getCycleDuration(manifest) - }); + return generateVendorManifest( + { + _id: { $oid: getVendorOid(typeName) }, + TypeName: typeName, + RandomSeedType: manifest.randomSeedType, + cycleDuration: getCycleDuration(manifest) + }, + fullStock ?? config.fullyStockedVendors + ); } return undefined; }; @@ -80,18 +84,21 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { for (const vendorInfo of generatableVendors) { if (vendorInfo._id.$oid == oid) { - return generateVendorManifest(vendorInfo); + return generateVendorManifest(vendorInfo, config.fullyStockedVendors); } } for (const [typeName, manifest] of Object.entries(ExportVendors)) { const typeNameOid = getVendorOid(typeName); if (typeNameOid == oid) { - return generateVendorManifest({ - _id: { $oid: typeNameOid }, - TypeName: typeName, - RandomSeedType: manifest.randomSeedType, - cycleDuration: getCycleDuration(manifest) - }); + return generateVendorManifest( + { + _id: { $oid: typeNameOid }, + TypeName: typeName, + RandomSeedType: manifest.randomSeedType, + cycleDuration: getCycleDuration(manifest) + }, + config.fullyStockedVendors + ); } } return undefined; @@ -169,9 +176,26 @@ const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => { } }; +let vendorManifestsUsingFullStock = false; const vendorManifestCache: Record = {}; -const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { +const clearVendorCache = (): void => { + for (const k of Object.keys(vendorManifestCache)) { + delete vendorManifestCache[k]; + } +}; + +const generateVendorManifest = ( + vendorInfo: IGeneratableVendorInfo, + fullStock: boolean | undefined +): IVendorManifest => { + fullStock ??= config.fullyStockedVendors; + fullStock ??= false; + if (vendorManifestsUsingFullStock != fullStock) { + vendorManifestsUsingFullStock = fullStock; + clearVendorCache(); + } + if (!(vendorInfo.TypeName in vendorManifestCache)) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo; @@ -208,7 +232,20 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const offersToAdd: IVendorOffer[] = []; - if (!manifest.isOneBinPerCycle) { + if (manifest.isOneBinPerCycle) { + if (fullStock) { + for (const rawItem of manifest.items) { + offersToAdd.push(rawItem); + } + } else { + const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now. + for (const rawItem of manifest.items) { + if (rawItem.bin == binThisCycle) { + offersToAdd.push(rawItem); + } + } + } + } else { // Compute vendor requirements, subtracting existing offers const remainingItemCapacity: Record = {}; const missingItemsPerBin: Record = {}; @@ -254,12 +291,14 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani manifest.numItems && (manifest.numItems.minValue != manifest.numItems.maxValue || manifest.numItems.minValue != numCountedOffers); - const numItemsTarget = manifest.numItems - ? numUncountedOffers + - (useRng - ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) - : manifest.numItems.minValue) - : manifest.items.length; + const numItemsTarget = fullStock + ? numUncountedOffers + numCountedOffers + : manifest.numItems + ? numUncountedOffers + + (useRng + ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) + : manifest.numItems.minValue) + : manifest.items.length; let i = 0; const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit< IVendorOffer, @@ -282,13 +321,6 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani i = 0; } } - } else { - const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. - for (const rawItem of manifest.items) { - if (rawItem.bin == binThisCycle) { - offersToAdd.push(rawItem); - } - } } const cycleStart = cycleOffset + cycleIndex * cycleDuration; for (const rawItem of offersToAdd) { @@ -387,34 +419,44 @@ if (args.dev) { logger.warn(`getCycleDuration self test failed`); } - const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")! - .VendorInfo.ItemManifest; - if ( - ads.length != 5 || - ads[0].Bin != "BIN_4" || - ads[1].Bin != "BIN_3" || - ads[2].Bin != "BIN_2" || - ads[3].Bin != "BIN_1" || - ads[4].Bin != "BIN_0" - ) { - logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); + for (let i = 0; i != 2; ++i) { + const fullStock = !!i; + + const ads = getVendorManifestByTypeName( + "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest", + fullStock + )!.VendorInfo.ItemManifest; + if ( + ads.length != 5 || + ads[0].Bin != "BIN_4" || + ads[1].Bin != "BIN_3" || + ads[2].Bin != "BIN_2" || + ads[3].Bin != "BIN_1" || + ads[4].Bin != "BIN_0" + ) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); + } + + const pall = getVendorManifestByTypeName( + "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest", + fullStock + )!.VendorInfo.ItemManifest; + if ( + pall.length != 5 || + pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" || + pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" || + pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" || + pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" || + pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" + ) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`); + } } - const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")! - .VendorInfo.ItemManifest; - if ( - pall.length != 5 || - pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" || - pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" || - pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" || - pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" || - pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" - ) { - logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`); - } - - const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")! - .VendorInfo.ItemManifest; + const cms = getVendorManifestByTypeName( + "/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest", + false + )!.VendorInfo.ItemManifest; if ( cms.length != 9 || cms[0].Bin != "BIN_2" || @@ -426,13 +468,15 @@ if (args.dev) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`); } - const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")! - .VendorInfo.ItemManifest; + const temple = getVendorManifestByTypeName( + "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest", + false + )!.VendorInfo.ItemManifest; if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`); } - const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest")! + const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)! .VendorInfo.ItemManifest; if ( nakak.length != 10 || diff --git a/static/webui/index.html b/static/webui/index.html index dca46e9d..5eb4b297 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -700,6 +700,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 6958e7ed..b82f547f 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -158,6 +158,7 @@ dict = { cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`, cheats_noDeathMarks: `Keine Todesmarkierungen`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, + cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 04fbe563..870f1d89 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -157,6 +157,7 @@ dict = { cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`, cheats_noDeathMarks: `No Death Marks`, cheats_noKimCooldowns: `No KIM Cooldowns`, + cheats_fullyStockedVendors: `Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 5f1e470d..eb56f094 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -158,6 +158,7 @@ dict = { cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`, cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, + cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 0c23c61a..5d522b2b 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -158,6 +158,7 @@ dict = { cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`, cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, + cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 22d873bc..e06907bf 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -158,6 +158,7 @@ dict = { cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`, cheats_noDeathMarks: `Без меток сметри`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`, + cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 303a1599..8e2e9344 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -158,6 +158,7 @@ dict = { cheats_noVendorPurchaseLimits: `商城或商人无购买限制`, cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noKimCooldowns: `无 KIM 冷却时间`, + cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, From cee622d5e95bf86bb28a5389580aaf223c1b3996 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:58:48 -0700 Subject: [PATCH 07/24] chore: add bun support (#2254) It definitely has some benefits: - It starts up insanely quickly compared to Node. - It can run typescript directly, allow the build step to be reduced to verify/noEmit. It does not implement NodeJS APIs perfectly, so I've had to add some special handling for Bun, but I think that's okay. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2254 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package.json | 3 +++ scripts/dev.js | 7 +++++-- src/services/configWatcherService.ts | 7 ++++++- src/services/webService.ts | 19 ++++++++++++------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 1ac5831f..a7bf6a69 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,11 @@ "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", "build:dev": "tsc --incremental --sourceMap", "build-and-start": "npm run build && npm run start", + "build-and-start:bun": "npm run verify && npm run bun-run", "dev": "node scripts/dev.js", + "dev:bun": "bun scripts/dev.js", "verify": "tsgo --noEmit", + "bun-run": "bun src/index.ts", "lint": "eslint --ext .ts .", "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .", "lint:fix": "eslint --fix --ext .ts .", diff --git a/scripts/dev.js b/scripts/dev.js index 10d6ac36..542528f1 100644 --- a/scripts/dev.js +++ b/scripts/dev.js @@ -14,6 +14,7 @@ args.push("--secret"); args.push(secret); let buildproc, runproc; +const spawnopts = { stdio: "inherit", shell: true }; function run(changedFile) { if (changedFile) { console.log(`Change to ${changedFile} detected`); @@ -28,7 +29,8 @@ function run(changedFile) { runproc = undefined; } - const thisbuildproc = spawn("npm", ["run", "build:dev"], { stdio: "inherit", shell: true }); + const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts); + const thisbuildstart = Date.now(); buildproc = thisbuildproc; buildproc.on("exit", code => { if (buildproc !== thisbuildproc) { @@ -36,7 +38,8 @@ function run(changedFile) { } buildproc = undefined; if (code === 0) { - runproc = spawn("npm", ["run", "start", "--", ...args], { stdio: "inherit", shell: true }); + console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`); + runproc = spawn("npm", ["run", process.versions.bun ? "bun-run" : "start", "--", ...args], spawnopts); runproc.on("exit", () => { runproc = undefined; }); diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index b6f8e83c..bb64d5da 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -5,7 +5,12 @@ import { config, configPath, loadConfig } from "./configService"; import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; let amnesia = false; -fs.watchFile(configPath, () => { +fs.watchFile(configPath, (now, then) => { + // https://github.com/oven-sh/bun/issues/20542 + if (process.versions.bun && now.mtimeMs == then.mtimeMs) { + return; + } + if (amnesia) { amnesia = false; } else { diff --git a/src/services/webService.ts b/src/services/webService.ts index 3be29a58..20ffcd39 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -46,16 +46,21 @@ export const startWebServer = (): void => { "Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort) ); - void runWsSelfTest("wss", httpsPort).then(ok => { - if (!ok) { - logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`); - if (process.platform == "win32") { + // https://github.com/oven-sh/bun/issues/20547 + if (!process.versions.bun) { + void runWsSelfTest("wss", httpsPort).then(ok => { + if (!ok) { logger.warn( - `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess` + `WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.` ); + if (process.platform == "win32") { + logger.warn( + `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess` + ); + } } - } - }); + }); + } }); }); }; From 2421a16b2ce4c6095a306b4d1ff0938f1bac8872 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 04:54:36 -0700 Subject: [PATCH 08/24] fix: cap spy rotations at C (#2251) for Jade Shadows' spy mission with 4 vaults. will simply do ABCC in this case. Closes #2250 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2251 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index e903f9de..0636e0cb 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -79,7 +79,7 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] if (rewardInfo.VaultsCracked) { const rotations: number[] = []; for (let i = 0; i != rewardInfo.VaultsCracked; ++i) { - rotations.push(i); + rotations.push(Math.min(i, 2)); } return rotations; } From cfd50e74024cb37ff97725acce2db330aec74ce7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 04:54:54 -0700 Subject: [PATCH 09/24] feat: unlockAllSimarisResearchEntries cheat (#2252) Closes #1869 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2252 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/controllers/api/inventoryController.ts | 13 +++++++++++++ src/services/configService.ts | 1 + static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 25 insertions(+) diff --git a/config.json.example b/config.json.example index 72495c8b..50061401 100644 --- a/config.json.example +++ b/config.json.example @@ -55,6 +55,7 @@ "noDojoResearchTime": false, "fastClanAscension": false, "missionsCanGiveAllRelics": false, + "unlockAllSimarisResearchEntries": false, "spoofMasteryRank": -1, "nightwaveStandingMultiplier": 1, "worldState": { diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index ed4b8cff..ecc32ec3 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -334,6 +334,19 @@ export const getInventoryResponse = async ( } } + if (config.unlockAllSimarisResearchEntries) { + inventoryResponse.LibraryPersonalTarget = undefined; + inventoryResponse.LibraryPersonalProgress = [ + "/Lotus/Types/Game/Library/Targets/Research1Target", + "/Lotus/Types/Game/Library/Targets/Research2Target", + "/Lotus/Types/Game/Library/Targets/Research3Target", + "/Lotus/Types/Game/Library/Targets/Research4Target", + "/Lotus/Types/Game/Library/Targets/Research5Target", + "/Lotus/Types/Game/Library/Targets/Research6Target", + "/Lotus/Types/Game/Library/Targets/Research7Target" + ].map(type => ({ TargetType: type, Scans: 10, Completed: true })); + } + return inventoryResponse; }; diff --git a/src/services/configService.ts b/src/services/configService.ts index 74b8f5b7..ab91cf79 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -62,6 +62,7 @@ export interface IConfig { noDojoResearchTime?: boolean; fastClanAscension?: boolean; missionsCanGiveAllRelics?: boolean; + unlockAllSimarisResearchEntries?: boolean; spoofMasteryRank?: number; nightwaveStandingMultiplier?: number; worldState?: { diff --git a/static/webui/index.html b/static/webui/index.html index 5eb4b297..e7fba33c 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -756,6 +756,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index b82f547f..b37c1972 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -172,6 +172,7 @@ dict = { cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, + cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 870f1d89..5da381f4 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -171,6 +171,7 @@ dict = { cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_fastClanAscension: `Fast Clan Ascension`, cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`, + cheats_unlockAllSimarisResearchEntries: `Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`, cheats_save: `Save`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index eb56f094..1a85f6de 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -172,6 +172,7 @@ dict = { cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`, + cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, cheats_save: `Guardar`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 5d522b2b..fffbdab0 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -172,6 +172,7 @@ dict = { cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_fastClanAscension: `Ascension de clan rapide`, cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, + cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e06907bf..d2b3ebb6 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -172,6 +172,7 @@ dict = { cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, + cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 8e2e9344..b2c64c54 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -172,6 +172,7 @@ dict = { cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`, + cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_save: `保存`, From f61d15b496f196dc68bcd0edba8fce154e6df7b9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 04:55:19 -0700 Subject: [PATCH 10/24] chore: replace 'websocket' with 'undici' (#2253) This is a lot more lightweight Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2253 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- Dockerfile | 2 +- package-lock.json | 173 ++++--------------------------------- package.json | 2 +- src/services/webService.ts | 23 ++--- 4 files changed, 26 insertions(+), 174 deletions(-) diff --git a/Dockerfile b/Dockerfile index df0a01ea..ec346634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:24-alpine3.21 -RUN apk add --no-cache bash jq python3 make gcc alpine-sdk +RUN apk add --no-cache bash jq COPY . /app WORKDIR /app diff --git a/package-lock.json b/package-lock.json index bbe6c67e..0fcd908d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,9 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", + "undici": "^7.10.0", "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", - "websocket": "^1.0.35", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "ws": "^8.18.2" @@ -927,6 +927,8 @@ "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -1152,19 +1154,6 @@ "node": ">= 8" } }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1276,46 +1265,6 @@ "node": ">= 0.4" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1477,21 +1426,6 @@ "node": "*" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1565,16 +1499,6 @@ "node": ">= 0.6" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1617,15 +1541,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2155,12 +2070,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2566,17 +2475,13 @@ "node": ">= 0.6" } }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", + "optional": true, + "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -3373,12 +3278,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3419,15 +3318,6 @@ "node": ">= 0.6" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -3441,6 +3331,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", + "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -3472,6 +3371,8 @@ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -3513,38 +3414,6 @@ "node": ">=12" } }, - "node_modules/websocket": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", - "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", - "license": "Apache-2.0", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.63", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/websocket/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/websocket/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/whatwg-url": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", @@ -3665,16 +3534,6 @@ } } }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "engines": { - "node": ">=0.10.32" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a7bf6a69..35886233 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", + "undici": "^7.10.0", "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", - "websocket": "^1.0.35", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "ws": "^8.18.2" diff --git a/src/services/webService.ts b/src/services/webService.ts index 20ffcd39..25e7b56b 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -10,7 +10,7 @@ import { Account } from "../models/loginModel"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; import { IDatabaseAccountJson } from "../types/loginTypes"; import { HydratedDocument } from "mongoose"; -import websocket from "websocket"; +import { Agent, WebSocket } from "undici"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; @@ -67,21 +67,14 @@ export const startWebServer = (): void => { const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise => { return new Promise(resolve => { - const client = new websocket.client({ tlsOptions: { rejectUnauthorized: false } }); - client.connect(`${protocol}://localhost:${port}/custom/selftest`); - client.on("connect", connection => { - connection.on("message", msg => { - if (msg.type == "utf8" && msg.utf8Data == "SpaceNinjaServer") { - resolve(true); - } - }); - connection.on("close", () => { - resolve(false); - }); - }); - client.on("connectFailed", () => { + const agent = new Agent({ connect: { rejectUnauthorized: false } }); + const client = new WebSocket(`${protocol}://localhost:${port}/custom/selftest`, { dispatcher: agent }); + client.onmessage = (e): void => { + resolve(e.data == "SpaceNinjaServer"); + }; + client.onerror = client.onclose = (): void => { resolve(false); - }); + }; }); }; From 271f5bd47ac6260353ee9390a4a19e346e4b51d2 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 04:58:21 -0700 Subject: [PATCH 11/24] fix: also increment LastCompletedDayIdx when completing a 1999 challenge (#2256) Fixes #2255 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2256 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 0636e0cb..7ee1b3c5 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -622,6 +622,7 @@ export const addMissionInventoryUpdates = async ( const calendarProgress = getCalendarProgress(inventory); for (const progress of value) { const challengeName = progress.challenge.substring(progress.challenge.lastIndexOf("/") + 1); + calendarProgress.SeasonProgress.LastCompletedDayIdx++; calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx++; calendarProgress.SeasonProgress.ActivatedChallenges.push(challengeName); } From 82b203e00bc8adef625776a4ae642e5c4a08b63b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:55:48 -0700 Subject: [PATCH 12/24] fix(nemesis): subtract charge from installed mods instead of ideal mods (#2259) Because oull might substitute one of them. Closes #2258 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2259 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 34 +++++++++++------------- src/helpers/nemesisHelpers.ts | 29 +++++++++++++++++--- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 93ea4ee5..dbdb10e3 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -8,16 +8,15 @@ import { getKnifeUpgrade, getNemesisManifest, getNemesisPasscode, - getNemesisPasscodeModTypes, GUESS_CORRECT, GUESS_INCORRECT, GUESS_NEUTRAL, GUESS_NONE, GUESS_WILDCARD, - IKnifeResponse + IKnifeResponse, + parseUpgrade } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; -import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService"; import { getAccountForRequest } from "@/src/services/loginService"; @@ -215,7 +214,19 @@ export const nemesisController: RequestHandler = async (req, res) => { } ]; inventory.Nemesis!.Weakened = true; - await consumePasscodeModCharges(inventory, response); + + // Subtract a charge from all requiem mods installed on parazon + const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; + const dataknifeLoadout = loadout.DATAKNIFE.id( + inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid + ); + const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0; + const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!; + for (let i = 3; i != 6; ++i) { + //logger.debug(`subtracting a charge from ${dataknifeUpgrades[i]}`); + const upgrade = parseUpgrade(inventory, dataknifeUpgrades[i]); + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + } } } else { // Guess was incorrect, increase rank @@ -380,18 +391,3 @@ interface IKnife { AttachedUpgrades: IUpgradeClient[]; HiddenWhenHolstered: boolean; } - -const consumePasscodeModCharges = async ( - inventory: TInventoryDatabaseDocument, - response: IKnifeResponse -): Promise => { - const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; - const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); - const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0; - const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!; - const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!); - for (const modType of modTypes) { - const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType); - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - } -}; diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 0c7e5bdd..4a5de0fb 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -237,7 +237,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti return passcode; }; -const requiemMods: readonly string[] = [ +/*const requiemMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod", @@ -246,7 +246,7 @@ const requiemMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod" -]; +];*/ export const antivirusMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod", @@ -259,12 +259,12 @@ export const antivirusMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod" ]; -export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => { +/*export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => { const passcode = getNemesisPasscode(nemesis); return nemesis.Faction == "FC_INFESTATION" ? passcode.map(i => antivirusMods[i]) : passcode.map(i => requiemMods[i]); -}; +};*/ // Symbols; 0-7 are the normal requiem mods. export const GUESS_NONE = 8; @@ -343,6 +343,27 @@ export const getKnifeUpgrade = ( throw new Error(`${type} does not seem to be installed on parazon?!`); }; +export const parseUpgrade = ( + inventory: TInventoryDatabaseDocument, + str: string +): { ItemId: IOid; ItemType: string } => { + if (str.length == 24) { + const upgrade = inventory.Upgrades.id(str); + if (upgrade) { + return { + ItemId: { $oid: str }, + ItemType: upgrade.ItemType + }; + } + throw new Error(`Could not resolve oid ${str}`); + } else { + return { + ItemId: { $oid: "000000000000000000000000" }, + ItemType: str + }; + } +}; + export const consumeModCharge = ( response: IKnifeResponse, inventory: TInventoryDatabaseDocument, From 7a88f6f4864deee79f850ae0ebb75d1c32d437a4 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:56:08 -0700 Subject: [PATCH 13/24] chore: create AGENTS.md (#2262) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2262 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- AGENTS.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..7a1b6292 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,17 @@ +## In General + +### Prerequisites + +Use `npm i` or `npm ci` to install all dependencies. + +### Testing + +Use `npm run verify` to verify that your changes pass TypeScript's checks. + +### Formatting + +Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure. + +## WebUI Specific + +The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context. From 653798b98773cb25bcae794814d2841f39f6ed3b Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:56:18 -0700 Subject: [PATCH 14/24] fix: use correct dropTable for bounty stage reward (#2263) Re #388 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2263 Reviewed-by: Sainan Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 7ee1b3c5..93c23cb4 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1629,7 +1629,19 @@ function getRandomMissionDrops( } rewardManifests = [job.rewards]; if (job.xpAmounts.length > 1) { - rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)]; + const curentStage = RewardInfo.JobStage! + 1; + const totalStage = job.xpAmounts.length; + let tableIndex = 1; // Stage 2, Stage 3 of 4, and Stage 3 of 5 + + if (curentStage == 1) { + tableIndex = 0; + } else if (curentStage == totalStage) { + tableIndex = 3; + } else if (totalStage == 5 && curentStage == 4) { + tableIndex = 2; + } + + rotations = [tableIndex]; } else { rotations = [0]; } @@ -1638,11 +1650,7 @@ function getRandomMissionDrops( (RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) && !isEndlessJob ) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (ExportRewards[job.rewards]) { - rewardManifests.push(job.rewards); - rotations.push(ExportRewards[job.rewards].length - 1); - } + rotations.push(3); } } } From 444c92f0c60d7bafc320b487105d610f0e1ff6af Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:02:30 -0700 Subject: [PATCH 15/24] fix: use shared count for calendar day indecies (#2265) I'm not sure if this was always this way and I was just really confused when I initially implemented this, or if this was changed in a later version, but at least now it seems to be tracking everything correctly for 38.6.0. Closes #2264 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2265 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/completeCalendarEventController.ts | 36 +++++++++---------- src/services/inventoryService.ts | 20 +++++++++++ src/services/missionInventoryUpdateService.ts | 22 ++++++++---- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/controllers/api/completeCalendarEventController.ts b/src/controllers/api/completeCalendarEventController.ts index 20c8abb3..993b55c7 100644 --- a/src/controllers/api/completeCalendarEventController.ts +++ b/src/controllers/api/completeCalendarEventController.ts @@ -1,4 +1,4 @@ -import { getCalendarProgress, getInventory } from "@/src/services/inventoryService"; +import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { getWorldState } from "@/src/services/worldStateService"; @@ -12,27 +12,23 @@ export const completeCalendarEventController: RequestHandler = async (req, res) const calendarProgress = getCalendarProgress(inventory); const currentSeason = getWorldState().KnownCalendarSeasons[0]; let inventoryChanges: IInventoryChanges = {}; - let dayIndex = 0; - for (const day of currentSeason.Days) { - if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") { - if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) { - if (day.events.length != 0) { - const selection = day.events[parseInt(req.query.CompletedEventIdx as string)]; - if (selection.type == "CET_REWARD") { - inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory)) - .InventoryChanges; - } else if (selection.type == "CET_UPGRADE") { - calendarProgress.YearProgress.Upgrades.push(selection.upgrade!); - } else if (selection.type != "CET_PLOT") { - throw new Error(`unexpected selection type: ${selection.type}`); - } - } - break; - } - ++dayIndex; + const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1; + const day = currentSeason.Days[dayIndex]; + if (day.events.length != 0) { + if (day.events[0].type == "CET_CHALLENGE") { + throw new Error(`completeCalendarEvent should not be used for challenges`); + } + const selection = day.events[parseInt(req.query.CompletedEventIdx as string)]; + if (selection.type == "CET_REWARD") { + inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory)).InventoryChanges; + } else if (selection.type == "CET_UPGRADE") { + calendarProgress.YearProgress.Upgrades.push(selection.upgrade!); + } else if (selection.type != "CET_PLOT") { + throw new Error(`unexpected selection type: ${selection.type}`); } } - calendarProgress.SeasonProgress.LastCompletedDayIdx++; + calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex; + checkCalendarChallengeCompletion(calendarProgress, currentSeason); await inventory.save(); res.json({ InventoryChanges: inventoryChanges, diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index a93dbaf6..a2145d3b 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -84,9 +84,11 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ". import { createMessage } from "./inboxService"; import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper"; import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService"; +import { ICalendarSeason } from "@/src/types/worldStateTypes"; import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers"; import { TAccountDocument } from "./loginService"; import { unixTimesInMs } from "../constants/timeConstants"; +import { addString } from "../helpers/stringHelpers"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -1783,6 +1785,10 @@ export const addChallenges = ( } else { inventory.ChallengeProgress.push({ Name, Progress }); } + + if (Name.startsWith("Calendar")) { + addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name); + } }); const affiliationMods: IAffiliationMods[] = []; @@ -2029,6 +2035,20 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal return inventory.CalendarProgress; }; +export const checkCalendarChallengeCompletion = ( + calendarProgress: ICalendarProgress, + currentSeason: ICalendarSeason +): void => { + const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1; + if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) { + const day = currentSeason.Days[dayIndex]; + if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") { + //logger.debug(`already completed the challenge, skipping ahead`); + calendarProgress.SeasonProgress.LastCompletedDayIdx++; + } + } +}; + export const giveNemesisWeaponRecipe = ( inventory: TInventoryDatabaseDocument, weaponType: string, diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 93c23cb4..c6d98de6 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -33,6 +33,7 @@ import { addSkin, addStanding, applyClientEquipmentUpdates, + checkCalendarChallengeCompletion, combineInventoryChanges, generateRewardSeed, getCalendarProgress, @@ -67,7 +68,15 @@ import { } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; -import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService"; +import { + getLiteSortie, + getSortie, + getWorldState, + idToBountyCycle, + idToDay, + idToWeek, + pushClassicBounties +} from "./worldStateService"; import { config } from "./configService"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import { ISyndicateMissionInfo } from "../types/worldStateTypes"; @@ -620,12 +629,11 @@ export const addMissionInventoryUpdates = async ( } case "CalendarProgress": { const calendarProgress = getCalendarProgress(inventory); - for (const progress of value) { - const challengeName = progress.challenge.substring(progress.challenge.lastIndexOf("/") + 1); - calendarProgress.SeasonProgress.LastCompletedDayIdx++; - calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx++; - calendarProgress.SeasonProgress.ActivatedChallenges.push(challengeName); - } + const currentSeason = getWorldState().KnownCalendarSeasons[0]; + calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex( + x => x.events[0].challenge == value[value.length - 1].challenge + ); + checkCalendarChallengeCompletion(calendarProgress, currentSeason); break; } case "duviriCaveOffers": { From 636d3100f3cdd1b6d8a1b8148e0a4fc6a9dffa32 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 24 Jun 2025 01:34:47 +0200 Subject: [PATCH 16/24] fixup for 444c92f0c60d7bafc320b487105d610f0e1ff6af I forgot to save this file --- src/controllers/api/updateChallengeProgressController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 9b7292e6..68ae6c55 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -13,7 +13,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res const inventory = await getInventory( account._id.toString(), - "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations" + "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress" ); let affiliationMods: IAffiliationMods[] = []; if (challenges.ChallengeProgress) { From 122950034e430e89ddb09cf5451ec09e3660ff8e Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:51:09 -0700 Subject: [PATCH 17/24] chore: cleanup purchase stuff (#2266) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2266 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/giftingController.ts | 4 +- src/services/purchaseService.ts | 52 +++++++++--------------- src/types/purchaseTypes.ts | 40 +++++++++++++++--- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/controllers/api/giftingController.ts b/src/controllers/api/giftingController.ts index 55865cee..9a532776 100644 --- a/src/controllers/api/giftingController.ts +++ b/src/controllers/api/giftingController.ts @@ -11,13 +11,13 @@ import { import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { IOid } from "@/src/types/commonTypes"; -import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes"; +import { IInventoryChanges, IPurchaseParams, PurchaseSource } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; import { ExportBundles, ExportFlavour } from "warframe-public-export-plus"; export const giftingController: RequestHandler = async (req, res) => { const data = getJSONfromString(String(req.body)); - if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) { + if (data.PurchaseParams.Source != PurchaseSource.Market || !data.PurchaseParams.UsePremium) { throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`); } diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 4fb5bb2d..56cc9ed7 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -11,7 +11,13 @@ import { import { getRandomWeightedRewardUc } from "@/src/services/rngService"; import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; -import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes"; +import { + IPurchaseRequest, + IPurchaseResponse, + SlotPurchase, + IInventoryChanges, + PurchaseSource +} from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; import worldState from "@/static/fixed_responses/worldState/worldState.json"; import { @@ -52,7 +58,7 @@ export const handlePurchase = async ( const prePurchaseInventoryChanges: IInventoryChanges = {}; let seed: bigint | undefined; - if (purchaseRequest.PurchaseParams.Source == 7) { + if (purchaseRequest.PurchaseParams.Source == PurchaseSource.Vendor) { let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!); if (manifest) { manifest = applyStandingToVendorManifest(inventory, manifest); @@ -69,18 +75,12 @@ export const handlePurchase = async ( } if (!config.dontSubtractPurchaseCreditCost) { if (offer.RegularPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.RegularPrice[0], false) - ); + updateCurrency(inventory, offer.RegularPrice[0], false, prePurchaseInventoryChanges); } } if (!config.dontSubtractPurchasePlatinumCost) { if (offer.PremiumPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.PremiumPrice[0], true) - ); + updateCurrency(inventory, offer.PremiumPrice[0], true, prePurchaseInventoryChanges); } } if (!config.dontSubtractPurchaseItemCost) { @@ -166,18 +166,15 @@ export const handlePurchase = async ( ); combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges); - const currencyChanges = updateCurrency( + updateCurrency( inventory, purchaseRequest.PurchaseParams.ExpectedPrice, - purchaseRequest.PurchaseParams.UsePremium + purchaseRequest.PurchaseParams.UsePremium, + prePurchaseInventoryChanges ); - purchaseResponse.InventoryChanges = { - ...currencyChanges, - ...purchaseResponse.InventoryChanges - }; switch (purchaseRequest.PurchaseParams.Source) { - case 1: { + case PurchaseSource.VoidTrader: { if (purchaseRequest.PurchaseParams.SourceId! != worldState.VoidTraders[0]._id.$oid) { throw new Error("invalid request source"); } @@ -186,10 +183,7 @@ export const handlePurchase = async ( ); if (offer) { if (!config.dontSubtractPurchaseCreditCost) { - combineInventoryChanges( - purchaseResponse.InventoryChanges, - updateCurrency(inventory, offer.RegularPrice, false) - ); + updateCurrency(inventory, offer.RegularPrice, false, purchaseResponse.InventoryChanges); } if (purchaseRequest.PurchaseParams.ExpectedPrice) { throw new Error(`vendor purchase should not have an expected price`); @@ -207,7 +201,7 @@ export const handlePurchase = async ( } break; } - case 2: + case PurchaseSource.SyndicateFavor: { const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!; if (purchaseRequest.PurchaseParams.UseFreeFavor!) { @@ -244,22 +238,16 @@ export const handlePurchase = async ( } } break; - case 7: + case PurchaseSource.Vendor: if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) { const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem); if (offer) { if (typeof offer.credits == "number" && !config.dontSubtractPurchaseCreditCost) { - combineInventoryChanges( - purchaseResponse.InventoryChanges, - updateCurrency(inventory, offer.credits, false) - ); + updateCurrency(inventory, offer.credits, false, purchaseResponse.InventoryChanges); } if (typeof offer.platinum == "number" && !config.dontSubtractPurchasePlatinumCost) { - combineInventoryChanges( - purchaseResponse.InventoryChanges, - updateCurrency(inventory, offer.platinum, true) - ); + updateCurrency(inventory, offer.platinum, true, purchaseResponse.InventoryChanges); } if (offer.itemPrices && !config.dontSubtractPurchaseItemCost) { handleItemPrices( @@ -275,7 +263,7 @@ export const handlePurchase = async ( throw new Error(`vendor purchase should not have an expected price`); } break; - case 18: { + case PurchaseSource.PrimeVaultTrader: { if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) { throw new Error("invalid request source"); } diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index 8cb92ccc..0ccff176 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -10,14 +10,42 @@ import { ICrewMemberClient } from "./inventoryTypes/inventoryTypes"; +export enum PurchaseSource { + Market = 0, + VoidTrader = 1, + SyndicateFavor = 2, + DailyDeal = 3, + Arsenal = 4, + Profile = 5, + Hub = 6, + Vendor = 7, + AppearancePreview = 8, + Museum = 9, + Operator = 10, + PlayerShip = 11, + Crewship = 12, + MenuStyle = 13, + MenuHud = 14, + Chat = 15, + Inventory = 16, + StarChart = 17, + PrimeVaultTrader = 18, + Incubator = 19, + Prompt = 20, + Kaithe = 21, + DuviriWeapon = 22, + UpdateScreen = 23, + Motorcycle = 24 +} + export interface IPurchaseRequest { PurchaseParams: IPurchaseParams; buildLabel: string; } export interface IPurchaseParams { - Source: number; - SourceId?: string; // for Source 1, 7 & 18 + Source: PurchaseSource; + SourceId?: string; // VoidTrader, Vendor, PrimeVaultTrader StoreItem: string; StorePage: string; SearchTerm: string; @@ -25,10 +53,10 @@ export interface IPurchaseParams { Quantity: number; UsePremium: boolean; ExpectedPrice: number; - SyndicateTag?: string; // for Source 2 - UseFreeFavor?: boolean; // for Source 2 - ExtraPurchaseInfoJson?: string; // for Source 7 - IsWeekly?: boolean; // for Source 7 + SyndicateTag?: string; // SyndicateFavor + UseFreeFavor?: boolean; // SyndicateFavor + ExtraPurchaseInfoJson?: string; // Vendor + IsWeekly?: boolean; // Vendor } export type IInventoryChanges = { From 9a034b1c8a609c6191e1cb4fc588adef2dbb1f6a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:51:48 -0700 Subject: [PATCH 18/24] feat: unfaithful bug fixes (#2267) Closes #2257 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2267 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 4 ++++ src/services/configService.ts | 4 ++++ src/services/missionInventoryUpdateService.ts | 4 +++- src/services/worldStateService.ts | 17 ++++++++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index 50061401..37300e0c 100644 --- a/config.json.example +++ b/config.json.example @@ -58,6 +58,10 @@ "unlockAllSimarisResearchEntries": false, "spoofMasteryRank": -1, "nightwaveStandingMultiplier": 1, + "unfaithfulBugFixes": { + "ignore1999LastRegionPlayed": false, + "fixXtraCheeseTimer": false + }, "worldState": { "creditBoost": false, "affinityBoost": false, diff --git a/src/services/configService.ts b/src/services/configService.ts index ab91cf79..ff7c0670 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -65,6 +65,10 @@ export interface IConfig { unlockAllSimarisResearchEntries?: boolean; spoofMasteryRank?: number; nightwaveStandingMultiplier?: number; + unfaithfulBugFixes?: { + ignore1999LastRegionPlayed?: boolean; + fixXtraCheeseTimer?: boolean; + }; worldState?: { creditBoost?: boolean; affinityBoost?: boolean; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index c6d98de6..cd523253 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -268,7 +268,9 @@ export const addMissionInventoryUpdates = async ( addMissionComplete(inventory, value); break; case "LastRegionPlayed": - inventory.LastRegionPlayed = value; + if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) { + inventory.LastRegionPlayed = value; + } break; case "RawUpgrades": addMods(inventory, value); diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index ea48ef44..a2902f40 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1325,6 +1325,17 @@ export const getWorldState = (buildLabel?: string): IWorldState => { const cheeseInterval = hourInSeconds * 8; const cheeseDuration = hourInSeconds * 2; const cheeseIndex = Math.trunc(timeSecs / cheeseInterval); + let cheeseStart = cheeseIndex * cheeseInterval; + let cheeseEnd = cheeseStart + cheeseDuration; + let cheeseNext = (cheeseIndex + 1) * cheeseInterval; + // Live servers only update the start time once it happens, which makes the + // client show a negative countdown during off-hours. Optionally adjust the + // times so the next activation is always in the future. + if (config.unfaithfulBugFixes?.fixXtraCheeseTimer && timeSecs >= cheeseEnd) { + cheeseStart = cheeseNext; + cheeseEnd = cheeseStart + cheeseDuration; + cheeseNext += cheeseInterval; + } const tmp: ITmp = { cavabegin: "1690761600", PurchasePlatformLockEnabled: true, @@ -1349,9 +1360,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => { ennnd: true, mbrt: true, fbst: { - a: cheeseIndex * cheeseInterval, // This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live. - e: cheeseIndex * cheeseInterval + cheeseDuration, - n: (cheeseIndex + 1) * cheeseInterval + a: cheeseStart, + e: cheeseEnd, + n: cheeseNext }, sfn: [550, 553, 554, 555][halfHour % 4] }; From f242d9f873cbf2b0013a8c87df3bb28e19d1f7b9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:52:02 -0700 Subject: [PATCH 19/24] chore: make ws self test work under bun (#2268) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2268 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/webService.ts | 52 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/services/webService.ts b/src/services/webService.ts index 25e7b56b..ecc5a494 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -10,7 +10,7 @@ import { Account } from "../models/loginModel"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; import { IDatabaseAccountJson } from "../types/loginTypes"; import { HydratedDocument } from "mongoose"; -import { Agent, WebSocket } from "undici"; +import { Agent, WebSocket as UnidiciWebSocket } from "undici"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; @@ -46,35 +46,45 @@ export const startWebServer = (): void => { "Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort) ); - // https://github.com/oven-sh/bun/issues/20547 - if (!process.versions.bun) { - void runWsSelfTest("wss", httpsPort).then(ok => { - if (!ok) { + void runWsSelfTest("wss", httpsPort).then(ok => { + if (!ok) { + logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`); + if (process.platform == "win32") { logger.warn( - `WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.` + `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess` ); - if (process.platform == "win32") { - logger.warn( - `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess` - ); - } } - }); - } + } + }); }); }); }; const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise => { return new Promise(resolve => { - const agent = new Agent({ connect: { rejectUnauthorized: false } }); - const client = new WebSocket(`${protocol}://localhost:${port}/custom/selftest`, { dispatcher: agent }); - client.onmessage = (e): void => { - resolve(e.data == "SpaceNinjaServer"); - }; - client.onerror = client.onclose = (): void => { - resolve(false); - }; + // https://github.com/oven-sh/bun/issues/20547 + if (process.versions.bun) { + const client = new WebSocket(`${protocol}://localhost:${port}/custom/selftest`, { + tls: { rejectUnauthorized: false } + } as unknown as string); + client.onmessage = (e): void => { + resolve(e.data == "SpaceNinjaServer"); + }; + client.onerror = client.onclose = (): void => { + resolve(false); + }; + } else { + const agent = new Agent({ connect: { rejectUnauthorized: false } }); + const client = new UnidiciWebSocket(`${protocol}://localhost:${port}/custom/selftest`, { + dispatcher: agent + }); + client.onmessage = (e): void => { + resolve(e.data == "SpaceNinjaServer"); + }; + client.onerror = client.onclose = (): void => { + resolve(false); + }; + } }); }; From ca3cfb5299683c2511462441be3df9bd0f2748e4 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:03:18 -0700 Subject: [PATCH 20/24] feat(webui): max focus schools (#2270) Closes #1433 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2270 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/index.html | 6 +- static/webui/script.js | 235 ++++++++++++++++++++++++++++++++ static/webui/translations/de.js | 2 + static/webui/translations/en.js | 2 + static/webui/translations/es.js | 2 + static/webui/translations/fr.js | 2 + static/webui/translations/ru.js | 2 + static/webui/translations/zh.js | 2 + 8 files changed, 252 insertions(+), 1 deletion(-) diff --git a/static/webui/index.html b/static/webui/index.html index e7fba33c..0780b78f 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -803,8 +803,12 @@

- + +

+
diff --git a/static/webui/script.js b/static/webui/script.js index 110dd1a8..c6c6727b 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -2339,3 +2339,238 @@ async function doUnlockAllMissions() { await fetch("/custom/completeAllMissions?" + window.authz); updateInventory(); } + +const importSamples = { + maxFocus: { + FocusUpgrades: [ + { + ItemType: "/Lotus/Upgrades/Focus/Attack/AttackFocusAbility" + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Stats/MoreAmmoFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Residual/PowerSnapFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Residual/PhysicalDamageFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/CloakAttackChargeFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Stats/RegenAmmoFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility" + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/WardFocusAbility" + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility" + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/PowerFocusAbility" + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/KnockdownImmunityFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/UnairuWispFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/SunderingDissipationUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/MagneticExtensionUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/MagneticFieldFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Residual/ArmourBuffFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/ClearStaticOnKillFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Residual/SecondChanceDamageBuffFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Residual/SecondChanceFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Ward/Active/InvulnerableReturnFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/ConsecutivePowerUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/AttackEfficiencyFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/GhostlyTouchUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/GhostWaveUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Attack/Active/ConsecutiveEfficienyUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/ProjectionStretchUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/ProjectionExecutionUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/FinisherTransferenceUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/ComboAmpDamageFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Residual/MeleeComboFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Residual/MeleeXpFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/LiftHitWaveUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/LiftHitDamageUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Stats/MoveSpeedFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Tactic/Active/SlamComboFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Active/PowerFieldFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Active/DisarmedEnergyUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Stats/EnergyPoolFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Residual/EnergyOverTimeFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Active/BlastSlowFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Stats/EnergyRestoreFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Residual/FreeAbilityCastsFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Active/DisarmingProjectionUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Residual/SlowHeadshotDamageFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Power/Active/DashBubbleFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Stats/HealthRegenFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Residual/RadialXpFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/DefenseShieldFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/CloakHealFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/DefenseShieldBreakFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/DashImmunityFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Residual/InstantReviveFocusUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/SonicDissipationUpgrade", + Level: 3 + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Stats/HealthMaxFocusUpgrade", + Level: 3, + IsUniversal: true + }, + { + ItemType: "/Lotus/Upgrades/Focus/Defense/Active/CloakHealOthersFocusUpgrade", + Level: 2 + } + ] + } +}; +function setImportSample(key) { + $("#import-inventory").val(JSON.stringify(importSamples[key], null, 2)); +} diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index b37c1972..a5f67e01 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -185,6 +185,8 @@ dict = { cheats_none: `Keines`, import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, werden in deinem Account überschrieben.`, import_submit: `Absenden`, + import_samples: `[UNTRANSLATED] Samples:`, + import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 5da381f4..a5ae9a12 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -184,6 +184,8 @@ dict = { 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`, + import_samples: `Samples:`, + import_samples_maxFocus: `All Focus Schools Maxed Out`, upgrade_Equilibrium: `+|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, upgrade_MeleeCritDamage: `+|VAL|% Melee Critical Damage`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 1a85f6de..7b77bb7f 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -185,6 +185,8 @@ dict = { cheats_none: `Ninguno`, import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador serán sobrescritos en tu cuenta.`, import_submit: `Enviar`, + import_samples: `[UNTRANSLATED] Samples:`, + import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`, upgrade_MeleeCritDamage: `+|VAL|% de daño crítico cuerpo a cuerpo`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index fffbdab0..6af1c70d 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -185,6 +185,8 @@ dict = { cheats_none: `Aucun`, import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire écraseront celles présentes dans la base de données.`, import_submit: `Soumettre`, + import_samples: `[UNTRANSLATED] Samples:`, + import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index d2b3ebb6..4b48cbfc 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -185,6 +185,8 @@ dict = { cheats_none: `Отсутствует`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить`, + import_samples: `[UNTRANSLATED] Samples:`, + import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index b2c64c54..49fbe711 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -185,6 +185,8 @@ dict = { cheats_none: `无`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, + import_samples: `[UNTRANSLATED] Samples:`, + import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, upgrade_Equilibrium: `+|VAL|% 能量 来自生命球, +|VAL|% 生命 来自能量球`, upgrade_MeleeCritDamage: `+|VAL|% 近战暴击伤害`, From 36f2828d37b42d0f903c0396acacd2f147730e0d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:19:32 -0700 Subject: [PATCH 21/24] feat: void trader (#2269) Closes #2245 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2269 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 2 + src/models/inboxModel.ts | 12 +- src/services/configService.ts | 2 + src/services/inboxService.ts | 48 +- src/services/missionInventoryUpdateService.ts | 2 +- src/services/purchaseService.ts | 10 +- src/services/worldStateService.ts | 75 + src/types/worldStateTypes.ts | 16 + static/fixed_responses/eventMessages.json | 12 - static/fixed_responses/worldState/baro.json | 413 ++++ .../worldState/worldState.json | 1866 ----------------- static/webui/index.html | 8 + static/webui/translations/de.js | 2 + static/webui/translations/en.js | 2 + static/webui/translations/es.js | 2 + static/webui/translations/fr.js | 2 + static/webui/translations/ru.js | 2 + static/webui/translations/zh.js | 2 + 18 files changed, 575 insertions(+), 1903 deletions(-) delete mode 100644 static/fixed_responses/eventMessages.json create mode 100644 static/fixed_responses/worldState/baro.json diff --git a/config.json.example b/config.json.example index 37300e0c..d75d619f 100644 --- a/config.json.example +++ b/config.json.example @@ -42,6 +42,8 @@ "noDeathMarks": false, "noKimCooldowns": false, "fullyStockedVendors": false, + "baroAlwaysAvailable": false, + "baroFullyStocked": false, "syndicateMissionsRepeatable": false, "unlockAllProfitTakerStages": false, "instantFinishRivenChallenge": false, diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index 37a7fc5e..d339707e 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -17,7 +17,6 @@ export interface IMessageDatabase extends IMessage { ownerId: Types.ObjectId; date: Date; //created at attVisualOnly?: boolean; - expiry?: Date; _id: Types.ObjectId; } @@ -33,6 +32,7 @@ export interface IMessage { att?: string[]; countedAtt?: ITypeCount[]; transmission?: string; + CrossPlatform?: boolean; arg?: Arg[]; gifts?: IGift[]; r?: boolean; @@ -107,7 +107,9 @@ const messageSchema = new Schema( lowPrioNewPlayers: Boolean, startDate: Date, endDate: Date, + date: { type: Date, required: true }, r: Boolean, + CrossPlatform: Boolean, att: { type: [String], default: undefined }, gifts: { type: [giftSchema], default: undefined }, countedAtt: { type: [typeCountSchema], default: undefined }, @@ -128,7 +130,7 @@ const messageSchema = new Schema( declineAction: String, hasAccountAction: Boolean }, - { timestamps: { createdAt: "date", updatedAt: false }, id: false } + { id: false } ); messageSchema.virtual("messageId").get(function (this: IMessageDatabase) { @@ -151,13 +153,15 @@ messageSchema.set("toJSON", { if (messageDatabase.startDate && messageDatabase.endDate) { messageClient.startDate = toMongoDate(messageDatabase.startDate); - messageClient.endDate = toMongoDate(messageDatabase.endDate); + } else { + delete messageClient.startDate; + delete messageClient.endDate; } } }); messageSchema.index({ ownerId: 1 }); -messageSchema.index({ expiry: 1 }, { expireAfterSeconds: 0 }); +messageSchema.index({ endDate: 1 }, { expireAfterSeconds: 0 }); export const Inbox = model("Inbox", messageSchema, "inbox"); diff --git a/src/services/configService.ts b/src/services/configService.ts index ff7c0670..404e4b49 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -49,6 +49,8 @@ export interface IConfig { noDeathMarks?: boolean; noKimCooldowns?: boolean; fullyStockedVendors?: boolean; + baroAlwaysAvailable?: boolean; + baroFullyStocked?: boolean; syndicateMissionsRepeatable?: boolean; unlockAllProfitTakerStages?: boolean; instantFinishRivenChallenge?: boolean; diff --git a/src/services/inboxService.ts b/src/services/inboxService.ts index ea837b01..cc5afc29 100644 --- a/src/services/inboxService.ts +++ b/src/services/inboxService.ts @@ -2,8 +2,8 @@ import { IMessageDatabase, Inbox } from "@/src/models/inboxModel"; import { getAccountForRequest } from "@/src/services/loginService"; import { HydratedDocument, Types } from "mongoose"; import { Request } from "express"; -import eventMessages from "@/static/fixed_responses/eventMessages.json"; -import { logger } from "@/src/utils/logger"; +import { unixTimesInMs } from "../constants/timeConstants"; +import { config } from "./configService"; export const getAllMessagesSorted = async (accountId: string): Promise[]> => { const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 }); @@ -29,40 +29,56 @@ export const deleteAllMessagesRead = async (accountId: string): Promise => export const createNewEventMessages = async (req: Request): Promise => { const account = await getAccountForRequest(req); - const latestEventMessageDate = account.LatestEventMessageDate; + const newEventMessages: IMessageCreationTemplate[] = []; - //TODO: is baroo there? create these kind of messages too (periodical messages) - const newEventMessages = eventMessages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate); + // Baro + const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14)); + const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000; + const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12); + if (account.LatestEventMessageDate.getTime() < baroActualStart) { + newEventMessages.push({ + sndr: "/Lotus/Language/G1Quests/VoidTraderName", + sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle", + msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage", + icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png", + startDate: new Date(baroActualStart), + endDate: new Date(baroStart + unixTimesInMs.day * 14), + CrossPlatform: true, + arg: [ + { + Key: "NODE_NAME", + Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4] + } + ], + date: new Date(baroActualStart) + }); + } if (newEventMessages.length === 0) { - logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`); return; } - const savedEventMessages = await createMessage(account._id, newEventMessages); - logger.debug("created event messages", savedEventMessages); + await createMessage(account._id, newEventMessages); const latestEventMessage = newEventMessages.reduce((prev, current) => - prev.eventMessageDate > current.eventMessageDate ? prev : current + prev.startDate! > current.startDate! ? prev : current ); - - account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate); + account.LatestEventMessageDate = new Date(latestEventMessage.startDate!); await account.save(); }; export const createMessage = async ( accountId: string | Types.ObjectId, messages: IMessageCreationTemplate[] -): Promise[]> => { +): Promise => { const ownerIdMessages = messages.map(m => ({ ...m, + date: m.date ?? new Date(), ownerId: accountId })); - - const savedMessages = await Inbox.insertMany(ownerIdMessages); - return savedMessages as HydratedDocument[]; + await Inbox.insertMany(ownerIdMessages); }; export interface IMessageCreationTemplate extends Omit { - ownerId?: string; + date?: Date; } diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index cd523253..d343694a 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -482,7 +482,7 @@ export const addMissionInventoryUpdates = async ( msg: "/Lotus/Language/G1Quests/DeathMarkMessage", icon: "/Lotus/Interface/Icons/Npcs/Stalker_d.png", highPriority: true, - expiry: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's clear if this is correct. + endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct. } ]); } diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 56cc9ed7..59647fdf 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -19,7 +19,8 @@ import { PurchaseSource } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; -import worldState from "@/static/fixed_responses/worldState/worldState.json"; +import { getWorldState } from "./worldStateService"; +import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; import { ExportBoosterPacks, ExportBoosters, @@ -175,6 +176,7 @@ export const handlePurchase = async ( switch (purchaseRequest.PurchaseParams.Source) { case PurchaseSource.VoidTrader: { + const worldState = getWorldState(); if (purchaseRequest.PurchaseParams.SourceId! != worldState.VoidTraders[0]._id.$oid) { throw new Error("invalid request source"); } @@ -264,14 +266,14 @@ export const handlePurchase = async ( } break; case PurchaseSource.PrimeVaultTrader: { - if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) { + if (purchaseRequest.PurchaseParams.SourceId! != staticWorldState.PrimeVaultTraders[0]._id.$oid) { throw new Error("invalid request source"); } const offer = - worldState.PrimeVaultTraders[0].Manifest.find( + staticWorldState.PrimeVaultTraders[0].Manifest.find( x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem ) ?? - worldState.PrimeVaultTraders[0].EvergreenManifest.find( + staticWorldState.PrimeVaultTraders[0].EvergreenManifest.find( x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem ); if (offer) { diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index a2902f40..66e07235 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1,4 +1,5 @@ import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; +import baro from "@/static/fixed_responses/worldState/baro.json"; import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json"; import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json"; import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json"; @@ -19,6 +20,8 @@ import { ISyndicateMissionInfo, ITmp, IVoidStorm, + IVoidTrader, + IVoidTraderOffer, IWorldState, TCircuitGameMode } from "../types/worldStateTypes"; @@ -1114,6 +1117,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { LiteSorties: [], ActiveMissions: [], GlobalUpgrades: [], + VoidTraders: [], VoidStorms: [], EndlessXpChoices: [], KnownCalendarSeasons: [], @@ -1242,6 +1246,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }); } + // Baro + { + const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14)); + const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000; + const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12); + const baroEnd = baroStart + unixTimesInMs.day * 14; + const baroNode = ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]; + const vt: IVoidTrader = { + _id: { $oid: ((baroStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "493c96d6067610bc" }, + Activation: { $date: { $numberLong: baroActualStart.toString() } }, + Expiry: { $date: { $numberLong: baroEnd.toString() } }, + Character: "Baro'Ki Teel", + Node: baroNode, + Manifest: [] + }; + worldState.VoidTraders.push(vt); + if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) { + vt.Manifest = []; + if (config.baroFullyStocked) { + for (const armorSet of baro.armorSets) { + if (Array.isArray(armorSet[0])) { + for (const set of armorSet as IVoidTraderOffer[][]) { + for (const item of set) { + vt.Manifest.push(item); + } + } + } else { + for (const item of armorSet as IVoidTraderOffer[]) { + vt.Manifest.push(item); + } + } + } + for (const item of baro.rest) { + vt.Manifest.push(item); + } + } else { + const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000)); + // TOVERIFY: Constraint for upgrades amount? + // TOVERIFY: Constraint for weapon amount? + // TOVERIFY: Constraint for relics amount? + let armorSet = rng.randomElement(baro.armorSets)!; + if (Array.isArray(armorSet[0])) { + armorSet = rng.randomElement(baro.armorSets)!; + } + while (vt.Manifest.length + armorSet.length < 31) { + const item = rng.randomElement(baro.rest)!; + if (vt.Manifest.indexOf(item) == -1) { + const set = baro.allIfAny.find(set => set.indexOf(item.ItemType) != -1); + if (set) { + for (const itemType of set) { + vt.Manifest.push(baro.rest.find(x => x.ItemType == itemType)!); + } + } else { + vt.Manifest.push(item); + } + } + } + const overflow = 31 - (vt.Manifest.length + armorSet.length); + if (overflow > 0) { + vt.Manifest.splice(0, overflow); + } + for (const armor of armorSet) { + vt.Manifest.push(armor as IVoidTraderOffer); + } + } + for (const item of baro.evergreen) { + vt.Manifest.push(item); + } + } + } + // Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST) { const rollover = getSortieTime(day); diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 73aa9d78..88544d6c 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -12,6 +12,7 @@ export interface IWorldState { ActiveMissions: IFissure[]; GlobalUpgrades: IGlobalUpgrade[]; NodeOverrides: INodeOverride[]; + VoidTraders: IVoidTrader[]; VoidStorms: IVoidStorm[]; PVPChallengeInstances: IPVPChallengeInstance[]; EndlessXpChoices: IEndlessXpChoice[]; @@ -140,6 +141,21 @@ export interface ILiteSortie { }[]; } +export interface IVoidTrader { + _id: IOid; + Activation: IMongoDate; + Expiry: IMongoDate; + Character: string; + Node: string; + Manifest: IVoidTraderOffer[]; +} + +export interface IVoidTraderOffer { + ItemType: string; + PrimePrice: number; + RegularPrice: number; +} + export interface IVoidStorm { _id: IOid; Node: string; diff --git a/static/fixed_responses/eventMessages.json b/static/fixed_responses/eventMessages.json deleted file mode 100644 index 62cb477a..00000000 --- a/static/fixed_responses/eventMessages.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Messages": [ - { - "sub": "Welcome to Space Ninja Server", - "sndr": "/Lotus/Language/Bosses/Ordis", - "msg": "Enjoy your Space Ninja Experience", - "icon": "/Lotus/Interface/Icons/Npcs/Ordis.png", - "eventMessageDate": "2025-01-30T13:00:00.000Z", - "r": false - } - ] -} diff --git a/static/fixed_responses/worldState/baro.json b/static/fixed_responses/worldState/baro.json new file mode 100644 index 00000000..80ad3726 --- /dev/null +++ b/static/fixed_responses/worldState/baro.json @@ -0,0 +1,413 @@ +{ + "evergreen": [ + { "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 } + ], + "armorSets": [ + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourA", "PrimePrice": 350, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourC", "PrimePrice": 150, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourL", "PrimePrice": 300, "RegularPrice": 150000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoA", "PrimePrice": 310, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoC", "PrimePrice": 175, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoL", "PrimePrice": 225, "RegularPrice": 150000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeA", "PrimePrice": 400, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeC", "PrimePrice": 350, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeL", "PrimePrice": 400, "RegularPrice": 350000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisAArmor", "PrimePrice": 300, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisCArmor", "PrimePrice": 250, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisLArmor", "PrimePrice": 225, "RegularPrice": 175000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesArmArmor", "PrimePrice": 350, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesChestArmor", "PrimePrice": 300, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesLegArmor", "PrimePrice": 350, "RegularPrice": 150000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorA", "PrimePrice": 315, "RegularPrice": 215000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorC", "PrimePrice": 325, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorL", "PrimePrice": 300, "RegularPrice": 200000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmLeftArmor", "PrimePrice": 65, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmRightArmor", "PrimePrice": 65, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeChestArmor", "PrimePrice": 150, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegLeftArmor", "PrimePrice": 65, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegRightArmor", "PrimePrice": 65, "RegularPrice": 75000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmLeftArmor", "PrimePrice": 100, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmRightArmor", "PrimePrice": 100, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoChestArmor", "PrimePrice": 225, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegLeftArmor", "PrimePrice": 100, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegRightArmor", "PrimePrice": 100, "RegularPrice": 55000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorElixis", "PrimePrice": 325, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorPrisma", "PrimePrice": 325, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorElixis", "PrimePrice": 275, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorPrisma", "PrimePrice": 275, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorElixis", "PrimePrice": 300, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorPrisma", "PrimePrice": 300, "RegularPrice": 175000 } + ], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorA", "PrimePrice": 315, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorC", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorL", "PrimePrice": 275, "RegularPrice": 115000 } + ], + [ + [{ "ItemType": "/Lotus/Types/StoreItems/Packages/VTEosArmourBundle", "PrimePrice": 285, "RegularPrice": 260000 }], + [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosALArmor", "PrimePrice": 50, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosARArmor", "PrimePrice": 50, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosChestArmor", "PrimePrice": 125, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLLArmor", "PrimePrice": 65, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLRArmor", "PrimePrice": 65, "RegularPrice": 50000 } + ] + ] + ], + "rest": [ + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/Halloween2014Wings/PrismaNaberusArmArmor", "PrimePrice": 220, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TennoCon2024GlyphAlt", "PrimePrice": 15, "RegularPrice": 1000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/Tennocon2024EmoteAlt", "PrimePrice": 15, "RegularPrice": 1000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/HeartOfDeimosAlbumCoverPoster", "PrimePrice": 80, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConC", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConJ", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConH", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T3VoidProjectionVoltOdonataPrimeBronze", "PrimePrice": 125, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionVoltOdonataPrimeBronze", "PrimePrice": 125, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionMagNovaVaultBBronze", "PrimePrice": 125, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeNelumboCape", "PrimePrice": 325, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeTwinGrakatas", "PrimePrice": 300, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/Staff/TnRibbonStaffSkin", "PrimePrice": 350, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GunBlade/GrnGunBlade/GrnGunblade", "PrimePrice": 550, "RegularPrice": 325000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpBFG/Vandal/VandalCrpBFG", "PrimePrice": 650, "RegularPrice": 550000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Event/AmbulasEvent/Expert/SecondaryExplosionRadiusModExpert", "PrimePrice": 350, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/Dragon2024BadgeItem", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingDamageOnReloadMod", "PrimePrice": 375, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingRifleFireIterationsMod", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaruukDoanStyle", "PrimePrice": 75, "RegularPrice": 60000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OctaviaBobbleHead", "PrimePrice": 50, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/GaussSentinelSkin", "PrimePrice": 500, "RegularPrice": 425000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusVinesSigil", "PrimePrice": 55, "RegularPrice": 60000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageExcaliburActionProto", "PrimePrice": 75, "RegularPrice": 60000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageIvaraAction", "PrimePrice": 75, "RegularPrice": 60000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/HornSkullScarf", "PrimePrice": 325, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/RhinoDeluxeSigil", "PrimePrice": 45, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Events/InfQuantaInfestedAladV", "PrimePrice": 325, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterD", "PrimePrice": 90, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterB", "PrimePrice": 90, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterC", "PrimePrice": 90, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponElectricityDamageMod", "PrimePrice": 350, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarShieldMaxModExpert", "PrimePrice": 350, "RegularPrice": 225000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterA", "PrimePrice": 90, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/EventSigilScarletSpear", "PrimePrice": 45, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnOrokinRifle/GrnOrokinRifleWeapon", "PrimePrice": 675, "RegularPrice": 625000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisNikana", "PrimePrice": 375, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/GaussSentinelWings", "PrimePrice": 400, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/GaussSentinelTail", "PrimePrice": 400, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/GaussSentinelMask", "PrimePrice": 450, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerCutter", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2023EmblemItem", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearFreeTigerSigil", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2022EmblemItem", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Leverian/IvaraLeverianPovisRecordsDecoration", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodDuviriOperator", "PrimePrice": 550, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpTonfa/CrpPrismaTonfa", "PrimePrice": 450, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneDuviri", "PrimePrice": 800, "RegularPrice": 650000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/AshLevarianTiara", "PrimePrice": 550, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraB", "PrimePrice": 250, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Promo/Warframe/PromoParis", "PrimePrice": 315, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/ThraxSigil", "PrimePrice": 50, "RegularPrice": 55000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/PrismaLenz/PrismaLenzWeapon", "PrimePrice": 575, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Vignettes/Warframes/ArchwingAFItem", "PrimePrice": 100, "RegularPrice": 330000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/LavosAlchemistWallpaper", "PrimePrice": 275, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GrendelOrokinDishSet", "PrimePrice": 110, "RegularPrice": 130000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemB", "PrimePrice": 200, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/NezhaEtchingsTablets", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GaussTowerOfAltraDeco", "PrimePrice": 110, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPlanter", "PrimePrice": 125, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPedestal", "PrimePrice": 150, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Leggings/LeggingsNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/BodySuits/BodySuitNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Sleeves/SleevesNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodNovaEngineer", "PrimePrice": 350, "RegularPrice": 375000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BekranZaftBucketBroom", "PrimePrice": 100, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Warfan/TnMoonWarfan/MoonWarfanWeapon", "PrimePrice": 410, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/MoonWarfanSugatraMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OstronHeadStatue", "PrimePrice": 125, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/DomsFinalDrink", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Wisp/WispAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pacifist/BaruukImmortalSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ErraBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OwlOrdisStatue", "PrimePrice": 350, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWVesoBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWTeshinBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Peculiars/EvilSpiritMod", "PrimePrice": 250, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape3Scarf", "PrimePrice": 500, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTiberon", "PrimePrice": 315, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/LotusFlowers", "PrimePrice": 250, "RegularPrice": 450000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/UmbraPedestal", "PrimePrice": 0, "RegularPrice": 1000000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Dragon/ChromaAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroB", "PrimePrice": 75, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisLatronPistol", "PrimePrice": 400, "RegularPrice": 215000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnKillBuffSecondary", "PrimePrice": 300, "RegularPrice": 115000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConA", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveSecondaryHeadshotKillMod", "PrimePrice": 300, "RegularPrice": 115000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConD", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConB", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponIncreaseRadialExplosionModExpert", "PrimePrice": 350, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Primary/ArchwingHeavyPistols/Prisma/PrismaArchHeavyPistols", "PrimePrice": 525, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TwinSnakesGlyph", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConF", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConE", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnSixKillsBuffSecondary", "PrimePrice": 300, "RegularPrice": 115000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearOxSigil", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConG", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponFreezeDamageModExpert", "PrimePrice": 350, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GarvLatroxPoster", "PrimePrice": 80, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrnBoomerang/HalikarWraithWeapon", "PrimePrice": 450, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConI", "PrimePrice": 75, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 300, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaMachete", "PrimePrice": 400, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MoaPet/BaroMoaPetSkin", "PrimePrice": 500, "RegularPrice": 325000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushySunMonsterCommon", "PrimePrice": 150, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushyMoonMonsterCommon", "PrimePrice": 150, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/TnShinaiSword/TnShinaiSwordSkin", "PrimePrice": 375, "RegularPrice": 280000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/DualSword/DualRibbonKamasSkin", "PrimePrice": 350, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Infestation/NidusAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/ActionFigureDioramas/EmpyreanRegionADiorama", "PrimePrice": 155, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerFlak", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerTaktis", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/AshLeverianLiosPistol", "PrimePrice": 400, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Glass/GaraAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponSnipersConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/EraHypnosisPoster", "PrimePrice": 100, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/NezhaLeverianCape", "PrimePrice": 400, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Leverian/NezhaLeverian/NezhaLeverianPolearm", "PrimePrice": 350, "RegularPrice": 325000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BoredTennoPoster", "PrimePrice": 90, "RegularPrice": 120000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusBasilisk", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusWeaver", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusHarpi", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Archwing/GrendelArchwingSkin", "PrimePrice": 400, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/JaviExecutionHood", "PrimePrice": 450, "RegularPrice": 450000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/ElectEventMeleeMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/FireEventMeleeMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/ClawCmbTwoMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/FireEventRifleMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/AxeCmbThreeMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/BowMultiShotOnHitMod", "PrimePrice": 300, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/ElectEventShotgunMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/FireEventPistolMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/FireEventShotgunMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventPistolImpactDamageMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponCritDamageMod", "PrimePrice": 400, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/Expert/ArchwingRifleDamageAmountModExpert", "PrimePrice": 350, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponRifleConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPrecepts/PrimedRegen", "PrimePrice": 300, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeRangeIncModExpert", "PrimePrice": 300, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponCritDamageModExpert", "PrimePrice": 280, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 375, "RegularPrice": 120000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeDamageModExpert", "PrimePrice": 385, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponDamageAmountModExpert", "PrimePrice": 300, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponCritChanceModBeginnerExpert", "PrimePrice": 400, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/Kubrow/Expert/KubrowPackLeaderExpertMod", "PrimePrice": 300, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Expert/ArchwingSuitAbilityStrengthModExpert", "PrimePrice": 350, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponImpactDamageModExpert", "PrimePrice": 350, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponFireDamageModExpert", "PrimePrice": 350, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarPowerMaxModExpert", "PrimePrice": 350, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponToxinDamageModExpert", "PrimePrice": 350, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 375, "RegularPrice": 120000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponFreezeDamageModExpert", "PrimePrice": 350, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarAbilityDurationModExpert", "PrimePrice": 350, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponElectricityDamageModExpert", "PrimePrice": 350, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageInfested", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageGrineer", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorrupted", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorpus", "PrimePrice": 400, "RegularPrice": 140000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/SentinelLootRadarEnemyRadarExpertMod", "PrimePrice": 300, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/GlaiveCmbTwoMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventMeleeImpactDamageMod", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventRifleImpactDamageMod", "PrimePrice": 330, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventShotgunImpactDamageMod", "PrimePrice": 365, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/ElectEventRifleMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/ElectEventPistolMod", "PrimePrice": 300, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/VTDetron", "PrimePrice": 500, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpFreezeRay/Vandal/CrpFreezeRayVandalRifle", "PrimePrice": 475, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Chemical/FlameThrowerWraith", "PrimePrice": 550, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/WraithMacheteWeapon", "PrimePrice": 410, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CrpHandRL/PrismaAngstrum", "PrimePrice": 475, "RegularPrice": 210000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaDualCleavers", "PrimePrice": 490, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/VoidTraderGorgon/VTGorgon", "PrimePrice": 600, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaGrakata", "PrimePrice": 610, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerLeverActionRifle/PrismaGrinlokWeapon", "PrimePrice": 500, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/PrismaObex", "PrimePrice": 500, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaSkana", "PrimePrice": 510, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CorpusUMP/PrismaCorpusUMP", "PrimePrice": 400, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/GrineerBulbousSMG/Prisma/PrismaTwinGremlinsWeapon", "PrimePrice": 500, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Melee/VoidTraderArchsword/VTArchSwordWeapon", "PrimePrice": 550, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Energy/VandalElectroProd", "PrimePrice": 410, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpShockRifle/QuantaVandal", "PrimePrice": 450, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/Machinegun/SupraVandal", "PrimePrice": 500, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/WraithSingleViper/WraithSingleViper", "PrimePrice": 400, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerSniperRifle/VulkarWraith", "PrimePrice": 450, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol", "PrimePrice": 500, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/FireMeleeDangle", "PrimePrice": 100, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroInarosPolearmSkin", "PrimePrice": 325, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroInarosMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/InfestedMeleeDangle", "PrimePrice": 250, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTHalloweenDarkSword", "PrimePrice": 320, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeGorgon", "PrimePrice": 300, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerIgnisSkin", "PrimePrice": 300, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroArrow", "PrimePrice": 375, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroScytheMacheteSkin", "PrimePrice": 375, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOdonataSkin", "PrimePrice": 350, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisBallasSword", "PrimePrice": 350, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/PrismaArrow", "PrimePrice": 350, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTRedeemerSkin", "PrimePrice": 325, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisSonicor", "PrimePrice": 380, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTigris", "PrimePrice": 300, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTQuanta", "PrimePrice": 300, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOpticor", "PrimePrice": 325, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Halloween/HalloweenDread", "PrimePrice": 300, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageBaroKiteer", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKavat", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKubrow", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/LisetScarf", "PrimePrice": 600, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerTwitchBItemA", "PrimePrice": 220, "RegularPrice": 220000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKavatBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKavatSigil", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/WraithTurbinesScarf", "PrimePrice": 400, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pirate/HydroidAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GrendelTreat", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemA", "PrimePrice": 150, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/KazBaroCape", "PrimePrice": 325, "RegularPrice": 450000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraA", "PrimePrice": 100, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape2Scarf", "PrimePrice": 400, "RegularPrice": 350000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroQuantumBadgeItem", "PrimePrice": 400, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeBaroCape", "PrimePrice": 425, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape", "PrimePrice": 500, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroIcon", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Magician/LimboImmortalSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Cowgirl/MesaImmortallSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Harlequin/MirageAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKubrowBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKubrowSigil", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTHornSkullScarf", "PrimePrice": 250, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/PrismaLotusEmblem", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroTwoIcon", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusSigil", "PrimePrice": 55, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrimeTraderSigil", "PrimePrice": 50, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/PrismaRazorScarf", "PrimePrice": 350, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTDinoSpikeScarf", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKavat", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKubrow", "PrimePrice": 80, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Tengu/ZephyrAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/KubrowPet/Patterns/KubrowPetPatternPrimeTraderA", "PrimePrice": 150, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Camo/DesertDirigaSkin", "PrimePrice": 225, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/KavatPetMask", "PrimePrice": 500, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/KavatPetTail", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/KavatPetWings", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorVoidTraderA", "PrimePrice": 500, "RegularPrice": 275000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorBaro", "PrimePrice": 500, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/BaroPetMask", "PrimePrice": 500, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/BaroPetTail", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/BaroPetWings", "PrimePrice": 400, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/Types/StoreItems/Packages/KavatColorPackNexus", "PrimePrice": 200, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/PrismaJetWings", "PrimePrice": 300, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/PrismaFishTail", "PrimePrice": 200, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/PrismaMechHeadMask", "PrimePrice": 175, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorPrisma", "PrimePrice": 400, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPowersuits/PrismaShadePowerSuit", "PrimePrice": 500, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/DesertTaxonSkin", "PrimePrice": 200, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorHalloweenA", "PrimePrice": 400, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/Types/StoreItems/Boosters/AffinityBooster3DayStoreItem", "PrimePrice": 450, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/Types/StoreItems/Boosters/CreditBooster3DayStoreItem", "PrimePrice": 350, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/Types/StoreItems/Boosters/ModDropChanceBooster3DayStoreItem", "PrimePrice": 500, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/Types/StoreItems/Boosters/ResourceAmount3DayStoreItem", "PrimePrice": 400, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionPBronze", "PrimePrice": 50, "RegularPrice": 45000 }, + { "ItemType": "/Lotus/StoreItems/Types/Recipes/Components/CorruptedBombardBallBlueprint", "PrimePrice": 100, "RegularPrice": 50000 }, + { "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/CorruptedHeavyGunnerBall", "PrimePrice": 100, "RegularPrice": 40000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OrbiterPictureFrameBaro", "PrimePrice": 100, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitC", "PrimePrice": 200, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileInarosTomb", "PrimePrice": 325, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/BaroFireWorksCrate", "PrimePrice": 50, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileOrokinExtraction", "PrimePrice": 325, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 }, + { "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBait", "PrimePrice": 200, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitB", "PrimePrice": 200, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationB", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationE", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneBaro", "PrimePrice": 700, "RegularPrice": 500000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerBobbleHead", "PrimePrice": 70, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroA", "PrimePrice": 75, "RegularPrice": 75000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KavatBust", "PrimePrice": 220, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowBust", "PrimePrice": 220, "RegularPrice": 250000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDesertSkate", "PrimePrice": 125, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationD", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ExcaliburArchwingBobbleHead", "PrimePrice": 90, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroTiara", "PrimePrice": 525, "RegularPrice": 375000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroC", "PrimePrice": 500, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroMouthPieceA", "PrimePrice": 500, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroVisor", "PrimePrice": 525, "RegularPrice": 375000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroHorn", "PrimePrice": 525, "RegularPrice": 375000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroA", "PrimePrice": 500, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroB", "PrimePrice": 250, "RegularPrice": 200000 }, + { "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/BaroWallpaper", "PrimePrice": 250, "RegularPrice": 175000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/InarosLisetSkin", "PrimePrice": 400, "RegularPrice": 300000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationA", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinInaros", "PrimePrice": 425, "RegularPrice": 320000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinPrimeTrader", "PrimePrice": 230, "RegularPrice": 375000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ParazonPoster", "PrimePrice": 100, "RegularPrice": 125000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowKavatLowPolyPoster", "PrimePrice": 90, "RegularPrice": 110000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetSkinVoidTrader", "PrimePrice": 120, "RegularPrice": 150000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinPrimeTrader", "PrimePrice": 210, "RegularPrice": 450000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationF", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinInaros", "PrimePrice": 375, "RegularPrice": 340000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationG", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropOstRugBaro", "PrimePrice": 225, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationH", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/Gyroscope/LisetGyroscopeSkinPrimeTrader", "PrimePrice": 220, "RegularPrice": 400000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationC", "PrimePrice": 100, "RegularPrice": 100000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/PedistalPrime", "PrimePrice": 0, "RegularPrice": 1000000 }, + { "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/BaroEmote", "PrimePrice": 0, "RegularPrice": 1000000 }, + { "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 } + ], + "allIfAny": [ + [ + "/Lotus/StoreItems/Upgrades/Skins/Operator/Leggings/LeggingsNovaEngineer", + "/Lotus/StoreItems/Upgrades/Skins/Operator/BodySuits/BodySuitNovaEngineer", + "/Lotus/StoreItems/Upgrades/Skins/Operator/Sleeves/SleevesNovaEngineer", + "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodNovaEngineer" + ] + ] +} diff --git a/static/fixed_responses/worldState/worldState.json b/static/fixed_responses/worldState/worldState.json index f973d69a..018d8175 100644 --- a/static/fixed_responses/worldState/worldState.json +++ b/static/fixed_responses/worldState/worldState.json @@ -347,1872 +347,6 @@ "Activation": { "$date": { "$numberLong": "1563030000000" } } } ], - "VoidTraders": [ - { - "_id": { "$oid": "5d1e07a0a38e4a4fdd7cefca" }, - "Activation": { "$date": { "$numberLong": "0" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Character": "Baro'Ki Teel", - "Node": "PlutoHUB", - "Manifest": [ - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TennoCon2024GlyphAlt", - "PrimePrice": 15, - "RegularPrice": 1000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/Tennocon2024EmoteAlt", - "PrimePrice": 15, - "RegularPrice": 1000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/HeartOfDeimosAlbumCoverPoster", - "PrimePrice": 80, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConC", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConJ", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConH", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T3VoidProjectionVoltOdonataPrimeBronze", - "PrimePrice": 125, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionVoltOdonataPrimeBronze", - "PrimePrice": 125, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionMagNovaVaultBBronze", - "PrimePrice": 125, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeNelumboCape", - "PrimePrice": 325, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeTwinGrakatas", - "PrimePrice": 300, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/Staff/TnRibbonStaffSkin", - "PrimePrice": 350, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GunBlade/GrnGunBlade/GrnGunblade", - "PrimePrice": 550, - "RegularPrice": 325000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpBFG/Vandal/VandalCrpBFG", - "PrimePrice": 650, - "RegularPrice": 550000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Event/AmbulasEvent/Expert/SecondaryExplosionRadiusModExpert", - "PrimePrice": 350, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/Dragon2024BadgeItem", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingDamageOnReloadMod", - "PrimePrice": 375, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingRifleFireIterationsMod", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaruukDoanStyle", - "PrimePrice": 75, - "RegularPrice": 60000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OctaviaBobbleHead", - "PrimePrice": 50, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/GaussSentinelSkin", - "PrimePrice": 500, - "RegularPrice": 425000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusVinesSigil", - "PrimePrice": 55, - "RegularPrice": 60000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageExcaliburActionProto", - "PrimePrice": 75, - "RegularPrice": 60000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageIvaraAction", - "PrimePrice": 75, - "RegularPrice": 60000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/HornSkullScarf", - "PrimePrice": 325, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/RhinoDeluxeSigil", - "PrimePrice": 45, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Events/InfQuantaInfestedAladV", - "PrimePrice": 325, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterD", - "PrimePrice": 90, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterB", - "PrimePrice": 90, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterC", - "PrimePrice": 90, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponElectricityDamageMod", - "PrimePrice": 350, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarShieldMaxModExpert", - "PrimePrice": 350, - "RegularPrice": 225000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterA", - "PrimePrice": 90, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/EventSigilScarletSpear", - "PrimePrice": 45, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnOrokinRifle/GrnOrokinRifleWeapon", - "PrimePrice": 675, - "RegularPrice": 625000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisNikana", - "PrimePrice": 375, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/GaussSentinelWings", - "PrimePrice": 400, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/GaussSentinelTail", - "PrimePrice": 400, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/GaussSentinelMask", - "PrimePrice": 450, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerCutter", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2023EmblemItem", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearFreeTigerSigil", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2022EmblemItem", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Leverian/IvaraLeverianPovisRecordsDecoration", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodDuviriOperator", - "PrimePrice": 550, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpTonfa/CrpPrismaTonfa", - "PrimePrice": 450, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneDuviri", - "PrimePrice": 800, - "RegularPrice": 650000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/AshLevarianTiara", - "PrimePrice": 550, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraB", - "PrimePrice": 250, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Promo/Warframe/PromoParis", - "PrimePrice": 315, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/ThraxSigil", - "PrimePrice": 50, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/PrismaLenz/PrismaLenzWeapon", - "PrimePrice": 575, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Vignettes/Warframes/ArchwingAFItem", - "PrimePrice": 100, - "RegularPrice": 330000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/LavosAlchemistWallpaper", - "PrimePrice": 275, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GrendelOrokinDishSet", - "PrimePrice": 110, - "RegularPrice": 130000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemB", - "PrimePrice": 200, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/NezhaEtchingsTablets", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GaussTowerOfAltraDeco", - "PrimePrice": 110, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPlanter", - "PrimePrice": 125, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPedestal", - "PrimePrice": 150, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Leggings/LeggingsNovaEngineer", - "PrimePrice": 300, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/BodySuits/BodySuitNovaEngineer", - "PrimePrice": 300, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Sleeves/SleevesNovaEngineer", - "PrimePrice": 300, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodNovaEngineer", - "PrimePrice": 350, - "RegularPrice": 375000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BekranZaftBucketBroom", - "PrimePrice": 100, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Warfan/TnMoonWarfan/MoonWarfanWeapon", - "PrimePrice": 410, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/MoonWarfanSugatraMeleeDangle", - "PrimePrice": 250, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OstronHeadStatue", - "PrimePrice": 125, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/DomsFinalDrink", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Wisp/WispAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pacifist/BaruukImmortalSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ErraBobbleHead", - "PrimePrice": 75, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OwlOrdisStatue", - "PrimePrice": 350, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWVesoBobbleHead", - "PrimePrice": 75, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWTeshinBobbleHead", - "PrimePrice": 75, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Peculiars/EvilSpiritMod", - "PrimePrice": 250, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeL", - "PrimePrice": 400, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeC", - "PrimePrice": 350, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeA", - "PrimePrice": 400, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape3Scarf", - "PrimePrice": 500, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTiberon", - "PrimePrice": 315, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/LotusFlowers", - "PrimePrice": 250, - "RegularPrice": 450000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/UmbraPedestal", - "PrimePrice": 0, - "RegularPrice": 1000000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Dragon/ChromaAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroB", - "PrimePrice": 75, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisLatronPistol", - "PrimePrice": 400, - "RegularPrice": 215000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnKillBuffSecondary", - "PrimePrice": 300, - "RegularPrice": 115000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConA", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveSecondaryHeadshotKillMod", - "PrimePrice": 300, - "RegularPrice": 115000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConD", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConB", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponIncreaseRadialExplosionModExpert", - "PrimePrice": 350, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Primary/ArchwingHeavyPistols/Prisma/PrismaArchHeavyPistols", - "PrimePrice": 525, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TwinSnakesGlyph", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConF", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConE", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnSixKillsBuffSecondary", - "PrimePrice": 300, - "RegularPrice": 115000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearOxSigil", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConG", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponFreezeDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GarvLatroxPoster", - "PrimePrice": 80, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrnBoomerang/HalikarWraithWeapon", - "PrimePrice": 450, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConI", - "PrimePrice": 75, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorC", - "PrimePrice": 325, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorL", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorA", - "PrimePrice": 315, - "RegularPrice": 215000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponReloadSpeedModExpert", - "PrimePrice": 300, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaMachete", - "PrimePrice": 400, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MoaPet/BaroMoaPetSkin", - "PrimePrice": 500, - "RegularPrice": 325000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushySunMonsterCommon", - "PrimePrice": 150, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushyMoonMonsterCommon", - "PrimePrice": 150, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponClipMaxModExpert", - "PrimePrice": 280, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponClipMaxModExpert", - "PrimePrice": 280, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/TnShinaiSword/TnShinaiSwordSkin", - "PrimePrice": 375, - "RegularPrice": 280000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorL", - "PrimePrice": 275, - "RegularPrice": 115000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorC", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorA", - "PrimePrice": 315, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/DualSword/DualRibbonKamasSkin", - "PrimePrice": 350, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Infestation/NidusAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/ActionFigureDioramas/EmpyreanRegionADiorama", - "PrimePrice": 155, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerFlak", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerTaktis", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/AshLeverianLiosPistol", - "PrimePrice": 400, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Glass/GaraAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponSnipersConvertAmmoModExpert", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/EraHypnosisPoster", - "PrimePrice": 100, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/NezhaLeverianCape", - "PrimePrice": 400, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Leverian/NezhaLeverian/NezhaLeverianPolearm", - "PrimePrice": 350, - "RegularPrice": 325000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BoredTennoPoster", - "PrimePrice": 90, - "RegularPrice": 120000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusBasilisk", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusWeaver", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusHarpi", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Archwing/GrendelArchwingSkin", - "PrimePrice": 400, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/JaviExecutionHood", - "PrimePrice": 450, - "RegularPrice": 450000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/ElectEventMeleeMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/FireEventMeleeMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/ClawCmbTwoMeleeTree", - "PrimePrice": 385, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/FireEventRifleMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/AxeCmbThreeMeleeTree", - "PrimePrice": 385, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventSlashDamageMod", - "PrimePrice": 375, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/BowMultiShotOnHitMod", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/ElectEventShotgunMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/FireEventPistolMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/FireEventShotgunMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventPistolImpactDamageMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponCritDamageMod", - "PrimePrice": 400, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageInfestedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageGrineerExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorruptedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorpusExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponClipMaxModExpert", - "PrimePrice": 280, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunConvertAmmoModExpert", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/Expert/ArchwingRifleDamageAmountModExpert", - "PrimePrice": 350, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponRifleConvertAmmoModExpert", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPrecepts/PrimedRegen", - "PrimePrice": 300, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeRangeIncModExpert", - "PrimePrice": 300, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponCritDamageModExpert", - "PrimePrice": 280, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponReloadSpeedModExpert", - "PrimePrice": 375, - "RegularPrice": 120000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeDamageModExpert", - "PrimePrice": 385, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponDamageAmountModExpert", - "PrimePrice": 300, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponCritChanceModBeginnerExpert", - "PrimePrice": 400, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolConvertAmmoModExpert", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/Kubrow/Expert/KubrowPackLeaderExpertMod", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Expert/ArchwingSuitAbilityStrengthModExpert", - "PrimePrice": 350, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponImpactDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponFireDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarPowerMaxModExpert", - "PrimePrice": 350, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponToxinDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponReloadSpeedModExpert", - "PrimePrice": 375, - "RegularPrice": 120000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageInfestedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageGrineerExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorruptedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorpusExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponFreezeDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarAbilityDurationModExpert", - "PrimePrice": 350, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageInfestedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageGrineerExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorruptedExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorpusExpert", - "PrimePrice": 350, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponElectricityDamageModExpert", - "PrimePrice": 350, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageInfested", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageGrineer", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorrupted", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorpus", - "PrimePrice": 400, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/SentinelLootRadarEnemyRadarExpertMod", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/GlaiveCmbTwoMeleeTree", - "PrimePrice": 385, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventSlashDamageMod", - "PrimePrice": 375, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventMeleeImpactDamageMod", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventRifleImpactDamageMod", - "PrimePrice": 330, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventSlashDamageMod", - "PrimePrice": 375, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventShotgunImpactDamageMod", - "PrimePrice": 365, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/ElectEventRifleMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/ElectEventPistolMod", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventSlashDamageMod", - "PrimePrice": 375, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/VTDetron", - "PrimePrice": 500, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpFreezeRay/Vandal/CrpFreezeRayVandalRifle", - "PrimePrice": 475, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Chemical/FlameThrowerWraith", - "PrimePrice": 550, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/WraithMacheteWeapon", - "PrimePrice": 410, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CrpHandRL/PrismaAngstrum", - "PrimePrice": 475, - "RegularPrice": 210000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaDualCleavers", - "PrimePrice": 490, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/VoidTraderGorgon/VTGorgon", - "PrimePrice": 600, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaGrakata", - "PrimePrice": 610, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerLeverActionRifle/PrismaGrinlokWeapon", - "PrimePrice": 500, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/PrismaObex", - "PrimePrice": 500, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaSkana", - "PrimePrice": 510, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CorpusUMP/PrismaCorpusUMP", - "PrimePrice": 400, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/GrineerBulbousSMG/Prisma/PrismaTwinGremlinsWeapon", - "PrimePrice": 500, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Melee/VoidTraderArchsword/VTArchSwordWeapon", - "PrimePrice": 550, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Energy/VandalElectroProd", - "PrimePrice": 410, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpShockRifle/QuantaVandal", - "PrimePrice": 450, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/Machinegun/SupraVandal", - "PrimePrice": 500, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/WraithSingleViper/WraithSingleViper", - "PrimePrice": 400, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerSniperRifle/VulkarWraith", - "PrimePrice": 450, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol", - "PrimePrice": 500, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/FireMeleeDangle", - "PrimePrice": 100, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroInarosPolearmSkin", - "PrimePrice": 325, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroInarosMeleeDangle", - "PrimePrice": 250, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/InfestedMeleeDangle", - "PrimePrice": 250, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTHalloweenDarkSword", - "PrimePrice": 320, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeGorgon", - "PrimePrice": 300, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerIgnisSkin", - "PrimePrice": 300, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroArrow", - "PrimePrice": 375, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroMeleeDangle", - "PrimePrice": 250, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroScytheMacheteSkin", - "PrimePrice": 375, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOdonataSkin", - "PrimePrice": 350, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisBallasSword", - "PrimePrice": 350, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/PrismaArrow", - "PrimePrice": 350, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTRedeemerSkin", - "PrimePrice": 325, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisSonicor", - "PrimePrice": 380, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTigris", - "PrimePrice": 300, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTQuanta", - "PrimePrice": 300, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOpticor", - "PrimePrice": 325, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Halloween/HalloweenDread", - "PrimePrice": 300, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageBaroKiteer", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKavat", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKubrow", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/LisetScarf", - "PrimePrice": 600, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorElixis", - "PrimePrice": 275, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorElixis", - "PrimePrice": 300, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorElixis", - "PrimePrice": 325, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerTwitchBItemA", - "PrimePrice": 220, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Packages/VTEosArmourBundle", - "PrimePrice": 285, - "RegularPrice": 260000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosChestArmor", - "PrimePrice": 125, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", - "PrimePrice": 15, - "RegularPrice": 1000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKavatBadgeItem", - "PrimePrice": 50, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKavatSigil", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesChestArmor", - "PrimePrice": 300, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/WraithTurbinesScarf", - "PrimePrice": 400, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesLegArmor", - "PrimePrice": 350, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesArmArmor", - "PrimePrice": 350, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pirate/HydroidAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GrendelTreat", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourC", - "PrimePrice": 150, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemA", - "PrimePrice": 150, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/KazBaroCape", - "PrimePrice": 325, - "RegularPrice": 450000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraA", - "PrimePrice": 100, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoC", - "PrimePrice": 175, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoL", - "PrimePrice": 225, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoA", - "PrimePrice": 310, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourL", - "PrimePrice": 300, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape2Scarf", - "PrimePrice": 400, - "RegularPrice": 350000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroQuantumBadgeItem", - "PrimePrice": 400, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourA", - "PrimePrice": 350, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeBaroCape", - "PrimePrice": 425, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape", - "PrimePrice": 500, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroIcon", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosALArmor", - "PrimePrice": 50, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLLArmor", - "PrimePrice": 65, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegLeftArmor", - "PrimePrice": 65, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmLeftArmor", - "PrimePrice": 65, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegLeftArmor", - "PrimePrice": 100, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmLeftArmor", - "PrimePrice": 100, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Magician/LimboImmortalSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Cowgirl/MesaImmortallSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Harlequin/MirageAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKubrowBadgeItem", - "PrimePrice": 50, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKubrowSigil", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisLArmor", - "PrimePrice": 225, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisCArmor", - "PrimePrice": 250, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisAArmor", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeChestArmor", - "PrimePrice": 150, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoChestArmor", - "PrimePrice": 225, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTHornSkullScarf", - "PrimePrice": 250, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorPrisma", - "PrimePrice": 275, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorPrisma", - "PrimePrice": 300, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorPrisma", - "PrimePrice": 325, - "RegularPrice": 220000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/PrismaLotusEmblem", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroTwoIcon", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusSigil", - "PrimePrice": 55, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/Halloween2014Wings/PrismaNaberusArmArmor", - "PrimePrice": 220, - "RegularPrice": 140000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrimeTraderSigil", - "PrimePrice": 50, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/PrismaRazorScarf", - "PrimePrice": 350, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTDinoSpikeScarf", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKavat", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKubrow", - "PrimePrice": 80, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosARArmor", - "PrimePrice": 50, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLRArmor", - "PrimePrice": 65, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegRightArmor", - "PrimePrice": 65, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmRightArmor", - "PrimePrice": 65, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegRightArmor", - "PrimePrice": 100, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmRightArmor", - "PrimePrice": 100, - "RegularPrice": 55000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Tengu/ZephyrAlternateSkin", - "PrimePrice": 550, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/KubrowPet/Patterns/KubrowPetPatternPrimeTraderA", - "PrimePrice": 150, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Camo/DesertDirigaSkin", - "PrimePrice": 225, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/KavatPetMask", - "PrimePrice": 500, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/KavatPetTail", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/KavatPetWings", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorVoidTraderA", - "PrimePrice": 500, - "RegularPrice": 275000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorBaro", - "PrimePrice": 500, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/BaroPetMask", - "PrimePrice": 500, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/BaroPetTail", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/BaroPetWings", - "PrimePrice": 400, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Packages/KavatColorPackNexus", - "PrimePrice": 200, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/PrismaJetWings", - "PrimePrice": 300, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/PrismaFishTail", - "PrimePrice": 200, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/PrismaMechHeadMask", - "PrimePrice": 175, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorPrisma", - "PrimePrice": 400, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPowersuits/PrismaShadePowerSuit", - "PrimePrice": 500, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/DesertTaxonSkin", - "PrimePrice": 200, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorHalloweenA", - "PrimePrice": 400, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Boosters/AffinityBooster3DayStoreItem", - "PrimePrice": 450, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Boosters/CreditBooster3DayStoreItem", - "PrimePrice": 350, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Boosters/ModDropChanceBooster3DayStoreItem", - "PrimePrice": 500, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/Types/StoreItems/Boosters/ResourceAmount3DayStoreItem", - "PrimePrice": 400, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionPBronze", - "PrimePrice": 50, - "RegularPrice": 45000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Recipes/Components/CorruptedBombardBallBlueprint", - "PrimePrice": 100, - "RegularPrice": 50000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/CorruptedHeavyGunnerBall", - "PrimePrice": 100, - "RegularPrice": 40000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OrbiterPictureFrameBaro", - "PrimePrice": 100, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitC", - "PrimePrice": 200, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileInarosTomb", - "PrimePrice": 325, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/BaroFireWorksCrate", - "PrimePrice": 50, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileOrokinExtraction", - "PrimePrice": 325, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", - "PrimePrice": 100, - "RegularPrice": 25000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBait", - "PrimePrice": 200, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitB", - "PrimePrice": 200, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationB", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationE", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneBaro", - "PrimePrice": 700, - "RegularPrice": 500000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerBobbleHead", - "PrimePrice": 70, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroA", - "PrimePrice": 75, - "RegularPrice": 75000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KavatBust", - "PrimePrice": 220, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowBust", - "PrimePrice": 220, - "RegularPrice": 250000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDesertSkate", - "PrimePrice": 125, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationD", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ExcaliburArchwingBobbleHead", - "PrimePrice": 90, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroTiara", - "PrimePrice": 525, - "RegularPrice": 375000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroC", - "PrimePrice": 500, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroMouthPieceA", - "PrimePrice": 500, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroVisor", - "PrimePrice": 525, - "RegularPrice": 375000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroHorn", - "PrimePrice": 525, - "RegularPrice": 375000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroA", - "PrimePrice": 500, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroB", - "PrimePrice": 250, - "RegularPrice": 200000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/BaroWallpaper", - "PrimePrice": 250, - "RegularPrice": 175000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/InarosLisetSkin", - "PrimePrice": 400, - "RegularPrice": 300000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationA", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinInaros", - "PrimePrice": 425, - "RegularPrice": 320000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinPrimeTrader", - "PrimePrice": 230, - "RegularPrice": 375000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ParazonPoster", - "PrimePrice": 100, - "RegularPrice": 125000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowKavatLowPolyPoster", - "PrimePrice": 90, - "RegularPrice": 110000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetSkinVoidTrader", - "PrimePrice": 120, - "RegularPrice": 150000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinPrimeTrader", - "PrimePrice": 210, - "RegularPrice": 450000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationF", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinInaros", - "PrimePrice": 375, - "RegularPrice": 340000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationG", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropOstRugBaro", - "PrimePrice": 225, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationH", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/Gyroscope/LisetGyroscopeSkinPrimeTrader", - "PrimePrice": 220, - "RegularPrice": 400000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationC", - "PrimePrice": 100, - "RegularPrice": 100000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/PedistalPrime", - "PrimePrice": 0, - "RegularPrice": 1000000 - }, - { - "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/BaroEmote", - "PrimePrice": 0, - "RegularPrice": 1000000 - }, - { - "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", - "PrimePrice": 2995, - "RegularPrice": 1000000 - } - ] - } - ], "PrimeVaultTraders": [ { "_id": { "$oid": "631f8c4ac36af423770eaa97" }, diff --git a/static/webui/index.html b/static/webui/index.html index 0780b78f..3d706ee9 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -704,6 +704,14 @@ +
+ + +
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index a5f67e01..d2e6ebfa 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -159,6 +159,8 @@ dict = { cheats_noDeathMarks: `Keine Todesmarkierungen`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, + cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index a5ae9a12..31f0d1e4 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -158,6 +158,8 @@ dict = { cheats_noDeathMarks: `No Death Marks`, cheats_noKimCooldowns: `No KIM Cooldowns`, cheats_fullyStockedVendors: `Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `Baro Always Available`, + cheats_baroFullyStocked: `Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 7b77bb7f..91724a89 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -159,6 +159,8 @@ dict = { cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, + cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 6af1c70d..39b008d5 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -159,6 +159,8 @@ dict = { cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, + cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 4b48cbfc..613cebd0 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -159,6 +159,8 @@ dict = { cheats_noDeathMarks: `Без меток сметри`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`, cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, + cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 49fbe711..a46f1949 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -159,6 +159,8 @@ dict = { cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noKimCooldowns: `无 KIM 冷却时间`, cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, + cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, + cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, From 4a434cea2b19046d4a2d7dd6017e0b1c4d3633a0 Mon Sep 17 00:00:00 2001 From: hxedcl Date: Tue, 24 Jun 2025 12:02:21 -0700 Subject: [PATCH 22/24] chore(webui): update to Spanish translation (#2275) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2275 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 91724a89..055c242a 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -158,9 +158,9 @@ dict = { cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`, cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, - cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`, - cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`, - cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`, + cheats_fullyStockedVendors: `Vendedores con stock completo`, + cheats_baroAlwaysAvailable: `Baro siempre disponible`, + cheats_baroFullyStocked: `Baro con stock completo`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, @@ -174,7 +174,7 @@ dict = { cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`, - cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, + cheats_unlockAllSimarisResearchEntries: `Desbloquear todas las entradas de investigación de Simaris`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, cheats_save: `Guardar`, @@ -187,8 +187,8 @@ dict = { cheats_none: `Ninguno`, import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador serán sobrescritos en tu cuenta.`, import_submit: `Enviar`, - import_samples: `[UNTRANSLATED] Samples:`, - import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, + import_samples: `Muestras:`, + import_samples_maxFocus: `Todas las escuelas de enfoque al máximo`, upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`, upgrade_MeleeCritDamage: `+|VAL|% de daño crítico cuerpo a cuerpo`, From e234af098d3f8066fcad5e11e93ba0f0a32894f3 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:12:39 -0700 Subject: [PATCH 23/24] fix(webui): incorrect value of upgrade_AvatarTimeLimitIncrease string (#2274) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2274 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/translations/de.js | 2 +- static/webui/translations/en.js | 2 +- static/webui/translations/es.js | 2 +- static/webui/translations/fr.js | 2 +- static/webui/translations/ru.js | 2 +- static/webui/translations/zh.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index d2e6ebfa..08b02c78 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 31f0d1e4..a97ee549 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -230,7 +230,7 @@ dict = { upgrade_DamageReductionOnHack: `75% Damage Reduction while Hacking`, upgrade_OnExecutionReviveCompanion: `Mercy Kills reduce Companion Recovery by 15s`, upgrade_OnExecutionParkourSpeed: `+60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `s to Hacking`, + upgrade_AvatarTimeLimitIncrease: `+8s to Hacking`, upgrade_ElectrifyOnHack: `Shock enemies within 20m while Hacking`, upgrade_OnExecutionTerrify: `50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, upgrade_OnHackLockers: `Unlock 5 lockers within 20m after Hacking`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 055c242a..a4e798d7 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `75% de reducción de daño al hackear`, upgrade_OnExecutionReviveCompanion: `Las ejecuciones reducen el tiempo de recuperación del compañero en 15s`, upgrade_OnExecutionParkourSpeed: `+60% de velocidad de parkour durante 15s tras una ejecución`, - upgrade_AvatarTimeLimitIncrease: `+|VAL|s al tiempo de hackeo`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_ElectrifyOnHack: `Electrocuta a los enemigos en un radio de 20m al hackear`, upgrade_OnExecutionTerrify: `50% de probabilidad de que enemigos en un radio de 15m entren en pánico por 8s tras una ejecución`, upgrade_OnHackLockers: `Desbloquea 5 casilleros en un radio de 20m tras hackear`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 39b008d5..26ead631 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 613cebd0..5b353690 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index a46f1949..79fe135f 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `入侵时,+75% 伤害减免`, upgrade_OnExecutionReviveCompanion: `怜悯之击 减少同伴复苏时间 15秒`, upgrade_OnExecutionParkourSpeed: `怜悯之击 15秒内 +60% 跑酷速度`, - upgrade_AvatarTimeLimitIncrease: `增加入侵限制时间`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, upgrade_ElectrifyOnHack: `入侵时震慑20米之内的敌人`, upgrade_OnExecutionTerrify: `怜悯之击 50% 几率让 15米 以内的敌人恐慌`, upgrade_OnHackLockers: `入侵后解锁20米内的5个储物柜`, From 3a6e4ac2e1eb14f538d3883a050bd46eb25b3f9a Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:05:19 -0700 Subject: [PATCH 24/24] feat: Arcana Isolation Vault rewards (#2276) Closes #388 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2276 Reviewed-by: Sainan Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- package-lock.json | 8 +-- package.json | 2 +- src/services/missionInventoryUpdateService.ts | 70 +++++++++++++------ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fcd908d..d3d0207d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.68", + "warframe-public-export-plus": "^0.5.69", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -3396,9 +3396,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.68", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.68.tgz", - "integrity": "sha512-KMmwCVeQ4k+EN73UZqxnM+qQdPsST8geWoJCP7US5LT6JcRxa8ptmqYXwCzaLtckBLZyVbamsxKZAxPPJckxsA==" + "version": "0.5.69", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.69.tgz", + "integrity": "sha512-vTU1tUzqpihzpseUSJMrM82pYbCDZCfW40jXIi+Ol9B3a3Acz0DccfP7i4eoXf7Abahu4H/sjRt/nSHLNBvLHA==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index 35886233..ce1614ba 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.68", + "warframe-public-export-plus": "^0.5.69", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index d343694a..6ab8b4e9 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1207,7 +1207,7 @@ export const addMissionRewards = async ( if (rewardInfo.JobStage != undefined && rewardInfo.jobId) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = rewardInfo.jobId.split("_"); + const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_"); const syndicateMissions: ISyndicateMissionInfo[] = []; if (syndicateMissionId) { pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId)); @@ -1216,12 +1216,27 @@ export const addMissionRewards = async ( if (syndicateEntry && syndicateEntry.Jobs) { let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - if (jobType.endsWith("VaultBounty")) { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault) currentJob = vault; + if ( + [ + "DeimosRuinsExterminateBounty", + "DeimosRuinsEscortBounty", + "DeimosRuinsMistBounty", + "DeimosRuinsPurifyBounty", + "DeimosRuinsSacBounty", + "VaultBounty" + ].some(ending => jobType.endsWith(ending)) + ) { + const vault = syndicateEntry.Jobs.find(j => j.locationTag == rewardInfo.jobId!.split("_").at(-1)); + if (vault) { + currentJob = vault; + if (jobType.endsWith("VaultBounty")) { + currentJob.xpAmounts = [currentJob.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)]; + } + } } - let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)); - + let medallionAmount = Math.floor( + Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1) + ); if ( ["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some( ending => jobType.endsWith(ending) @@ -1554,7 +1569,7 @@ function getRandomMissionDrops( if (RewardInfo.jobId) { if (RewardInfo.JobStage! >= 0) { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = RewardInfo.jobId.split("_"); + const [jobType, unkIndex, hubNode, syndicateMissionId] = RewardInfo.jobId.split("_"); let isEndlessJob = false; if (syndicateMissionId) { const syndicateMissions: ISyndicateMissionInfo[] = []; @@ -1566,21 +1581,30 @@ function getRandomMissionDrops( let job = syndicateEntry.Jobs[RewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - if (jobType.endsWith("VaultBounty")) { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault) job = vault; + if ( + [ + "DeimosRuinsExterminateBounty", + "DeimosRuinsEscortBounty", + "DeimosRuinsMistBounty", + "DeimosRuinsPurifyBounty", + "DeimosRuinsSacBounty", + "VaultBounty" + ].some(ending => jobType.endsWith(ending)) + ) { + const vault = syndicateEntry.Jobs.find( + j => j.locationTag === RewardInfo.jobId!.split("_").at(-1) + ); + if (vault) { + job = vault; + if (jobType.endsWith("VaultBounty")) { + job.rewards = job.rewards.replace( + "/Lotus/Types/Game/MissionDecks/", + "/Supplementals/" + ); + job.xpAmounts = [job.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)]; + } + } } - // if ( - // [ - // "DeimosRuinsExterminateBounty", - // "DeimosRuinsEscortBounty", - // "DeimosRuinsMistBounty", - // "DeimosRuinsPurifyBounty", - // "DeimosRuinsSacBounty" - // ].some(ending => jobType.endsWith(ending)) - // ) { - // job.rewards = "TODO"; // Droptable for Arcana Isolation Vault - // } if ( [ "DeimosEndlessAreaDefenseBounty", @@ -1657,10 +1681,10 @@ function getRandomMissionDrops( } if ( RewardInfo.Q && - (RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) && + (RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) && !isEndlessJob ) { - rotations.push(3); + rotations.push(ExportRewards[job.rewards].length - 1); } } }