forked from OpenWF/SpaceNinjaServer
		
	feat: sortie rotation (#1453)
Reviewed-on: OpenWF/SpaceNinjaServer#1453 Reviewed-by: Sainan <sainan@calamity.inc> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									94993a16aa
								
							
						
					
					
						commit
						919f12b8f9
					
				@ -10,8 +10,16 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
				
			|||||||
import { config } from "@/src/services/configService";
 | 
					import { config } from "@/src/services/configService";
 | 
				
			||||||
import { CRng } from "@/src/services/rngService";
 | 
					import { CRng } from "@/src/services/rngService";
 | 
				
			||||||
import { ExportNightwave, ExportRegions } from "warframe-public-export-plus";
 | 
					import { ExportNightwave, ExportRegions } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
 | 
					    EPOCH,
 | 
				
			||||||
 | 
					    getSortieTime,
 | 
				
			||||||
 | 
					    missionTags,
 | 
				
			||||||
 | 
					    sortieBosses,
 | 
				
			||||||
 | 
					    sortieBossNode,
 | 
				
			||||||
 | 
					    sortieBossToFaction,
 | 
				
			||||||
 | 
					    sortieFactionToFactionIndexes,
 | 
				
			||||||
 | 
					    sortieFactionToSystemIndexes
 | 
				
			||||||
 | 
					} from "@/src/helpers/worlstateHelper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const worldStateController: RequestHandler = (req, res) => {
 | 
					export const worldStateController: RequestHandler = (req, res) => {
 | 
				
			||||||
    const day = Math.trunc((Date.now() - EPOCH) / 86400000);
 | 
					    const day = Math.trunc((Date.now() - EPOCH) / 86400000);
 | 
				
			||||||
@ -27,6 +35,7 @@ export const worldStateController: RequestHandler = (req, res) => {
 | 
				
			|||||||
        Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
 | 
					        Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
 | 
				
			||||||
        Goals: [],
 | 
					        Goals: [],
 | 
				
			||||||
        GlobalUpgrades: [],
 | 
					        GlobalUpgrades: [],
 | 
				
			||||||
 | 
					        Sorties: [],
 | 
				
			||||||
        LiteSorties: [],
 | 
					        LiteSorties: [],
 | 
				
			||||||
        EndlessXpChoices: [],
 | 
					        EndlessXpChoices: [],
 | 
				
			||||||
        SeasonInfo: {
 | 
					        SeasonInfo: {
 | 
				
			||||||
@ -154,6 +163,142 @@ export const worldStateController: RequestHandler = (req, res) => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sortie cycling every day
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let genDay;
 | 
				
			||||||
 | 
					        let dayStart;
 | 
				
			||||||
 | 
					        let dayEnd;
 | 
				
			||||||
 | 
					        const sortieRolloverToday = getSortieTime(day);
 | 
				
			||||||
 | 
					        if (Date.now() < sortieRolloverToday) {
 | 
				
			||||||
 | 
					            // Early in the day, generate sortie for `day - 1`, expiring at `sortieRolloverToday`.
 | 
				
			||||||
 | 
					            genDay = day - 1;
 | 
				
			||||||
 | 
					            dayStart = getSortieTime(genDay);
 | 
				
			||||||
 | 
					            dayEnd = sortieRolloverToday;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Late in the day, generate sortie for `day`, expiring at `getSortieTime(day + 1)`.
 | 
				
			||||||
 | 
					            genDay = day;
 | 
				
			||||||
 | 
					            dayStart = sortieRolloverToday;
 | 
				
			||||||
 | 
					            dayEnd = getSortieTime(day + 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const rng = new CRng(genDay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const boss = rng.randomElement(sortieBosses);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const modifiers = [
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_LOW_ENERGY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_IMPACT",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_SLASH",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_PUNCTURE",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_EXIMUS",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_MAGNETIC",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_CORROSIVE",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_VIRAL",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_ELECTRICITY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_RADIATION",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_GAS",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_FIRE",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_EXPLOSION",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_FREEZE",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_TOXIN",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_POISON",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_RADIATION",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_MAGNETIC",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_ICE",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_HAZARD_COLD",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_SECONDARY_ONLY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_SHOTGUN_ONLY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_SNIPER_ONLY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_RIFLE_ONLY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_MELEE_ONLY",
 | 
				
			||||||
 | 
					            "SORTIE_MODIFIER_BOW_ONLY"
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
 | 
				
			||||||
 | 
					        if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const nodes: string[] = [];
 | 
				
			||||||
 | 
					        const availableMissionIndexes: number[] = [];
 | 
				
			||||||
 | 
					        for (const [key, value] of Object.entries(ExportRegions)) {
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
 | 
				
			||||||
 | 
					                sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
 | 
				
			||||||
 | 
					                value.name.indexOf("Archwing") == -1 &&
 | 
				
			||||||
 | 
					                value.missionIndex != 0 && // Exclude MT_ASSASSINATION
 | 
				
			||||||
 | 
					                value.missionIndex != 5 && // Exclude MT_CAPTURE
 | 
				
			||||||
 | 
					                value.missionIndex != 21 && // Exclude MT_PURIFY
 | 
				
			||||||
 | 
					                value.missionIndex != 23 && // Exclude MT_JUNCTION
 | 
				
			||||||
 | 
					                value.missionIndex <= 28
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                if (!availableMissionIndexes.includes(value.missionIndex)) {
 | 
				
			||||||
 | 
					                    availableMissionIndexes.push(value.missionIndex);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                nodes.push(key);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
 | 
				
			||||||
 | 
					        const missionTypes = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < 3; i++) {
 | 
				
			||||||
 | 
					            const randomIndex = rng.randomInt(0, nodes.length - 1);
 | 
				
			||||||
 | 
					            const node = nodes[randomIndex];
 | 
				
			||||||
 | 
					            let missionIndex = ExportRegions[node].missionIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                !["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
 | 
				
			||||||
 | 
					                missionIndex != 28 &&
 | 
				
			||||||
 | 
					                rng.randomInt(0, 2) == 2
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                missionIndex = rng.randomElement(availableMissionIndexes);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (i == 2 && rng.randomInt(0, 2) == 2) {
 | 
				
			||||||
 | 
					                const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
 | 
				
			||||||
 | 
					                const modifierType = rng.randomElement(filteredModifiers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (boss == "SORTIE_BOSS_PHORID") {
 | 
				
			||||||
 | 
					                    selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
 | 
				
			||||||
 | 
					                    nodes.splice(randomIndex, 1);
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                } else if (sortieBossNode[boss]) {
 | 
				
			||||||
 | 
					                    selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const missionType = missionTags[missionIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (missionTypes.has(missionType)) {
 | 
				
			||||||
 | 
					                i--;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const filteredModifiers =
 | 
				
			||||||
 | 
					                missionType === "MT_TERRITORY"
 | 
				
			||||||
 | 
					                    ? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
 | 
				
			||||||
 | 
					                    : modifiers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const modifierType = rng.randomElement(filteredModifiers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            selectedNodes.push({ missionType, modifierType, node });
 | 
				
			||||||
 | 
					            nodes.splice(randomIndex, 1);
 | 
				
			||||||
 | 
					            missionTypes.add(missionType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        worldState.Sorties.push({
 | 
				
			||||||
 | 
					            _id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
				
			||||||
 | 
					            Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
				
			||||||
 | 
					            Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
 | 
				
			||||||
 | 
					            Seed: genDay,
 | 
				
			||||||
 | 
					            Boss: boss,
 | 
				
			||||||
 | 
					            Variants: selectedNodes
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Archon Hunt cycling every week
 | 
					    // Archon Hunt cycling every week
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
 | 
					        const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
 | 
				
			||||||
@ -298,6 +443,7 @@ interface IWorldState {
 | 
				
			|||||||
    Goals: IGoal[];
 | 
					    Goals: IGoal[];
 | 
				
			||||||
    SyndicateMissions: ISyndicateMission[];
 | 
					    SyndicateMissions: ISyndicateMission[];
 | 
				
			||||||
    GlobalUpgrades: IGlobalUpgrade[];
 | 
					    GlobalUpgrades: IGlobalUpgrade[];
 | 
				
			||||||
 | 
					    Sorties: ISortie[];
 | 
				
			||||||
    LiteSorties: ILiteSortie[];
 | 
					    LiteSorties: ILiteSortie[];
 | 
				
			||||||
    NodeOverrides: INodeOverride[];
 | 
					    NodeOverrides: INodeOverride[];
 | 
				
			||||||
    EndlessXpChoices: IEndlessXpChoice[];
 | 
					    EndlessXpChoices: IEndlessXpChoice[];
 | 
				
			||||||
@ -361,6 +507,20 @@ interface INodeOverride {
 | 
				
			|||||||
    CustomNpcEncounters?: string;
 | 
					    CustomNpcEncounters?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISortie {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards";
 | 
				
			||||||
 | 
					    Seed: number;
 | 
				
			||||||
 | 
					    Boss: string;
 | 
				
			||||||
 | 
					    Variants: {
 | 
				
			||||||
 | 
					        missionType: string;
 | 
				
			||||||
 | 
					        modifierType: string;
 | 
				
			||||||
 | 
					        node: string;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ILiteSortie {
 | 
					interface ILiteSortie {
 | 
				
			||||||
    _id: IOid;
 | 
					    _id: IOid;
 | 
				
			||||||
    Activation: IMongoDate;
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										130
									
								
								src/helpers/worlstateHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/helpers/worlstateHelper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					export const missionTags = [
 | 
				
			||||||
 | 
					    "MT_ASSASSINATION",
 | 
				
			||||||
 | 
					    "MT_EXTERMINATION",
 | 
				
			||||||
 | 
					    "MT_SURVIVAL",
 | 
				
			||||||
 | 
					    "MT_RESCUE",
 | 
				
			||||||
 | 
					    "MT_SABOTAGE",
 | 
				
			||||||
 | 
					    "MT_CAPTURE",
 | 
				
			||||||
 | 
					    "MT_COUNTER_INTEL",
 | 
				
			||||||
 | 
					    "MT_INTEL",
 | 
				
			||||||
 | 
					    "MT_DEFENSE",
 | 
				
			||||||
 | 
					    "MT_MOBILE_DEFENSE",
 | 
				
			||||||
 | 
					    "MT_PVP",
 | 
				
			||||||
 | 
					    "MT_MASTERY",
 | 
				
			||||||
 | 
					    "MT_RECOVERY",
 | 
				
			||||||
 | 
					    "MT_TERRITORY",
 | 
				
			||||||
 | 
					    "MT_RETRIEVAL",
 | 
				
			||||||
 | 
					    "MT_HIVE",
 | 
				
			||||||
 | 
					    "MT_SALVAGE",
 | 
				
			||||||
 | 
					    "MT_EXCAVATE",
 | 
				
			||||||
 | 
					    "MT_RAID",
 | 
				
			||||||
 | 
					    "MT_PURGE",
 | 
				
			||||||
 | 
					    "MT_GENERIC",
 | 
				
			||||||
 | 
					    "MT_PURIFY",
 | 
				
			||||||
 | 
					    "MT_ARENA",
 | 
				
			||||||
 | 
					    "MT_JUNCTION",
 | 
				
			||||||
 | 
					    "MT_PURSUIT",
 | 
				
			||||||
 | 
					    "MT_RACE",
 | 
				
			||||||
 | 
					    "MT_ASSAULT",
 | 
				
			||||||
 | 
					    "MT_EVACUATION",
 | 
				
			||||||
 | 
					    "MT_LANDSCAPE",
 | 
				
			||||||
 | 
					    "MT_RESOURCE_THEFT",
 | 
				
			||||||
 | 
					    "MT_ENDLESS_EXTERMINATION",
 | 
				
			||||||
 | 
					    "MT_ENDLESS_DUVIRI",
 | 
				
			||||||
 | 
					    "MT_RAILJACK",
 | 
				
			||||||
 | 
					    "MT_ARTIFACT",
 | 
				
			||||||
 | 
					    "MT_CORRUPTION",
 | 
				
			||||||
 | 
					    "MT_VOID_CASCADE",
 | 
				
			||||||
 | 
					    "MT_ARMAGEDDON",
 | 
				
			||||||
 | 
					    "MT_VAULTS",
 | 
				
			||||||
 | 
					    "MT_ALCHEMY",
 | 
				
			||||||
 | 
					    "MT_ASCENSION",
 | 
				
			||||||
 | 
					    "MT_ENDLESS_CAPTURE",
 | 
				
			||||||
 | 
					    "MT_OFFERING",
 | 
				
			||||||
 | 
					    "MT_PVPVE"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortieBosses = [
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_HYENA",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_KELA",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_VOR",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_RUK",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_HEK",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_KRIL",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_TYL",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_JACKAL",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_ALAD",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_AMBULAS",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_NEF",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_RAPTOR",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_PHORID",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_LEPHANTIS",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_INFALAD",
 | 
				
			||||||
 | 
					    "SORTIE_BOSS_CORRUPTED_VOR"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortieBossToFaction: Record<string, string> = {
 | 
				
			||||||
 | 
					    SORTIE_BOSS_HYENA: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_KELA: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_VOR: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_RUK: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_HEK: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_KRIL: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_TYL: "FC_GRINEER",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_JACKAL: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_ALAD: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_AMBULAS: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_NEF: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_RAPTOR: "FC_CORPUS",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_PHORID: "FC_INFESTATION",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_INFALAD: "FC_INFESTATION",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_CORRUPTED_VOR: "FC_CORRUPTED"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortieFactionToSystemIndexes: Record<string, number[]> = {
 | 
				
			||||||
 | 
					    FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18],
 | 
				
			||||||
 | 
					    FC_CORPUS: [1, 4, 7, 8, 12, 15],
 | 
				
			||||||
 | 
					    FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15],
 | 
				
			||||||
 | 
					    FC_CORRUPTED: [14]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortieFactionToFactionIndexes: Record<string, number[]> = {
 | 
				
			||||||
 | 
					    FC_GRINEER: [0],
 | 
				
			||||||
 | 
					    FC_CORPUS: [1],
 | 
				
			||||||
 | 
					    FC_INFESTATION: [0, 1, 2],
 | 
				
			||||||
 | 
					    FC_CORRUPTED: [3]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortieBossNode: Record<string, string> = {
 | 
				
			||||||
 | 
					    SORTIE_BOSS_HYENA: "SolNode127",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_KELA: "SolNode193",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_VOR: "SolNode108",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_RUK: "SolNode32",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_HEK: "SolNode24",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_KRIL: "SolNode99",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_TYL: "SolNode105",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_JACKAL: "SolNode104",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_ALAD: "SolNode53",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_AMBULAS: "SolNode51",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_NEF: "SettlementNode20",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_RAPTOR: "SolNode210",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_LEPHANTIS: "SolNode712",
 | 
				
			||||||
 | 
					    SORTIE_BOSS_INFALAD: "SolNode705"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getSortieTime = (day: number): number => {
 | 
				
			||||||
 | 
					    const dayStart = EPOCH + day * 86400000;
 | 
				
			||||||
 | 
					    const date = new Date(dayStart);
 | 
				
			||||||
 | 
					    date.setUTCHours(12);
 | 
				
			||||||
 | 
					    const isDst = new Intl.DateTimeFormat("en-US", {
 | 
				
			||||||
 | 
					        timeZone: "America/Toronto",
 | 
				
			||||||
 | 
					        timeZoneName: "short"
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					        .formatToParts(date)
 | 
				
			||||||
 | 
					        .find(part => part.type === "timeZoneName")!
 | 
				
			||||||
 | 
					        .value.includes("DT");
 | 
				
			||||||
 | 
					    return dayStart + (isDst ? 16 : 17) * 3600000;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -62,23 +62,6 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "Sorties": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "_id": { "$oid": "663a4c7d4d932c97c0a3acd7" },
 | 
					 | 
				
			||||||
      "Activation": { "$date": { "$numberLong": "1715097600000" } },
 | 
					 | 
				
			||||||
      "Expiry": { "$date": { "$numberLong": "2000000000000" } },
 | 
					 | 
				
			||||||
      "Reward": "/Lotus/Types/Game/MissionDecks/SortieRewards",
 | 
					 | 
				
			||||||
      "Seed": 24491,
 | 
					 | 
				
			||||||
      "Boss": "SORTIE_BOSS_TYL",
 | 
					 | 
				
			||||||
      "ExtraDrops": [],
 | 
					 | 
				
			||||||
      "Variants": [
 | 
					 | 
				
			||||||
        { "missionType": "MT_TERRITORY", "modifierType": "SORTIE_MODIFIER_ARMOR", "node": "SolNode122", "tileset": "GrineerOceanTileset" },
 | 
					 | 
				
			||||||
        { "missionType": "MT_MOBILE_DEFENSE", "modifierType": "SORTIE_MODIFIER_LOW_ENERGY", "node": "SolNode184", "tileset": "GrineerGalleonTileset" },
 | 
					 | 
				
			||||||
        { "missionType": "MT_LANDSCAPE", "modifierType": "SORTIE_MODIFIER_EXIMUS", "node": "SolNode228", "tileset": "EidolonTileset" }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "Twitter": true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "SyndicateMissions": [
 | 
					  "SyndicateMissions": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "_id": { "$oid": "663a4fc5ba6f84724fa48049" },
 | 
					      "_id": { "$oid": "663a4fc5ba6f84724fa48049" },
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user