From fd2ec696a08ba8f665154a79e879aea6d8440c93 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Wed, 13 Aug 2025 07:13:05 -0700 Subject: [PATCH] feat: tactical alerts (#2607) Includes all `Tactical Alerts` since Star Chart 3.0 with exception: `Snowday Showdown` `Wolf Hunt (2019)` (couldn't find corresponded `EventNode` for that) `Void Corruption` (that's goes into `Alerts`) All `Warframe's Anniversary` Re #1103 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2607 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- config-vanilla.json | 9 + src/controllers/api/inboxController.ts | 6 +- src/models/inboxModel.ts | 4 +- src/services/configService.ts | 9 + src/services/missionInventoryUpdateService.ts | 236 ++++++- src/services/worldStateService.ts | 641 +++++++++++++++++- src/types/worldStateTypes.ts | 48 +- static/webui/index.html | 70 ++ static/webui/script.js | 11 + static/webui/style.css | 5 +- static/webui/translations/de.js | 12 + static/webui/translations/en.js | 12 + static/webui/translations/es.js | 12 + static/webui/translations/fr.js | 12 + static/webui/translations/ru.js | 12 + static/webui/translations/uk.js | 12 + static/webui/translations/zh.js | 12 + 17 files changed, 1100 insertions(+), 23 deletions(-) diff --git a/config-vanilla.json b/config-vanilla.json index a8834907..d285922c 100644 --- a/config-vanilla.json +++ b/config-vanilla.json @@ -71,10 +71,19 @@ "affinityBoost": false, "resourceBoost": false, "tennoLiveRelay": false, + "wolfHunt": false, + "longShadow": false, + "hallowedFlame": false, + "hallowedNightmares": false, + "hallowedNightmaresRewardsOverride": 0, + "proxyRebellion": false, + "proxyRebellionRewardsOverride": 0, "galleonOfGhouls": 0, "ghoulEmergenceOverride": null, "plagueStarOverride": null, "starDaysOverride": null, + "dogDaysOverride": null, + "dogDaysRewardsOverride": null, "eidolonOverride": "", "vallisOverride": "", "duviriOverride": "", diff --git a/src/controllers/api/inboxController.ts b/src/controllers/api/inboxController.ts index 8e775c10..5f36c998 100644 --- a/src/controllers/api/inboxController.ts +++ b/src/controllers/api/inboxController.ts @@ -13,7 +13,8 @@ import { addItems, combineInventoryChanges, getEffectiveAvatarImageType, - getInventory + getInventory, + updateCurrency } from "@/src/services/inventoryService"; import { logger } from "@/src/utils/logger"; import { ExportFlavour } from "warframe-public-export-plus"; @@ -100,6 +101,9 @@ export const inboxController: RequestHandler = async (req, res) => { } } } + if (message.RegularCredits) { + updateCurrency(inventory, -message.RegularCredits, false, inventoryChanges); + } await inventory.save(); res.json({ InventoryChanges: inventoryChanges }); } else if (latestClientMessageId) { diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index 0ec39442..27a5f0a7 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -47,6 +47,7 @@ export interface IMessage { acceptAction?: string; declineAction?: string; hasAccountAction?: boolean; + RegularCredits?: number; } export interface Arg { @@ -139,7 +140,8 @@ const messageSchema = new Schema( contextInfo: String, acceptAction: String, declineAction: String, - hasAccountAction: Boolean + hasAccountAction: Boolean, + RegularCredits: Number }, { id: false } ); diff --git a/src/services/configService.ts b/src/services/configService.ts index 0ad8acc7..0d800477 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -83,10 +83,19 @@ export interface IConfig { resourceBoost?: boolean; tennoLiveRelay?: boolean; baroTennoConRelay?: boolean; + wolfHunt?: boolean; + longShadow?: boolean; + hallowedFlame?: boolean; + hallowedNightmares?: boolean; + hallowedNightmaresRewardsOverride?: number; + proxyRebellion?: boolean; + proxyRebellionRewardsOverride?: number; galleonOfGhouls?: number; ghoulEmergenceOverride?: boolean; plagueStarOverride?: boolean; starDaysOverride?: boolean; + dogDaysOverride?: boolean; + dogDaysRewardsOverride?: number; eidolonOverride?: string; vallisOverride?: string; duviriOverride?: string; diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 6bd6ce58..907ddc70 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -50,7 +50,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes"; import { crackRelic } from "@/src/helpers/relicHelper"; -import { createMessage } from "@/src/services/inboxService"; +import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService"; import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json"; import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json"; import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; @@ -624,37 +624,93 @@ export const addMissionInventoryUpdates = async ( 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 { + if (!goalProgress) { inventory.PersonalGoalProgress.push({ Best: uploadProgress.Best, Count: uploadProgress.Count, Tag: goal.Tag, goalId: new Types.ObjectId(goal._id.$oid) }); + } + const currentNode = inventoryUpdates.RewardInfo!.node; + let currentMissionKey; + if (currentNode == goal.Node) { + currentMissionKey = goal.MissionKeyName; + } else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) { + for (let i = 0; i < goal.ConcurrentNodes.length; i++) { + if (currentNode == goal.ConcurrentNodes[i]) { + currentMissionKey = goal.ConcurrentMissionKeyNames[i]; + break; + } + } + } + if (currentMissionKey && currentMissionKey in goalMessagesByKey) { + const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count; + let reward; + + if (goal.InterimGoals && goal.InterimRewards) { + for (let i = 0; i < goal.InterimGoals.length; i++) { + if ( + goal.InterimGoals[i] && + goal.InterimGoals[i] <= totalCount && + (!goalProgress || goalProgress.Count < goal.InterimGoals[i]) && + goal.InterimRewards[i] + ) { + reward = goal.InterimRewards[i]; + break; + } + } + } if ( - goal.Reward && - goal.Reward.items && - goal.MissionKeyName && - goal.MissionKeyName in goalMessagesByKey + !reward && + goal.Goal && + goal.Goal <= totalCount && + (!goalProgress || goalProgress.Count < goal.Goal) && + goal.Reward ) { - // Send reward via inbox - const info = goalMessagesByKey[goal.MissionKeyName]; - await createMessage(inventory.accountOwnerId, [ - { + reward = goal.Reward; + } + if ( + !reward && + goal.BonusGoal && + goal.BonusGoal <= totalCount && + (!goalProgress || goalProgress.Count < goal.BonusGoal) && + goal.BonusReward + ) { + reward = goal.BonusReward; + } + if (reward) { + if (currentMissionKey in goalMessagesByKey) { + // Send reward via inbox + const info = goalMessagesByKey[currentMissionKey]; + const message: IMessageCreationTemplate = { 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 + }; + + if (reward.items) { + message.att = reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)); } - ]); + if (reward.countedItems) { + message.countedAtt = reward.countedItems; + } + if (reward.credits) { + message.RegularCredits = reward.credits; + } + + await createMessage(inventory.accountOwnerId, [message]); + } } } + + if (goalProgress) { + goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best); + goalProgress.Count += uploadProgress.Count; + } } } break; @@ -1011,8 +1067,16 @@ export const addMissionRewards = async ( if (rewardInfo.goalId) { const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId); - if (goal?.MissionKeyName) { - levelKeyName = goal.MissionKeyName; + if (goal) { + if (rewardInfo.node == goal.Node && goal.MissionKeyName) levelKeyName = goal.MissionKeyName; + if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) { + for (let i = 0; i < goal.ConcurrentNodes.length && i < goal.ConcurrentMissionKeyNames.length; i++) { + if (rewardInfo.node == goal.ConcurrentNodes[i]) { + levelKeyName = goal.ConcurrentMissionKeyNames[i]; + break; + } + } + } } } @@ -2149,5 +2213,143 @@ const goalMessagesByKey: Record { Sorties: [], LiteSorties: [], ActiveMissions: [], + FlashSales: [], GlobalUpgrades: [], Invasions: [], VoidTraders: [], @@ -1401,7 +1402,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => { EndlessXpChoices: [], KnownCalendarSeasons: [], ...staticWorldState, - SyndicateMissions: [...staticWorldState.SyndicateMissions] + SyndicateMissions: [...staticWorldState.SyndicateMissions], + InGameMarket: { + LandingPage: { + Categories: staticWorldState.InGameMarket.LandingPage.Categories.map(c => ({ + ...c, + Items: [...c.Items] + })) + } + } }; // Old versions seem to really get hung up on not being able to load these. @@ -1526,7 +1535,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Personal: true, Bounty: true, ClampNodeScores: true, - Node: "EventNode28", + Node: "EventNode28", // Incompatible with Wolf Hunt (2025) MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB", Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", @@ -1629,6 +1638,634 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }); } + const firstAugustWeekday = new Date(Date.UTC(date.getUTCFullYear(), 7, 1)).getUTCDay(); + const firstAugustWednesdayOffset = (3 - firstAugustWeekday + 7) % 7; + const dogDaysStart = Date.UTC(date.getUTCFullYear(), 7, 1 + firstAugustWednesdayOffset, 15); + + const firstSeptemberWeekday = new Date(Date.UTC(date.getUTCFullYear(), 8, 1)).getUTCDay(); + const firstSeptemberWednesdayOffset = (3 - firstSeptemberWeekday + 7) % 7; + const dogDaysEnd = Date.UTC(date.getUTCFullYear(), 8, 1 + firstSeptemberWednesdayOffset, 15); + + const isDogDaysActive = timeMs >= dogDaysStart && timeMs < dogDaysEnd; + if (config.worldState?.dogDaysOverride ?? isDogDaysActive) { + const activationTimeStamp = config.worldState?.dogDaysOverride ? "1699372800000" : dogDaysStart.toString(); + const expiryTimeStamp = config.worldState?.dogDaysOverride ? "2000000000000" : dogDaysEnd.toString(); + const rewards = [ + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Upgrades/Skins/Weapons/Redeemer/RedeemerRelayWaterSkin"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileHydroidRelay"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/RelayHydroidBobbleHead"] + }, + { + items: [ + "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + ], + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DogDays2023ASigil"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 25 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyBeachKavat"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 50 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyRucksackKubrow"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 75 + } + ] + }, + { + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneBeachcomber"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 100 + } + ] + } + ], + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageDogDays2024Glyph"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 25 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/DogDays2024Poster"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 50 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/DogDaysKubrowBadgeItem"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 75 + } + ] + }, + { + items: ["/Lotus/StoreItems/Types/Items/ShipDecos/DogDays2024LisetPropCleaningDroneBeachcomber"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 100 + } + ] + } + ], + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysHydroidGlyph"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 25 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysLokiGlyph"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 50 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysNovaGlyph"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 75 + } + ] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageDogDaysValkyrGlyph"], + countedItems: [ + { + ItemType: "/Lotus/Types/Items/MiscItems/WaterFightBucks", + ItemCount: 100 + } + ] + } + ] + ]; + + const year = config.worldState?.dogDaysRewardsOverride ?? 3; + + worldState.Goals.push({ + _id: { + $oid: ((dogDaysStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "c57487c3768936df" + }, + Activation: { $date: { $numberLong: activationTimeStamp } }, + Expiry: { $date: { $numberLong: expiryTimeStamp } }, + Count: 0, + Goal: 100, + InterimGoals: [25, 50], + BonusGoal: 200, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode25", // Incompatible with Hallowed Flame, Hallowed Nightmares + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/TacAlertKeyWaterFightB", + "/Lotus/Types/Keys/TacAlertKeyWaterFightC", + "/Lotus/Types/Keys/TacAlertKeyWaterFightD" + ], + ConcurrentNodeReqs: [25, 50, 100], + ConcurrentNodes: ["EventNode24", "EventNode34", "EventNode35"], // Incompatible with Hallowed Flame, Hallowed Nightmares + MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyWaterFightA", + Faction: "FC_CORPUS", + Desc: "/Lotus/Language/Alerts/TacAlertWaterFight", + Icon: "/Lotus/Interface/Icons/StoreIcons/Emblems/SplashEventIcon.png", + Tag: "WaterFight", + InterimRewards: rewards[year].slice(0, 2), + Reward: rewards[year][2], + BonusReward: rewards[year][3], + ScoreVar: "Team1Score", + 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 + } + ]; + + worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...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?.wolfHunt) { + worldState.Goals.push({ + _id: { + $oid: "67ed7672798d6466172e3b9d" + }, + Activation: { + $date: { + $numberLong: "1743616800000" + } + }, + Expiry: { + $date: { + $numberLong: "2000000000000" + } + }, + Count: 0, + Goal: 3, + InterimGoals: [1, 2], + BonusGoal: 4, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode29", + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/WolfTacAlertReduxB", + "/Lotus/Types/Keys/WolfTacAlertReduxC", + "/Lotus/Types/Keys/WolfTacAlertReduxD" + ], + ConcurrentNodeReqs: [1, 2, 3], + ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls + MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA", + Faction: "FC_GRINEER", + Desc: "/Lotus/Language/Alerts/WolfAlert", + Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png", + Tag: "WolfHuntRedux", + InterimRewards: [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"] + } + ], + Reward: { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"] + }, + BonusReward: { + credits: 50000, + items: [ + "/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint", + "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + }); + } + + if (config.worldState?.hallowedFlame) { + worldState.Goals.push( + { + _id: { $oid: "5db305403d34b5158873519a" }, + Activation: { $date: { $numberLong: "1699372800000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 3, + InterimGoals: [1, 2], + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode24", // Incompatible with Hallowed Nightmares, Dog Days + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/LanternEndlessEventKeyB", + "/Lotus/Types/Keys/LanternEndlessEventKeyC" + ], + ConcurrentNodeReqs: [1, 2], + ConcurrentNodes: ["EventNode25", "EventNode34"], // Incompatible with Hallowed Nightmares, Dog Days + MissionKeyName: "/Lotus/Types/Keys/LanternEndlessEventKeyA", + Faction: "FC_INFESTATION", + Desc: "/Lotus/Language/Events/TacAlertHalloweenLantern", + Icon: "/Lotus/Interface/Icons/JackOLanternColour.png", + Tag: "Halloween19", + InterimRewards: [ + { items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"] }, + { items: ["/Lotus/StoreItems/Types/Items/MiscItems/Forma"] } + ], + Reward: { + items: ["/Lotus/StoreItems/Types/Items/MiscItems/FormaAura"] + } + }, + { + _id: { $oid: "5db3054a3d34b5158873519c" }, + Activation: { $date: { $numberLong: "1699372800000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 900, + Success: 0, + Personal: true, + Bounty: true, + Best: true, + ClampNodeScores: true, + Node: "EventNode35", + MissionKeyName: "/Lotus/Types/Keys/LanternEndlessEventKeyD", + Faction: "FC_INFESTATION", + Desc: "/Lotus/Language/Events/TacAlertHalloweenLanternEndless", + Icon: "/Lotus/Interface/Icons/JackOLanternColour.png", + Tag: "Halloween19Endless", + PrereqGoalTags: ["Halloween19"], + Reward: { + items: [ + "/Lotus/StoreItems/Upgrades/Skins/Effects/BatsEphemera", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + }, + ScoreVar: "EndlessMissionTimeElapsed", + ScoreMaxTag: "Halloween19ScoreMax" + } + ); + } + + if (config.worldState?.hallowedNightmares) { + const rewards = [ + // 2018 + [ + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"] + }, + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Halloween/HalloweenDread"] + }, + { + items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"] + } + ], + // 2016 + [ + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] + }, + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"] + }, + { + items: [ + "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + ], + // 2015 + [ + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] + }, + { + items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] + } + ] + ]; + const year = config.worldState.hallowedNightmaresRewardsOverride ?? 0; + + worldState.Goals.push({ + _id: { $oid: "5bc9e8f7972d7d184c8398c9" }, + Activation: { $date: { $numberLong: "1539972000000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + InterimGoals: [1], + Goal: 2, + Success: 0, + Personal: true, + Bounty: true, + Tag: "Halloween", + Faction: "FC_INFESTATION", + Desc: "/Lotus/Language/G1Quests/TacAlertHalloweenTitle", + ToolTip: "/Lotus/Language/G1Quests/TacAlertHalloweenToolTip", + Icon: "/Lotus/Interface/Icons/JackOLanternColour.png", + ClampNodeScores: true, + Node: "EventNode2", + MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyHalloween", + ConcurrentMissionKeyNames: ["/Lotus/Types/Keys/TacAlertKeyHalloweenBonus"], + ConcurrentNodeReqs: [1], + ConcurrentNodes: ["EventNode24"], // Incompatible with Hallowed Flame, Dog Days + InterimRewards: [rewards[year][0]], + Reward: rewards[year][1] + }); + if (year != 2) { + worldState.Goals.push({ + _id: { $oid: "5bca18b1e12d9e14a0b6ad27" }, + Activation: { $date: { $numberLong: "1539972000000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 666, + Success: 0, + Personal: true, + Bounty: true, + Best: true, + Tag: "Halloween", + PrereqGoalTags: ["Halloween"], + Faction: "FC_INFESTATION", + Desc: "Hallowed Nightmares - Time Attack", + ToolTip: "/Lotus/Language/G1Quests/TacAlertHalloweenToolTip", + Icon: "/Lotus/Interface/Icons/JackOLanternColour.png", + ClampNodeScores: true, + Node: "EventNode25", // Incompatible with Hallowed Flame, Dog Days + MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack", + ScoreVar: "TimeAttackScore", + ScoreMaxTag: "Halloween16", + Reward: rewards[year][2] + }); + } + } + + if (config.worldState?.proxyRebellion) { + const rewards = [ + // 2019 + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod"] + }, + { + credits: 50000, + items: ["/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle"] + }, + { + items: [ + "/Lotus/StoreItems/Upgrades/Skins/Scarves/HornSkullScarf", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + ], + // 2018 + [ + { + credits: 50000, + items: ["/Lotus/StoreItems/Upgrades/Mods/FusionBundles/NightwatchFusionBundle"] + }, + { + credits: 50000, + items: ["/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker"] + }, + { + credits: 50000, + items: ["/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle"] + }, + { + items: [ + "/Lotus/StoreItems/Upgrades/Skins/Sigils/EnergySigilA", + "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" + ] + } + ] + ]; + const year = config.worldState.proxyRebellionRewardsOverride ?? 0; + + worldState.Goals.push({ + _id: { $oid: "5b5743ac972d7d3ed0517b0d" }, + Activation: { $date: { $numberLong: "1532714400000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + Goal: 3, + InterimGoals: [1, 2], + BonusGoal: 4, + Success: 0, + Personal: true, + Bounty: true, + ClampNodeScores: true, + Node: "EventNode18", + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo", + "/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree", + "/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour" + ], + ConcurrentNodeReqs: [1, 2, 3], + ConcurrentNodes: ["EventNode7", "EventNode4", "EventNode17"], + MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne", + Faction: "FC_CORPUS", + Desc: "/Lotus/Language/Alerts/TacAlertProxyRebellion", + Icon: "/Lotus/Materials/Emblems/BountyBadge_e.png", + Tag: "ProxyRebellion", + InterimRewards: rewards[year].slice(0, 2), + Reward: rewards[year][2], + BonusReward: rewards[year][3] + }); + } + + if (config.worldState?.longShadow) { + worldState.Goals.push({ + _id: { $oid: "5bc9e8f7272d5d184c8398c9" }, + Activation: { $date: { $numberLong: "1539972000000" } }, + Expiry: { $date: { $numberLong: "2000000000000" } }, + Count: 0, + InterimGoals: [1, 2], + Goal: 3, + BonusGoal: 4, + Success: 0, + Personal: true, + Bounty: true, + Tag: "NightwatchTacAlert", + Faction: "FC_GRINEER", + Desc: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertTitle", + Icon: "/Lotus/Materials/Emblems/BountyBadge_e.png", + ClampNodeScores: true, + Node: "EventNode9", + MissionKeyName: "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy", + ConcurrentMissionKeyNames: [ + "/Lotus/Types/Keys/TacAlertKeyProjectNightwatch", + "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard", + "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus" + ], + ConcurrentNodeReqs: [1, 2, 3], + ConcurrentNodes: ["SolNode136", "EventNode3", "EventNode0"], + InterimRewards: [ + { + credits: 50000, + countedItems: [ + { ItemType: "/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle", ItemCount: 10 } // Not sure about that + ] + }, + { + items: ["/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker"] + } + ], + Reward: { + items: ["/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle"] + }, + BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] } + }); + } + // Nightwave Challenges const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel); if (nightwaveSyndicateTag) { diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index a1b7feca..b390f40d 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -5,12 +5,14 @@ export interface IWorldState { Version: number; // for goals BuildLabel: string; Time: number; + InGameMarket: IInGameMarket; Goals: IGoal[]; Alerts: []; Sorties: ISortie[]; LiteSorties: ILiteSortie[]; SyndicateMissions: ISyndicateMissionInfo[]; ActiveMissions: IFissure[]; + FlashSales: IFlashSale[]; GlobalUpgrades: IGlobalUpgrade[]; Invasions: IInvasion[]; NodeOverrides: INodeOverride[]; @@ -39,10 +41,13 @@ export interface IGoal { Expiry: IMongoDate; Count?: number; Goal?: number; + InterimGoals?: number[]; + BonusGoal?: number; HealthPct?: number; Success?: number; Personal?: boolean; - Bounty?: boolean; + Best?: boolean; + Bounty?: boolean; // Tactical Alert Faction?: string; ClampNodeScores?: boolean; Desc: string; @@ -51,18 +56,28 @@ export interface IGoal { InstructionalItem?: string; Icon: string; Tag: string; + PrereqGoalTags?: string[]; Node?: string; VictimNode?: string; + ConcurrentMissionKeyNames?: string[]; + ConcurrentNodeReqs?: number[]; + ConcurrentNodes?: string[]; RegionIdx?: number; Regions?: number[]; MissionKeyName?: string; Reward?: IMissionReward; + InterimRewards?: IMissionReward[]; + BonusReward?: IMissionReward; JobAffiliationTag?: string; Jobs?: ISyndicateJob[]; PreviousJobs?: ISyndicateJob[]; JobCurrentVersion?: IOid; JobPreviousVersion?: IOid; + + ScoreVar?: string; + ScoreMaxTag?: string; + NightLevel?: string; } export interface ISyndicateJob { @@ -321,6 +336,37 @@ export type TCircuitGameMode = | "Assassination" | "Alchemy"; +export interface IFlashSale { + TypeName: string; + ShowInMarket: boolean; + HideFromMarket: boolean; + SupporterPack: boolean; + Discount: number; + BogoBuy: number; + BogoGet: number; + PremiumOverride: number; + RegularOverride: number; + ProductExpiryOverride?: IMongoDate; + StartDate: IMongoDate; + EndDate: IMongoDate; +} + +export interface IInGameMarket { + LandingPage: ILandingPage; +} + +export interface ILandingPage { + Categories: IGameMarketCategory[]; +} + +export interface IGameMarketCategory { + CategoryName: string; + Name: string; + Icon: string; + AddToMenu?: boolean; + Items?: string[]; +} + export interface ITmp { cavabegin: string; PurchasePlatformLockEnabled: boolean; // Seems unused diff --git a/static/webui/index.html b/static/webui/index.html index 99193f78..e9a5e2a2 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -937,8 +937,57 @@ +
+ + + +
+
+ + +
+
+ + + +
+
+
+ + + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + + +
+
+ + +
+