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 01/43] 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 02/43] 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 03/43] 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 04/43] 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 05/43] 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); } } } From d5be20283579c6401179226cf662b528e149be15 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 08:03:49 -0700 Subject: [PATCH 06/43] fix: ensure every bounty tier has a unique job type (#2273) I saw "trash their traps" show up twice on Eudico in different tiers, I don't think that's correct. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2273 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/rngService.ts | 10 +++++++ src/services/worldStateService.ts | 49 ++++++++++++++++--------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/services/rngService.ts b/src/services/rngService.ts index 72379ab0..01426a3f 100644 --- a/src/services/rngService.ts +++ b/src/services/rngService.ts @@ -107,6 +107,16 @@ export class SRng { return arr[this.randomInt(0, arr.length - 1)]; } + randomElementPop(arr: T[]): T | undefined { + if (arr.length != 0) { + const index = this.randomInt(0, arr.length - 1); + const elm = arr[index]; + arr.splice(index, 1); + return elm; + } + return undefined; + } + randomFloat(): number { this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn; return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 66e07235..f7099220 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -101,7 +101,7 @@ const sortieBossNode: Record, SORTIE_BOSS_VOR: "SolNode108" }; -const eidolonJobs = [ +const eidolonJobs: readonly string[] = [ "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap", "/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab", @@ -117,14 +117,14 @@ const eidolonJobs = [ "/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc" ]; -const eidolonNarmerJobs = [ +const eidolonNarmerJobs: readonly string[] = [ "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss", "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt", "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft", "/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib" ]; -const venusJobs = [ +const venusJobs: readonly string[] = [ "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush", "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation", "/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery", @@ -150,14 +150,14 @@ const venusJobs = [ "/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy" ]; -const venusNarmerJobs = [ +const venusNarmerJobs: readonly string[] = [ "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate", "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate", "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense", "/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation" ]; -const microplanetJobs = [ +const microplanetJobs: readonly string[] = [ "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty", @@ -167,7 +167,7 @@ const microplanetJobs = [ "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty" ]; -const microplanetEndlessJobs = [ +const microplanetEndlessJobs: readonly string[] = [ "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty", "/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty" @@ -498,6 +498,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], { const rng = new SRng(seed); + const pool = [...eidolonJobs]; syndicateMissions.push({ _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008" @@ -509,7 +510,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], Nodes: [], Jobs: [ { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`, masteryReq: 0, minEnemyLevel: 5, @@ -517,7 +518,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) }, { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`, masteryReq: 1, minEnemyLevel: 10, @@ -525,7 +526,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) }, { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`, masteryReq: 2, minEnemyLevel: 20, @@ -533,7 +534,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) }, { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`, masteryReq: 3, minEnemyLevel: 30, @@ -541,7 +542,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) }, { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`, masteryReq: 5, minEnemyLevel: 40, @@ -549,7 +550,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) }, { - jobType: rng.randomElement(eidolonJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`, masteryReq: 10, minEnemyLevel: 100, @@ -570,6 +571,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], { const rng = new SRng(seed); + const pool = [...venusJobs]; syndicateMissions.push({ _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025" @@ -581,7 +583,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], Nodes: [], Jobs: [ { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`, masteryReq: 0, minEnemyLevel: 5, @@ -589,7 +591,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) }, { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`, masteryReq: 1, minEnemyLevel: 10, @@ -597,7 +599,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) }, { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`, masteryReq: 2, minEnemyLevel: 20, @@ -605,7 +607,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) }, { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`, masteryReq: 3, minEnemyLevel: 30, @@ -613,7 +615,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) }, { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, masteryReq: 5, minEnemyLevel: 40, @@ -621,7 +623,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) }, { - jobType: rng.randomElement(venusJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`, masteryReq: 10, minEnemyLevel: 100, @@ -642,6 +644,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], { const rng = new SRng(seed); + const pool = [...microplanetJobs]; syndicateMissions.push({ _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002" @@ -653,7 +656,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], Nodes: [], Jobs: [ { - jobType: rng.randomElement(microplanetJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`, masteryReq: 0, minEnemyLevel: 5, @@ -661,7 +664,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 3, 12, 18) }, { - jobType: rng.randomElement(microplanetJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`, masteryReq: 1, minEnemyLevel: 15, @@ -678,7 +681,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: [14, 14, 14] }, { - jobType: rng.randomElement(microplanetJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`, masteryReq: 2, minEnemyLevel: 30, @@ -686,7 +689,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 4, 72, 88) }, { - jobType: rng.randomElement(microplanetJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`, masteryReq: 3, minEnemyLevel: 40, @@ -694,7 +697,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], xpAmounts: generateXpAmounts(rng, 5, 115, 135) }, { - jobType: rng.randomElement(microplanetJobs), + jobType: rng.randomElementPop(pool), rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`, masteryReq: 10, minEnemyLevel: 100, From 39630c5af7d33fd30e19928f4c0c71939ba00f12 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 08:03:56 -0700 Subject: [PATCH 07/43] fix: properly convert personal room decos to and from inventory types (#2279) Closes #2277 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2279 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/models/personalRoomsModel.ts | 1 + src/services/shipCustomizationsService.ts | 25 +++++++++++++++++++---- src/types/shipTypes.ts | 2 ++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/models/personalRoomsModel.ts b/src/models/personalRoomsModel.ts index 9a19d06b..20f94a9a 100644 --- a/src/models/personalRoomsModel.ts +++ b/src/models/personalRoomsModel.ts @@ -40,6 +40,7 @@ const placedDecosSchema = new Schema( Pos: [Number], Rot: [Number], Scale: Number, + Sockets: Number, PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined } }, { id: false } diff --git a/src/services/shipCustomizationsService.ts b/src/services/shipCustomizationsService.ts index 47764917..a01901fd 100644 --- a/src/services/shipCustomizationsService.ts +++ b/src/services/shipCustomizationsService.ts @@ -8,11 +8,12 @@ import { } from "@/src/types/shipTypes"; import { logger } from "@/src/utils/logger"; import { Types } from "mongoose"; -import { addShipDecorations, getInventory } from "./inventoryService"; +import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService"; import { config } from "./configService"; import { Guild } from "../models/guildModel"; import { hasGuildPermission } from "./guildService"; import { GuildPermission } from "../types/guildTypes"; +import { ExportResources } from "warframe-public-export-plus"; export const setShipCustomizations = async ( accountId: string, @@ -101,6 +102,7 @@ export const handleSetShipDecorations = async ( Pos: placedDecoration.Pos, Rot: placedDecoration.Rot, Scale: placedDecoration.Scale, + Sockets: placedDecoration.Sockets, _id: placedDecoration.MoveId }; @@ -116,12 +118,19 @@ export const handleSetShipDecorations = async ( } if (placedDecoration.RemoveId) { - roomToPlaceIn.PlacedDecos.pull({ _id: placedDecoration.RemoveId }); + const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId)); + const deco = roomToPlaceIn.PlacedDecos[decoIndex]; + roomToPlaceIn.PlacedDecos.splice(decoIndex, 1); await personalRooms.save(); if (!config.unlockAllShipDecorations) { const inventory = await getInventory(accountId); - addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: 1 }]); + const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; + if (deco.Sockets !== undefined) { + addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]); + } else { + addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]); + } await inventory.save(); } @@ -134,7 +143,14 @@ export const handleSetShipDecorations = async ( } else { if (!config.unlockAllShipDecorations) { const inventory = await getInventory(accountId); - addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: -1 }]); + const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0]; + if (placedDecoration.Sockets !== undefined) { + addFusionTreasures(inventory, [ + { ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 } + ]); + } else { + addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]); + } await inventory.save(); } } @@ -148,6 +164,7 @@ export const handleSetShipDecorations = async ( Pos: placedDecoration.Pos, Rot: placedDecoration.Rot, Scale: placedDecoration.Scale, + Sockets: placedDecoration.Sockets, _id: decoId }); diff --git a/src/types/shipTypes.ts b/src/types/shipTypes.ts index 74eaaeae..6ac738b2 100644 --- a/src/types/shipTypes.ts +++ b/src/types/shipTypes.ts @@ -96,6 +96,7 @@ export interface IPlacedDecosDatabase { Pos: [number, number, number]; Rot: [number, number, number]; Scale?: number; + Sockets?: number; PictureFrameInfo?: IPictureFrameInfo; _id: Types.ObjectId; } @@ -136,6 +137,7 @@ export interface IShipDecorationsRequest { MoveId?: string; OldRoom?: string; Scale?: number; + Sockets?: number; } export interface IShipDecorationsResponse { From 731ce6c2152e024a39a4b8af98060949bae11b15 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 08:04:03 -0700 Subject: [PATCH 08/43] feat: galleon of ghouls (#2280) Re #1103 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2280 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + package-lock.json | 8 +-- package.json | 2 +- src/controllers/api/inventoryController.ts | 15 +++- src/index.ts | 3 +- src/models/inboxModel.ts | 8 ++- src/models/inventoryModels/inventoryModel.ts | 39 +++++++--- src/services/configService.ts | 1 + src/services/configWatcherService.ts | 18 +++++ src/services/inboxService.ts | 16 +++++ src/services/missionInventoryUpdateService.ts | 72 ++++++++++++++++++- src/services/worldStateService.ts | 71 ++++++++++++++++++ src/types/inventoryTypes/inventoryTypes.ts | 22 +++--- src/types/requestTypes.ts | 10 +++ src/types/worldStateTypes.ts | 7 +- 15 files changed, 263 insertions(+), 30 deletions(-) diff --git a/config.json.example b/config.json.example index d75d619f..11da5729 100644 --- a/config.json.example +++ b/config.json.example @@ -69,6 +69,7 @@ "affinityBoost": false, "resourceBoost": false, "starDays": true, + "galleonOfGhouls": 0, "eidolonOverride": "", "vallisOverride": "", "duviriOverride": "", diff --git a/package-lock.json b/package-lock.json index d3d0207d..70184b5e 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.69", + "warframe-public-export-plus": "^0.5.70", "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.69", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.69.tgz", - "integrity": "sha512-vTU1tUzqpihzpseUSJMrM82pYbCDZCfW40jXIi+Ol9B3a3Acz0DccfP7i4eoXf7Abahu4H/sjRt/nSHLNBvLHA==" + "version": "0.5.70", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.70.tgz", + "integrity": "sha512-d5dQ/a0rakQnW9tl1HitST8439jDvEgMhkkntQIw7HmdM7s7mvIxvaYSl5wjlYawpUVfGyvGBdZVoAJ7kkQRWw==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index ce1614ba..063cd966 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.69", + "warframe-public-export-plus": "^0.5.70", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index ecc32ec3..3880186e 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -22,7 +22,8 @@ import { getNemesisManifest } from "@/src/helpers/nemesisHelpers"; import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { Ship } from "@/src/models/shipModel"; -import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers"; +import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers"; +import { Inbox } from "@/src/models/inboxModel"; export const inventoryController: RequestHandler = async (request, response) => { const account = await getAccountForRequest(request); @@ -128,13 +129,21 @@ export const getInventoryResponse = async ( xpBasedLevelCapDisabled: boolean, buildLabel: string | undefined ): Promise => { - const [inventoryWithLoadOutPresets, ships] = await Promise.all([ + const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([ inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"), - Ship.find({ ShipOwnerId: inventory.accountOwnerId }) + Ship.find({ ShipOwnerId: inventory.accountOwnerId }), + Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 }) ]); const inventoryResponse = inventoryWithLoadOutPresets.toJSON(); inventoryResponse.Ships = ships.map(x => x.toJSON()); + // In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it. + if (latestMessage) { + inventoryResponse.Mailbox = { + LastInboxId: toOid(latestMessage._id) + }; + } + if (config.infiniteCredits) { inventoryResponse.RegularCredits = 999999999; } diff --git a/src/index.ts b/src/index.ts index 7afd9387..f9d671a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,13 +21,14 @@ import mongoose from "mongoose"; import { JSONStringify } from "json-with-bigint"; import { startWebServer } from "./services/webService"; -import { validateConfig } from "@/src/services/configWatcherService"; +import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService"; import { updateWorldStateCollections } from "./services/worldStateService"; // Patch JSON.stringify to work flawlessly with Bigints. JSON.stringify = JSONStringify; validateConfig(); +syncConfigWithDatabase(); mongoose .connect(config.mongodbUrl) diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index d339707e..139e8b44 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -27,11 +27,12 @@ export interface IMessage { icon?: string; highPriority?: boolean; lowPrioNewPlayers?: boolean; - startDate?: Date; - endDate?: Date; + transmission?: string; att?: string[]; countedAtt?: ITypeCount[]; - transmission?: string; + startDate?: Date; + endDate?: Date; + goalTag?: string; CrossPlatform?: boolean; arg?: Arg[]; gifts?: IGift[]; @@ -107,6 +108,7 @@ const messageSchema = new Schema( lowPrioNewPlayers: Boolean, startDate: Date, endDate: Date, + goalTag: String, date: { type: Date, required: true }, r: Boolean, CrossPlatform: Boolean, diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 0daeb2c1..9b91c7ca 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1,4 +1,4 @@ -import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose"; +import { Document, Model, Schema, Types, model } from "mongoose"; import { IFlavourItem, IRawUpgrade, @@ -7,7 +7,6 @@ import { IBooster, IInventoryClient, ISlots, - IMailboxDatabase, IDuviriInfo, IPendingRecipeDatabase, IPendingRecipeClient, @@ -54,7 +53,6 @@ import { IUpgradeDatabase, ICrewShipMemberDatabase, ICrewShipMemberClient, - IMailboxClient, TEquipmentKey, equipmentKeys, IKubrowPetDetailsDatabase, @@ -99,7 +97,9 @@ import { IAccolades, IHubNpcCustomization, ILotusCustomization, - IEndlessXpReward + IEndlessXpReward, + IPersonalGoalProgressDatabase, + IPersonalGoalProgressClient } from "../../types/inventoryTypes/inventoryTypes"; import { IOid } from "../../types/commonTypes"; import { @@ -371,7 +371,7 @@ FlavourItemSchema.set("toJSON", { } }); -const MailboxSchema = new Schema( +/*const MailboxSchema = new Schema( { LastInboxId: Schema.Types.ObjectId }, @@ -384,7 +384,7 @@ MailboxSchema.set("toJSON", { delete mailboxDatabase.__v; (returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId); } -}); +});*/ const DuviriInfoSchema = new Schema( { @@ -457,6 +457,29 @@ const discoveredMarkerSchema = new Schema( { _id: false } ); +const personalGoalProgressSchema = new Schema( + { + Best: Number, + Count: Number, + Tag: String, + goalId: Types.ObjectId + }, + { _id: false } +); + +personalGoalProgressSchema.set("toJSON", { + virtuals: true, + transform(_doc, obj) { + const db = obj as IPersonalGoalProgressDatabase; + const client = obj as IPersonalGoalProgressClient; + + client._id = toOid(db.goalId); + + delete obj.goalId; + delete obj.__v; + } +}); + const challengeProgressSchema = new Schema( { Progress: Number, @@ -1630,7 +1653,7 @@ const inventorySchema = new Schema( //CompletedJobs: [Schema.Types.Mixed], //Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258, - //PersonalGoalProgress: [Schema.Types.Mixed], + PersonalGoalProgress: { type: [personalGoalProgressSchema], default: undefined }, //Setting interface Style ThemeStyle: String, @@ -1701,7 +1724,7 @@ const inventorySchema = new Schema( //Unknown and system DuviriInfo: DuviriInfoSchema, LastInventorySync: Schema.Types.ObjectId, - Mailbox: MailboxSchema, + //Mailbox: MailboxSchema, HandlerPoints: Number, ChallengesFixVersion: Number, PlayedParkourTutorial: Boolean, diff --git a/src/services/configService.ts b/src/services/configService.ts index 404e4b49..fe74a584 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -76,6 +76,7 @@ export interface IConfig { affinityBoost?: boolean; resourceBoost?: boolean; starDays?: boolean; + galleonOfGhouls?: number; eidolonOverride?: string; vallisOverride?: string; duviriOverride?: string; diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index bb64d5da..8df1acd9 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -3,6 +3,7 @@ import fsPromises from "fs/promises"; import { logger } from "../utils/logger"; import { config, configPath, loadConfig } from "./configService"; import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; +import { Inbox } from "../models/inboxModel"; let amnesia = false; fs.watchFile(configPath, (now, then) => { @@ -22,6 +23,7 @@ fs.watchFile(configPath, (now, then) => { process.exit(1); } validateConfig(); + syncConfigWithDatabase(); const webPorts = getWebPorts(); if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) { @@ -51,6 +53,15 @@ export const validateConfig = (): void => { } } } + if ( + config.worldState?.galleonOfGhouls && + config.worldState.galleonOfGhouls != 1 && + config.worldState.galleonOfGhouls != 2 && + config.worldState.galleonOfGhouls != 3 + ) { + config.worldState.galleonOfGhouls = 0; + modified = true; + } if (modified) { logger.info(`Updating config file to fix some issues with it.`); void saveConfig(); @@ -61,3 +72,10 @@ export const saveConfig = async (): Promise => { amnesia = true; await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2)); }; + +export const syncConfigWithDatabase = (): void => { + // Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false. + if (!config.worldState?.galleonOfGhouls) { + void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus. + } +}; diff --git a/src/services/inboxService.ts b/src/services/inboxService.ts index cc5afc29..d623030d 100644 --- a/src/services/inboxService.ts +++ b/src/services/inboxService.ts @@ -54,6 +54,22 @@ export const createNewEventMessages = async (req: Request): Promise => { }); } + // BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events. + if (config.worldState?.galleonOfGhouls) { + if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) { + newEventMessages.push({ + sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek", + sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle", + msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc", + icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png", + transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek", + att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"], + startDate: new Date(), + goalTag: "GalleonRobbery" + }); + } + } + if (newEventMessages.length === 0) { return; } diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 6ab8b4e9..ec3ee941 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -46,7 +46,7 @@ import { import { updateQuestKey } from "@/src/services/questService"; import { Types } from "mongoose"; import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; -import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService"; +import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; @@ -609,6 +609,47 @@ export const addMissionInventoryUpdates = async ( inventoryChanges.RegularCredits -= value; break; } + case "GoalProgress": { + for (const uploadProgress of value) { + const goal = getWorldState().Goals.find(x => x._id.$oid == uploadProgress._id.$oid); + if (goal && goal.Personal) { + inventory.PersonalGoalProgress ??= []; + const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid)); + if (goalProgress) { + goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best); + goalProgress.Count += uploadProgress.Count; + } else { + inventory.PersonalGoalProgress.push({ + Best: uploadProgress.Best, + Count: uploadProgress.Count, + Tag: goal.Tag, + goalId: new Types.ObjectId(goal._id.$oid) + }); + + if ( + goal.Reward && + goal.Reward.items && + goal.MissionKeyName && + goal.MissionKeyName in goalMessagesByKey + ) { + // Send reward via inbox + const info = goalMessagesByKey[goal.MissionKeyName]; + await createMessage(inventory.accountOwnerId, [ + { + sndr: info.sndr, + msg: info.msg, + att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)), + sub: info.sub, + icon: info.icon, + highPriority: true + } + ]); + } + } + } + } + break; + } case "InvasionProgress": { for (const clientProgress of value) { const dbProgress = inventory.QualifyingInvasions.find(x => @@ -962,6 +1003,14 @@ export const addMissionRewards = async ( let missionCompletionCredits = 0; //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display + + if (rewardInfo.goalId) { + const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId); + if (goal?.MissionKeyName) { + levelKeyName = goal.MissionKeyName; + } + } + if (levelKeyName) { const fixedLevelRewards = getLevelKeyRewards(levelKeyName); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); @@ -1978,3 +2027,24 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } => } return { nodes, buddies }; };*/ + +const goalMessagesByKey: Record = { + "/Lotus/Types/Keys/GalleonRobberyAlert": { + sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek", + msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgA", + sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleA", + icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png" + }, + "/Lotus/Types/Keys/GalleonRobberyAlertB": { + sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek", + msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgB", + sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleB", + icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png" + }, + "/Lotus/Types/Keys/GalleonRobberyAlertC": { + sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek", + msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC", + sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC", + icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png" + } +}; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index f7099220..0a2bfd4f 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1149,6 +1149,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Node: "SolarisUnitedHub1" }); } + // The client gets kinda confused when multiple goals have the same tag, so considering these mutually exclusive. + if (config.worldState?.galleonOfGhouls == 1) { + worldState.Goals.push({ + _id: { $oid: "6814ddf00000000000000000" }, + Activation: { $date: { $numberLong: "1746198000000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 1, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode19", + MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlert", + Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", + Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", + Tag: "GalleonRobbery", + Reward: { + items: [ + "/Lotus/StoreItems/Types/Recipes/Weapons/GrnChainSawTonfaBlueprint", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + }); + } else if (config.worldState?.galleonOfGhouls == 2) { + worldState.Goals.push({ + _id: { $oid: "681e18700000000000000000" }, + Activation: { $date: { $numberLong: "1746802800000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 1, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode28", + MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB", + Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", + Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", + Tag: "GalleonRobbery", + Reward: { + items: [ + "/Lotus/StoreItems/Types/Recipes/Weapons/MortiforShieldAndSwordBlueprint", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + }); + } else if (config.worldState?.galleonOfGhouls == 3) { + worldState.Goals.push({ + _id: { $oid: "682752f00000000000000000" }, + Activation: { $date: { $numberLong: "1747407600000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 1, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode19", + MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertC", + Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", + Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", + Tag: "GalleonRobbery", + Reward: { + items: [ + "/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + }); + } // Nightwave Challenges const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel); diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index c3a8a7d1..30aced6b 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -56,6 +56,7 @@ export interface IInventoryDatabase | "QualifyingInvasions" | "LastInventorySync" | "EndlessXP" + | "PersonalGoalProgress" | TEquipmentKey >, InventoryDatabaseEquipment { @@ -63,7 +64,7 @@ export interface IInventoryDatabase Created: Date; TrainingDate: Date; LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population - Mailbox?: IMailboxDatabase; + //Mailbox?: IMailboxDatabase; GuildId?: Types.ObjectId; PendingRecipes: IPendingRecipeDatabase[]; QuestKeys: IQuestKeyDatabase[]; @@ -95,6 +96,7 @@ export interface IInventoryDatabase QualifyingInvasions: IInvasionProgressDatabase[]; LastInventorySync?: Types.ObjectId; EndlessXP?: IEndlessXpProgressDatabase[]; + PersonalGoalProgress?: IPersonalGoalProgressDatabase[]; } export interface IQuestKeyDatabase { @@ -150,9 +152,9 @@ export interface IMailboxClient { LastInboxId: IOid; } -export interface IMailboxDatabase { +/*export interface IMailboxDatabase { LastInboxId: Types.ObjectId; -} +}*/ export type TSolarMapRegion = | "Earth" @@ -306,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu HWIDProtectEnabled?: boolean; //KubrowPetPrints: IKubrowPetPrint[]; AlignmentReplay?: IAlignment; - //PersonalGoalProgress: IPersonalGoalProgress[]; + PersonalGoalProgress?: IPersonalGoalProgressClient[]; ThemeStyle: string; ThemeBackground: string; ThemeSounds: string; @@ -1015,13 +1017,17 @@ export interface IPeriodicMissionCompletionResponse extends Omit { + goalId: Types.ObjectId; } export interface IPersonalTechProjectDatabase { diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index a6b3f1c1..e2e5da92 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -139,6 +139,14 @@ export type IMissionInventoryUpdateRequest = { }; wagerTier?: number; // the index creditsFee?: number; // the index + GoalProgress?: { + _id: IOid; + Count: number; + Best: number; + Tag: string; + IsMultiProgress: boolean; + MultiProgress: unknown[]; + }[]; InvasionProgress?: IInvasionProgressClient[]; ConquestMissionsCompleted?: number; duviriSuitSelection?: string; @@ -156,6 +164,8 @@ export type IMissionInventoryUpdateRequest = { export interface IRewardInfo { node: string; + goalId?: string; + goalManifest?: string; invasionId?: string; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; sortieId?: string; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 88544d6c..7301333f 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -1,3 +1,4 @@ +import { IMissionReward } from "warframe-public-export-plus"; import { IMongoDate, IOid } from "./commonTypes"; export interface IWorldState { @@ -37,11 +38,15 @@ export interface IGoal { Goal: number; Success: number; Personal: boolean; + Bounty?: boolean; + ClampNodeScores?: boolean; Desc: string; - ToolTip: string; + ToolTip?: string; Icon: string; Tag: string; Node: string; + MissionKeyName?: string; + Reward?: IMissionReward; } export interface ISyndicateMissionInfo { From 285b1bbf6003aa014d676ec2e1785842ab1f550c Mon Sep 17 00:00:00 2001 From: Corvus Date: Wed, 25 Jun 2025 08:04:46 -0700 Subject: [PATCH 09/43] chore(webui): update Chinese translation (#2281) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2281 Co-authored-by: Corvus Co-committed-by: Corvus --- static/webui/translations/zh.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 79fe135f..04463ace 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -143,7 +143,7 @@ dict = { cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, - cheats_unlockAllFlavourItems: `解锁所有装饰物品`, + cheats_unlockAllFlavourItems: `解锁所有装饰物品`, cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllDecoRecipes: `解锁所有道场配方`, @@ -157,10 +157,10 @@ dict = { cheats_noMasteryRankUpCooldown: `段位考核无冷却时间`, cheats_noVendorPurchaseLimits: `商城或商人无购买限制`, 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_noKimCooldowns: `即时通无冷却时间`, + cheats_fullyStockedVendors: `商人贩卖所有商品`, + cheats_baroAlwaysAvailable: `虚空商人可永久访问`, + cheats_baroFullyStocked: `虚空商人贩卖所有商品`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, @@ -174,7 +174,7 @@ dict = { cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`, - cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, + cheats_unlockAllSimarisResearchEntries: `解锁所有Simaris研究条目`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_save: `保存`, @@ -187,8 +187,8 @@ dict = { cheats_none: `无`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, - import_samples: `[UNTRANSLATED] Samples:`, - import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`, + import_samples: `示例:`, + import_samples_maxFocus: `所有专精学派完全精通`, upgrade_Equilibrium: `+|VAL|% 能量 来自生命球, +|VAL|% 生命 来自能量球`, upgrade_MeleeCritDamage: `+|VAL|% 近战暴击伤害`, @@ -231,7 +231,7 @@ dict = { upgrade_DamageReductionOnHack: `入侵时,+75% 伤害减免`, upgrade_OnExecutionReviveCompanion: `怜悯之击 减少同伴复苏时间 15秒`, upgrade_OnExecutionParkourSpeed: `怜悯之击 15秒内 +60% 跑酷速度`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`, + upgrade_AvatarTimeLimitIncrease: `+8秒 入侵时间`, upgrade_ElectrifyOnHack: `入侵时震慑20米之内的敌人`, upgrade_OnExecutionTerrify: `怜悯之击 50% 几率让 15米 以内的敌人恐慌`, upgrade_OnHackLockers: `入侵后解锁20米内的5个储物柜`, From 0fdf8b2c7501b11d8f687aee4468b5fcb4c606c3 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:33:21 +0200 Subject: [PATCH 10/43] chore(webui): update fr Co-authored-by: Vitruvio --- static/webui/translations/fr.js | 130 ++++++++++++++++---------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 26ead631..c3986cc7 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -3,8 +3,8 @@ dict = { general_inventoryUpdateNote: `Note : Les changements effectués ici seront appliqués lors de la syncrhonisation. Visiter la navigation appliquera les changements apportés à l'inventaire.`, general_addButton: `Ajouter`, general_bulkActions: `Action groupée`, - code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, - code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, + code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`, + code_regFail: `Enregistrement impossible. Compte existant?`, code_changeNameConfirm: `Nouveau nom du compte :`, code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`, code_archgun: `Archgun`, @@ -85,7 +85,7 @@ dict = { inventory_moaPets: `Moas`, inventory_kubrowPets: `Bêtes`, inventory_evolutionProgress: `Progrès de l'évolution Incarnon`, - inventory_Boosters: `[UNTRANSLATED] Boosters`, + inventory_Boosters: `Boosters`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, @@ -100,7 +100,7 @@ dict = { inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, - inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, + inventory_maxPlexus: `Plexus au rang max`, quests_list: `Quêtes`, quests_completeAll: `Compléter toutes les quêtes`, @@ -135,10 +135,10 @@ dict = { cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`, - cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, - cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, - cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, - cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, + cheats_dontSubtractPurchaseCreditCost: `Ne pas retirer le coût en crédits`, + cheats_dontSubtractPurchasePlatinumCost: `Ne pas retirer le coût en platines`, + cheats_dontSubtractPurchaseItemCost: `Ne pas retirer le coût d'achat`, + cheats_dontSubtractPurchaseStandingCost: `Ne pas retirer le coût en réputation`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, @@ -158,11 +158,11 @@ 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_fullyStockedVendors: `Les vendeurs ont un stock à 100%`, 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_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, @@ -173,11 +173,11 @@ dict = { cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`, 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_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`, + cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, - cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, - cheats_save: `[UNTRANSLATED] Save`, + cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`, + cheats_save: `Sauvegarder`, cheats_account: `Compte`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_helminthUnlockAll: `Helminth niveau max`, @@ -187,59 +187,59 @@ 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`, + import_samples: `Echantillons :`, + import_samples_maxFocus: `Toutes les écoles de focus au rang max`, - upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, - upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, - upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, - upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, - upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, - upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, - upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, - upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, - upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, - upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, - upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, - upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, - upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, - upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, - upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, - upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, - upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, - upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, - upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, - upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, - upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, - upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, - upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, - upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, - upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, - upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, - upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, - upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, - upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, - upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, - upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, - upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, - upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, - upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, - upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, - upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`, + upgrade_MeleeCritDamage: `+|VAL|% de dégâts critique en mêlée`, + upgrade_PrimaryStatusChance: `+|VAL|% de chance de statut sur arme primaire`, + upgrade_SecondaryCritChance: `+|VAL|% de chance critique sur arme secondaire`, + upgrade_WarframeAbilityDuration: `+|VAL|% de durée de pouvoir`, + upgrade_WarframeAbilityStrength: `+|VAL|% de puissance de pouvoir`, + upgrade_WarframeArmourMax: `+|VAL| d'armure`, + upgrade_WarframeBlastProc: `+|VAL| de boucliers sur élimination avec des dégats d'explosion`, + upgrade_WarframeCastingSpeed: `+|VAL|% de vitesse de lancement de pouvoir`, + upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut corrosif`, + upgrade_WarframeCorrosiveStack: `+|VAL| de cumuls maximum de Statut Corrosif`, + upgrade_WarframeCritDamageBoost: `+|VAL|% de dégâts critique en mêlée (Doublé si l'énergie dépasse 500)`, + upgrade_WarframeElectricDamage: `+|VAL1|% de dégâts électrique sur arme primaire (+|VAL2|% par fragment supplémentaire)`, + upgrade_WarframeElectricDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut électrique`, + upgrade_WarframeEnergyMax: `+|VAL| d'énergie max`, + upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`, + upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`, + upgrade_WarframeHealthMax: `+|VAL| de santé`, + upgrade_WarframeHPBoostFromImpact: `+|VAL1| de santé par ennemi tué avec des dégâts explosifs (Max |VAL2| de santé)`, + upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`, + upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`, + upgrade_WarframeRegen: `+|VAL| régénération de santé/s`, + upgrade_WarframeShieldMax: `+|VAL| de boucliers`, + upgrade_WarframeStartingEnergy: `+|VAL|% d'énergie sur apparition`, + upgrade_WarframeToxinDamage: `+|VAL|% de dégâts sur le statut poison`, + upgrade_WarframeToxinHeal: `+|VAL| de santé récupérée à chaque dégât de statut poison`, + upgrade_WeaponCritBoostFromHeat: `+|VAL1|% de chance critique sur arme secondaire pour chaque ennemi affecté puis tué par du feu (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `+7.5% de portée de pouvoir`, + upgrade_AvatarAbilityEfficiency: `+5% d'efficacité de pouvoir`, + upgrade_AvatarEnergyRegen: `+0.5 de régénération d'énergie/s`, + upgrade_AvatarEnemyRadar: `+5m de radar ennemi`, + upgrade_AvatarLootRadar: `+7m de radar à butin`, + upgrade_WeaponAmmoMax: `+15% de munitions max`, + upgrade_EnemyArmorReductionAura: `-3% d'armure ennemi`, + upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`, + upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`, + upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`, upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, - 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] +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`, - upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, - upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, - upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, - upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, - upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`, + upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`, + upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`, + upgrade_AvatarTimeLimitIncrease: `+8s de temps de piratage`, + upgrade_ElectrifyOnHack: `Electrifie les ennemis dans un rayon de 20m pendant un piratage`, + upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`, + upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`, + upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`, + upgrade_OnExecutionDrainPower: `100% pour le prochain pouvoir de gagner +50% de puissance de pouvoir sur miséricorde`, + upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`, + upgrade_SwiftExecute: `Vitesse des miséricordes augmentée de 50%`, + upgrade_OnHackInvis: `Invisible pendant 15 secondes après un piratage`, prettier_sucks_ass: `` }; From 86f86d0476a34426e0459bad9d9ad8b854581953 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:39:54 +0200 Subject: [PATCH 11/43] chore: fully adopt tsgo It's finally able to emit this project without issues. It doesn't yet support incremental builds, but a full build with it is faster than an incremental build with the old tsc, so we're not losing anything. --- package-lock.json | 72 +++++++++++++++++++++-------------------------- package.json | 8 ++++-- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70184b5e..339a6611 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@types/morgan": "^1.9.9", "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", + "@typescript/native-preview": "^7.0.0-dev.20250625.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -30,7 +31,6 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", - "@typescript/native-preview": "^7.0.0-dev.20250523.1", "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", @@ -605,10 +605,9 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-CgdgP/gmyaMThY7Fho19nDaTVryn9QV/zD/6w1KfDCn3M4Rq4WvkSc7Ob1ohc4V1XjCSIzg6Ul+HbLEc7xvV4Q==", - "dev": true, + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-7781zmsKURCHknc37H4U4la4kZduyxmmUshZLBzNhPHhV5DKo++K8MF69kxhRG3/vS4HBhozf0YI0mZMIbkSDA==", "license": "Apache-2.0", "bin": { "tsgo": "bin/tsgo.js" @@ -617,23 +616,22 @@ "node": ">=20.6.0" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250523.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250523.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20250523.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250523.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20250523.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250523.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20250523.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250625.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250625.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20250625.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250625.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20250625.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250625.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20250625.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-oWJMPD+lfH9/dvHhPSZdTv43lfyZGrn7crytefhkiQPSwP0MIUCpnDkofGP/ML1nv0xx0pwWhH+Ein88NW3LuA==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-JcLCql0O6+0iHIMllvax02kqpNtY1RUckGKomuO5kSbrOo9PsR+6r5MEcspfj47gwOl7AS0vrGhBCFFogF+KGw==", "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -644,13 +642,12 @@ } }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-Yk8bJEsYsRKgRqYlwPvh7DPdgBMC/oPN60X0LWeuMLci65+4kyqF8Cv6K/W3ABc005cB4tYn4iR+9T6zipvrKw==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-0vCkk3FdS92W625JyzA8Slu/0vgkeu10fRQNfgIbf+E29DKMKnwXW56WhHSdGXAivU44Mewwc589+CbsABq3Sw==", "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -661,13 +658,12 @@ } }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-B+8CRIv6ebL8gzAagnJP8wml3baFV2FtFWuXYl6jlAcLGoQOh/yGdcAueZoJjJKNod4gAOl8OJoTicuC0BVIxw==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-MumU7p+09ikH/x5IOJRV6DUj6N5/0kSlI4IsAUPtpT2WGkQdDtL2CC523/94YvOfWB1/+9r01636LVCGOJ135g==", "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -678,13 +674,12 @@ } }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-IErNI08z9qE6mHaJaT6tM7il8j21ryH3DNVyFP4yz5FTKnkXFj1Kb4NcI41Q8w226LTQgBR8kNErVlbUWr7ywA==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-IgnoWQSKeoeL7Y7tvlbcDQx0nidK3UWa/bbm1zJv+AfQlAGMrEMygp+ZzocmycUCYOVM0dcIbymjoiI/QRHTng==", "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -695,13 +690,12 @@ } }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-TCZtknsLUgPRaEfX9CvBZNgrHhMRZPYYZgF1Aasdv0PONv9mB8w0Xforgxoo4UFjdF5ZzOu2icgc7sKJJeu5vw==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-6fE8piqPfzPPqmQ37ewTSbm4HW0cNqOEhfLG2F37zJd4525mefhIpWvj2iCkEHWp+BDlF2dYCbB4cY2nmfrNNw==", "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -712,13 +706,12 @@ } }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-bulwrkLEkoY4Jqeuvfz24RiVOiZZ7Rr9TblFqZAgZFZOnyXuhjM1jE8F1hnJFC5AghJe2HdLD3EKfabqlffrIw==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-ppCkjBAFotPxL8j9Vk5cNSwMreOvAt02AMa5Hko3JQGSVA2TQCIlvTFn+SHSIWzYbzomc9j4j5WOcOR0rmAAHg==", "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -729,13 +722,12 @@ } }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20250523.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250523.1.tgz", - "integrity": "sha512-ztzfO0oF/rj8xO5y3SyAcigmgvgczrqobCugEWFqiYumteWZPN2MYWcNYk2k8Y5LAgg1fN1xHIg8RRSPoo6XUg==", + "version": "7.0.0-dev.20250625.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250625.1.tgz", + "integrity": "sha512-BsnJqso5MKAW4Y7fPmcamJ+EIrWOTqwLjeZP74NNFvTqCsA4RkITCw4NpLwD0lzrv9VsQcQ+bNwB8DrT+oDqoQ==", "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ diff --git a/package.json b/package.json index 063cd966..bb63fc57 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "main": "index.ts", "scripts": { "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js", - "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", - "build:dev": "tsc --incremental --sourceMap", + "build": "tsgo --sourceMap && ncp static/webui build/static/webui", + "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", + "build:dev": "tsgo --sourceMap", + "build:dev:tsc": "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", @@ -25,6 +27,7 @@ "@types/morgan": "^1.9.9", "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", + "@typescript/native-preview": "^7.0.0-dev.20250625.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -42,7 +45,6 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", - "@typescript/native-preview": "^7.0.0-dev.20250523.1", "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", From b42182c85fb365a29f4f05de6be7e31852b3487a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:22:24 -0700 Subject: [PATCH 12/43] fix(webui): handle existing entries for unlock all missions (#2290) Closes #2283 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2290 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../custom/completeAllMissionsController.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/controllers/custom/completeAllMissionsController.ts b/src/controllers/custom/completeAllMissionsController.ts index 2e7ac2fb..f6ab486d 100644 --- a/src/controllers/custom/completeAllMissionsController.ts +++ b/src/controllers/custom/completeAllMissionsController.ts @@ -12,18 +12,24 @@ export const completeAllMissionsController: RequestHandler = async (req, res) => 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 - }); - + let mission = inventory.Missions.find(x => x.Tag == tag); + if (!mission) { + mission = + inventory.Missions[ + inventory.Missions.push({ + Completes: 0, + Tier: 0, + Tag: tag + }) - 1 + ]; + } + if (mission.Completes == 0) { + mission.Completes++; if (node.missionReward) { - console.log(node.missionReward); addFixedLevelRewards(node.missionReward, inventory, MissionRewards); } } + mission.Tier = 1; } for (const reward of MissionRewards) { await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true); From c9edef39f8aced8b2362d0365a9121c406c97b1c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:24:16 -0700 Subject: [PATCH 13/43] feat: claimJunctionChallengeReward (#2289) Closes #2285 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2289 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 ++--- package.json | 2 +- .../claimJunctionChallengeRewardController.ts | 35 +++++++++++++++++++ src/models/inventoryModels/inventoryModel.ts | 9 +++-- src/routes/api.ts | 2 ++ src/types/inventoryTypes/inventoryTypes.ts | 4 ++- 6 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 src/controllers/api/claimJunctionChallengeRewardController.ts diff --git a/package-lock.json b/package-lock.json index 339a6611..f9ab11d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.70", + "warframe-public-export-plus": "^0.5.71", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -3388,9 +3388,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.70", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.70.tgz", - "integrity": "sha512-d5dQ/a0rakQnW9tl1HitST8439jDvEgMhkkntQIw7HmdM7s7mvIxvaYSl5wjlYawpUVfGyvGBdZVoAJ7kkQRWw==" + "version": "0.5.71", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.71.tgz", + "integrity": "sha512-TCS2wPRsBzuURJlIMDhygAHaLsKVZ7dGuC73WZ/iMyn3gKVwA98nnaIj24D+UceWS08fwq4ilWAfUzHJd6X29A==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index bb63fc57..ed49ea11 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.70", + "warframe-public-export-plus": "^0.5.71", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/src/controllers/api/claimJunctionChallengeRewardController.ts b/src/controllers/api/claimJunctionChallengeRewardController.ts new file mode 100644 index 00000000..849126cb --- /dev/null +++ b/src/controllers/api/claimJunctionChallengeRewardController.ts @@ -0,0 +1,35 @@ +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; +import { RequestHandler } from "express"; +import { ExportChallenges } from "warframe-public-export-plus"; + +export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const data = getJSONfromString(String(req.body)); + const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!; + if (challengeProgress.ReceivedJunctionReward) { + throw new Error(`attempt to double-claim junction reward`); + } + challengeProgress.ReceivedJunctionReward = true; + inventory.ClaimedJunctionChallengeRewards ??= []; + inventory.ClaimedJunctionChallengeRewards.push(data.Challenge); + const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1]; + const inventoryChanges = {}; + for (const reward of challengeMeta.countedRewards!) { + combineInventoryChanges( + inventoryChanges, + (await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges + ); + } + await inventory.save(); + res.json({ + inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here. + }); +}; + +interface IClaimJunctionChallengeRewardRequest { + Challenge: string; +} diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 9b91c7ca..cd597bb4 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -483,8 +483,9 @@ personalGoalProgressSchema.set("toJSON", { const challengeProgressSchema = new Schema( { Progress: Number, - Name: String, - Completed: [String] + Completed: { type: [String], default: undefined }, + ReceivedJunctionReward: Boolean, + Name: { type: String, required: true } }, { _id: false } ); @@ -1777,7 +1778,9 @@ const inventorySchema = new Schema( BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined }, LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined }, - HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined } + HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }, + + ClaimedJunctionChallengeRewards: { type: [String], default: undefined } }, { timestamps: { createdAt: "Created", updatedAt: false } } ); diff --git a/src/routes/api.ts b/src/routes/api.ts index 63df81d0..2a3255cf 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; +import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController"; import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController"; @@ -237,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController); apiRouter.post("/artifactTransmutation.php", artifactTransmutationController); apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); +apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController); apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?) diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 30aced6b..4c4a9821 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -380,6 +380,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu LockedWeaponGroup?: ILockedWeaponGroupClient; HubNpcCustomizations?: IHubNpcCustomization[]; Ship?: IOrbiter; // U22 and below, response only + ClaimedJunctionChallengeRewards?: string[]; // U39 } export interface IAffiliation { @@ -448,8 +449,9 @@ export interface IVendorPurchaseHistoryEntryDatabase { export interface IChallengeProgress { Progress: number; - Name: string; Completed?: string[]; + ReceivedJunctionReward?: boolean; // U39 + Name: string; } export interface ICollectibleEntry { From 8c225559046f5e5bb87b9b2954d0048436ceae20 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:24:29 -0700 Subject: [PATCH 14/43] feat(webui): add missing subsumed abilities (#2287) Closes #1984 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2287 Reviewed-by: Sainan Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- .../addMissingHelminthBlueprintsController.ts | 24 +++++++++++++++++++ src/routes/custom.ts | 2 ++ static/webui/index.html | 1 + static/webui/script.js | 5 ++++ 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, 38 insertions(+) create mode 100644 src/controllers/custom/addMissingHelminthBlueprintsController.ts diff --git a/src/controllers/custom/addMissingHelminthBlueprintsController.ts b/src/controllers/custom/addMissingHelminthBlueprintsController.ts new file mode 100644 index 00000000..4de501fe --- /dev/null +++ b/src/controllers/custom/addMissingHelminthBlueprintsController.ts @@ -0,0 +1,24 @@ +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getInventory, addRecipes } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; +import { ExportRecipes } from "warframe-public-export-plus"; + +export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "Recipes"); + const allHelminthRecipes = Object.keys(ExportRecipes).filter( + key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY" + ); + const inventoryHelminthRecipes = inventory.Recipes.filter(recipe => + recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/") + ).map(recipe => recipe.ItemType); + + const missingHelminthRecipes = allHelminthRecipes + .filter(key => !inventoryHelminthRecipes.includes(key)) + .map(ItemType => ({ ItemType, ItemCount: 1 })); + + addRecipes(inventory, missingHelminthRecipes); + + await inventory.save(); + res.end(); +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 35d89d4d..5ed4906e 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -13,6 +13,7 @@ import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAl import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController"; import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController"; import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController"; +import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController"; import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createMessageController } from "@/src/controllers/custom/createMessageController"; @@ -42,6 +43,7 @@ customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController); customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController); customRouter.get("/completeAllMissions", completeAllMissionsController); +customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController); customRouter.post("/createAccount", createAccountController); customRouter.post("/createMessage", createMessageController); diff --git a/static/webui/index.html b/static/webui/index.html index 3d706ee9..caf7be01 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -794,6 +794,7 @@ +
diff --git a/static/webui/script.js b/static/webui/script.js index c6c6727b..209d727e 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1449,6 +1449,11 @@ function addMissingEquipment(categories) { } } +async function addMissingHelminthRecipes() { + await revalidateAuthz(); + await fetch("/custom/addMissingHelminthBlueprints?" + window.authz); +} + function addMissingEvolutionProgress() { const requests = []; document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => { diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 08b02c78..a56103b6 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -181,6 +181,7 @@ dict = { cheats_account: `Account`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_helminthUnlockAll: `Helminth vollständig aufleveln`, + cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`, cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeButton: `Ändern`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index a97ee549..36d38301 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -180,6 +180,7 @@ dict = { cheats_account: `Account`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_helminthUnlockAll: `Fully Level Up Helminth`, + cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`, cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`, cheats_changeSupportedSyndicate: `Supported syndicate`, cheats_changeButton: `Change`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index a4e798d7..3fbb7bf1 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -181,6 +181,7 @@ dict = { cheats_account: `Cuenta`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_helminthUnlockAll: `Subir al máximo el Helminto`, + cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`, cheats_changeSupportedSyndicate: `Sindicatos disponibles`, cheats_changeButton: `Cambiar`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index c3986cc7..a52f5bef 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -181,6 +181,7 @@ dict = { cheats_account: `Compte`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_helminthUnlockAll: `Helminth niveau max`, + cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, cheats_intrinsicsUnlockAll: `Inhérences niveau max`, cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeButton: `Changer`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 5b353690..26fef974 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -181,6 +181,7 @@ dict = { cheats_account: `Аккаунт`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, + cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`, cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`, cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeButton: `Изменить`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 04463ace..e0ba1ff1 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -181,6 +181,7 @@ dict = { cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_helminthUnlockAll: `完全升级Helminth`, + cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`, cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeButton: `更改`, From d94cd38120b15f09fad6900b2257cf74a7c1246c Mon Sep 17 00:00:00 2001 From: Corvus Date: Thu, 26 Jun 2025 06:44:03 -0700 Subject: [PATCH 15/43] chore(webui): update Chinese translation (#2291) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2291 Co-authored-by: Corvus Co-committed-by: Corvus --- static/webui/translations/zh.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index e0ba1ff1..ca7f726a 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -74,9 +74,9 @@ dict = { inventory_longGuns: `主要武器`, inventory_pistols: `次要武器`, inventory_melee: `近战武器`, - inventory_spaceSuits: `Archwings`, - inventory_spaceGuns: `Archwing主武器`, - inventory_spaceMelee: `Archwing近战武器`, + inventory_spaceSuits: `载具`, + inventory_spaceGuns: `载具主武器`, + inventory_spaceMelee: `载具近战武器`, inventory_mechSuits: `殁世机甲`, inventory_sentinels: `守护`, inventory_sentinelWeapons: `守护武器`, @@ -88,15 +88,15 @@ dict = { inventory_Boosters: `加成器`, inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddWeapons: `添加缺失武器`, - inventory_bulkAddSpaceSuits: `添加缺失Archwing`, - inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, + inventory_bulkAddSpaceSuits: `添加缺失载具`, + inventory_bulkAddSpaceWeapons: `添加缺失载具武器`, inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`, inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`, - inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, - inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, + inventory_bulkRankUpSpaceSuits: `所有载具升满级`, + inventory_bulkRankUpSpaceWeapons: `所有载具武器升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, @@ -181,7 +181,7 @@ dict = { cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_helminthUnlockAll: `完全升级Helminth`, - cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`, + cheats_addMissingSubsumedAbilities: `添加Helminth未汲取的战甲技能`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`, cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeButton: `更改`, From a3be376489e0d2182c180f281a08c80da41fc504 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:02:40 -0700 Subject: [PATCH 16/43] chore(webui): add Thalys to Incarnon List (#2299) Closes #2298 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2299 Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- static/fixed_responses/allIncarnonList.json | 3 ++- static/webui/script.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/static/fixed_responses/allIncarnonList.json b/static/fixed_responses/allIncarnonList.json index 3d85db63..a27798e1 100644 --- a/static/fixed_responses/allIncarnonList.json +++ b/static/fixed_responses/allIncarnonList.json @@ -45,5 +45,6 @@ "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon", "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol", "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon", - "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon" + "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon", + "/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon" ] diff --git a/static/webui/script.js b/static/webui/script.js index 209d727e..91cc54a1 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -248,7 +248,8 @@ const permanentEvolutionWeapons = new Set([ "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon", "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol", "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon", - "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon" + "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon", + "/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon" ]); let uniqueLevelCaps = {}; From eb7b51852b57dc8b6178b70709e3eab761e29363 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:31:40 -0700 Subject: [PATCH 17/43] fix: use exact quantity when adding gear items by StoreItem (#2310) Closes #2304 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2310 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/inventoryService.ts | 1 + src/services/purchaseService.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index a2145d3b..cf5208ce 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -499,6 +499,7 @@ export const addItem = async ( // - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity. if (!exactQuantity) { quantity *= ExportGear[typeName].purchaseQuantity ?? 1; + logger.debug(`non-exact acquisition of ${typeName}; factored quantity is ${quantity}`); } const consumablesChanges = [ { diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 59647fdf..0c4cad35 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -371,18 +371,28 @@ export const handleStoreItemAcquisition = async ( } else { const storeCategory = getStoreItemCategory(storeItemName); const internalName = fromStoreItem(storeItemName); - logger.debug(`store category ${storeCategory}`); if (!ignorePurchaseQuantity) { if (internalName in ExportGear) { quantity *= ExportGear[internalName].purchaseQuantity || 1; + logger.debug(`factored quantity is ${quantity}`); } else if (internalName in ExportResources) { quantity *= ExportResources[internalName].purchaseQuantity || 1; + logger.debug(`factored quantity is ${quantity}`); } } + logger.debug(`store category ${storeCategory}`); switch (storeCategory) { default: { purchaseResponse = { - InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed) + InventoryChanges: await addItem( + inventory, + internalName, + quantity, + premiumPurchase, + seed, + undefined, + true + ) }; break; } @@ -524,7 +534,9 @@ const handleTypesPurchase = async ( logger.debug(`type category ${typeCategory}`); switch (typeCategory) { default: - return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) }; + return { + InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed, undefined, true) + }; case "BoosterPacks": return handleBoosterPackPurchase(typesName, inventory, quantity); case "SlotItems": From 0ba641a2acc75150469395a65023628a36b3d695 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:31:52 -0700 Subject: [PATCH 18/43] chore: update PE+ (#2311) Closes #2309 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2311 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9ab11d0..699c5d54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.71", + "warframe-public-export-plus": "^0.5.72", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -3388,9 +3388,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.71", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.71.tgz", - "integrity": "sha512-TCS2wPRsBzuURJlIMDhygAHaLsKVZ7dGuC73WZ/iMyn3gKVwA98nnaIj24D+UceWS08fwq4ilWAfUzHJd6X29A==" + "version": "0.5.72", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.72.tgz", + "integrity": "sha512-oOZgtU6L0MGcPRKfA6+bonu+Db1kie1lVdLmA7/DbheTPweNkBEx3Hx3Seib+hEaFW+nLj3T5GtmGxGcFHCHfg==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index ed49ea11..848ece87 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.71", + "warframe-public-export-plus": "^0.5.72", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", From 764cdd1ab859663dd32d22e9e2e9935d8e022c7c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:32:26 -0700 Subject: [PATCH 19/43] feat: worldState.allTheFissures (#2313) Closes #2294 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2313 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 1 + config.json.example | 1 + src/services/configService.ts | 1 + src/services/worldStateService.ts | 48 ++++++++++++++++++++++--------- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0bd7de22..5b2425ef 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi - `RadioLegion2Syndicate` for The Emissary - `RadioLegionIntermissionSyndicate` for Intermission I - `RadioLegionSyndicate` for The Wolf of Saturn Six +- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively. - `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`) diff --git a/config.json.example b/config.json.example index 11da5729..baf51baa 100644 --- a/config.json.example +++ b/config.json.example @@ -74,6 +74,7 @@ "vallisOverride": "", "duviriOverride": "", "nightwaveOverride": "", + "allTheFissures": "", "circuitGameModes": null }, "dev": { diff --git a/src/services/configService.ts b/src/services/configService.ts index fe74a584..4f2e93e7 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -81,6 +81,7 @@ export interface IConfig { vallisOverride?: string; duviriOverride?: string; nightwaveOverride?: string; + allTheFissures?: string; circuitGameModes?: string[]; }; dev?: { diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 0a2bfd4f..5aba5813 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1524,20 +1524,40 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }; export const populateFissures = async (worldState: IWorldState): Promise => { - const fissures = await Fissure.find({}); - for (const fissure of fissures) { - const meta = ExportRegions[fissure.Node]; - worldState.ActiveMissions.push({ - _id: toOid(fissure._id), - Region: meta.systemIndex + 1, - Seed: 1337, - Activation: toMongoDate(fissure.Activation), - Expiry: toMongoDate(fissure.Expiry), - Node: fissure.Node, - MissionType: eMissionType[meta.missionIndex].tag, - Modifier: fissure.Modifier, - Hard: fissure.Hard - }); + if (config.worldState?.allTheFissures) { + let i = 0; + for (const [tier, nodes] of Object.entries(fissureMissions)) { + for (const node of nodes) { + const meta = ExportRegions[node]; + worldState.ActiveMissions.push({ + _id: { $oid: (i++).toString().padStart(8, "0") + "8e0c70ba050f1eb7" }, + Region: meta.systemIndex + 1, + Seed: 1337, + Activation: { $date: { $numberLong: "1000000000000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Node: node, + MissionType: eMissionType[meta.missionIndex].tag, + Modifier: tier, + Hard: config.worldState.allTheFissures == "hard" + }); + } + } + } else { + const fissures = await Fissure.find({}); + for (const fissure of fissures) { + const meta = ExportRegions[fissure.Node]; + worldState.ActiveMissions.push({ + _id: toOid(fissure._id), + Region: meta.systemIndex + 1, + Seed: 1337, + Activation: toMongoDate(fissure.Activation), + Expiry: toMongoDate(fissure.Expiry), + Node: fissure.Node, + MissionType: eMissionType[meta.missionIndex].tag, + Modifier: fissure.Modifier, + Hard: fissure.Hard + }); + } } }; From 4f1f9592b0b52818908a7638675e6a89928ed18a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:30:01 -0700 Subject: [PATCH 20/43] chore: use chokidar for configWatcherService (#2315) It's just a lot snappier + works flawlessly under Bun. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2315 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 4 +--- package.json | 2 +- src/services/configWatcherService.ts | 9 ++------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 699c5d54..f272a944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", "@typescript/native-preview": "^7.0.0-dev.20250625.1", + "chokidar": "^4.0.3", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -31,7 +32,6 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", - "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", "prettier": "^3.5.3", @@ -997,7 +997,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2803,7 +2802,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" diff --git a/package.json b/package.json index 848ece87..de7411b4 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/websocket": "^1.0.10", "@types/ws": "^8.18.1", "@typescript/native-preview": "^7.0.0-dev.20250625.1", + "chokidar": "^4.0.3", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -45,7 +46,6 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", - "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", "prettier": "^3.5.3", diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 8df1acd9..cab235ff 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -1,4 +1,4 @@ -import fs from "fs"; +import chokidar from "chokidar"; import fsPromises from "fs/promises"; import { logger } from "../utils/logger"; import { config, configPath, loadConfig } from "./configService"; @@ -6,12 +6,7 @@ import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./w import { Inbox } from "../models/inboxModel"; let amnesia = false; -fs.watchFile(configPath, (now, then) => { - // https://github.com/oven-sh/bun/issues/20542 - if (process.versions.bun && now.mtimeMs == then.mtimeMs) { - return; - } - +chokidar.watch(configPath).on("change", () => { if (amnesia) { amnesia = false; } else { From d79e7c0274eca24da4684417424286fa98c593a7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:32:53 -0700 Subject: [PATCH 21/43] feat(webui): world state config (#2318) Re #2312. Will need some follow-up considerations for circuit game modes. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2318 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/custom/configController.ts | 42 +++++++++ .../custom/getConfigDataController.ts | 14 --- .../custom/updateConfigDataController.ts | 21 ----- src/routes/custom.ts | 7 +- static/webui/index.html | 91 ++++++++++++++++++- static/webui/script.js | 70 ++++++++------ static/webui/translations/de.js | 45 +++++++++ static/webui/translations/en.js | 45 +++++++++ static/webui/translations/es.js | 45 +++++++++ static/webui/translations/fr.js | 45 +++++++++ static/webui/translations/ru.js | 45 +++++++++ static/webui/translations/zh.js | 45 +++++++++ 12 files changed, 446 insertions(+), 69 deletions(-) create mode 100644 src/controllers/custom/configController.ts delete mode 100644 src/controllers/custom/getConfigDataController.ts delete mode 100644 src/controllers/custom/updateConfigDataController.ts diff --git a/src/controllers/custom/configController.ts b/src/controllers/custom/configController.ts new file mode 100644 index 00000000..2c1e10ac --- /dev/null +++ b/src/controllers/custom/configController.ts @@ -0,0 +1,42 @@ +import { RequestHandler } from "express"; +import { config } from "@/src/services/configService"; +import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; +import { saveConfig } from "@/src/services/configWatcherService"; + +export const getConfigController: RequestHandler = async (req, res) => { + const account = await getAccountForRequest(req); + if (isAdministrator(account)) { + const responseData: Record = {}; + for (const id of req.body as string[]) { + const [obj, idx] = configIdToIndexable(id); + responseData[id] = obj[idx] ?? null; + } + res.json(responseData); + } else { + res.status(401).end(); + } +}; + +export const setConfigController: RequestHandler = async (req, res) => { + const account = await getAccountForRequest(req); + if (isAdministrator(account)) { + for (const [id, value] of Object.entries(req.body as Record)) { + const [obj, idx] = configIdToIndexable(id); + obj[idx] = value; + } + await saveConfig(); + res.end(); + } else { + res.status(401).end(); + } +}; + +const configIdToIndexable = (id: string): [Record, string] => { + let obj = config as unknown as Record; + const arr = id.split("."); + while (arr.length > 1) { + obj = obj[arr[0]]; + arr.splice(0, 1); + } + return [obj, arr[0]]; +}; diff --git a/src/controllers/custom/getConfigDataController.ts b/src/controllers/custom/getConfigDataController.ts deleted file mode 100644 index 12208527..00000000 --- a/src/controllers/custom/getConfigDataController.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { RequestHandler } from "express"; -import { config } from "@/src/services/configService"; -import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; - -const getConfigDataController: RequestHandler = async (req, res) => { - const account = await getAccountForRequest(req); - if (isAdministrator(account)) { - res.json(config); - } else { - res.status(401).end(); - } -}; - -export { getConfigDataController }; diff --git a/src/controllers/custom/updateConfigDataController.ts b/src/controllers/custom/updateConfigDataController.ts deleted file mode 100644 index 7c87c372..00000000 --- a/src/controllers/custom/updateConfigDataController.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { RequestHandler } from "express"; -import { saveConfig } from "@/src/services/configWatcherService"; -import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; -import { config, IConfig } from "@/src/services/configService"; - -export const updateConfigDataController: RequestHandler = async (req, res) => { - const account = await getAccountForRequest(req); - if (isAdministrator(account)) { - const data = req.body as IUpdateConfigDataRequest; - config[data.key] = data.value; - await saveConfig(); - res.end(); - } else { - res.status(401).end(); - } -}; - -interface IUpdateConfigDataRequest { - key: keyof IConfig; - value: never; -} diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 5ed4906e..e10bc721 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -25,8 +25,7 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController"; import { setBoosterController } from "@/src/controllers/custom/setBoosterController"; -import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; -import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; +import { getConfigController, setConfigController } from "@/src/controllers/custom/configController"; const customRouter = express.Router(); @@ -55,7 +54,7 @@ customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController); customRouter.post("/setBooster", setBoosterController); -customRouter.get("/config", getConfigDataController); -customRouter.post("/config", updateConfigDataController); +customRouter.post("/getConfig", getConfigController); +customRouter.post("/setConfig", setConfigController); export { customRouter }; diff --git a/static/webui/index.html b/static/webui/index.html index caf7be01..70ac6843 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -568,10 +568,10 @@
-
+

-
+
@@ -807,6 +807,93 @@
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/static/webui/script.js b/static/webui/script.js index 91cc54a1..479f0014 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1847,16 +1847,28 @@ function doAcquireMod() { } } -const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id); +const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id); for (const id of uiConfigs) { const elm = document.getElementById(id); - if (elm.type == "checkbox") { + if (elm.tagName == "SELECT") { + elm.onchange = function () { + let value = this.value; + if (!isNaN(parseInt(value))) { + value = parseInt(value); + } + $.post({ + url: "/custom/setConfig?" + window.authz, + contentType: "application/json", + data: JSON.stringify({ [id]: value }) + }); + }; + } else if (elm.type == "checkbox") { elm.onchange = function () { $.post({ - url: "/custom/config?" + window.authz, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", - data: JSON.stringify({ key: id, value: this.checked }) + data: JSON.stringify({ [id]: this.checked }) }).then(() => { if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) { updateInventory(); @@ -1869,9 +1881,9 @@ for (const id of uiConfigs) { function doSaveConfig(id) { const elm = document.getElementById(id); $.post({ - url: "/custom/config?" + window.authz, + url: "/custom/setConfig?" + window.authz, contentType: "application/json", - data: JSON.stringify({ key: id, value: parseInt(elm.value) }) + data: JSON.stringify({ [id]: parseInt(elm.value) }) }); } @@ -1882,26 +1894,29 @@ single.getRoute("/webui/cheats").on("beforeload", function () { interval = setInterval(() => { if (window.authz) { clearInterval(interval); - fetch("/custom/config?" + window.authz).then(async res => { - if (res.status == 200) { + $.post({ + url: "/custom/getConfig?" + window.authz, + contentType: "application/json", + data: JSON.stringify(uiConfigs) + }) + .done(json => { //window.is_admin = true; - $("#server-settings-no-perms").addClass("d-none"); - $("#server-settings").removeClass("d-none"); - res.json().then(json => - Object.entries(json).forEach(entry => { - const [key, value] = entry; - var x = document.getElementById(`${key}`); - if (x != null) { - if (x.type == "checkbox") { - x.checked = value; - } else if (x.type == "number") { - x.setAttribute("value", `${value}`); - } + $(".config-admin-hide").addClass("d-none"); + $(".config-admin-show").removeClass("d-none"); + Object.entries(json).forEach(entry => { + const [key, value] = entry; + var x = document.getElementById(`${key}`); + if (x != null) { + if (x.type == "checkbox") { + x.checked = value; + } else if (x.type == "number") { + x.setAttribute("value", `${value}`); } - }) - ); - } else { - if ((await res.text()) == "Log-in expired") { + } + }); + }) + .fail(res => { + if (res.responseText == "Log-in expired") { revalidateAuthz().then(() => { if (single.getCurrentPath() == "/webui/cheats") { single.loadRoute("/webui/cheats"); @@ -1909,11 +1924,10 @@ single.getRoute("/webui/cheats").on("beforeload", function () { }); } else { //window.is_admin = false; - $("#server-settings-no-perms").removeClass("d-none"); - $("#server-settings").addClass("d-none"); + $(".config-admin-hide").removeClass("d-none"); + $(".config-admin-show").addClass("d-none"); } - } - }); + }); } }, 10); }); diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index a56103b6..22148c9d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -186,6 +186,51 @@ dict = { cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeButton: `Ändern`, cheats_none: `Keines`, + + worldState: `[UNTRANSLATED] World State`, + worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, + worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, + worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, + worldState_starDays: `[UNTRANSLATED] Star Days`, + worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, + disabled: `[UNTRANSLATED] Disabled`, + worldState_we1: `[UNTRANSLATED] Weekend 1`, + worldState_we2: `[UNTRANSLATED] Weekend 2`, + worldState_we3: `[UNTRANSLATED] Weekend 3`, + worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, + worldState_day: `[UNTRANSLATED] Day`, + worldState_night: `[UNTRANSLATED] Night`, + worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, + worldState_warm: `[UNTRANSLATED] Warm`, + worldState_cold: `[UNTRANSLATED] Cold`, + worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, + worldState_joy: `[UNTRANSLATED] Joy`, + worldState_anger: `[UNTRANSLATED] Anger`, + worldState_envy: `[UNTRANSLATED] Envy`, + worldState_sorrow: `[UNTRANSLATED] Sorrow`, + worldState_fear: `[UNTRANSLATED] Fear`, + worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, + worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, + worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, + worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, + worldState_fissures: `[UNTRANSLATED] Fissures`, + normal: `[UNTRANSLATED] Normal`, + worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, + worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + 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:`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 36d38301..22481df6 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -185,6 +185,51 @@ dict = { cheats_changeSupportedSyndicate: `Supported syndicate`, cheats_changeButton: `Change`, cheats_none: `None`, + + worldState: `World State`, + worldState_creditBoost: `Credit Boost`, + worldState_affinityBoost: `Affinity Boost`, + worldState_resourceBoost: `Resource Boost`, + worldState_starDays: `Star Days`, + worldState_galleonOfGhouls: `Galleon of Ghouls`, + disabled: `Disabled`, + worldState_we1: `Weekend 1`, + worldState_we2: `Weekend 2`, + worldState_we3: `Weekend 3`, + worldState_eidolonOverride: `Eidolon Override`, + worldState_day: `Day`, + worldState_night: `Night`, + worldState_vallisOverride: `Orb Vallis Override`, + worldState_warm: `Warm`, + worldState_cold: `Cold`, + worldState_duviriOverride: `Duviri Override`, + worldState_joy: `Joy`, + worldState_anger: `Anger`, + worldState_envy: `Envy`, + worldState_sorrow: `Sorrow`, + worldState_fear: `Fear`, + worldState_nightwaveOverride: `Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `Intermission III`, + worldState_RadioLegion3Syndicate: `Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `Intermission II`, + worldState_RadioLegion2Syndicate: `The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `Intermission I`, + worldState_RadioLegionSyndicate: `The Wolf of Saturn Six`, + worldState_fissures: `Fissures`, + normal: `Normal`, + worldState_allAtOnceNormal: `All At Once, Normal`, + worldState_allAtOnceSteelPath: `All At Once, Steel Path`, + 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:`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 3fbb7bf1..0a7b7ac5 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -186,6 +186,51 @@ dict = { cheats_changeSupportedSyndicate: `Sindicatos disponibles`, cheats_changeButton: `Cambiar`, cheats_none: `Ninguno`, + + worldState: `[UNTRANSLATED] World State`, + worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, + worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, + worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, + worldState_starDays: `[UNTRANSLATED] Star Days`, + worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, + disabled: `[UNTRANSLATED] Disabled`, + worldState_we1: `[UNTRANSLATED] Weekend 1`, + worldState_we2: `[UNTRANSLATED] Weekend 2`, + worldState_we3: `[UNTRANSLATED] Weekend 3`, + worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, + worldState_day: `[UNTRANSLATED] Day`, + worldState_night: `[UNTRANSLATED] Night`, + worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, + worldState_warm: `[UNTRANSLATED] Warm`, + worldState_cold: `[UNTRANSLATED] Cold`, + worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, + worldState_joy: `[UNTRANSLATED] Joy`, + worldState_anger: `[UNTRANSLATED] Anger`, + worldState_envy: `[UNTRANSLATED] Envy`, + worldState_sorrow: `[UNTRANSLATED] Sorrow`, + worldState_fear: `[UNTRANSLATED] Fear`, + worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, + worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, + worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, + worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, + worldState_fissures: `[UNTRANSLATED] Fissures`, + normal: `[UNTRANSLATED] Normal`, + worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, + worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + 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: `Muestras:`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index a52f5bef..9dca3dbe 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -186,6 +186,51 @@ dict = { cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeButton: `Changer`, cheats_none: `Aucun`, + + worldState: `[UNTRANSLATED] World State`, + worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, + worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, + worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, + worldState_starDays: `[UNTRANSLATED] Star Days`, + worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, + disabled: `[UNTRANSLATED] Disabled`, + worldState_we1: `[UNTRANSLATED] Weekend 1`, + worldState_we2: `[UNTRANSLATED] Weekend 2`, + worldState_we3: `[UNTRANSLATED] Weekend 3`, + worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, + worldState_day: `[UNTRANSLATED] Day`, + worldState_night: `[UNTRANSLATED] Night`, + worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, + worldState_warm: `[UNTRANSLATED] Warm`, + worldState_cold: `[UNTRANSLATED] Cold`, + worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, + worldState_joy: `[UNTRANSLATED] Joy`, + worldState_anger: `[UNTRANSLATED] Anger`, + worldState_envy: `[UNTRANSLATED] Envy`, + worldState_sorrow: `[UNTRANSLATED] Sorrow`, + worldState_fear: `[UNTRANSLATED] Fear`, + worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, + worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, + worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, + worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, + worldState_fissures: `[UNTRANSLATED] Fissures`, + normal: `[UNTRANSLATED] Normal`, + worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, + worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + 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: `Echantillons :`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 26fef974..b276c0fb 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -186,6 +186,51 @@ dict = { cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeButton: `Изменить`, cheats_none: `Отсутствует`, + + worldState: `[UNTRANSLATED] World State`, + worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, + worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, + worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, + worldState_starDays: `[UNTRANSLATED] Star Days`, + worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, + disabled: `[UNTRANSLATED] Disabled`, + worldState_we1: `[UNTRANSLATED] Weekend 1`, + worldState_we2: `[UNTRANSLATED] Weekend 2`, + worldState_we3: `[UNTRANSLATED] Weekend 3`, + worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, + worldState_day: `[UNTRANSLATED] Day`, + worldState_night: `[UNTRANSLATED] Night`, + worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, + worldState_warm: `[UNTRANSLATED] Warm`, + worldState_cold: `[UNTRANSLATED] Cold`, + worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, + worldState_joy: `[UNTRANSLATED] Joy`, + worldState_anger: `[UNTRANSLATED] Anger`, + worldState_envy: `[UNTRANSLATED] Envy`, + worldState_sorrow: `[UNTRANSLATED] Sorrow`, + worldState_fear: `[UNTRANSLATED] Fear`, + worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, + worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, + worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, + worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, + worldState_fissures: `[UNTRANSLATED] Fissures`, + normal: `[UNTRANSLATED] Normal`, + worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, + worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить`, import_samples: `[UNTRANSLATED] Samples:`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index ca7f726a..46349a72 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -186,6 +186,51 @@ dict = { cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeButton: `更改`, cheats_none: `无`, + + worldState: `[UNTRANSLATED] World State`, + worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, + worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, + worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, + worldState_starDays: `[UNTRANSLATED] Star Days`, + worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, + disabled: `[UNTRANSLATED] Disabled`, + worldState_we1: `[UNTRANSLATED] Weekend 1`, + worldState_we2: `[UNTRANSLATED] Weekend 2`, + worldState_we3: `[UNTRANSLATED] Weekend 3`, + worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, + worldState_day: `[UNTRANSLATED] Day`, + worldState_night: `[UNTRANSLATED] Night`, + worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, + worldState_warm: `[UNTRANSLATED] Warm`, + worldState_cold: `[UNTRANSLATED] Cold`, + worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, + worldState_joy: `[UNTRANSLATED] Joy`, + worldState_anger: `[UNTRANSLATED] Anger`, + worldState_envy: `[UNTRANSLATED] Envy`, + worldState_sorrow: `[UNTRANSLATED] Sorrow`, + worldState_fear: `[UNTRANSLATED] Fear`, + worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, + worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, + worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, + worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, + worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, + worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, + worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, + worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, + worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, + worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, + worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, + worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, + worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, + worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, + worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, + worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, + worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, + worldState_fissures: `[UNTRANSLATED] Fissures`, + normal: `[UNTRANSLATED] Normal`, + worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, + worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, import_samples: `示例:`, From d8ff601be78bc7aa61de60b45e1b0c12d26fb131 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:37:00 +0200 Subject: [PATCH 22/43] fix: array out of bounds when processing CalendarProgress --- 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 ec3ee941..c4a62f2c 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -674,7 +674,7 @@ export const addMissionInventoryUpdates = async ( const calendarProgress = getCalendarProgress(inventory); const currentSeason = getWorldState().KnownCalendarSeasons[0]; calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex( - x => x.events[0].challenge == value[value.length - 1].challenge + day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge ); checkCalendarChallengeCompletion(calendarProgress, currentSeason); break; From 4acd87aae65b869907e3d0d7f2d3aa413dbdf8c7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:41:15 +0200 Subject: [PATCH 23/43] chore: handle CalendarProgress in updateChallengeProgress --- .../api/updateChallengeProgressController.ts | 13 +++++++++++-- src/services/inventoryService.ts | 9 +++++++++ src/services/missionInventoryUpdateService.ts | 12 +++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 68ae6c55..b75e820a 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -1,7 +1,7 @@ import { RequestHandler } from "express"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getAccountForRequest } from "@/src/services/loginService"; -import { addChallenges, getInventory } from "@/src/services/inventoryService"; +import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService"; import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes"; import { IAffiliationMods } from "@/src/types/purchaseTypes"; import { getEntriesUnsafe } from "@/src/utils/ts-utils"; @@ -25,13 +25,17 @@ export const updateChallengeProgressController: RequestHandler = async (req, res ); } for (const [key, value] of getEntriesUnsafe(challenges)) { + if (value === undefined) { + logger.error(`Challenge progress update key ${key} has no value`); + continue; + } switch (key) { case "ChallengesFixVersion": inventory.ChallengesFixVersion = value; break; case "SeasonChallengeHistory": - value!.forEach(({ challenge, id }) => { + value.forEach(({ challenge, id }) => { const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge); if (itemIndex !== -1) { inventory.SeasonChallengeHistory[itemIndex].id = id; @@ -41,6 +45,10 @@ export const updateChallengeProgressController: RequestHandler = async (req, res }); break; + case "CalendarProgress": + addCalendarProgress(inventory, value); + break; + case "ChallengeProgress": case "SeasonChallengeCompletions": case "ChallengePTS": @@ -63,5 +71,6 @@ interface IUpdateChallengeProgressRequest { ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[]; + CalendarProgress?: { challenge: string }[]; crossPlaySetting?: string; } diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index cf5208ce..fafbca6c 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -1832,6 +1832,15 @@ export const addChallenges = ( return affiliationMods; }; +export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value: { challenge: string }[]): void => { + const calendarProgress = getCalendarProgress(inventory); + const currentSeason = getWorldState().KnownCalendarSeasons[0]; + calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex( + day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge + ); + checkCalendarChallengeCompletion(calendarProgress, currentSeason); +}; + export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => { const { Missions } = inventory; const itemIndex = Missions.findIndex(item => item.Tag === Tag); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index c4a62f2c..09bdc1c6 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -14,6 +14,7 @@ import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/servi import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { addBooster, + addCalendarProgress, addChallenges, addConsumables, addCrewShipAmmo, @@ -33,10 +34,8 @@ import { addSkin, addStanding, applyClientEquipmentUpdates, - checkCalendarChallengeCompletion, combineInventoryChanges, generateRewardSeed, - getCalendarProgress, getDialogue, giveNemesisPetRecipe, giveNemesisWeaponRecipe, @@ -235,7 +234,7 @@ export const addMissionInventoryUpdates = async ( } for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) { if (value === undefined) { - logger.error(`Inventory update key ${key} has no value `); + logger.error(`Inventory update key ${key} has no value`); continue; } switch (key) { @@ -671,12 +670,7 @@ export const addMissionInventoryUpdates = async ( break; } case "CalendarProgress": { - const calendarProgress = getCalendarProgress(inventory); - const currentSeason = getWorldState().KnownCalendarSeasons[0]; - calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex( - day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge - ); - checkCalendarChallengeCompletion(calendarProgress, currentSeason); + addCalendarProgress(inventory, value); break; } case "duviriCaveOffers": { From 31e24c27ad8c865a756f5c92dfdf3041907889e3 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:39:46 -0700 Subject: [PATCH 24/43] chore: ignore invalid item ids in saveLoadout (#2320) With the 'IsNew' flag + webui delete item, this is quite easy to trigger and shouldn't prevent the other changes from going through. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2320 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/saveLoadoutService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/saveLoadoutService.ts b/src/services/saveLoadoutService.ts index 0676d113..ff42ad82 100644 --- a/src/services/saveLoadoutService.ts +++ b/src/services/saveLoadoutService.ts @@ -149,7 +149,8 @@ export const handleInventoryItemConfigChange = async ( } else { const inventoryItem = inventory.WeaponSkins.id(itemId); if (!inventoryItem) { - throw new Error(`inventory item WeaponSkins not found with id ${itemId}`); + logger.warn(`inventory item WeaponSkins not found with id ${itemId}`); + continue; } if ("Favorite" in itemConfigEntries) { inventoryItem.Favorite = itemConfigEntries.Favorite; @@ -177,7 +178,8 @@ export const handleInventoryItemConfigChange = async ( const inventoryItem = inventory[equipmentName].id(itemId); if (!inventoryItem) { - throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`); + logger.warn(`inventory item ${equipmentName} not found with id ${itemId}`); + continue; } for (const [configId, config] of Object.entries(itemConfigEntries)) { From bbccee0637a1f3930b71b4232e4f70ca0b569f26 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:40:06 -0700 Subject: [PATCH 25/43] fix: ignore purchaseQuantity for login reward items (#2321) cryotic amount should not be multiplied by 3000... Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2321 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/loginRewardService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/loginRewardService.ts b/src/services/loginRewardService.ts index c8a213ea..49a6501a 100644 --- a/src/services/loginRewardService.ts +++ b/src/services/loginRewardService.ts @@ -144,7 +144,8 @@ export const claimLoginReward = async ( case "RT_STORE_ITEM": case "RT_RECIPE": case "RT_RANDOM_RECIPE": - return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges; + return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount, undefined, true)) + .InventoryChanges; case "RT_CREDITS": return updateCurrency(inventory, -reward.Amount, false); From 3cae42c7d61cdb49c7da202246d859487e84e6d7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:25:31 -0700 Subject: [PATCH 26/43] fix: acrithis vendor freezing with fullyStockedVendors (#2326) Permanent offers were not satisfying bin constraints Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2326 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index d5ea7410..9a7db1a5 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -281,6 +281,10 @@ const generateVendorManifest = ( offersToAdd.push(item); ++offset; } + if (missingItemsPerBin[item.bin]) { + missingItemsPerBin[item.bin] -= 1; + numOffersThatNeedToMatchABin -= 1; + } } else { numCountedOffers += 1 + item.duplicates; } From d77fe60cd828991186ed55bdf641f82824a25a39 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:26:22 -0700 Subject: [PATCH 27/43] fix(webui): apply consistent margins (#2327) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2327 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/index.html | 74 ++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/static/webui/index.html b/static/webui/index.html index 70ac6843..455ff556 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -98,9 +98,9 @@
-
+
-
+

@@ -112,7 +112,7 @@
-
+

@@ -124,7 +124,7 @@
-
+

@@ -136,7 +136,7 @@
-
+

@@ -148,9 +148,9 @@
-
+
-
+
@@ -164,7 +164,7 @@
-
+
@@ -183,9 +183,9 @@
-
+
-
+
@@ -204,7 +204,7 @@
-
+
@@ -223,9 +223,9 @@
-
+
-
+
@@ -239,7 +239,7 @@
-
+
@@ -253,9 +253,9 @@
-
+
-
+
@@ -269,7 +269,7 @@
-
+
@@ -283,9 +283,9 @@
-
+
-
+
@@ -299,7 +299,7 @@
-
+
@@ -325,9 +325,9 @@
-
+
-
+
@@ -349,7 +349,7 @@
-
+
@@ -363,9 +363,9 @@
-
+
-
+
@@ -384,7 +384,7 @@
-
+
@@ -401,9 +401,9 @@
-
+
-
+
@@ -417,7 +417,7 @@
-
+
@@ -431,7 +431,7 @@
-
+
@@ -458,7 +458,7 @@

-
+

@@ -498,7 +498,7 @@

-
+
@@ -521,7 +521,7 @@
-
+
@@ -535,7 +535,7 @@
-
+
@@ -549,7 +549,7 @@
-
+
@@ -565,7 +565,7 @@
-
+
@@ -807,7 +807,7 @@
-
+
From 690b872b5eabf0995910aa0424d348b5725c255d Mon Sep 17 00:00:00 2001 From: Corvus Date: Thu, 26 Jun 2025 22:26:35 -0700 Subject: [PATCH 28/43] chore(webui): update Chinese translation (#2328) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2328 Co-authored-by: Corvus Co-committed-by: Corvus --- static/webui/translations/zh.js | 88 ++++++++++++++++----------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 46349a72..e6973c34 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -1,4 +1,4 @@ -// Chinese translation by meb154 & bishan178 +// Chinese translation by meb154, bishan178 & Corvus dict = { general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_addButton: `添加`, @@ -187,49 +187,49 @@ dict = { cheats_changeButton: `更改`, cheats_none: `无`, - worldState: `[UNTRANSLATED] World State`, - worldState_creditBoost: `[UNTRANSLATED] Credit Boost`, - worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`, - worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`, - worldState_starDays: `[UNTRANSLATED] Star Days`, - worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`, - disabled: `[UNTRANSLATED] Disabled`, - worldState_we1: `[UNTRANSLATED] Weekend 1`, - worldState_we2: `[UNTRANSLATED] Weekend 2`, - worldState_we3: `[UNTRANSLATED] Weekend 3`, - worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`, - worldState_day: `[UNTRANSLATED] Day`, - worldState_night: `[UNTRANSLATED] Night`, - worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`, - worldState_warm: `[UNTRANSLATED] Warm`, - worldState_cold: `[UNTRANSLATED] Cold`, - worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`, - worldState_joy: `[UNTRANSLATED] Joy`, - worldState_anger: `[UNTRANSLATED] Anger`, - worldState_envy: `[UNTRANSLATED] Envy`, - worldState_sorrow: `[UNTRANSLATED] Sorrow`, - worldState_fear: `[UNTRANSLATED] Fear`, - worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`, - worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`, - worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`, - worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`, - worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`, - worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`, - worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`, - worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`, - worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`, - worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`, - worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`, - worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`, - worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`, - worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`, - worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`, - worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`, - worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`, - worldState_fissures: `[UNTRANSLATED] Fissures`, - normal: `[UNTRANSLATED] Normal`, - worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, - worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + worldState: `世界状态配置`, + worldState_creditBoost: `现金加成`, + worldState_affinityBoost: `经验加成`, + worldState_resourceBoost: `资源加成`, + worldState_starDays: `活动:星日`, + worldState_galleonOfGhouls: `战术警报:尸鬼的帆船战舰`, + disabled: `关闭/取消配置`, + worldState_we1: `活动阶段:第一周`, + worldState_we2: `活动阶段:第二周`, + worldState_we3: `活动阶段:第三周`, + worldState_eidolonOverride: `夜灵平原/魔胎之境状态`, + worldState_day: `白昼/FASS`, + worldState_night: `黑夜/VOME`, + worldState_vallisOverride: `奥布山谷状态`, + worldState_warm: `温暖`, + worldState_cold: `寒冷`, + worldState_duviriOverride: `双衍王镜状态`, + worldState_joy: `喜悦`, + worldState_anger: `愤怒`, + worldState_envy: `嫉妒`, + worldState_sorrow: `悲伤`, + worldState_fear: `恐惧`, + worldState_nightwaveOverride: `午夜电波系列`, + worldState_RadioLegionIntermission13Syndicate: `诺拉的混选VOL.9`, + worldState_RadioLegionIntermission12Syndicate: `诺拉的混选VOL.8`, + worldState_RadioLegionIntermission11Syndicate: `诺拉的混选VOL.7`, + worldState_RadioLegionIntermission10Syndicate: `诺拉的混选VOL.6`, + worldState_RadioLegionIntermission9Syndicate: `诺拉的混选VOL.5`, + worldState_RadioLegionIntermission8Syndicate: `诺拉的混选VOL.4`, + worldState_RadioLegionIntermission7Syndicate: `诺拉的混选VOL.3`, + worldState_RadioLegionIntermission6Syndicate: `诺拉的混选VOL.2`, + worldState_RadioLegionIntermission5Syndicate: `诺拉的混选VOL.1`, + worldState_RadioLegionIntermission4Syndicate: `诺拉的精选`, + worldState_RadioLegionIntermission3Syndicate: `间歇III`, + worldState_RadioLegion3Syndicate: `系列3 — 玻璃匠`, + worldState_RadioLegionIntermission2Syndicate: `间歇II`, + worldState_RadioLegion2Syndicate: `系列2 — 使徒`, + worldState_RadioLegionIntermissionSyndicate: `间歇I`, + worldState_RadioLegionSyndicate: `系列1 — 土星六号之狼`, + worldState_fissures: `虚空裂缝难度设定`, + normal: `正常`, + worldState_allAtOnceNormal: `全部开启(普通)`, + worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, From 4895b4630b930478ebde136b3bb04a9e3c60a0b1 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:20:37 -0700 Subject: [PATCH 29/43] feat: credit boosters (+ daily first win) (#2324) Daily first win is kinda weird because the client doesn't even seem to acknowledge it. Also fixed missionCompletionCredits being added to inventory inconsistently (sometimes once, sometimes twice). Closes #1086 Closes #2322 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2324 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/checkDailyMissionBonusController.ts | 22 +++---- .../api/missionInventoryUpdateController.ts | 2 +- .../custom/completeAllMissionsController.ts | 2 +- .../custom/setBoosterController.ts | 4 +- src/models/loginModel.ts | 3 +- src/services/missionInventoryUpdateService.ts | 65 ++++++++++++------- src/services/questService.ts | 2 +- src/types/loginTypes.ts | 1 + src/types/missionTypes.ts | 6 +- static/webui/script.js | 3 +- 10 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/controllers/api/checkDailyMissionBonusController.ts b/src/controllers/api/checkDailyMissionBonusController.ts index 97b838fe..8e457142 100644 --- a/src/controllers/api/checkDailyMissionBonusController.ts +++ b/src/controllers/api/checkDailyMissionBonusController.ts @@ -1,16 +1,12 @@ +import { getAccountForRequest } from "@/src/services/loginService"; import { RequestHandler } from "express"; -const checkDailyMissionBonusController: RequestHandler = (_req, res) => { - const data = Buffer.from([ - 0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a, - 0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, - 0x3a, 0x31, 0x0a - ]); - res.writeHead(200, { - "Content-Type": "text/html", - "Content-Length": data.length - }); - res.end(data); +export const checkDailyMissionBonusController: RequestHandler = async (req, res) => { + const account = await getAccountForRequest(req); + const today = Math.trunc(Date.now() / 86400000) * 86400; + if (account.DailyFirstWinDate != today) { + res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n"); + } else { + res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n"); + } }; - -export { checkDailyMissionBonusController }; diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 93f8033c..e3b86b71 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -88,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) AffiliationMods, SyndicateXPItemReward, ConquestCompletedMissionsCount - } = await addMissionRewards(inventory, missionReport, firstCompletion); + } = await addMissionRewards(account, inventory, missionReport, firstCompletion); if (missionReport.EndOfMatchUpload) { inventory.RewardSeed = generateRewardSeed(); diff --git a/src/controllers/custom/completeAllMissionsController.ts b/src/controllers/custom/completeAllMissionsController.ts index f6ab486d..66ac3c13 100644 --- a/src/controllers/custom/completeAllMissionsController.ts +++ b/src/controllers/custom/completeAllMissionsController.ts @@ -26,7 +26,7 @@ export const completeAllMissionsController: RequestHandler = async (req, res) => if (mission.Completes == 0) { mission.Completes++; if (node.missionReward) { - addFixedLevelRewards(node.missionReward, inventory, MissionRewards); + addFixedLevelRewards(node.missionReward, MissionRewards); } } mission.Tier = 1; diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts index 28939614..b0a1ddbf 100644 --- a/src/controllers/custom/setBoosterController.ts +++ b/src/controllers/custom/setBoosterController.ts @@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => { res.status(400).send("Invalid ItemType provided."); return; } - const now = Math.floor(Date.now() / 1000); + const now = Math.trunc(Date.now() / 1000); for (const { ItemType, ExpiryDate } of requests) { - if (ExpiryDate < now) { + if (ExpiryDate <= now) { // remove expired boosters const index = boosters.findIndex(item => item.ItemType === ItemType); if (index !== -1) { diff --git a/src/models/loginModel.ts b/src/models/loginModel.ts index 44dab113..aea5b993 100644 --- a/src/models/loginModel.ts +++ b/src/models/loginModel.ts @@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema( LastLogin: { type: Date, default: 0 }, LatestEventMessageDate: { type: Date, default: 0 }, LastLoginRewardDate: { type: Number, default: 0 }, - LoginDays: { type: Number, default: 1 } + LoginDays: { type: Number, default: 1 }, + DailyFirstWinDate: { type: Number, default: 0 } }, opts ); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 09bdc1c6..af8a2e40 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -962,6 +962,7 @@ const droptableAliases: Record = { //TODO: return type of partial missioninventoryupdate response export const addMissionRewards = async ( + account: TAccountDocument, inventory: TInventoryDatabaseDocument, { wagerTier: wagerTier, @@ -1009,13 +1010,17 @@ export const addMissionRewards = async ( const fixedLevelRewards = getLevelKeyRewards(levelKeyName); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); if (fixedLevelRewards.levelKeyRewards) { - addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo); + missionCompletionCredits += addFixedLevelRewards( + fixedLevelRewards.levelKeyRewards, + MissionRewards, + rewardInfo + ); } if (fixedLevelRewards.levelKeyRewards2) { for (const reward of fixedLevelRewards.levelKeyRewards2) { //quest stage completion credit rewards if (reward.rewardType == "RT_CREDITS") { - missionCompletionCredits += reward.amount; // will be added to inventory in addCredits + missionCompletionCredits += reward.amount; continue; } MissionRewards.push({ @@ -1044,12 +1049,11 @@ export const addMissionRewards = async ( ) { const levelCreditReward = getLevelCreditRewards(node); missionCompletionCredits += levelCreditReward; - inventory.RegularCredits += levelCreditReward; logger.debug(`levelCreditReward ${levelCreditReward}`); } if (node.missionReward) { - missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo); + missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo); } if (rewardInfo.sortieTag == "Mission1") { @@ -1159,7 +1163,9 @@ export const addMissionRewards = async ( combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges); } - const credits = addCredits(inventory, { + inventory.RegularCredits += missionCompletionCredits; + + const credits = await addCredits(account, inventory, { missionCompletionCredits, missionDropCredits: creditDrops ?? 0, rngRewardCredits: inventoryChanges.RegularCredits ?? 0 @@ -1382,48 +1388,61 @@ export const addMissionRewards = async ( }; }; -//creditBonus is not entirely accurate. -//TODO: consider ActiveBoosters -export const addCredits = ( +export const addCredits = async ( + account: TAccountDocument, inventory: TInventoryDatabaseDocument, { missionDropCredits, missionCompletionCredits, rngRewardCredits }: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number } -): IMissionCredits => { - const hasDailyCreditBonus = true; - const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits; - +): Promise => { const finalCredits: IMissionCredits = { MissionCredits: [missionDropCredits, missionDropCredits], - CreditBonus: [missionCompletionCredits, missionCompletionCredits], - TotalCredits: [totalCredits, totalCredits] + CreditsBonus: [missionCompletionCredits, missionCompletionCredits], + TotalCredits: [0, 0] }; - if (hasDailyCreditBonus) { + const today = Math.trunc(Date.now() / 86400000) * 86400; + if (account.DailyFirstWinDate != today) { + account.DailyFirstWinDate = today; + await account.save(); + + logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`); + + finalCredits.DailyMissionBonus = true; inventory.RegularCredits += missionCompletionCredits; - finalCredits.CreditBonus[1] *= 2; - finalCredits.MissionCredits[1] *= 2; - finalCredits.TotalCredits[1] *= 2; + finalCredits.CreditsBonus[1] *= 2; } - if (!hasDailyCreditBonus) { - return finalCredits; + const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits; + finalCredits.TotalCredits = [totalCredits, totalCredits]; + + if (config.worldState?.creditBoost) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; } - return { ...finalCredits, DailyMissionBonus: true }; + const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission? + if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; + } + if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) { + inventory.RegularCredits += finalCredits.TotalCredits[1]; + finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; + } + + return finalCredits; }; export const addFixedLevelRewards = ( rewards: IMissionRewardExternal, - inventory: TInventoryDatabaseDocument, MissionRewards: IMissionReward[], rewardInfo?: IRewardInfo ): number => { let missionBonusCredits = 0; if (rewards.credits) { missionBonusCredits += rewards.credits; - inventory.RegularCredits += rewards.credits; } if (rewards.items) { for (const item of rewards.items) { diff --git a/src/services/questService.ts b/src/services/questService.ts index 053f6eb4..633f1022 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async ( const fixedLevelRewards = getLevelKeyRewards(missionName); if (fixedLevelRewards.levelKeyRewards) { const missionRewards: { StoreItem: string; ItemCount: number }[] = []; - addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards); + inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards); for (const reward of missionRewards) { await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount); diff --git a/src/types/loginTypes.ts b/src/types/loginTypes.ts index ef280188..0f5cea60 100644 --- a/src/types/loginTypes.ts +++ b/src/types/loginTypes.ts @@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields { LatestEventMessageDate: Date; LastLoginRewardDate: number; LoginDays: number; + DailyFirstWinDate: number; } // Includes virtual ID diff --git a/src/types/missionTypes.ts b/src/types/missionTypes.ts index 3de75318..bb70fc30 100644 --- a/src/types/missionTypes.ts +++ b/src/types/missionTypes.ts @@ -17,9 +17,9 @@ export interface IMissionReward { } export interface IMissionCredits { - MissionCredits: number[]; - CreditBonus: number[]; - TotalCredits: number[]; + MissionCredits: [number, number]; + CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus + TotalCredits: [number, number]; DailyMissionBonus?: boolean; } diff --git a/static/webui/script.js b/static/webui/script.js index 479f0014..e90f0ed0 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -2289,14 +2289,13 @@ function doAcquireBoosters() { const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days setBooster(uniqueName, ExpiryDate, () => { $("#acquire-type-Boosters").val(""); - updateInventory(); }); } function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value); // cast local datetime string to unix timestamp - const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000; + const ExpiryDate = Math.trunc(new Date(ExpiryDateInput.value).getTime() / 1000); if (isNaN(ExpiryDate)) { ExpiryDateInput.addClass("is-invalid").focus(); return false; From abb5b8880fe268546088da87e442b0ace1887b3f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:21:05 -0700 Subject: [PATCH 30/43] chore(webui): keep config in sync with multiple tabs (#2325) Adding "wsid" to uniquely identify a given tab (by the websocket connection) so we can avoid needless refreshing on the same tab. Closes #2316 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2325 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/custom/configController.ts | 2 ++ src/services/webService.ts | 26 ++++++++++++++++++++++ static/webui/script.js | 12 ++++++---- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/controllers/custom/configController.ts b/src/controllers/custom/configController.ts index 2c1e10ac..2249f48f 100644 --- a/src/controllers/custom/configController.ts +++ b/src/controllers/custom/configController.ts @@ -2,6 +2,7 @@ import { RequestHandler } from "express"; import { config } from "@/src/services/configService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; import { saveConfig } from "@/src/services/configWatcherService"; +import { sendWsBroadcastExcept } from "@/src/services/webService"; export const getConfigController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); @@ -24,6 +25,7 @@ export const setConfigController: RequestHandler = async (req, res) => { const [obj, idx] = configIdToIndexable(id); obj[idx] = value; } + sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true }); await saveConfig(); res.end(); } else { diff --git a/src/services/webService.ts b/src/services/webService.ts index ecc5a494..11ff2654 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -136,7 +136,10 @@ export const stopWebServer = async (): Promise => { await Promise.all(promises); }; +let lastWsid: number = 0; + interface IWsCustomData extends ws { + id?: number; accountId?: string; } @@ -150,6 +153,7 @@ interface IWsMsgFromClient { } interface IWsMsgToClient { + //wsid?: number; reload?: boolean; ports?: { http: number | undefined; @@ -174,6 +178,10 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => { ws.close(); return; } + + (ws as IWsCustomData).id = ++lastWsid; + ws.send(JSON.stringify({ wsid: lastWsid })); + // eslint-disable-next-line @typescript-eslint/no-misused-promises ws.on("message", async msg => { const data = JSON.parse(String(msg)) as IWsMsgFromClient; @@ -268,3 +276,21 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void } } }; + +export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => { + const msg = JSON.stringify(data); + if (wsServer) { + for (const client of wsServer.clients) { + if ((client as IWsCustomData).id != wsid) { + client.send(msg); + } + } + } + if (wssServer) { + for (const client of wssServer.clients) { + if ((client as IWsCustomData).id != wsid) { + client.send(msg); + } + } + } +}; diff --git a/static/webui/script.js b/static/webui/script.js index e90f0ed0..10c69dff 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -10,7 +10,8 @@ let auth_pending = false, did_initial_auth = false, - ws_is_open = false; + ws_is_open = false, + wsid = 0; const sendAuth = isRegister => { if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) { auth_pending = true; @@ -34,6 +35,9 @@ function openWebSocket() { }; window.ws.onmessage = e => { const msg = JSON.parse(e.data); + if ("wsid" in msg) { + wsid = msg.wsid; + } if ("reload" in msg) { setTimeout(() => { getWebSocket().then(() => { @@ -1858,7 +1862,7 @@ for (const id of uiConfigs) { value = parseInt(value); } $.post({ - url: "/custom/setConfig?" + window.authz, + url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, contentType: "application/json", data: JSON.stringify({ [id]: value }) }); @@ -1866,7 +1870,7 @@ for (const id of uiConfigs) { } else if (elm.type == "checkbox") { elm.onchange = function () { $.post({ - url: "/custom/setConfig?" + window.authz, + url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, contentType: "application/json", data: JSON.stringify({ [id]: this.checked }) }).then(() => { @@ -1881,7 +1885,7 @@ for (const id of uiConfigs) { function doSaveConfig(id) { const elm = document.getElementById(id); $.post({ - url: "/custom/setConfig?" + window.authz, + url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, contentType: "application/json", data: JSON.stringify({ [id]: parseInt(elm.value) }) }); From b36d524953a4376a60e65db7a275ed3060635e75 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:21:27 -0700 Subject: [PATCH 31/43] feat: personal deco capacity costs (#2329) Closes #2278 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2329 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/placeDecoInComponentController.ts | 6 ++- src/services/guildService.ts | 3 +- src/services/shipCustomizationsService.ts | 50 +++++++++++-------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/controllers/api/placeDecoInComponentController.ts b/src/controllers/api/placeDecoInComponentController.ts index a45806a8..088f1f11 100644 --- a/src/controllers/api/placeDecoInComponentController.ts +++ b/src/controllers/api/placeDecoInComponentController.ts @@ -57,7 +57,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) = component.DecoCapacity -= meta.capacityCost; } } else { - const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; + const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!; + if (!itemType || meta.dojoCapacityCost === undefined) { + throw new Error(`unknown deco type: ${deco.Type}`); + } + component.DecoCapacity -= meta.dojoCapacityCost; if (deco.Sockets !== undefined) { guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -= 1; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 5486c17f..edf07ce1 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -349,7 +349,8 @@ export const removeDojoDeco = ( component.DecoCapacity! += meta.capacityCost; } } else { - const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; + const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!; + component.DecoCapacity! += meta.dojoCapacityCost!; if (deco.Sockets !== undefined) { addVaultFusionTreasures(guild, [ { diff --git a/src/services/shipCustomizationsService.ts b/src/services/shipCustomizationsService.ts index a01901fd..1e5dcf26 100644 --- a/src/services/shipCustomizationsService.ts +++ b/src/services/shipCustomizationsService.ts @@ -59,7 +59,12 @@ export const handleSetShipDecorations = async ( const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room); if (!roomToPlaceIn) { - throw new Error("room not found"); + throw new Error(`unknown room: ${placedDecoration.Room}`); + } + + const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!; + if (!itemType || meta.capacityCost === undefined) { + throw new Error(`unknown deco type: ${placedDecoration.Type}`); } if (placedDecoration.MoveId) { @@ -83,7 +88,7 @@ export const handleSetShipDecorations = async ( OldRoom: placedDecoration.OldRoom, NewRoom: placedDecoration.Room, IsApartment: placedDecoration.IsApartment, - MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal + MaxCapacityIncrease: 0 }; } @@ -96,6 +101,7 @@ export const handleSetShipDecorations = async ( } oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId }); + oldRoom.MaxCapacity += meta.capacityCost; const newDecoration = { Type: placedDecoration.Type, @@ -108,12 +114,14 @@ export const handleSetShipDecorations = async ( //the new room is still roomToPlaceIn roomToPlaceIn.PlacedDecos.push(newDecoration); + roomToPlaceIn.MaxCapacity -= meta.capacityCost; + await personalRooms.save(); return { OldRoom: placedDecoration.OldRoom, NewRoom: placedDecoration.Room, IsApartment: placedDecoration.IsApartment, - MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal + MaxCapacityIncrease: -meta.capacityCost }; } @@ -121,11 +129,11 @@ export const handleSetShipDecorations = async ( const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId)); const deco = roomToPlaceIn.PlacedDecos[decoIndex]; roomToPlaceIn.PlacedDecos.splice(decoIndex, 1); + roomToPlaceIn.MaxCapacity += meta.capacityCost; await personalRooms.save(); if (!config.unlockAllShipDecorations) { const inventory = await getInventory(accountId); - const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; if (deco.Sockets !== undefined) { addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]); } else { @@ -138,24 +146,20 @@ export const handleSetShipDecorations = async ( DecoId: placedDecoration.RemoveId, Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment, - MaxCapacityIncrease: 0 + MaxCapacityIncrease: 0 // Client already implies the capacity being refunded. }; - } else { - if (!config.unlockAllShipDecorations) { - const inventory = await getInventory(accountId); - const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0]; - if (placedDecoration.Sockets !== undefined) { - addFusionTreasures(inventory, [ - { ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 } - ]); - } else { - addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]); - } - await inventory.save(); - } } - // TODO: handle capacity + if (!config.unlockAllShipDecorations) { + const inventory = await getInventory(accountId); + const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0]; + if (placedDecoration.Sockets !== undefined) { + addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]); + } else { + addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]); + } + await inventory.save(); + } //place decoration const decoId = new Types.ObjectId(); @@ -167,10 +171,16 @@ export const handleSetShipDecorations = async ( Sockets: placedDecoration.Sockets, _id: decoId }); + roomToPlaceIn.MaxCapacity -= meta.capacityCost; await personalRooms.save(); - return { DecoId: decoId.toString(), Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment }; + return { + DecoId: decoId.toString(), + Room: placedDecoration.Room, + IsApartment: placedDecoration.IsApartment, + MaxCapacityIncrease: -meta.capacityCost + }; }; export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise => { From d2cae012a7fd44f9b386e5b1d5fec3d58c7b3380 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:21:48 -0700 Subject: [PATCH 32/43] chore: add operation eight claw trophies to allDecoRecipes (#2330) Closes #2295 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2330 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/fixed_responses/allDecoRecipes.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/fixed_responses/allDecoRecipes.json b/static/fixed_responses/allDecoRecipes.json index 4ed19d5b..efbbeed2 100644 --- a/static/fixed_responses/allDecoRecipes.json +++ b/static/fixed_responses/allDecoRecipes.json @@ -20,6 +20,10 @@ "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe", From 4fcac6dc378789e4d40b8ce07e2651704322a6b1 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:22:00 -0700 Subject: [PATCH 33/43] feat: duviri murmur reward tiers (#2331) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2331 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 +++---- package.json | 2 +- src/services/missionInventoryUpdateService.ts | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f272a944..0ca3fde8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.72", + "warframe-public-export-plus": "^0.5.73", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -3386,9 +3386,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.72", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.72.tgz", - "integrity": "sha512-oOZgtU6L0MGcPRKfA6+bonu+Db1kie1lVdLmA7/DbheTPweNkBEx3Hx3Seib+hEaFW+nLj3T5GtmGxGcFHCHfg==" + "version": "0.5.73", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.73.tgz", + "integrity": "sha512-v5lmaq/rNICg7WIZcosyfz92RpmrNyfW6+/Pbi9Iu8HbZH74PfaQKT6suAyC9xQn6xp8/cG3NLinqlLZovbKpw==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index de7411b4..a2035729 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.72", + "warframe-public-export-plus": "^0.5.73", "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 af8a2e40..340e7ba4 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1616,6 +1616,27 @@ function getRandomMissionDrops( ? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards" : "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards" ]; + } else if (RewardInfo.T == 17) { + if (mission?.Tier == 1) { + logger.warn(`non-steel path duviri murmur tier used on steel path?!`); + } + /*if (operation eight claw is active) { + drops.push({ + StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent", + ItemCount: 10 + }); + }*/ + rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"]; + } else if (RewardInfo.T == 19) { + /*if (operation eight claw is active) { + drops.push({ + StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent", + ItemCount: 15 + }); + }*/ + rewardManifests = [ + "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards" + ]; } else if (RewardInfo.T == 70) { // Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path. drops.push({ From 0f2b6c68cdcf117b38a0dc3745d38030d4de9015 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 19:29:55 +0200 Subject: [PATCH 34/43] chore: use some instead of find --- src/services/worldStateService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 5aba5813..4a0cf5c3 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -372,7 +372,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge hardWeekly: syndicate.weeklyChallenges!.filter(x => x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/") ), - hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x => + hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x => x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent") ) }; From 7cad8317024448b09f84f4a0d447b67a39292454 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:16:22 -0700 Subject: [PATCH 35/43] chore: update PE+ (#2336) Closes #2332 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2336 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ca3fde8..db97566d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.73", + "warframe-public-export-plus": "^0.5.74", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", @@ -3386,9 +3386,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.73", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.73.tgz", - "integrity": "sha512-v5lmaq/rNICg7WIZcosyfz92RpmrNyfW6+/Pbi9Iu8HbZH74PfaQKT6suAyC9xQn6xp8/cG3NLinqlLZovbKpw==" + "version": "0.5.74", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.74.tgz", + "integrity": "sha512-pA7dxA0lKn9w/2Sc97oxnn+CEzL1SrT9XriNLTDF4Xp+2SBEpGcfbqbdR9ljPQJopIbrc9Zy02R+uBQVomcwyA==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index a2035729..99f8c63d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "ncp": "^2.0.0", "typescript": "^5.5", "undici": "^7.10.0", - "warframe-public-export-plus": "^0.5.73", + "warframe-public-export-plus": "^0.5.74", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", From 3d21813a7978da594162892bf6e8675acb810968 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 01:28:45 +0200 Subject: [PATCH 36/43] fix(vscode): update launch.json --- .vscode/launch.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 26899db7..fbeaa0be 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,7 @@ "type": "node", "request": "launch", "name": "Debug and Watch", - "runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"], - "args": ["${workspaceFolder}/src/index.ts"], + "args": ["${workspaceFolder}/scripts/dev.js"], "console": "integratedTerminal" } ] From a9359bd989cfb7a25ceb5b625b45efffd08e04f8 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:28:44 -0700 Subject: [PATCH 37/43] feat(webui): the circuit override (#2335) Re #2312 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2335 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 2 +- .../custom/getItemListsController.ts | 35 +++++++++++++ static/webui/index.html | 25 ++++++--- static/webui/script.js | 52 +++++++++++++++++-- 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, 109 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5b2425ef..0985b846 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,4 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi - `RadioLegionIntermissionSyndicate` for Intermission I - `RadioLegionSyndicate` for The Wolf of Saturn Six - `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively. -- `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`) +- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`. diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index f804f81c..95f85b01 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -55,6 +55,7 @@ interface ItemLists { EvolutionProgress: ListedItem[]; mods: ListedItem[]; Boosters: ListedItem[]; + //circuitGameModes: ListedItem[]; } const relicQualitySuffixes: Record = { @@ -64,6 +65,10 @@ const relicQualitySuffixes: Record = { VPQ_PLATINUM: " [Exceptional]" }; +/*const toTitleCase = (str: string): string => { + return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()); +};*/ + const getItemListsController: RequestHandler = (req, response) => { const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const res: ItemLists = { @@ -87,6 +92,36 @@ const getItemListsController: RequestHandler = (req, response) => { EvolutionProgress: [], mods: [], Boosters: [] + /*circuitGameModes: [ + { + uniqueName: "Survival", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang)) + }, + { + uniqueName: "VoidFlood", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang)) + }, + { + uniqueName: "Excavation", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang)) + }, + { + uniqueName: "Defense", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang)) + }, + { + uniqueName: "Exterminate", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang)) + }, + { + uniqueName: "Assassination", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang)) + }, + { + uniqueName: "Alchemy", + name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang)) + } + ]*/ }; for (const [uniqueName, item] of Object.entries(ExportWarframes)) { res[item.productCategory].push({ diff --git a/static/webui/index.html b/static/webui/index.html index 455ff556..425269b6 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -768,14 +768,14 @@
-
+
-
+
@@ -892,6 +892,13 @@
+ + +
+ + +
+
@@ -923,10 +930,7 @@ - - - - + @@ -958,6 +962,15 @@ + + + + + + + + + diff --git a/static/webui/script.js b/static/webui/script.js index 10c69dff..e6ba4989 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1882,12 +1882,27 @@ for (const id of uiConfigs) { } } -function doSaveConfig(id) { - const elm = document.getElementById(id); +function doSaveConfigInt(id) { $.post({ url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, contentType: "application/json", - data: JSON.stringify({ [id]: parseInt(elm.value) }) + data: JSON.stringify({ + [id]: parseInt(document.getElementById(id).value) + }) + }); +} + +function doSaveConfigStringArray(id) { + $.post({ + url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + contentType: "application/json", + data: JSON.stringify({ + [id]: document + .getElementById(id) + .getAttribute("data-tags-value") + .split(", ") + .filter(x => x) + }) }); } @@ -1914,7 +1929,12 @@ single.getRoute("/webui/cheats").on("beforeload", function () { if (x.type == "checkbox") { x.checked = value; } else if (x.type == "number") { - x.setAttribute("value", `${value}`); + x.setAttribute("value", value); + } else if (x.classList.contains("tags-input")) { + x.value = value.join(", "); + x.oninput(); + } else { + x.value = value; } } }); @@ -2597,3 +2617,27 @@ const importSamples = { function setImportSample(key) { $("#import-inventory").val(JSON.stringify(importSamples[key], null, 2)); } + +document.querySelectorAll(".tags-input").forEach(input => { + const datalist = document.getElementById(input.getAttribute("list")); + const options = [...datalist.querySelectorAll("option")].map(x => x.textContent); + input.oninput = function () { + const value = []; + for (const tag of this.value.split(",")) { + const index = options.map(x => x.toLowerCase()).indexOf(tag.trim().toLowerCase()); + if (index != -1) { + value.push(options[index]); + } + } + + this.setAttribute("data-tags-value", value.join(", ")); + + datalist.innerHTML = ""; + for (const option of options) { + const elm = document.createElement("option"); + elm.textContent = [...value, option, ""].join(", "); + datalist.appendChild(elm); + } + }; + input.oninput(); +}); diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 22148c9d..6da6847d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -230,6 +230,7 @@ dict = { normal: `[UNTRANSLATED] Normal`, worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, 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`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 22481df6..31631cde 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -229,6 +229,7 @@ dict = { normal: `Normal`, worldState_allAtOnceNormal: `All At Once, Normal`, worldState_allAtOnceSteelPath: `All At Once, Steel Path`, + worldState_theCircuitOverride: `The Circuit Override`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, import_submit: `Submit`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 0a7b7ac5..85a4e0ad 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -230,6 +230,7 @@ dict = { normal: `[UNTRANSLATED] Normal`, worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, 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`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 9dca3dbe..000a9387 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -230,6 +230,7 @@ dict = { normal: `[UNTRANSLATED] Normal`, worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, 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`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index b276c0fb..ba727562 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -230,6 +230,7 @@ dict = { normal: `[UNTRANSLATED] Normal`, worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, + worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index e6973c34..9a37d110 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -230,6 +230,7 @@ dict = { normal: `正常`, worldState_allAtOnceNormal: `全部开启(普通)`, worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`, + worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, From ef3d3b92c7dccf0ff39e2581f283fce80986f815 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:39:41 -0700 Subject: [PATCH 38/43] feat: darvo deal (#2261) Closes #2260 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2261 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 3 +- .../api/getDailyDealStockLevelsController.ts | 6 +- src/controllers/api/giftingController.ts | 36 ++-- src/controllers/api/inventoryController.ts | 30 +++- .../dynamic/worldStateController.ts | 8 +- src/models/inventoryModels/inventoryModel.ts | 4 +- src/models/worldStateModel.ts | 18 +- src/services/configService.ts | 1 + src/services/purchaseService.ts | 29 +++- src/services/worldStateService.ts | 59 ++++++- src/types/inventoryTypes/inventoryTypes.ts | 2 +- src/types/purchaseTypes.ts | 1 + src/types/worldStateTypes.ts | 23 +++ .../worldState/darvoDeals.json | 158 ++++++++++++++++++ .../worldState/worldState.json | 12 -- static/webui/index.html | 7 + static/webui/script.js | 12 +- 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 + 23 files changed, 374 insertions(+), 41 deletions(-) create mode 100644 static/fixed_responses/worldState/darvoDeals.json diff --git a/config.json.example b/config.json.example index baf51baa..85d2dcd7 100644 --- a/config.json.example +++ b/config.json.example @@ -75,7 +75,8 @@ "duviriOverride": "", "nightwaveOverride": "", "allTheFissures": "", - "circuitGameModes": null + "circuitGameModes": null, + "darvoStockMultiplier": 1 }, "dev": { "keepVendorsExpired": false diff --git a/src/controllers/api/getDailyDealStockLevelsController.ts b/src/controllers/api/getDailyDealStockLevelsController.ts index efbb0cec..b67522c5 100644 --- a/src/controllers/api/getDailyDealStockLevelsController.ts +++ b/src/controllers/api/getDailyDealStockLevelsController.ts @@ -1,8 +1,10 @@ +import { DailyDeal } from "@/src/models/worldStateModel"; import { RequestHandler } from "express"; -export const getDailyDealStockLevelsController: RequestHandler = (req, res) => { +export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => { + const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!; res.json({ StoreItem: req.query.productName, - AmountSold: 0 + AmountSold: dailyDeal.AmountSold }); }; diff --git a/src/controllers/api/giftingController.ts b/src/controllers/api/giftingController.ts index 9a532776..3e714a8d 100644 --- a/src/controllers/api/giftingController.ts +++ b/src/controllers/api/giftingController.ts @@ -9,15 +9,26 @@ import { updateCurrency } from "@/src/services/inventoryService"; import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService"; -import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; +import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { IOid } from "@/src/types/commonTypes"; -import { IInventoryChanges, IPurchaseParams, PurchaseSource } from "@/src/types/purchaseTypes"; +import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; import { ExportBundles, ExportFlavour } from "warframe-public-export-plus"; +const checkPurchaseParams = (params: IPurchaseParams): boolean => { + switch (params.Source) { + case PurchaseSource.Market: + return params.UsePremium; + + case PurchaseSource.DailyDeal: + return true; + } + return false; +}; + export const giftingController: RequestHandler = async (req, res) => { const data = getJSONfromString(String(req.body)); - if (data.PurchaseParams.Source != PurchaseSource.Market || !data.PurchaseParams.UsePremium) { + if (!checkPurchaseParams(data.PurchaseParams)) { throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`); } @@ -58,16 +69,19 @@ export const giftingController: RequestHandler = async (req, res) => { } senderInventory.GiftsRemaining -= 1; - const inventoryChanges: IInventoryChanges = updateCurrency( - senderInventory, - data.PurchaseParams.ExpectedPrice, - true - ); + const response: IPurchaseResponse = { + InventoryChanges: {} + }; + if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) { + await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response); + } else { + updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges); + } if (data.PurchaseParams.StoreItem in ExportBundles) { const bundle = ExportBundles[data.PurchaseParams.StoreItem]; if (bundle.giftingBonus) { combineInventoryChanges( - inventoryChanges, + response.InventoryChanges, (await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges ); } @@ -99,9 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => { } ]); - res.json({ - InventoryChanges: inventoryChanges - }); + res.json(response); }; interface IGiftingRequest { diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index 3880186e..f7049532 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -24,6 +24,8 @@ import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes"; import { Ship } from "@/src/models/shipModel"; import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers"; import { Inbox } from "@/src/models/inboxModel"; +import { unixTimesInMs } from "@/src/constants/timeConstants"; +import { DailyDeal } from "@/src/models/worldStateModel"; export const inventoryController: RequestHandler = async (request, response) => { const account = await getAccountForRequest(request); @@ -37,6 +39,8 @@ export const inventoryController: RequestHandler = async (request, response) => // Handle daily reset if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) { + const today = Math.trunc(Date.now() / 86400000); + for (const key of allDailyAffiliationKeys) { inventory[key] = 16000 + inventory.PlayerLevel * 500; } @@ -47,12 +51,12 @@ export const inventoryController: RequestHandler = async (request, response) => inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); if (inventory.NextRefill) { + const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1; + const daysPassed = today - lastLoginDay; + if (config.noArgonCrystalDecay) { inventory.FoundToday = undefined; } else { - const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1; - const today = Math.trunc(Date.now() / 86400000); - const daysPassed = today - lastLoginDay; for (let i = 0; i != daysPassed; ++i) { const numArgonCrystals = inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal") @@ -84,11 +88,29 @@ export const inventoryController: RequestHandler = async (request, response) => inventory.FoundToday = undefined; } } + + if (inventory.UsedDailyDeals.length != 0) { + if (daysPassed == 1) { + const todayAt0Utc = today * 86400000; + const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour)); + const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000; + const darvoOid = + ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95"; + const deal = await DailyDeal.findById(darvoOid); + if (deal) { + inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us + } else { + inventory.UsedDailyDeals = []; + } + } else { + inventory.UsedDailyDeals = []; + } + } } cleanupInventory(inventory); - inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000); + inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC //await inventory.save(); } diff --git a/src/controllers/dynamic/worldStateController.ts b/src/controllers/dynamic/worldStateController.ts index 75d78ff0..556eb91e 100644 --- a/src/controllers/dynamic/worldStateController.ts +++ b/src/controllers/dynamic/worldStateController.ts @@ -1,15 +1,19 @@ import { RequestHandler } from "express"; -import { getWorldState, populateFissures } from "@/src/services/worldStateService"; +import { getWorldState, populateDailyDeal, populateFissures } from "@/src/services/worldStateService"; import { version_compare } from "@/src/helpers/inventoryHelpers"; export const worldStateController: RequestHandler = async (req, res) => { const buildLabel = req.query.buildLabel as string | undefined; const worldState = getWorldState(buildLabel); + const populatePromises = [populateDailyDeal(worldState)]; + // Omitting void fissures for versions prior to Dante Unbound to avoid script errors. if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) { - await populateFissures(worldState); + populatePromises.push(populateFissures(worldState)); } + await Promise.all(populatePromises); + res.json(worldState); }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index cd597bb4..fafdb8b3 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1625,6 +1625,9 @@ const inventorySchema = new Schema( PendingSpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined }, SpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined }, + //Darvo Deal + UsedDailyDeals: [String], + //New Quest Email EmailItems: [typeCountSchema], @@ -1741,7 +1744,6 @@ const inventorySchema = new Schema( //ChallengeInstanceStates: [Schema.Types.Mixed], RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined }, //Robotics: [Schema.Types.Mixed], - //UsedDailyDeals: [Schema.Types.Mixed], CollectibleSeries: { type: [collectibleEntrySchema], default: undefined }, HasResetAccount: { type: Boolean, default: false }, diff --git a/src/models/worldStateModel.ts b/src/models/worldStateModel.ts index 37615d7e..e7a712fe 100644 --- a/src/models/worldStateModel.ts +++ b/src/models/worldStateModel.ts @@ -1,4 +1,4 @@ -import { IFissureDatabase } from "@/src/types/worldStateTypes"; +import { IDailyDealDatabase, IFissureDatabase } from "@/src/types/worldStateTypes"; import { model, Schema } from "mongoose"; const fissureSchema = new Schema({ @@ -12,3 +12,19 @@ const fissureSchema = new Schema({ fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries. export const Fissure = model("Fissure", fissureSchema); + +const dailyDealSchema = new Schema({ + StoreItem: { type: String, required: true }, + Activation: { type: Date, required: true }, + Expiry: { type: Date, required: true }, + Discount: { type: Number, required: true }, + OriginalPrice: { type: Number, required: true }, + SalePrice: { type: Number, required: true }, + AmountTotal: { type: Number, required: true }, + AmountSold: { type: Number, required: true } +}); + +dailyDealSchema.index({ StoreItem: 1 }, { unique: true }); +dailyDealSchema.index({ Expiry: 1 }, { expireAfterSeconds: 86400 }); + +export const DailyDeal = model("DailyDeal", dailyDealSchema); diff --git a/src/services/configService.ts b/src/services/configService.ts index 4f2e93e7..7e30a94b 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -83,6 +83,7 @@ export interface IConfig { nightwaveOverride?: string; allTheFissures?: string; circuitGameModes?: string[]; + darvoStockMultiplier?: number; }; dev?: { keepVendorsExpired?: boolean; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 0c4cad35..2573719b 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -16,7 +16,8 @@ import { IPurchaseResponse, SlotPurchase, IInventoryChanges, - PurchaseSource + PurchaseSource, + IPurchaseParams } from "@/src/types/purchaseTypes"; import { logger } from "@/src/utils/logger"; import { getWorldState } from "./worldStateService"; @@ -35,6 +36,7 @@ import { import { config } from "./configService"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { fromStoreItem, toStoreItem } from "./itemDataService"; +import { DailyDeal } from "../models/worldStateModel"; export const getStoreItemCategory = (storeItem: string): string => { const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); @@ -240,6 +242,12 @@ export const handlePurchase = async ( } } break; + case PurchaseSource.DailyDeal: + if (purchaseRequest.PurchaseParams.ExpectedPrice) { + throw new Error(`daily deal purchase should not have an expected price`); + } + await handleDailyDealPurchase(inventory, purchaseRequest.PurchaseParams, purchaseResponse); + break; case PurchaseSource.Vendor: if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) { const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; @@ -328,6 +336,25 @@ const handleItemPrices = ( } }; +export const handleDailyDealPurchase = async ( + inventory: TInventoryDatabaseDocument, + purchaseParams: IPurchaseParams, + purchaseResponse: IPurchaseResponse +): Promise => { + const dailyDeal = (await DailyDeal.findOne({ StoreItem: purchaseParams.StoreItem }))!; + dailyDeal.AmountSold += 1; + await dailyDeal.save(); + + if (!config.dontSubtractPurchasePlatinumCost) { + updateCurrency(inventory, dailyDeal.SalePrice, true, purchaseResponse.InventoryChanges); + } + + if (!config.noVendorPurchaseLimits) { + inventory.UsedDailyDeals.push(purchaseParams.StoreItem); + purchaseResponse.DailyDealUsed = purchaseParams.StoreItem; + } +}; + export const handleBundleAcqusition = async ( storeItemName: string, inventory: TInventoryDatabaseDocument, diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 4a0cf5c3..501a6536 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -4,6 +4,7 @@ import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json"; import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json"; import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json"; +import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json"; import { buildConfig } from "@/src/services/buildConfigService"; import { unixTimesInMs } from "@/src/constants/timeConstants"; import { config } from "@/src/services/configService"; @@ -27,7 +28,7 @@ import { } from "../types/worldStateTypes"; import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers"; import { logger } from "../utils/logger"; -import { Fissure } from "../models/worldStateModel"; +import { DailyDeal, Fissure } from "../models/worldStateModel"; const sortieBosses = [ "SORTIE_BOSS_HYENA", @@ -1122,6 +1123,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { GlobalUpgrades: [], VoidTraders: [], VoidStorms: [], + DailyDeals: [], EndlessXpChoices: [], KnownCalendarSeasons: [], ...staticWorldState, @@ -1561,6 +1563,24 @@ export const populateFissures = async (worldState: IWorldState): Promise = } }; +export const populateDailyDeal = async (worldState: IWorldState): Promise => { + const dailyDeals = await DailyDeal.find({}); + for (const dailyDeal of dailyDeals) { + if (dailyDeal.Expiry.getTime() > Date.now()) { + worldState.DailyDeals.push({ + StoreItem: dailyDeal.StoreItem, + Activation: toMongoDate(dailyDeal.Activation), + Expiry: toMongoDate(dailyDeal.Expiry), + Discount: dailyDeal.Discount, + OriginalPrice: dailyDeal.OriginalPrice, + SalePrice: dailyDeal.SalePrice, + AmountTotal: Math.round(dailyDeal.AmountTotal * (config.worldState?.darvoStockMultiplier ?? 1)), + AmountSold: dailyDeal.AmountSold + }); + } + } +}; + export const idToBountyCycle = (id: string): number => { return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000); }; @@ -1689,7 +1709,7 @@ const nightwaveTagToSeason: Record = { RadioLegionSyndicate: 0 // The Wolf of Saturn Six }; -export const updateWorldStateCollections = async (): Promise => { +const updateFissures = async (): Promise => { const fissures = await Fissure.find(); const activeNodes = new Set(); @@ -1742,3 +1762,38 @@ export const updateWorldStateCollections = async (): Promise => { } } }; + +const updateDailyDeal = async (): Promise => { + let darvoIndex = Math.trunc((Date.now() - 25200000) / (26 * unixTimesInMs.hour)); + let darvoEnd; + do { + const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000; + darvoEnd = darvoStart + 26 * unixTimesInMs.hour; + const darvoOid = ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95"; + if (!(await DailyDeal.findById(darvoOid))) { + const seed = new SRng(darvoIndex).randomInt(0, 100_000); + const rng = new SRng(seed); + let deal; + do { + deal = rng.randomReward(darvoDeals)!; // Using an actual sampling collected over roughly a year because I can't extrapolate an algorithm from it with enough certainty. + //const [storeItem, meta] = rng.randomElement(Object.entries(darvoDeals))!; + //const discount = Math.min(rng.randomInt(1, 9) * 10, (meta as { MaxDiscount?: number }).MaxDiscount ?? 1); + } while (await DailyDeal.exists({ StoreItem: deal.StoreItem })); + await DailyDeal.insertOne({ + _id: darvoOid, + StoreItem: deal.StoreItem, + Activation: new Date(darvoStart), + Expiry: new Date(darvoEnd), + Discount: deal.Discount, + OriginalPrice: deal.OriginalPrice, + SalePrice: deal.SalePrice, //Math.trunc(deal.OriginalPrice * (1 - discount)) + AmountTotal: deal.AmountTotal, + AmountSold: 0 + }); + } + } while (darvoEnd < Date.now() + 6 * unixTimesInMs.minute && ++darvoIndex); +}; + +export const updateWorldStateCollections = async (): Promise => { + await Promise.all([updateFissures(), updateDailyDeal()]); +}; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 4c4a9821..3fd6ba68 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -287,6 +287,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu ArchwingEnabled?: boolean; PendingSpectreLoadouts?: ISpectreLoadout[]; SpectreLoadouts?: ISpectreLoadout[]; + UsedDailyDeals: string[]; EmailItems: ITypeCount[]; CompletedSyndicates: string[]; FocusXP?: IFocusXP; @@ -351,7 +352,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu //LeagueTickets: any[]; //Quests: any[]; //Robotics: any[]; - //UsedDailyDeals: any[]; LibraryPersonalTarget?: string; LibraryPersonalProgress: ILibraryPersonalProgress[]; CollectibleSeries?: ICollectibleEntry[]; diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index 0ccff176..be26268b 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -105,6 +105,7 @@ export interface IPurchaseResponse { Standing?: IAffiliationMods[]; FreeFavorsUsed?: IAffiliationMods[]; BoosterPackItems?: string; + DailyDealUsed?: string; } export type IBinChanges = { diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 7301333f..309b8187 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -15,6 +15,7 @@ export interface IWorldState { NodeOverrides: INodeOverride[]; VoidTraders: IVoidTrader[]; VoidStorms: IVoidStorm[]; + DailyDeals: IDailyDeal[]; PVPChallengeInstances: IPVPChallengeInstance[]; EndlessXpChoices: IEndlessXpChoice[]; SeasonInfo?: { @@ -169,6 +170,28 @@ export interface IVoidStorm { ActiveMissionTier: string; } +export interface IDailyDeal { + StoreItem: string; + Activation: IMongoDate; + Expiry: IMongoDate; + Discount: number; + OriginalPrice: number; + SalePrice: number; + AmountTotal: number; + AmountSold: number; +} + +export interface IDailyDealDatabase { + StoreItem: string; + Activation: Date; + Expiry: Date; + Discount: number; + OriginalPrice: number; + SalePrice: number; + AmountTotal: number; + AmountSold: number; +} + export interface IPVPChallengeInstance { _id: IOid; challengeTypeRefID: string; diff --git a/static/fixed_responses/worldState/darvoDeals.json b/static/fixed_responses/worldState/darvoDeals.json new file mode 100644 index 00000000..598846b9 --- /dev/null +++ b/static/fixed_responses/worldState/darvoDeals.json @@ -0,0 +1,158 @@ +[ + { "StoreItem": "/Lotus/StoreItems/Powersuits/Archwing/DemolitionJetPack/DemolitionJetPack", "Discount": 60, "OriginalPrice": 275, "SalePrice": 110, "AmountTotal": 300, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Bard/Bard", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 20, "OriginalPrice": 200, "SalePrice": 160, "AmountTotal": 200, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 30, "OriginalPrice": 200, "SalePrice": 140, "AmountTotal": 200, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 40, "OriginalPrice": 200, "SalePrice": 120, "AmountTotal": 200, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 50, "OriginalPrice": 200, "SalePrice": 100, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 100, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 40, "OriginalPrice": 300, "SalePrice": 180, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 50, "OriginalPrice": 300, "SalePrice": 150, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 20, "OriginalPrice": 5, "SalePrice": 4, "AmountTotal": 500, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 30, "OriginalPrice": 5, "SalePrice": 3, "AmountTotal": 415, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 50, "OriginalPrice": 10, "SalePrice": 5, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 60, "OriginalPrice": 10, "SalePrice": 4, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 30, "OriginalPrice": 20, "SalePrice": 14, "AmountTotal": 150, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 45, "OriginalPrice": 20, "SalePrice": 11, "AmountTotal": 150, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", "Discount": 40, "OriginalPrice": 20, "SalePrice": 12, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/PowerLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 30, "OriginalPrice": 40, "SalePrice": 28, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/WardLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 20, "OriginalPrice": 235, "SalePrice": 188, "AmountTotal": 300, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 30, "OriginalPrice": 235, "SalePrice": 164, "AmountTotal": 250, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 50, "OriginalPrice": 235, "SalePrice": 117, "AmountTotal": 150, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 60, "OriginalPrice": 235, "SalePrice": 94, "AmountTotal": 100, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 90, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 60, "OriginalPrice": 125, "SalePrice": 50, "AmountTotal": 65, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 90, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 80, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 70, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 60, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 10, "OriginalPrice": 75, "SalePrice": 67, "AmountTotal": 100, "probability": 6 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 20, "OriginalPrice": 75, "SalePrice": 60, "AmountTotal": 100, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 500, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 500, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 500, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 300, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 250, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 150, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 200, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 175, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 125, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 10, "OriginalPrice": 150, "SalePrice": 135, "AmountTotal": 300, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 270, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 240, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 205, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 145, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 80, "OriginalPrice": 150, "SalePrice": 30, "AmountTotal": 80, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 200, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 165, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 350, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 40, "OriginalPrice": 75, "SalePrice": 45, "AmountTotal": 300, "probability": 7 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 50, "OriginalPrice": 75, "SalePrice": 37, "AmountTotal": 250, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 60, "OriginalPrice": 75, "SalePrice": 30, "AmountTotal": 200, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 10, "OriginalPrice": 125, "SalePrice": 112, "AmountTotal": 500, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 250, "probability": 6 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 125, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 40, "OriginalPrice": 125, "SalePrice": 75, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 300, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 250, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 200, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 20, "OriginalPrice": 165, "SalePrice": 132, "AmountTotal": 300, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 30, "OriginalPrice": 165, "SalePrice": 115, "AmountTotal": 250, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 40, "OriginalPrice": 165, "SalePrice": 99, "AmountTotal": 200, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 50, "OriginalPrice": 165, "SalePrice": 82, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 60, "OriginalPrice": 165, "SalePrice": 66, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 300, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 265, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 70, "OriginalPrice": 150, "SalePrice": 45, "AmountTotal": 225, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 90, "OriginalPrice": 150, "SalePrice": 15, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 40, "OriginalPrice": 230, "SalePrice": 138, "AmountTotal": 250, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 60, "OriginalPrice": 230, "SalePrice": 92, "AmountTotal": 150, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 90, "OriginalPrice": 175, "SalePrice": 17, "AmountTotal": 100, "probability": 1 }, + { + "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield", + "Discount": 30, + "OriginalPrice": 150, + "SalePrice": 105, + "AmountTotal": 100, + "probability": 1 + }, + { + "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield", + "Discount": 70, + "OriginalPrice": 150, + "SalePrice": 45, + "AmountTotal": 100, + "probability": 1 + }, + { + "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield", + "Discount": 90, + "OriginalPrice": 150, + "SalePrice": 15, + "AmountTotal": 100, + "probability": 1 + }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 300, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 250, "probability": 6 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 200, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 5 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 200, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 150, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 50, "probability": 4 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 250, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 200, "probability": 2 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 150, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 60, "OriginalPrice": 190, "SalePrice": 76, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 10, "OriginalPrice": 250, "SalePrice": 225, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 30, "OriginalPrice": 250, "SalePrice": 175, "AmountTotal": 100, "probability": 3 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 50, "OriginalPrice": 250, "SalePrice": 125, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 10, "OriginalPrice": 175, "SalePrice": 157, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 }, + { "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 } +] diff --git a/static/fixed_responses/worldState/worldState.json b/static/fixed_responses/worldState/worldState.json index 018d8175..74d5c9ea 100644 --- a/static/fixed_responses/worldState/worldState.json +++ b/static/fixed_responses/worldState/worldState.json @@ -510,18 +510,6 @@ "PrimeAccessAvailability": { "State": "PRIME1" }, "PrimeVaultAvailabilities": [false, false, false, false, false], "PrimeTokenAvailability": true, - "DailyDeals": [ - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/PowerLensGreater", - "Activation": { "$date": { "$numberLong": "1715058000000" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Discount": 50, - "OriginalPrice": 40, - "SalePrice": 20, - "AmountTotal": 50, - "AmountSold": 0 - } - ], "LibraryInfo": { "LastCompletedTargetType": "/Lotus/Types/Game/Library/Targets/Research7Target" }, "PVPChallengeInstances": [ { diff --git a/static/webui/index.html b/static/webui/index.html index 425269b6..86e4d861 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -899,6 +899,13 @@
+
+ +
+ + +
+
diff --git a/static/webui/script.js b/static/webui/script.js index e6ba4989..5a13fa4f 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1892,6 +1892,16 @@ function doSaveConfigInt(id) { }); } +function doSaveConfigFloat(id) { + $.post({ + url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, + contentType: "application/json", + data: JSON.stringify({ + [id]: parseFloat(document.getElementById(id).value) + }) + }); +} + function doSaveConfigStringArray(id) { $.post({ url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid, @@ -1928,8 +1938,6 @@ single.getRoute("/webui/cheats").on("beforeload", function () { if (x != null) { if (x.type == "checkbox") { x.checked = value; - } else if (x.type == "number") { - x.setAttribute("value", value); } else if (x.classList.contains("tags-input")) { x.value = value.join(", "); x.oninput(); diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 6da6847d..44f57f5b 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -231,6 +231,7 @@ dict = { worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, 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`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 31631cde..faf15aa1 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -230,6 +230,7 @@ dict = { worldState_allAtOnceNormal: `All At Once, Normal`, worldState_allAtOnceSteelPath: `All At Once, Steel Path`, worldState_theCircuitOverride: `The Circuit Override`, + worldState_darvoStockMultiplier: `Darvo Stock Multiplier`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, import_submit: `Submit`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 85a4e0ad..ca9a62f6 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -231,6 +231,7 @@ dict = { worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, 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`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 000a9387..32524abb 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -231,6 +231,7 @@ dict = { worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, 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`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index ba727562..dd6833fc 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -231,6 +231,7 @@ dict = { worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`, worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`, worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 9a37d110..34b240e9 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -231,6 +231,7 @@ dict = { worldState_allAtOnceNormal: `全部开启(普通)`, worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`, worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, From f0547cb9e695d7d44afe747e5b066954b26f2312 Mon Sep 17 00:00:00 2001 From: Corvus Date: Sat, 28 Jun 2025 09:45:45 -0700 Subject: [PATCH 39/43] chore(webui): update Chinese translation (#2341) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2341 Co-authored-by: Corvus Co-committed-by: Corvus --- static/webui/translations/zh.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 34b240e9..aea75fa0 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -230,7 +230,7 @@ dict = { normal: `正常`, worldState_allAtOnceNormal: `全部开启(普通)`, worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`, - worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`, + worldState_theCircuitOverride: `无尽回廊任务循环配置:`, worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, From a9c5e309941b81a1534315267136b42dc6f737f5 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:46:45 -0700 Subject: [PATCH 40/43] chore: deal with visiting navigation not resyncing inventory (#2340) Closes #2339 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2340 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/inventoryController.ts | 3 --- 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 +- 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index f7049532..730756a1 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -313,9 +313,6 @@ export const getInventoryResponse = async ( applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry); } - // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage. - inventoryResponse.LastInventorySync = undefined; - // Set 2FA enabled so trading post can be used inventoryResponse.HWIDProtectEnabled = true; diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 44f57f5b..009ae528 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -1,6 +1,6 @@ // German translation by Animan8000 dict = { - general_inventoryUpdateNote: `Hinweis: Änderungen, die hier vorgenommen werden, werden erst im Spiel angewendet, sobald das Inventar synchronisiert wird. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, + general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `Hinzufügen`, general_bulkActions: `Massenaktionen`, code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index faf15aa1..4c2d114a 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -1,5 +1,5 @@ dict = { - general_inventoryUpdateNote: `Note: Changes made here will only be applied in-game when the game syncs the inventory. Visiting the navigation should be the easiest way to trigger that.`, + general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `Add`, general_bulkActions: `Bulk Actions`, code_loginFail: `Login failed. Double-check the email and password.`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index ca9a62f6..5171d51b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -1,6 +1,6 @@ // Spanish translation by hxedcl dict = { - general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`, + general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `Agregar`, general_bulkActions: `Acciones masivas`, code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 32524abb..d5ba0b00 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -1,6 +1,6 @@ // French translation by Vitruvio dict = { - general_inventoryUpdateNote: `Note : Les changements effectués ici seront appliqués lors de la syncrhonisation. Visiter la navigation appliquera les changements apportés à l'inventaire.`, + general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `Ajouter`, general_bulkActions: `Action groupée`, code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index dd6833fc..85b64e13 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -1,6 +1,6 @@ // Russian translation by AMelonInsideLemon dict = { - general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`, + general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `Добавить`, general_bulkActions: `Массовые действия`, code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index aea75fa0..0458f157 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -1,6 +1,6 @@ // Chinese translation by meb154, bishan178 & Corvus dict = { - general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, + general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`, general_addButton: `添加`, general_bulkActions: `批量操作`, code_loginFail: `登录失败。请检查邮箱和密码。`, From 5a7caa5ba90a7287802a8ee6c1adb1141f9c7631 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:47:39 -0700 Subject: [PATCH 41/43] feat: disableDailyTribute config (#2338) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2338 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/loginRewardsController.ts | 3 ++- 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, 14 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index 85d2dcd7..70044e43 100644 --- a/config.json.example +++ b/config.json.example @@ -58,6 +58,7 @@ "fastClanAscension": false, "missionsCanGiveAllRelics": false, "unlockAllSimarisResearchEntries": false, + "disableDailyTribute": false, "spoofMasteryRank": -1, "nightwaveStandingMultiplier": 1, "unfaithfulBugFixes": { diff --git a/src/controllers/api/loginRewardsController.ts b/src/controllers/api/loginRewardsController.ts index 5280b77f..8959c5c7 100644 --- a/src/controllers/api/loginRewardsController.ts +++ b/src/controllers/api/loginRewardsController.ts @@ -8,6 +8,7 @@ import { setAccountGotLoginRewardToday } from "@/src/services/loginRewardService"; import { getInventory } from "@/src/services/inventoryService"; +import { config } from "@/src/services/configService"; export const loginRewardsController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); @@ -15,7 +16,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => { const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0; const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50; - if (today == account.LastLoginRewardDate) { + if (today == account.LastLoginRewardDate || config.disableDailyTribute) { res.json({ DailyTributeInfo: { IsMilestoneDay: isMilestoneDay, diff --git a/src/services/configService.ts b/src/services/configService.ts index 7e30a94b..67fca37e 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -65,6 +65,7 @@ export interface IConfig { fastClanAscension?: boolean; missionsCanGiveAllRelics?: boolean; unlockAllSimarisResearchEntries?: boolean; + disableDailyTribute?: boolean; spoofMasteryRank?: number; nightwaveStandingMultiplier?: number; unfaithfulBugFixes?: { diff --git a/static/webui/index.html b/static/webui/index.html index 86e4d861..bcca5015 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -768,6 +768,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 009ae528..5e22c97d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -175,6 +175,7 @@ dict = { cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, + cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, 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 4c2d114a..33f9c97b 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -174,6 +174,7 @@ dict = { cheats_fastClanAscension: `Fast Clan Ascension`, cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`, cheats_unlockAllSimarisResearchEntries: `Unlock All Simaris Research Entries`, + cheats_disableDailyTribute: `Disable Daily Tribute`, 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 5171d51b..bd726737 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -175,6 +175,7 @@ dict = { cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`, cheats_unlockAllSimarisResearchEntries: `Desbloquear todas las entradas de investigación de Simaris`, + cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, 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 d5ba0b00..c3f6c86c 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -175,6 +175,7 @@ dict = { cheats_fastClanAscension: `Ascension de clan rapide`, cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`, cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`, + cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`, cheats_save: `Sauvegarder`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 85b64e13..d73bae48 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -175,6 +175,7 @@ dict = { cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`, + cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, 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 0458f157..a64c01e5 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -175,6 +175,7 @@ dict = { cheats_fastClanAscension: `快速升级氏族`, cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`, cheats_unlockAllSimarisResearchEntries: `解锁所有Simaris研究条目`, + cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_save: `保存`, From 44a129ab0b11eb105115016b174e47edddf85cb1 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:48:12 -0700 Subject: [PATCH 42/43] feat: create genetic imprint (#2337) Re #2212 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2337 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .eslintrc | 18 ++++---- .../api/claimCompletedRecipeController.ts | 6 ++- src/controllers/api/startRecipeController.ts | 8 +++- src/models/inventoryModels/inventoryModel.ts | 28 +++++++++++- src/services/inventoryService.ts | 44 ++++++++++++++----- src/services/missionInventoryUpdateService.ts | 1 - src/types/inventoryTypes/inventoryTypes.ts | 20 +++++---- src/types/purchaseTypes.ts | 4 +- 8 files changed, 94 insertions(+), 35 deletions(-) diff --git a/.eslintrc b/.eslintrc index c7994fc1..f5af1b0e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,17 +11,17 @@ "node": true }, "rules": { - "@typescript-eslint/explicit-function-return-type": "warn", - "@typescript-eslint/restrict-template-expressions": "warn", - "@typescript-eslint/restrict-plus-operands": "warn", - "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/restrict-template-expressions": "error", + "@typescript-eslint/restrict-plus-operands": "error", + "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }], "@typescript-eslint/no-unsafe-argument": "error", - "@typescript-eslint/no-unsafe-call": "warn", - "@typescript-eslint/no-unsafe-assignment": "warn", - "@typescript-eslint/no-explicit-any": "warn", - "no-loss-of-precision": "warn", - "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-unsafe-call": "error", + "@typescript-eslint/no-unsafe-assignment": "error", + "@typescript-eslint/no-explicit-any": "error", + "no-loss-of-precision": "error", + "@typescript-eslint/no-unnecessary-condition": "error", "@typescript-eslint/no-base-to-string": "off", "no-case-declarations": "error", "prettier/prettier": "error", diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index 071a6c9c..c70a40be 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -13,7 +13,8 @@ import { addItem, addRecipes, occupySlot, - combineInventoryChanges + combineInventoryChanges, + addKubrowPetPrint } from "@/src/services/inventoryService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; @@ -119,6 +120,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = } } pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis; + } else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") { + const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!; + addKubrowPetPrint(inventory, pet, InventoryChanges); } else if (recipe.secretIngredientAction != "SIA_UNBRAND") { InventoryChanges = { ...InventoryChanges, diff --git a/src/controllers/api/startRecipeController.ts b/src/controllers/api/startRecipeController.ts index 8f68fc28..42f138e5 100644 --- a/src/controllers/api/startRecipeController.ts +++ b/src/controllers/api/startRecipeController.ts @@ -45,9 +45,9 @@ export const startRecipeController: RequestHandler = async (req, res) => { for (let i = 0; i != recipe.ingredients.length; ++i) { if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") { if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") { - const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i])); + const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i])); if (index != -1) { - inventory.KubrowPetEggs!.splice(index, 1); + inventory.KubrowPetEggs.splice(index, 1); } } else { const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory; @@ -72,6 +72,10 @@ export const startRecipeController: RequestHandler = async (req, res) => { if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") { inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType); pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId)); + } else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") { + pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]); + const pet = inventory.KubrowPets.id(pr.KubrowPet)!; + pet.Details!.PrintsRemaining -= 1; } else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { const spectreLoadout: ISpectreLoadout = { ItemType: recipe.resultType, diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index fafdb8b3..57a1e7a3 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -99,7 +99,9 @@ import { ILotusCustomization, IEndlessXpReward, IPersonalGoalProgressDatabase, - IPersonalGoalProgressClient + IPersonalGoalProgressClient, + IKubrowPetPrintClient, + IKubrowPetPrintDatabase } from "../../types/inventoryTypes/inventoryTypes"; import { IOid } from "../../types/commonTypes"; import { @@ -1008,6 +1010,27 @@ const traitsSchema = new Schema( { _id: false } ); +const kubrowPetPrintSchema = new Schema({ + ItemType: String, + Name: String, + IsMale: Boolean, + Size: Number, + DominantTraits: traitsSchema, + RecessiveTraits: traitsSchema +}); +kubrowPetPrintSchema.set("toJSON", { + virtuals: true, + transform(_doc, obj) { + const db = obj as IKubrowPetPrintDatabase; + const client = obj as IKubrowPetPrintClient; + + client.ItemId = toOid(db._id); + + delete obj._id; + delete obj.__v; + } +}); + const detailsSchema = new Schema( { Name: String, @@ -1511,7 +1534,7 @@ const inventorySchema = new Schema( KubrowPetEggs: [kubrowPetEggSchema], //Prints Cat(3 Prints)\Kubrow(2 Prints) Pets - //KubrowPetPrints: [Schema.Types.Mixed], + KubrowPetPrints: [kubrowPetPrintSchema], //Item for EquippedGear example:Scaner,LoadoutTechSummon etc Consumables: [typeCountSchema], @@ -1852,6 +1875,7 @@ export type InventoryDocumentProps = { CrewShipSalvagedWeaponSkins: Types.DocumentArray; PersonalTechProjects: Types.DocumentArray; CrewMembers: Types.DocumentArray; + KubrowPetPrints: Types.DocumentArray; } & { [K in TEquipmentKey]: Types.DocumentArray }; // eslint-disable-next-line @typescript-eslint/no-empty-object-type diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index fafbca6c..d7e2bb88 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -29,7 +29,8 @@ import { ICalendarProgress, INemesisWeaponTargetFingerprint, INemesisPetTargetFingerprint, - IDialogueDatabase + IDialogueDatabase, + IKubrowPetPrintClient } from "@/src/types/inventoryTypes/inventoryTypes"; import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate"; import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes"; @@ -424,7 +425,6 @@ export const addItem = async ( ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg", _id: new Types.ObjectId() }; - inventory.KubrowPetEggs ??= []; inventory.KubrowPetEggs.push(egg); changes.push({ ItemType: egg.ItemType, @@ -784,7 +784,11 @@ export const addItem = async ( typeName.substr(1).split("/")[3] == "CatbrowPet" || typeName.substr(1).split("/")[3] == "KubrowPet" ) { - if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") { + if ( + typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" && + typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" && + typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint" + ) { return addKubrowPet(inventory, typeName, undefined, premiumPurchase); } } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) { @@ -1048,8 +1052,13 @@ export const addKubrowPet = ( const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades); if (!details) { - let traits: ITraits; + const isCatbrow = [ + "/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit", + "/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit", + "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit" + ].includes(kubrowPetName); + let traits: ITraits; if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") { traits = { BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire", @@ -1064,12 +1073,7 @@ export const addKubrowPet = ( Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire" }; } else { - const isCatbrow = [ - "/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit", - "/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit" - ].includes(kubrowPetName); const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails; - traits = { BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type, SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type, @@ -1088,7 +1092,7 @@ export const addKubrowPet = ( Name: "", IsPuppy: !premiumPurchase, HasCollar: true, - PrintsRemaining: 3, + PrintsRemaining: isCatbrow ? 3 : 2, Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating, HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start. IsMale: !!getRandomInt(0, 1), @@ -1112,6 +1116,26 @@ export const addKubrowPet = ( return inventoryChanges; }; +export const addKubrowPetPrint = ( + inventory: TInventoryDatabaseDocument, + pet: IEquipmentDatabase, + inventoryChanges: IInventoryChanges +): void => { + inventoryChanges.KubrowPetPrints ??= []; + inventoryChanges.KubrowPetPrints.push( + inventory.KubrowPetPrints[ + inventory.KubrowPetPrints.push({ + ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint", + Name: pet.Details!.Name, + IsMale: pet.Details!.IsMale, + Size: pet.Details!.Size, + DominantTraits: pet.Details!.DominantTraits, + RecessiveTraits: pet.Details!.RecessiveTraits + }) - 1 + ].toJSON() + ); +}; + export const updateSlots = ( inventory: TInventoryDatabaseDocument, slotName: SlotNames, diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 340e7ba4..69e165e7 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -525,7 +525,6 @@ export const addMissionInventoryUpdates = async ( } case "KubrowPetEggs": { for (const egg of value) { - inventory.KubrowPetEggs ??= []; inventory.KubrowPetEggs.push({ ItemType: egg.ItemType, _id: new Types.ObjectId() diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 3fd6ba68..11d4daff 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -40,6 +40,7 @@ export interface IInventoryDatabase | "InfestedFoundry" | "DialogueHistory" | "KubrowPetEggs" + | "KubrowPetPrints" | "PendingCoupon" | "Drones" | "RecentVendorPurchases" @@ -79,7 +80,8 @@ export interface IInventoryDatabase KahlLoadOuts: IOperatorConfigDatabase[]; InfestedFoundry?: IInfestedFoundryDatabase; DialogueHistory?: IDialogueHistoryDatabase; - KubrowPetEggs?: IKubrowPetEggDatabase[]; + KubrowPetEggs: IKubrowPetEggDatabase[]; + KubrowPetPrints: IKubrowPetPrintDatabase[]; PendingCoupon?: IPendingCouponDatabase; Drones: IDroneDatabase[]; RecentVendorPurchases?: IRecentVendorPurchaseDatabase[]; @@ -307,7 +309,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu FocusUpgrades: IFocusUpgrade[]; HasContributedToDojo?: boolean; HWIDProtectEnabled?: boolean; - //KubrowPetPrints: IKubrowPetPrint[]; + KubrowPetPrints: IKubrowPetPrintClient[]; AlignmentReplay?: IAlignment; PersonalGoalProgress?: IPersonalGoalProgressClient[]; ThemeStyle: string; @@ -722,8 +724,8 @@ export interface IKubrowPetEggDatabase { _id: Types.ObjectId; } -export interface IKubrowPetPrint { - ItemType: KubrowPetPrintItemType; +export interface IKubrowPetPrintClient { + ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"; Name: string; IsMale: boolean; Size: number; // seems to be 0.7 to 1.0 @@ -733,6 +735,10 @@ export interface IKubrowPetPrint { InheritedModularParts?: any[]; } +export interface IKubrowPetPrintDatabase extends Omit { + _id: Types.ObjectId; +} + export interface ITraits { BaseColor: string; SecondaryColor: string; @@ -746,15 +752,11 @@ export interface ITraits { Tail?: string; } -export enum KubrowPetPrintItemType { - LotusTypesGameKubrowPetImprintedTraitPrint = "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint" -} - export interface IKubrowPetDetailsDatabase { Name?: string; IsPuppy?: boolean; HasCollar: boolean; - PrintsRemaining?: number; + PrintsRemaining: number; Status: Status; HatchDate?: Date; DominantTraits: ITraits; diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index be26268b..a1f475aa 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -7,7 +7,8 @@ import { ITypeCount, IRecentVendorPurchaseClient, TEquipmentKey, - ICrewMemberClient + ICrewMemberClient, + IKubrowPetPrintClient } from "./inventoryTypes/inventoryTypes"; export enum PurchaseSource { @@ -78,6 +79,7 @@ export type IInventoryChanges = { NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0 RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0 CrewMembers?: ICrewMemberClient[]; + KubrowPetPrints?: IKubrowPetPrintClient[]; } & Record< Exclude< string, From c4c622d82b1a5d8daebd412627ee42afbd4a7a8b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:50:40 -0700 Subject: [PATCH 43/43] feat: baro's void surplus (#2334) Closes #2284 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2334 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/purchaseController.ts | 1 + src/models/inventoryModels/inventoryModel.ts | 14 +- src/services/importService.ts | 6 + src/services/inventoryService.ts | 16 +- src/services/purchaseService.ts | 181 +++++++++++++------ src/types/inventoryTypes/inventoryTypes.ts | 5 +- src/types/worldStateTypes.ts | 1 + static/fixed_responses/worldState/baro.json | 3 +- 8 files changed, 165 insertions(+), 62 deletions(-) diff --git a/src/controllers/api/purchaseController.ts b/src/controllers/api/purchaseController.ts index ba314845..390e8d72 100644 --- a/src/controllers/api/purchaseController.ts +++ b/src/controllers/api/purchaseController.ts @@ -11,6 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => { const inventory = await getInventory(accountId); const response = await handlePurchase(purchaseRequest, inventory); await inventory.save(); + //console.log(JSON.stringify(response, null, 2)); res.json(response); sendWsBroadcastTo(accountId, { update_inventory: true }); }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 57a1e7a3..067e44a5 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -91,7 +91,7 @@ import { ICrewMemberSkillEfficiency, ICrewMemberDatabase, ICrewMemberClient, - ISortieRewardAttenuation, + IRewardAttenuation, IInvasionProgressDatabase, IInvasionProgressClient, IAccolades, @@ -1417,10 +1417,10 @@ lastSortieRewardSchema.set("toJSON", { } }); -const sortieRewardAttenutationSchema = new Schema( +const rewardAttenutationSchema = new Schema( { - Tag: String, - Atten: Number + Tag: { type: String, required: true }, + Atten: { type: Number, required: true } }, { _id: false } ); @@ -1666,7 +1666,7 @@ const inventorySchema = new Schema( CompletedSorties: [String], LastSortieReward: { type: [lastSortieRewardSchema], default: undefined }, LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined }, - SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined }, + SortieRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }, // Resource Extractor Drones Drones: [droneSchema], @@ -1805,7 +1805,9 @@ const inventorySchema = new Schema( HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }, - ClaimedJunctionChallengeRewards: { type: [String], default: undefined } + ClaimedJunctionChallengeRewards: { type: [String], default: undefined }, + + SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined } }, { timestamps: { createdAt: "Created", updatedAt: false } } ); diff --git a/src/services/importService.ts b/src/services/importService.ts index 3f7f0051..29eee138 100644 --- a/src/services/importService.ts +++ b/src/services/importService.ts @@ -296,6 +296,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial< db[key] = client[key]; } } + // IRewardAtten[] + for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) { + if (client[key] !== undefined) { + db[key] = client[key]; + } + } if (client.XPInfo !== undefined) { db.XPInfo = client.XPInfo; } diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index d7e2bb88..df9ac473 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -44,6 +44,7 @@ import { } from "../types/inventoryTypes/commonInventoryTypes"; import { ExportArcanes, + ExportBoosters, ExportBundles, ExportChallenges, ExportCustoms, @@ -671,6 +672,17 @@ export const addItem = async ( return await addEmailItem(inventory, typeName); } + // Boosters are an odd case. They're only added like this via Baro's Void Surplus afaik. + { + const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == typeName); + if (boosterEntry) { + addBooster(typeName, quantity, inventory); + return { + Boosters: [{ ItemType: typeName, ExpiryDate: quantity }] + }; + } + } + // Path-based duck typing switch (typeName.substr(1).split("/")[1]) { case "Powersuits": @@ -1354,7 +1366,7 @@ export const addCustomization = ( customizationName: string, inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { - if (!inventory.FlavourItems.find(x => x.ItemType == customizationName)) { + if (!inventory.FlavourItems.some(x => x.ItemType == customizationName)) { const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition inventoryChanges.FlavourItems ??= []; @@ -1370,7 +1382,7 @@ export const addSkin = ( typeName: string, inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { - if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) { + if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) { logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`); } else { const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 2573719b..1d58e744 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -8,7 +8,7 @@ import { updateCurrency, updateSlots } from "@/src/services/inventoryService"; -import { getRandomWeightedRewardUc } from "@/src/services/rngService"; +import { getRandomReward, getRandomWeightedRewardUc } from "@/src/services/rngService"; import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { @@ -37,6 +37,7 @@ import { config } from "./configService"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { fromStoreItem, toStoreItem } from "./itemDataService"; import { DailyDeal } from "../models/worldStateModel"; +import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers"; export const getStoreItemCategory = (storeItem: string): string => { const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); @@ -53,6 +54,58 @@ export const getStoreItemTypesCategory = (typesItem: string): string => { return typeElements[1]; }; +const tallyVendorPurchase = ( + inventory: TInventoryDatabaseDocument, + inventoryChanges: IInventoryChanges, + VendorType: string, + ItemId: string, + numPurchased: number, + Expiry: Date +): void => { + if (!config.noVendorPurchaseLimits) { + inventory.RecentVendorPurchases ??= []; + let vendorPurchases = inventory.RecentVendorPurchases.find(x => x.VendorType == VendorType); + if (!vendorPurchases) { + vendorPurchases = + inventory.RecentVendorPurchases[ + inventory.RecentVendorPurchases.push({ + VendorType: VendorType, + PurchaseHistory: [] + }) - 1 + ]; + } + let historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId); + if (historyEntry) { + if (Date.now() >= historyEntry.Expiry.getTime()) { + historyEntry.NumPurchased = numPurchased; + historyEntry.Expiry = Expiry; + } else { + historyEntry.NumPurchased += numPurchased; + } + } else { + historyEntry = + vendorPurchases.PurchaseHistory[ + vendorPurchases.PurchaseHistory.push({ + ItemId: ItemId, + NumPurchased: numPurchased, + Expiry: Expiry + }) - 1 + ]; + } + inventoryChanges.NewVendorPurchase = { + VendorType: VendorType, + PurchaseHistory: [ + { + ItemId: ItemId, + NumPurchased: historyEntry.NumPurchased, + Expiry: toMongoDate(Expiry) + } + ] + }; + inventoryChanges.RecentVendorPurchases = inventoryChanges.NewVendorPurchase; + } +}; + export const handlePurchase = async ( purchaseRequest: IPurchaseRequest, inventory: TInventoryDatabaseDocument @@ -99,20 +152,7 @@ export const handlePurchase = async ( if (offer.LocTagRandSeed !== undefined) { seed = BigInt(offer.LocTagRandSeed); } - if (!config.noVendorPurchaseLimits && ItemId) { - inventory.RecentVendorPurchases ??= []; - let vendorPurchases = inventory.RecentVendorPurchases.find( - x => x.VendorType == manifest!.VendorInfo.TypeName - ); - if (!vendorPurchases) { - vendorPurchases = - inventory.RecentVendorPurchases[ - inventory.RecentVendorPurchases.push({ - VendorType: manifest.VendorInfo.TypeName, - PurchaseHistory: [] - }) - 1 - ]; - } + if (ItemId) { let expiry = parseInt(offer.Expiry.$date.$numberLong); if (purchaseRequest.PurchaseParams.IsWeekly) { const EPOCH = 1734307200 * 1000; // Monday @@ -120,34 +160,14 @@ export const handlePurchase = async ( const weekStart = EPOCH + week * 604800000; expiry = weekStart + 604800000; } - const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId); - let numPurchased = purchaseRequest.PurchaseParams.Quantity; - if (historyEntry) { - if (Date.now() >= historyEntry.Expiry.getTime()) { - historyEntry.NumPurchased = numPurchased; - historyEntry.Expiry = new Date(expiry); - } else { - numPurchased += historyEntry.NumPurchased; - historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity; - } - } else { - vendorPurchases.PurchaseHistory.push({ - ItemId: ItemId, - NumPurchased: purchaseRequest.PurchaseParams.Quantity, - Expiry: new Date(expiry) - }); - } - prePurchaseInventoryChanges.NewVendorPurchase = { - VendorType: manifest.VendorInfo.TypeName, - PurchaseHistory: [ - { - ItemId: ItemId, - NumPurchased: numPurchased, - Expiry: { $date: { $numberLong: expiry.toString() } } - } - ] - }; - prePurchaseInventoryChanges.RecentVendorPurchases = prePurchaseInventoryChanges.NewVendorPurchase; + tallyVendorPurchase( + inventory, + prePurchaseInventoryChanges, + manifest.VendorInfo.TypeName, + ItemId, + purchaseRequest.PurchaseParams.Quantity, + new Date(expiry) + ); } purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier; } else { @@ -193,7 +213,7 @@ export const handlePurchase = async ( throw new Error(`vendor purchase should not have an expected price`); } - if (!config.dontSubtractPurchaseItemCost) { + if (offer.PrimePrice && !config.dontSubtractPurchaseItemCost) { const invItem: IMiscItem = { ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1 @@ -202,6 +222,17 @@ export const handlePurchase = async ( purchaseResponse.InventoryChanges.MiscItems ??= []; purchaseResponse.InventoryChanges.MiscItems.push(invItem); } + + if (offer.Limit) { + tallyVendorPurchase( + inventory, + purchaseResponse.InventoryChanges, + "VoidTrader", + offer.ItemType, + purchaseRequest.PurchaseParams.Quantity, + fromMongoDate(worldState.VoidTraders[0].Expiry) + ); + } } break; } @@ -509,12 +540,57 @@ const handleBoosterPackPurchase = async ( "attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server." ); } + const specialItemReward = pack.components.find(x => x.PityIncreaseRate); for (let i = 0; i != quantity; ++i) { - const disallowedItems = new Set(); - for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) { - const weights = pack.rarityWeightsPerRoll[roll]; - const result = getRandomWeightedRewardUc(pack.components, weights); - if (result) { + if (specialItemReward) { + { + const normalComponents = []; + for (const comp of pack.components) { + if (!comp.PityIncreaseRate) { + const { Probability, ...rest } = comp; + normalComponents.push({ + ...rest, + probability: Probability! + }); + } + } + const result = getRandomReward(normalComponents)!; + logger.debug(`booster pack rolled`, result); + purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};'; + combineInventoryChanges( + purchaseResponse.InventoryChanges, + await addItem(inventory, result.Item, result.Amount) + ); + } + + if (!inventory.WeaponSkins.some(x => x.ItemType == specialItemReward.Item)) { + inventory.SpecialItemRewardAttenuation ??= []; + let atten = inventory.SpecialItemRewardAttenuation.find(x => x.Tag == specialItemReward.Item); + if (!atten) { + atten = + inventory.SpecialItemRewardAttenuation[ + inventory.SpecialItemRewardAttenuation.push({ + Tag: specialItemReward.Item, + Atten: specialItemReward.Probability! + }) - 1 + ]; + } + if (Math.random() < atten.Atten) { + purchaseResponse.BoosterPackItems += toStoreItem(specialItemReward.Item) + ',{"lvl":0};'; + combineInventoryChanges( + purchaseResponse.InventoryChanges, + await addItem(inventory, specialItemReward.Item) + ); + // TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now? + } else { + atten.Atten += specialItemReward.PityIncreaseRate!; + } + } + } else { + const disallowedItems = new Set(); + for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) { + const weights = pack.rarityWeightsPerRoll[roll]; + const result = getRandomWeightedRewardUc(pack.components, weights)!; logger.debug(`booster pack rolled`, result); if (disallowedItems.has(result.Item)) { logger.debug(`oops, can't use that one; trying again`); @@ -524,9 +600,12 @@ const handleBoosterPackPurchase = async ( disallowedItems.add(result.Item); } purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};'; - combineInventoryChanges(purchaseResponse.InventoryChanges, await addItem(inventory, result.Item, 1)); + combineInventoryChanges( + purchaseResponse.InventoryChanges, + await addItem(inventory, result.Item, result.Amount) + ); + ++roll; } - ++roll; } } return purchaseResponse; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 11d4daff..06c47147 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -298,7 +298,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu CompletedSorties: string[]; LastSortieReward?: ILastSortieRewardClient[]; LastLiteSortieReward?: ILastSortieRewardClient[]; - SortieRewardAttenuation?: ISortieRewardAttenuation[]; + SortieRewardAttenuation?: IRewardAttenuation[]; Drones: IDroneClient[]; StepSequencers: IStepSequencer[]; ActiveAvatarImageType?: string; @@ -383,6 +383,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu HubNpcCustomizations?: IHubNpcCustomization[]; Ship?: IOrbiter; // U22 and below, response only ClaimedJunctionChallengeRewards?: string[]; // U39 + SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus } export interface IAffiliation { @@ -785,7 +786,7 @@ export interface ILastSortieRewardDatabase extends Omit