diff --git a/src/controllers/api/focusController.ts b/src/controllers/api/focusController.ts index f838a9f4..89fccdb3 100644 --- a/src/controllers/api/focusController.ts +++ b/src/controllers/api/focusController.ts @@ -1,23 +1,91 @@ import type { RequestHandler } from "express"; -import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getAccountForRequest } from "../../services/loginService.ts"; import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts"; import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { logger } from "../../utils/logger.ts"; import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; +import { version_compare } from "../../helpers/inventoryHelpers.ts"; export const focusController: RequestHandler = async (req, res) => { - const accountId = await getAccountIdForRequest(req); - switch (req.query.op) { + const account = await getAccountForRequest(req); + + let op = req.query.op as string; + const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0; + if (focus2) { + // Focus 2.0 + switch (req.query.op) { + case Focus2Operation.InstallLens: + op = "InstallLens"; + break; + case Focus2Operation.UnlockWay: + op = "UnlockWay"; + break; + case Focus2Operation.UnlockUpgrade: + op = "UnlockUpgrade"; + break; + case Focus2Operation.IncreasePool: + op = "IncreasePool"; + break; + case Focus2Operation.LevelUpUpgrade: + op = "LevelUpUpgrade"; + break; + case Focus2Operation.ActivateWay: + op = "ActivateWay"; + break; + case Focus2Operation.UpdateUpgrade: + op = "UpdateUpgrade"; + break; + case Focus2Operation.SentTrainingAmplifier: + op = "SentTrainingAmplifier"; + break; + case Focus2Operation.UnbindUpgrade: + op = "UnbindUpgrade"; + break; + case Focus2Operation.ConvertShard: + op = "ConvertShard"; + break; + } + } else { + // Focus 3.0 + switch (req.query.op) { + case Focus3Operation.InstallLens: + op = "InstallLens"; + break; + case Focus3Operation.UnlockWay: + op = "UnlockWay"; + break; + case Focus3Operation.UnlockUpgrade: + op = "UnlockUpgrade"; + break; + case Focus3Operation.LevelUpUpgrade: + op = "LevelUpUpgrade"; + break; + case Focus3Operation.ActivateWay: + op = "ActivateWay"; + break; + case Focus3Operation.SentTrainingAmplifier: + op = "SentTrainingAmplifier"; + break; + case Focus3Operation.UnbindUpgrade: + op = "UnbindUpgrade"; + break; + case Focus3Operation.ConvertShard: + op = "ConvertShard"; + break; + } + } + + switch (op) { default: logger.error("Unhandled focus op type: " + String(req.query.op)); logger.debug(String(req.body)); res.end(); break; - case FocusOperation.InstallLens: { + case "InstallLens": { const request = JSON.parse(String(req.body)) as ILensInstallRequest; - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const item = inventory[request.Category].id(request.WeaponId); if (item) { item.FocusLens = request.LensType; @@ -35,10 +103,10 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.UnlockWay: { + case "UnlockWay": { const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusPolarity = focusTypeToPolarity(focusType); - const inventory = await getInventory(accountId, "FocusAbility FocusUpgrades FocusXP"); + const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP"); const cost = inventory.FocusAbility ? 50_000 : 0; inventory.FocusAbility ??= focusType; inventory.FocusUpgrades.push({ ItemType: focusType }); @@ -52,12 +120,29 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.ActivateWay: { + case "IncreasePool": { + const request = JSON.parse(String(req.body)) as IIncreasePoolRequest; + const focusPolarity = focusTypeToPolarity(request.FocusType); + const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity"); + let cost = 0; + for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) { + cost += increasePoolCost[capacity - 5]; + } + inventory.FocusXP![focusPolarity]! -= cost; + inventory.FocusCapacity = request.NewTotalCapacity; + await inventory.save(); + res.json({ + TotalCapacity: request.NewTotalCapacity, + FocusPointCosts: { [focusPolarity]: cost } + }); + break; + } + case "ActivateWay": { const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; await Inventory.updateOne( { - accountOwnerId: accountId + accountOwnerId: account._id.toString() }, { FocusAbility: focusType @@ -69,13 +154,20 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.UnlockUpgrade: { + case "UnlockUpgrade": { const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); let cost = 0; for (const focusType of request.FocusTypes) { - cost += ExportFocusUpgrades[focusType].baseFocusPointCost; + if (focusType in ExportFocusUpgrades) { + cost += ExportFocusUpgrades[focusType].baseFocusPointCost; + } else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") { + // Zenurik's Inner Might (Focus 2.0) + cost += 50_000; + } else { + logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`); + } inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); } inventory.FocusXP![focusPolarity]! -= cost; @@ -86,15 +178,20 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.LevelUpUpgrade: { + case "LevelUpUpgrade": + case "UpdateUpgrade": { const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); let cost = 0; for (const focusUpgrade of request.FocusInfos) { cost += focusUpgrade.FocusXpCost; const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; - focusUpgradeDb.Level = focusUpgrade.Level; + if (op == "UpdateUpgrade") { + focusUpgradeDb.IsActive = focusUpgrade.IsActive; + } else { + focusUpgradeDb.Level = focusUpgrade.Level; + } } inventory.FocusXP![focusPolarity]! -= cost; await inventory.save(); @@ -104,9 +201,9 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.SentTrainingAmplifier: { + case "SentTrainingAmplifier": { const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, { ModularParts: [ "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", @@ -119,10 +216,10 @@ export const focusController: RequestHandler = async (req, res) => { res.json(inventoryChanges.OperatorAmps![0]); break; } - case FocusOperation.UnbindUpgrade: { + case "UnbindUpgrade": { const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length; addMiscItems(inventory, [ { @@ -149,7 +246,7 @@ export const focusController: RequestHandler = async (req, res) => { }); break; } - case FocusOperation.ConvertShard: { + case "ConvertShard": { const request = JSON.parse(String(req.body)) as IConvertShardRequest; // Tally XP let xp = 0; @@ -167,7 +264,7 @@ export const focusController: RequestHandler = async (req, res) => { for (const shard of request.Shards) { shard.ItemCount *= -1; } - const inventory = await getInventory(accountId); + const inventory = await getInventory(account._id.toString()); const polarity = request.Polarity; inventory.FocusXP ??= {}; inventory.FocusXP[polarity] ??= 0; @@ -179,7 +276,8 @@ export const focusController: RequestHandler = async (req, res) => { } }; -enum FocusOperation { +// Focus 3.0 +enum Focus3Operation { InstallLens = "1", UnlockWay = "2", UnlockUpgrade = "3", @@ -190,6 +288,20 @@ enum FocusOperation { ConvertShard = "9" } +// Focus 2.0 +enum Focus2Operation { + InstallLens = "1", + UnlockWay = "2", + UnlockUpgrade = "3", + IncreasePool = "4", + LevelUpUpgrade = "5", + ActivateWay = "6", + UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest + SentTrainingAmplifier = "9", + UnbindUpgrade = "10", + ConvertShard = "11" +} + // For UnlockWay & ActivateWay interface IWayRequest { FocusType: string; @@ -199,6 +311,13 @@ interface IUnlockUpgradeRequest { FocusTypes: string[]; } +// Focus 2.0 +interface IIncreasePoolRequest { + FocusType: string; + CurrentTotalCapacity: number; + NewTotalCapacity: number; +} + interface ILevelUpUpgradeRequest { FocusInfos: { ItemType: string; @@ -206,6 +325,7 @@ interface ILevelUpUpgradeRequest { IsUniversal: boolean; Level: number; IsActiveAbility: boolean; + IsActive?: number; // Focus 2.0 }[]; } @@ -240,3 +360,19 @@ const shardValues = { "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000, "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000 }; + +// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0) +const increasePoolCost = [ + 2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918, + 13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344, + 25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775, + 37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961, + 50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759, + 64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078, + 78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849, + 93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065, + 108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624, + 121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443, + 135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503, + 149517 +]; diff --git a/src/controllers/custom/tunablesController.ts b/src/controllers/custom/tunablesController.ts index 87419fcc..7405075a 100644 --- a/src/controllers/custom/tunablesController.ts +++ b/src/controllers/custom/tunablesController.ts @@ -1,16 +1,9 @@ import type { RequestHandler } from "express"; +import type { ITunables } from "../../types/bootstrapperTypes.ts"; // This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual -interface ITunables { - prohibit_skip_mission_start_timer?: boolean; - prohibit_fov_override?: boolean; - prohibit_freecam?: boolean; - prohibit_teleport?: boolean; - prohibit_scripts?: boolean; -} - -const tunablesController: RequestHandler = (_req, res) => { +export const tunablesController: RequestHandler = (_req, res) => { const tunables: ITunables = {}; //tunables.prohibit_skip_mission_start_timer = true; //tunables.prohibit_fov_override = true; @@ -19,5 +12,3 @@ const tunablesController: RequestHandler = (_req, res) => { //tunables.prohibit_scripts = true; res.json(tunables); }; - -export { tunablesController }; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 78b0effe..ac95f362 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -146,7 +146,8 @@ const focusUpgradeSchema = new Schema( { ItemType: String, Level: Number, - IsUniversal: Boolean + IsUniversal: Boolean, + IsActive: Number }, { _id: false } ); @@ -1542,6 +1543,8 @@ const inventorySchema = new Schema( FocusAbility: String, //The treeways of the Focus school.(Active and passive Ability) FocusUpgrades: [focusUpgradeSchema], + //Focus 2.0 Pool + FocusCapacity: Number, //Achievement ChallengeProgress: [challengeProgressSchema], diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 8d14f356..ba7278cb 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -35,7 +35,8 @@ import type { IVoidTrader, IVoidTraderOffer, IWorldState, - TCircuitGameMode + TCircuitGameMode, + IFlashSale } from "../types/worldStateTypes.ts"; import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts"; import { logger } from "../utils/logger.ts"; @@ -1580,21 +1581,16 @@ export const getWorldState = (buildLabel?: string): IWorldState => { } if (config.worldState?.qtccAlerts) { + const activationTimeStamp = "1759327200000"; + const expiryTimeStamp = "2000000000000"; + worldState.Alerts.push( { _id: { $oid: "68dc23c42e9d3acfa708ff3b" }, - Activation: { - $date: { - $numberLong: "1759327200000" - } - }, - Expiry: { - $date: { - $numberLong: "2000000000000" - } - }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, MissionInfo: { location: "SolNode123", missionType: "MT_SURVIVAL", @@ -1618,16 +1614,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => { _id: { $oid: "68dc2466e298b4f04206687a" }, - Activation: { - $date: { - $numberLong: "1759327200000" - } - }, - Expiry: { - $date: { - $numberLong: "2000000000000" - } - }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, MissionInfo: { location: "SolNode149", missionType: "MT_DEFENSE", @@ -1651,16 +1639,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => { _id: { $oid: "68dc26865e7cb56b820b4252" }, - Activation: { - $date: { - $numberLong: "1759327200000" - } - }, - Expiry: { - $date: { - $numberLong: "2000000000000" - } - }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, MissionInfo: { location: "SolNode39", missionType: "MT_EXCAVATE", @@ -1681,6 +1661,72 @@ export const getWorldState = (buildLabel?: string): IWorldState => { ForceUnlock: true } ); + + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021D", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoI", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoH", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Clan/QTCC2024EmblemItem", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2024Display", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVII", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVI", RegularOverride: 1 }, + { TypeName: "/Lotus/Interface/Graphics/CustomUI/ConqueraStyle", RegularOverride: 1 }, + { TypeName: "/Lotus/Interface/Graphics/CustomUI/Backgrounds/ConqueraBackground", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2021Deco", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2022A", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021B", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021A", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/TnCharityRibbonSyandana", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021C", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Venus/Conquera2023CommunityDisplay", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphUpdated", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Sigils/QTCC2023ConqueraSigil", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Sigils/ConqueraSigil", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Effects/Conquera2022Ephemera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Effects/ConqueraEphemera", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorL", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorA", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraChestRibbon", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Plushies/PlushyProtectorStalker", PremiumOverride: 35 } + ]; + + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); + + const seasonalItems = storeItems.map(item => item.TypeName); + + const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find( + c => c.CategoryName == "COMMUNITY" + ); + if (seasonalCategory) { + seasonalCategory.Items ??= []; + seasonalCategory.Items.push(...seasonalItems); + } else { + worldState.InGameMarket.LandingPage.Categories.push({ + CategoryName: "COMMUNITY", + Name: "/Lotus/Language/Store/CommunityCategoryTitle", + Icon: "community", + AddToMenu: true, + Items: seasonalItems + }); + } } const isFebruary = date.getUTCMonth() == 1; @@ -2048,82 +2094,39 @@ export const getWorldState = (buildLabel?: string): IWorldState => { NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level" }); - const baseStoreItem = { - ShowInMarket: true, - HideFromMarket: false, - SupporterPack: false, - Discount: 0, - BogoBuy: 0, - BogoGet: 0, - StartDate: { $date: { $numberLong: activationTimeStamp } }, - EndDate: { $date: { $numberLong: expiryTimeStamp } }, - ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } } - }; - - const storeItems = [ - { - TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", - PremiumOverride: 240, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", - PremiumOverride: 0, - RegularOverride: 1 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", - PremiumOverride: 35, - RegularOverride: 0 - }, - { - TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", - PremiumOverride: 75, - RegularOverride: 0 - } + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", PremiumOverride: 240 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", RegularOverride: 1 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", PremiumOverride: 75 } ]; - worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...item }))); + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); const seasonalItems = storeItems.map(item => item.TypeName); @@ -2762,22 +2765,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => { const isOctober = date.getUTCMonth() == 9; // October = month index 9 if (config.worldState?.naberusNightsOverride ?? isOctober) { + const activationTimeStamp = config.worldState?.naberusNightsOverride + ? "1727881200000" + : Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString(); + + const expiryTimeStamp = config.worldState?.naberusNightsOverride + ? "2000000000000" + : Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString(); + worldState.Goals.push({ _id: { $oid: "66fd602de1778d583419e8e7" }, - Activation: { - $date: { - $numberLong: config.worldState?.naberusNightsOverride - ? "1727881200000" - : Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString() - } - }, - Expiry: { - $date: { - $numberLong: config.worldState?.naberusNightsOverride - ? "2000000000000" - : Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString() - } - }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, Count: 0, Goal: 0, Success: 0, @@ -2788,6 +2787,181 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Tag: "DeimosHalloween", Node: "DeimosHub" }); + + const storeItems: (Partial & { TypeName: string })[] = [ + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023GlyphBundleA", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2021GlyphBundle", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleA", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleB", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenGlyphBundle", PremiumOverride: 65 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023ArmorBundle", PremiumOverride: 125 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenCrpCircArmorPack", PremiumOverride: 100 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundleB", PremiumOverride: 80 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPack", PremiumOverride: 175 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenShipSkinBundle", PremiumOverride: 80 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackC", PremiumOverride: 175 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackII", PremiumOverride: 145 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundle", PremiumOverride: 130 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/AcolyteNoggleBundle", PremiumOverride: 160 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteAreaCasterBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteDuellistBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteControlBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteHeavyBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteRogueBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteStrikerBobbleHead", PremiumOverride: 35 }, + { TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerHalloweenItemA", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/Halloween2014Wings/Halloween2014ArmArmor", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Festivities/PumpkinHead", RegularOverride: 1 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenRegorAxeShield", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CheshireKavat", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAkvasto", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAngstrum", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBoltor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhostChibiWisp", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBraton", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016A", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016C", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016B", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBuzlok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016D", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CreepyClem", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDaikyu", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Dethcube", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDragonNikana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDualZoren", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019FrankenCorpus", + PremiumOverride: 20 + }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Grineer", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaive", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGalatine", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrakata", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGorgon", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaxion", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGremlins", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrinlok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenFireFlyScarf", PremiumOverride: 90 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenImperator", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKronen", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Lotus", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenJatKittag", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenKyropteraScarf", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKunai", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Liset/LisetSkinHalloween", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Liset/LisetInsectSkinHalloween", PremiumOverride: 50 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphFour", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenMarelok", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNikana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNukor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Loid", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOpticor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOrthos", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenParis", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphTwo", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircC", PremiumOverride: 45 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircA", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircL", PremiumOverride: 35 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenScindo", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhoulGrave", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Pumpkin", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSarpa", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphThree", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSilvaAndAegis", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphOne", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSoma", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSkana", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019SlimeLoki", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSobek", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSonicor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSimulor", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTonkor", PremiumOverride: 20 }, + { + TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019TrickOrBalas", + PremiumOverride: 20 + }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSpira", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenStradavar", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGrakatas", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenArchSword", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenLato", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019Werefested", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenErosionCape", PremiumOverride: 50 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenVasto", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkSplitSword", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkDagger", PremiumOverride: 20 }, + { TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenGrnBannerScarf", PremiumOverride: 75 }, + { TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAmprex", PremiumOverride: 20 }, + { TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackD", PremiumOverride: 180 } + ]; + + worldState.FlashSales.push( + ...storeItems.map(item => ({ + ...{ + StartDate: { $date: { $numberLong: activationTimeStamp } }, + EndDate: { $date: { $numberLong: expiryTimeStamp } }, + ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } }, + ShowInMarket: item.ShowInMarket ?? true, + HideFromMarket: item.HideFromMarket ?? false, + SupporterPack: item.SupporterPack ?? false, + Discount: item.Discount ?? 0, + BogoBuy: item.BogoBuy ?? 0, + BogoGet: item.BogoGet ?? 0, + RegularOverride: item.RegularOverride ?? 0, + PremiumOverride: item.PremiumOverride ?? 0 + }, + ...item + })) + ); + + const seasonalItems = storeItems.map(item => item.TypeName); + const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == "SEASONAL"); + + if (seasonalCategory) { + seasonalCategory.Items ??= []; + seasonalCategory.Items.push(...seasonalItems); + } else { + worldState.InGameMarket.LandingPage.Categories.push({ + CategoryName: "SEASONAL", + Name: "/Lotus/Language/Store/SeasonalCategoryTitle", + Icon: "seasonal", + AddToMenu: true, + Items: seasonalItems + }); + } } if (config.worldState?.bellyOfTheBeast) { diff --git a/src/services/wsService.ts b/src/services/wsService.ts index 0e35d0c2..6850e1a2 100644 --- a/src/services/wsService.ts +++ b/src/services/wsService.ts @@ -8,6 +8,7 @@ import type { IDatabaseAccountJson } from "../types/loginTypes.ts"; import type { HydratedDocument } from "mongoose"; import { logError, logger } from "../utils/logger.ts"; import type { Request } from "express"; +import type { ITunables } from "../types/bootstrapperTypes.ts"; let wsServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined; @@ -89,8 +90,9 @@ interface IWsMsgToClient { logged_out?: boolean; have_game_ws?: boolean; - // to game + // to game/bootstrapper (https://openwf.io/bootstrapper-manual) sync_inventory?: boolean; + tunables?: ITunables; } const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { diff --git a/src/types/bootstrapperTypes.ts b/src/types/bootstrapperTypes.ts new file mode 100644 index 00000000..f011b85a --- /dev/null +++ b/src/types/bootstrapperTypes.ts @@ -0,0 +1,9 @@ +// This is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual +export interface ITunables { + token?: string; + prohibit_skip_mission_start_timer?: boolean; + prohibit_fov_override?: boolean; + prohibit_freecam?: boolean; + prohibit_teleport?: boolean; + prohibit_scripts?: boolean; +} diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index ffbec905..bb181b61 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -340,6 +340,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu EmailItems: ITypeCount[]; CompletedSyndicates: string[]; FocusXP?: IFocusXP; + FocusCapacity?: number; Wishlist: string[]; Alignment?: IAlignment; CompletedSorties: string[]; @@ -638,6 +639,7 @@ export interface IFocusUpgrade { ItemType: string; Level?: number; IsUniversal?: boolean; + IsActive?: number; // Focus 2.0 } export interface IFocusXP { diff --git a/static/webui/script.js b/static/webui/script.js index 0ca1ff4b..ab1a894d 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -3276,13 +3276,13 @@ function unlockFocusSchool(upgradeType) { return new Promise(resolve => { // Deselect current FocusAbility so we will be able to unlock the way for free $.post({ - url: "/api/focus.php?" + window.authz + "&op=5", + url: "/api/focus.php?" + window.authz + "&op=ActivateWay", contentType: "text/plain", data: JSON.stringify({ FocusType: null }) }).done(function () { // Unlock the way now $.post({ - url: "/api/focus.php?" + window.authz + "&op=2", + url: "/api/focus.php?" + window.authz + "&op=UnlockWay", contentType: "text/plain", data: JSON.stringify({ FocusType: upgradeType