From 1a0577a4f471702699cf5f9b23c10bc8531db6c9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 19 Apr 2025 09:12:17 +0200 Subject: [PATCH] chore: improve LiteSortie handling at week rollover WorldState now provides the upcoming LiteSortie if relevant and the boss is derived from the sortieId so completing it at rollover should work as expected. --- src/services/missionInventoryUpdateService.ts | 8 +- src/services/worldStateService.ts | 138 ++++++++++-------- src/types/worldStateTypes.ts | 2 +- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index cf0b032a..0b563540 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -53,7 +53,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js import { getInfNodes } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; -import { getWorldState } from "./worldStateService"; +import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; import { config } from "./configService"; const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => { @@ -988,11 +988,7 @@ function getRandomMissionDrops( if (sortieId == "Lite") { sortieId = arr[2]; - // TODO: Some way to get from sortieId to reward to make this faster + more reliable at week rollover. - const boss = getWorldState().LiteSorties[0].Boss as - | "SORTIE_BOSS_AMAR" - | "SORTIE_BOSS_NIRA" - | "SORTIE_BOSS_BOREAL"; + const boss = getLiteSortie(idToWeek(sortieId)).Boss; let crystalType = { SORTIE_BOSS_AMAR: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar", SORTIE_BOSS_NIRA: "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira", diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index ed65a8fc..6083dcb5 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -4,7 +4,14 @@ import { unixTimesInMs } from "@/src/constants/timeConstants"; import { config } from "@/src/services/configService"; import { CRng } from "@/src/services/rngService"; import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus"; -import { ICalendarDay, ICalendarSeason, ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes"; +import { + ICalendarDay, + ICalendarSeason, + ILiteSortie, + ISeasonChallenge, + ISortie, + IWorldState +} from "../types/worldStateTypes"; const sortieBosses = [ "SORTIE_BOSS_HYENA", @@ -941,65 +948,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => { pushSortieIfRelevant(worldState.Sorties, day); // Archon Hunt cycling every week - // TODO: Handle imminent rollover - { - const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3]; - const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3]; - const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth - - const nodes: string[] = []; - for (const [key, value] of Object.entries(ExportRegions)) { - if ( - value.systemIndex === systemIndex && - value.factionIndex !== undefined && - value.factionIndex < 2 && - value.name.indexOf("Archwing") == -1 && - value.missionIndex != 0 // Exclude MT_ASSASSINATION - ) { - nodes.push(key); - } - } - - const rng = new CRng(week); - const firstNodeIndex = rng.randomInt(0, nodes.length - 1); - const firstNode = nodes[firstNodeIndex]; - nodes.splice(firstNodeIndex, 1); - worldState.LiteSorties.push({ - _id: { - $oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c" - }, - Activation: { $date: { $numberLong: weekStart.toString() } }, - Expiry: { $date: { $numberLong: weekEnd.toString() } }, - Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards", - Seed: week, - Boss: boss, - Missions: [ - { - missionType: rng.randomElement([ - "MT_INTEL", - "MT_MOBILE_DEFENSE", - "MT_EXTERMINATION", - "MT_SABOTAGE", - "MT_RESCUE" - ]), - node: firstNode - }, - { - missionType: rng.randomElement([ - "MT_DEFENSE", - "MT_TERRITORY", - "MT_ARTIFACT", - "MT_EXCAVATE", - "MT_SURVIVAL" - ]), - node: rng.randomElement(nodes) - }, - { - missionType: "MT_ASSASSINATION", - node: showdownNode - } - ] - }); + worldState.LiteSorties.push(getLiteSortie(week)); + if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) { + worldState.LiteSorties.push(getLiteSortie(week + 1)); } // Circuit choices cycling every week @@ -1071,3 +1022,70 @@ export const getWorldState = (buildLabel?: string): IWorldState => { return worldState; }; + +export const idToWeek = (id: string): number => { + return (parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800000; +}; + +export const getLiteSortie = (week: number): ILiteSortie => { + const boss = (["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"] as const)[week % 3]; + const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3]; + const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth + + const nodes: string[] = []; + for (const [key, value] of Object.entries(ExportRegions)) { + if ( + value.systemIndex === systemIndex && + value.factionIndex !== undefined && + value.factionIndex < 2 && + value.name.indexOf("Archwing") == -1 && + value.missionIndex != 0 // Exclude MT_ASSASSINATION + ) { + nodes.push(key); + } + } + + const rng = new CRng(week); + const firstNodeIndex = rng.randomInt(0, nodes.length - 1); + const firstNode = nodes[firstNodeIndex]; + nodes.splice(firstNodeIndex, 1); + + const weekStart = EPOCH + week * 604800000; + const weekEnd = weekStart + 604800000; + return { + _id: { + $oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c" + }, + Activation: { $date: { $numberLong: weekStart.toString() } }, + Expiry: { $date: { $numberLong: weekEnd.toString() } }, + Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards", + Seed: week, + Boss: boss, + Missions: [ + { + missionType: rng.randomElement([ + "MT_INTEL", + "MT_MOBILE_DEFENSE", + "MT_EXTERMINATION", + "MT_SABOTAGE", + "MT_RESCUE" + ]), + node: firstNode + }, + { + missionType: rng.randomElement([ + "MT_DEFENSE", + "MT_TERRITORY", + "MT_ARTIFACT", + "MT_EXCAVATE", + "MT_SURVIVAL" + ]), + node: rng.randomElement(nodes) + }, + { + missionType: "MT_ASSASSINATION", + node: showdownNode + } + ] + }; +}; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 278f6e95..67178c73 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -103,7 +103,7 @@ export interface ILiteSortie { Expiry: IMongoDate; Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards"; Seed: number; - Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL" + Boss: "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"; Missions: { missionType: string; node: string;