chore: split worldState stuff into types & service #1548
@ -1,610 +1,6 @@
 | 
				
			|||||||
import { RequestHandler } from "express";
 | 
					import { RequestHandler } from "express";
 | 
				
			||||||
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
 | 
					import { getWorldState } from "@/src/services/worldStateService";
 | 
				
			||||||
import static1999FallDays from "@/static/fixed_responses/worldState/1999_fall_days.json";
 | 
					 | 
				
			||||||
import static1999SpringDays from "@/static/fixed_responses/worldState/1999_spring_days.json";
 | 
					 | 
				
			||||||
import static1999SummerDays from "@/static/fixed_responses/worldState/1999_summer_days.json";
 | 
					 | 
				
			||||||
import static1999WinterDays from "@/static/fixed_responses/worldState/1999_winter_days.json";
 | 
					 | 
				
			||||||
import { buildConfig } from "@/src/services/buildConfigService";
 | 
					 | 
				
			||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
 | 
					 | 
				
			||||||
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 {
 | 
					 | 
				
			||||||
    EPOCH,
 | 
					 | 
				
			||||||
    getSortieTime,
 | 
					 | 
				
			||||||
    sortieBosses,
 | 
					 | 
				
			||||||
    sortieBossNode,
 | 
					 | 
				
			||||||
    sortieBossToFaction,
 | 
					 | 
				
			||||||
    sortieFactionToFactionIndexes,
 | 
					 | 
				
			||||||
    sortieFactionToSystemIndexes
 | 
					 | 
				
			||||||
} from "@/src/helpers/worldStateHelper";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const worldStateController: RequestHandler = (req, res) => {
 | 
					export const worldStateController: RequestHandler = (req, res) => {
 | 
				
			||||||
    const day = Math.trunc((Date.now() - EPOCH) / 86400000);
 | 
					    res.json(getWorldState(req.query.buildLabel as string | undefined));
 | 
				
			||||||
    const week = Math.trunc(day / 7);
 | 
					 | 
				
			||||||
    const weekStart = EPOCH + week * 604800000;
 | 
					 | 
				
			||||||
    const weekEnd = weekStart + 604800000;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const worldState: IWorldState = {
 | 
					 | 
				
			||||||
        BuildLabel:
 | 
					 | 
				
			||||||
            typeof req.query.buildLabel == "string"
 | 
					 | 
				
			||||||
                ? req.query.buildLabel.split(" ").join("+")
 | 
					 | 
				
			||||||
                : buildConfig.buildLabel,
 | 
					 | 
				
			||||||
        Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
 | 
					 | 
				
			||||||
        Goals: [],
 | 
					 | 
				
			||||||
        GlobalUpgrades: [],
 | 
					 | 
				
			||||||
        Sorties: [],
 | 
					 | 
				
			||||||
        LiteSorties: [],
 | 
					 | 
				
			||||||
        EndlessXpChoices: [],
 | 
					 | 
				
			||||||
        SeasonInfo: {
 | 
					 | 
				
			||||||
            Activation: { $date: { $numberLong: "1715796000000" } },
 | 
					 | 
				
			||||||
            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
					 | 
				
			||||||
            AffiliationTag: "RadioLegionIntermission12Syndicate",
 | 
					 | 
				
			||||||
            Season: 14,
 | 
					 | 
				
			||||||
            Phase: 0,
 | 
					 | 
				
			||||||
            Params: "",
 | 
					 | 
				
			||||||
            ActiveChallenges: [
 | 
					 | 
				
			||||||
                getSeasonDailyChallenge(day - 2),
 | 
					 | 
				
			||||||
                getSeasonDailyChallenge(day - 1),
 | 
					 | 
				
			||||||
                getSeasonDailyChallenge(day - 0),
 | 
					 | 
				
			||||||
                getSeasonWeeklyChallenge(week, 0),
 | 
					 | 
				
			||||||
                getSeasonWeeklyChallenge(week, 1),
 | 
					 | 
				
			||||||
                getSeasonWeeklyHardChallenge(week, 2),
 | 
					 | 
				
			||||||
                getSeasonWeeklyHardChallenge(week, 3),
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
					 | 
				
			||||||
                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
					 | 
				
			||||||
                    Challenge:
 | 
					 | 
				
			||||||
                        "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
					 | 
				
			||||||
                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
					 | 
				
			||||||
                    Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
					 | 
				
			||||||
                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
					 | 
				
			||||||
                    Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        ...staticWorldState
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.worldState?.starDays) {
 | 
					 | 
				
			||||||
        worldState.Goals.push({
 | 
					 | 
				
			||||||
            _id: { $oid: "67a4dcce2a198564d62e1647" },
 | 
					 | 
				
			||||||
            Activation: { $date: { $numberLong: "1738868400000" } },
 | 
					 | 
				
			||||||
            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
					 | 
				
			||||||
            Count: 0,
 | 
					 | 
				
			||||||
            Goal: 0,
 | 
					 | 
				
			||||||
            Success: 0,
 | 
					 | 
				
			||||||
            Personal: true,
 | 
					 | 
				
			||||||
            Desc: "/Lotus/Language/Events/ValentinesFortunaName",
 | 
					 | 
				
			||||||
            ToolTip: "/Lotus/Language/Events/ValentinesFortunaName",
 | 
					 | 
				
			||||||
            Icon: "/Lotus/Interface/Icons/WorldStatePanel/ValentinesEventIcon.png",
 | 
					 | 
				
			||||||
            Tag: "FortunaValentines",
 | 
					 | 
				
			||||||
            Node: "SolarisUnitedHub1"
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Elite Sanctuary Onslaught cycling every week
 | 
					 | 
				
			||||||
    worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
 | 
					 | 
				
			||||||
    const bountyCycle = Math.trunc(Date.now() / 9000000);
 | 
					 | 
				
			||||||
    const bountyCycleStart = bountyCycle * 9000000;
 | 
					 | 
				
			||||||
    const bountyCycleEnd = bountyCycleStart + 9000000;
 | 
					 | 
				
			||||||
    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "ZarimanSyndicate")] = {
 | 
					 | 
				
			||||||
        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" },
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
 | 
					 | 
				
			||||||
        Tag: "ZarimanSyndicate",
 | 
					 | 
				
			||||||
        Seed: bountyCycle,
 | 
					 | 
				
			||||||
        Nodes: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "EntratiLabSyndicate")] = {
 | 
					 | 
				
			||||||
        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" },
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
 | 
					 | 
				
			||||||
        Tag: "EntratiLabSyndicate",
 | 
					 | 
				
			||||||
        Seed: bountyCycle,
 | 
					 | 
				
			||||||
        Nodes: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "HexSyndicate")] = {
 | 
					 | 
				
			||||||
        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" },
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
 | 
					 | 
				
			||||||
        Tag: "HexSyndicate",
 | 
					 | 
				
			||||||
        Seed: bountyCycle,
 | 
					 | 
				
			||||||
        Nodes: []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.worldState?.creditBoost) {
 | 
					 | 
				
			||||||
        worldState.GlobalUpgrades.push({
 | 
					 | 
				
			||||||
            _id: { $oid: "5b23106f283a555109666672" },
 | 
					 | 
				
			||||||
            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
					 | 
				
			||||||
            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
					 | 
				
			||||||
            UpgradeType: "GAMEPLAY_MONEY_REWARD_AMOUNT",
 | 
					 | 
				
			||||||
            OperationType: "MULTIPLY",
 | 
					 | 
				
			||||||
            Value: 2,
 | 
					 | 
				
			||||||
            LocalizeTag: "",
 | 
					 | 
				
			||||||
            LocalizeDescTag: ""
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (config.worldState?.affinityBoost) {
 | 
					 | 
				
			||||||
        worldState.GlobalUpgrades.push({
 | 
					 | 
				
			||||||
            _id: { $oid: "5b23106f283a555109666673" },
 | 
					 | 
				
			||||||
            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
					 | 
				
			||||||
            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
					 | 
				
			||||||
            UpgradeType: "GAMEPLAY_KILL_XP_AMOUNT",
 | 
					 | 
				
			||||||
            OperationType: "MULTIPLY",
 | 
					 | 
				
			||||||
            Value: 2,
 | 
					 | 
				
			||||||
            LocalizeTag: "",
 | 
					 | 
				
			||||||
            LocalizeDescTag: ""
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (config.worldState?.resourceBoost) {
 | 
					 | 
				
			||||||
        worldState.GlobalUpgrades.push({
 | 
					 | 
				
			||||||
            _id: { $oid: "5b23106f283a555109666674" },
 | 
					 | 
				
			||||||
            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
					 | 
				
			||||||
            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
					 | 
				
			||||||
            UpgradeType: "GAMEPLAY_PICKUP_AMOUNT",
 | 
					 | 
				
			||||||
            OperationType: "MULTIPLY",
 | 
					 | 
				
			||||||
            Value: 2,
 | 
					 | 
				
			||||||
            LocalizeTag: "",
 | 
					 | 
				
			||||||
            LocalizeDescTag: ""
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 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 = eMissionType[missionIndex].tag;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            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
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Circuit choices cycling every week
 | 
					 | 
				
			||||||
    worldState.EndlessXpChoices.push({
 | 
					 | 
				
			||||||
        Category: "EXC_NORMAL",
 | 
					 | 
				
			||||||
        Choices: [
 | 
					 | 
				
			||||||
            ["Nidus", "Octavia", "Harrow"],
 | 
					 | 
				
			||||||
            ["Gara", "Khora", "Revenant"],
 | 
					 | 
				
			||||||
            ["Garuda", "Baruuk", "Hildryn"],
 | 
					 | 
				
			||||||
            ["Excalibur", "Trinity", "Ember"],
 | 
					 | 
				
			||||||
            ["Loki", "Mag", "Rhino"],
 | 
					 | 
				
			||||||
            ["Ash", "Frost", "Nyx"],
 | 
					 | 
				
			||||||
            ["Saryn", "Vauban", "Nova"],
 | 
					 | 
				
			||||||
            ["Nekros", "Valkyr", "Oberon"],
 | 
					 | 
				
			||||||
            ["Hydroid", "Mirage", "Limbo"],
 | 
					 | 
				
			||||||
            ["Mesa", "Chroma", "Atlas"],
 | 
					 | 
				
			||||||
            ["Ivara", "Inaros", "Titania"]
 | 
					 | 
				
			||||||
        ][week % 12]
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    worldState.EndlessXpChoices.push({
 | 
					 | 
				
			||||||
        Category: "EXC_HARD",
 | 
					 | 
				
			||||||
        Choices: [
 | 
					 | 
				
			||||||
            ["Boar", "Gammacor", "Angstrum", "Gorgon", "Anku"],
 | 
					 | 
				
			||||||
            ["Bo", "Latron", "Furis", "Furax", "Strun"],
 | 
					 | 
				
			||||||
            ["Lex", "Magistar", "Boltor", "Bronco", "CeramicDagger"],
 | 
					 | 
				
			||||||
            ["Torid", "DualToxocyst", "DualIchor", "Miter", "Atomos"],
 | 
					 | 
				
			||||||
            ["AckAndBrunt", "Soma", "Vasto", "NamiSolo", "Burston"],
 | 
					 | 
				
			||||||
            ["Zylok", "Sibear", "Dread", "Despair", "Hate"],
 | 
					 | 
				
			||||||
            ["Dera", "Sybaris", "Cestra", "Sicarus", "Okina"],
 | 
					 | 
				
			||||||
            ["Braton", "Lato", "Skana", "Paris", "Kunai"]
 | 
					 | 
				
			||||||
        ][week % 8]
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // 1999 Calendar Season cycling every week + YearIteration every 4 weeks
 | 
					 | 
				
			||||||
    worldState.KnownCalendarSeasons[0].Activation = { $date: { $numberLong: weekStart.toString() } };
 | 
					 | 
				
			||||||
    worldState.KnownCalendarSeasons[0].Expiry = { $date: { $numberLong: weekEnd.toString() } };
 | 
					 | 
				
			||||||
    worldState.KnownCalendarSeasons[0].Season = ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][week % 4];
 | 
					 | 
				
			||||||
    worldState.KnownCalendarSeasons[0].Days = [
 | 
					 | 
				
			||||||
        static1999WinterDays,
 | 
					 | 
				
			||||||
        static1999SpringDays,
 | 
					 | 
				
			||||||
        static1999SummerDays,
 | 
					 | 
				
			||||||
        static1999FallDays
 | 
					 | 
				
			||||||
    ][week % 4];
 | 
					 | 
				
			||||||
    worldState.KnownCalendarSeasons[0].YearIteration = Math.trunc(week / 4);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Sentient Anomaly cycling every 30 minutes
 | 
					 | 
				
			||||||
    const halfHour = Math.trunc(Date.now() / (unixTimesInMs.hour / 2));
 | 
					 | 
				
			||||||
    const tmp = {
 | 
					 | 
				
			||||||
        cavabegin: "1690761600",
 | 
					 | 
				
			||||||
        PurchasePlatformLockEnabled: true,
 | 
					 | 
				
			||||||
        tcsn: true,
 | 
					 | 
				
			||||||
        pgr: {
 | 
					 | 
				
			||||||
            ts: "1732572900",
 | 
					 | 
				
			||||||
            en: "CUSTOM DECALS @ ZEVILA",
 | 
					 | 
				
			||||||
            fr: "DECALS CUSTOM @ ZEVILA",
 | 
					 | 
				
			||||||
            it: "DECALCOMANIE PERSONALIZZATE @ ZEVILA",
 | 
					 | 
				
			||||||
            de: "AUFKLEBER NACH WUNSCH @ ZEVILA",
 | 
					 | 
				
			||||||
            es: "CALCOMANÍAS PERSONALIZADAS @ ZEVILA",
 | 
					 | 
				
			||||||
            pt: "DECALQUES PERSONALIZADOS NA ZEVILA",
 | 
					 | 
				
			||||||
            ru: "ПОЛЬЗОВАТЕЛЬСКИЕ НАКЛЕЙКИ @ ЗеВиЛа",
 | 
					 | 
				
			||||||
            pl: "NOWE NAKLEJKI @ ZEVILA",
 | 
					 | 
				
			||||||
            uk: "КОРИСТУВАЦЬКІ ДЕКОЛІ @ ЗІВІЛА",
 | 
					 | 
				
			||||||
            tr: "ÖZEL ÇIKARTMALAR @ ZEVILA",
 | 
					 | 
				
			||||||
            ja: "カスタムデカール @ ゼビラ",
 | 
					 | 
				
			||||||
            zh: "定制贴花认准泽威拉",
 | 
					 | 
				
			||||||
            ko: "커스텀 데칼 @ ZEVILA",
 | 
					 | 
				
			||||||
            tc: "自訂貼花 @ ZEVILA",
 | 
					 | 
				
			||||||
            th: "รูปลอกสั่งทำที่ ZEVILA"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        ennnd: true,
 | 
					 | 
				
			||||||
        mbrt: true,
 | 
					 | 
				
			||||||
        sfn: [550, 553, 554, 555][halfHour % 4]
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    worldState.Tmp = JSON.stringify(tmp);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    res.json(worldState);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IWorldState {
 | 
					 | 
				
			||||||
    Version: number; // for goals
 | 
					 | 
				
			||||||
    BuildLabel: string;
 | 
					 | 
				
			||||||
    Time: number;
 | 
					 | 
				
			||||||
    Goals: IGoal[];
 | 
					 | 
				
			||||||
    SyndicateMissions: ISyndicateMission[];
 | 
					 | 
				
			||||||
    GlobalUpgrades: IGlobalUpgrade[];
 | 
					 | 
				
			||||||
    Sorties: ISortie[];
 | 
					 | 
				
			||||||
    LiteSorties: ILiteSortie[];
 | 
					 | 
				
			||||||
    NodeOverrides: INodeOverride[];
 | 
					 | 
				
			||||||
    EndlessXpChoices: IEndlessXpChoice[];
 | 
					 | 
				
			||||||
    SeasonInfo: {
 | 
					 | 
				
			||||||
        Activation: IMongoDate;
 | 
					 | 
				
			||||||
        Expiry: IMongoDate;
 | 
					 | 
				
			||||||
        AffiliationTag: string;
 | 
					 | 
				
			||||||
        Season: number;
 | 
					 | 
				
			||||||
        Phase: number;
 | 
					 | 
				
			||||||
        Params: string;
 | 
					 | 
				
			||||||
        ActiveChallenges: ISeasonChallenge[];
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    KnownCalendarSeasons: ICalendarSeason[];
 | 
					 | 
				
			||||||
    Tmp?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IGoal {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry: IMongoDate;
 | 
					 | 
				
			||||||
    Count: number;
 | 
					 | 
				
			||||||
    Goal: number;
 | 
					 | 
				
			||||||
    Success: number;
 | 
					 | 
				
			||||||
    Personal: boolean;
 | 
					 | 
				
			||||||
    Desc: string;
 | 
					 | 
				
			||||||
    ToolTip: string;
 | 
					 | 
				
			||||||
    Icon: string;
 | 
					 | 
				
			||||||
    Tag: string;
 | 
					 | 
				
			||||||
    Node: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ISyndicateMission {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry: IMongoDate;
 | 
					 | 
				
			||||||
    Tag: string;
 | 
					 | 
				
			||||||
    Seed: number;
 | 
					 | 
				
			||||||
    Nodes: string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IGlobalUpgrade {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    ExpiryDate: IMongoDate;
 | 
					 | 
				
			||||||
    UpgradeType: string;
 | 
					 | 
				
			||||||
    OperationType: string;
 | 
					 | 
				
			||||||
    Value: number;
 | 
					 | 
				
			||||||
    LocalizeTag: string;
 | 
					 | 
				
			||||||
    LocalizeDescTag: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface INodeOverride {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Activation?: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry?: IMongoDate;
 | 
					 | 
				
			||||||
    Node: string;
 | 
					 | 
				
			||||||
    Hide?: boolean;
 | 
					 | 
				
			||||||
    Seed?: number;
 | 
					 | 
				
			||||||
    LevelOverride?: string;
 | 
					 | 
				
			||||||
    Faction?: 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 {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry: IMongoDate;
 | 
					 | 
				
			||||||
    Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
 | 
					 | 
				
			||||||
    Seed: number;
 | 
					 | 
				
			||||||
    Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"
 | 
					 | 
				
			||||||
    Missions: {
 | 
					 | 
				
			||||||
        missionType: string;
 | 
					 | 
				
			||||||
        node: string;
 | 
					 | 
				
			||||||
    }[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IEndlessXpChoice {
 | 
					 | 
				
			||||||
    Category: string;
 | 
					 | 
				
			||||||
    Choices: string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ISeasonChallenge {
 | 
					 | 
				
			||||||
    _id: IOid;
 | 
					 | 
				
			||||||
    Daily?: boolean;
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry: IMongoDate;
 | 
					 | 
				
			||||||
    Challenge: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ICalendarSeason {
 | 
					 | 
				
			||||||
    Activation: IMongoDate;
 | 
					 | 
				
			||||||
    Expiry: IMongoDate;
 | 
					 | 
				
			||||||
    Season: string; // "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL"
 | 
					 | 
				
			||||||
    Days: {
 | 
					 | 
				
			||||||
        day: number;
 | 
					 | 
				
			||||||
    }[];
 | 
					 | 
				
			||||||
    YearIteration: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
 | 
					 | 
				
			||||||
    x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
 | 
					 | 
				
			||||||
    const dayStart = EPOCH + day * 86400000;
 | 
					 | 
				
			||||||
    const dayEnd = EPOCH + (day + 3) * 86400000;
 | 
					 | 
				
			||||||
    const rng = new CRng(day);
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
        Daily: true,
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
					 | 
				
			||||||
        Challenge: rng.randomElement(dailyChallenges)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const weeklyChallenges = Object.keys(ExportNightwave.challenges).filter(
 | 
					 | 
				
			||||||
    x =>
 | 
					 | 
				
			||||||
        x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/") &&
 | 
					 | 
				
			||||||
        !x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge => {
 | 
					 | 
				
			||||||
    const weekStart = EPOCH + week * 604800000;
 | 
					 | 
				
			||||||
    const weekEnd = weekStart + 604800000;
 | 
					 | 
				
			||||||
    const challengeId = week * 7 + id;
 | 
					 | 
				
			||||||
    const rng = new CRng(challengeId);
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
					 | 
				
			||||||
        Challenge: rng.randomElement(weeklyChallenges)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const weeklyHardChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
 | 
					 | 
				
			||||||
    x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChallenge => {
 | 
					 | 
				
			||||||
    const weekStart = EPOCH + week * 604800000;
 | 
					 | 
				
			||||||
    const weekEnd = weekStart + 604800000;
 | 
					 | 
				
			||||||
    const challengeId = week * 7 + id;
 | 
					 | 
				
			||||||
    const rng = new CRng(challengeId);
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
					 | 
				
			||||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
					 | 
				
			||||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
					 | 
				
			||||||
        Challenge: rng.randomElement(weeklyHardChallenges)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,84 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										561
									
								
								src/services/worldStateService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										561
									
								
								src/services/worldStateService.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,561 @@
 | 
				
			|||||||
 | 
					import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
 | 
				
			||||||
 | 
					import static1999FallDays from "@/static/fixed_responses/worldState/1999_fall_days.json";
 | 
				
			||||||
 | 
					import static1999SpringDays from "@/static/fixed_responses/worldState/1999_spring_days.json";
 | 
				
			||||||
 | 
					import static1999SummerDays from "@/static/fixed_responses/worldState/1999_summer_days.json";
 | 
				
			||||||
 | 
					import static1999WinterDays from "@/static/fixed_responses/worldState/1999_winter_days.json";
 | 
				
			||||||
 | 
					import { buildConfig } from "@/src/services/buildConfigService";
 | 
				
			||||||
 | 
					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 { ISeasonChallenge, IWorldState } from "../types/worldStateTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sortieFactionToFactionIndexes: Record<string, number[]> = {
 | 
				
			||||||
 | 
					    FC_GRINEER: [0],
 | 
				
			||||||
 | 
					    FC_CORPUS: [1],
 | 
				
			||||||
 | 
					    FC_INFESTATION: [0, 1, 2],
 | 
				
			||||||
 | 
					    FC_CORRUPTED: [3]
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
 | 
				
			||||||
 | 
					    x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
 | 
				
			||||||
 | 
					    const dayStart = EPOCH + day * 86400000;
 | 
				
			||||||
 | 
					    const dayEnd = EPOCH + (day + 3) * 86400000;
 | 
				
			||||||
 | 
					    const rng = new CRng(day);
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					        Daily: true,
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
				
			||||||
 | 
					        Challenge: rng.randomElement(dailyChallenges)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const weeklyChallenges = Object.keys(ExportNightwave.challenges).filter(
 | 
				
			||||||
 | 
					    x =>
 | 
				
			||||||
 | 
					        x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/") &&
 | 
				
			||||||
 | 
					        !x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge => {
 | 
				
			||||||
 | 
					    const weekStart = EPOCH + week * 604800000;
 | 
				
			||||||
 | 
					    const weekEnd = weekStart + 604800000;
 | 
				
			||||||
 | 
					    const challengeId = week * 7 + id;
 | 
				
			||||||
 | 
					    const rng = new CRng(challengeId);
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
				
			||||||
 | 
					        Challenge: rng.randomElement(weeklyChallenges)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const weeklyHardChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
 | 
				
			||||||
 | 
					    x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChallenge => {
 | 
				
			||||||
 | 
					    const weekStart = EPOCH + week * 604800000;
 | 
				
			||||||
 | 
					    const weekEnd = weekStart + 604800000;
 | 
				
			||||||
 | 
					    const challengeId = week * 7 + id;
 | 
				
			||||||
 | 
					    const rng = new CRng(challengeId);
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
				
			||||||
 | 
					        Challenge: rng.randomElement(weeklyHardChallenges)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			||||||
 | 
					    const day = Math.trunc((Date.now() - EPOCH) / 86400000);
 | 
				
			||||||
 | 
					    const week = Math.trunc(day / 7);
 | 
				
			||||||
 | 
					    const weekStart = EPOCH + week * 604800000;
 | 
				
			||||||
 | 
					    const weekEnd = weekStart + 604800000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const worldState: IWorldState = {
 | 
				
			||||||
 | 
					        BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
 | 
				
			||||||
 | 
					        Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
 | 
				
			||||||
 | 
					        Goals: [],
 | 
				
			||||||
 | 
					        GlobalUpgrades: [],
 | 
				
			||||||
 | 
					        Sorties: [],
 | 
				
			||||||
 | 
					        LiteSorties: [],
 | 
				
			||||||
 | 
					        EndlessXpChoices: [],
 | 
				
			||||||
 | 
					        SeasonInfo: {
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: "1715796000000" } },
 | 
				
			||||||
 | 
					            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
 | 
					            AffiliationTag: "RadioLegionIntermission12Syndicate",
 | 
				
			||||||
 | 
					            Season: 14,
 | 
				
			||||||
 | 
					            Phase: 0,
 | 
				
			||||||
 | 
					            Params: "",
 | 
				
			||||||
 | 
					            ActiveChallenges: [
 | 
				
			||||||
 | 
					                getSeasonDailyChallenge(day - 2),
 | 
				
			||||||
 | 
					                getSeasonDailyChallenge(day - 1),
 | 
				
			||||||
 | 
					                getSeasonDailyChallenge(day - 0),
 | 
				
			||||||
 | 
					                getSeasonWeeklyChallenge(week, 0),
 | 
				
			||||||
 | 
					                getSeasonWeeklyChallenge(week, 1),
 | 
				
			||||||
 | 
					                getSeasonWeeklyHardChallenge(week, 2),
 | 
				
			||||||
 | 
					                getSeasonWeeklyHardChallenge(week, 3),
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
				
			||||||
 | 
					                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
				
			||||||
 | 
					                    Challenge:
 | 
				
			||||||
 | 
					                        "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
				
			||||||
 | 
					                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
				
			||||||
 | 
					                    Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
 | 
				
			||||||
 | 
					                    Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
				
			||||||
 | 
					                    Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
				
			||||||
 | 
					                    Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ...staticWorldState
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.worldState?.starDays) {
 | 
				
			||||||
 | 
					        worldState.Goals.push({
 | 
				
			||||||
 | 
					            _id: { $oid: "67a4dcce2a198564d62e1647" },
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: "1738868400000" } },
 | 
				
			||||||
 | 
					            Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
 | 
					            Count: 0,
 | 
				
			||||||
 | 
					            Goal: 0,
 | 
				
			||||||
 | 
					            Success: 0,
 | 
				
			||||||
 | 
					            Personal: true,
 | 
				
			||||||
 | 
					            Desc: "/Lotus/Language/Events/ValentinesFortunaName",
 | 
				
			||||||
 | 
					            ToolTip: "/Lotus/Language/Events/ValentinesFortunaName",
 | 
				
			||||||
 | 
					            Icon: "/Lotus/Interface/Icons/WorldStatePanel/ValentinesEventIcon.png",
 | 
				
			||||||
 | 
					            Tag: "FortunaValentines",
 | 
				
			||||||
 | 
					            Node: "SolarisUnitedHub1"
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Elite Sanctuary Onslaught cycling every week
 | 
				
			||||||
 | 
					    worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
 | 
				
			||||||
 | 
					    const bountyCycle = Math.trunc(Date.now() / 9000000);
 | 
				
			||||||
 | 
					    const bountyCycleStart = bountyCycle * 9000000;
 | 
				
			||||||
 | 
					    const bountyCycleEnd = bountyCycleStart + 9000000;
 | 
				
			||||||
 | 
					    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "ZarimanSyndicate")] = {
 | 
				
			||||||
 | 
					        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" },
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
 | 
				
			||||||
 | 
					        Tag: "ZarimanSyndicate",
 | 
				
			||||||
 | 
					        Seed: bountyCycle,
 | 
				
			||||||
 | 
					        Nodes: []
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "EntratiLabSyndicate")] = {
 | 
				
			||||||
 | 
					        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" },
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
 | 
				
			||||||
 | 
					        Tag: "EntratiLabSyndicate",
 | 
				
			||||||
 | 
					        Seed: bountyCycle,
 | 
				
			||||||
 | 
					        Nodes: []
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "HexSyndicate")] = {
 | 
				
			||||||
 | 
					        _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" },
 | 
				
			||||||
 | 
					        Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
 | 
				
			||||||
 | 
					        Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
 | 
				
			||||||
 | 
					        Tag: "HexSyndicate",
 | 
				
			||||||
 | 
					        Seed: bountyCycle,
 | 
				
			||||||
 | 
					        Nodes: []
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (config.worldState?.creditBoost) {
 | 
				
			||||||
 | 
					        worldState.GlobalUpgrades.push({
 | 
				
			||||||
 | 
					            _id: { $oid: "5b23106f283a555109666672" },
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
				
			||||||
 | 
					            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
 | 
					            UpgradeType: "GAMEPLAY_MONEY_REWARD_AMOUNT",
 | 
				
			||||||
 | 
					            OperationType: "MULTIPLY",
 | 
				
			||||||
 | 
					            Value: 2,
 | 
				
			||||||
 | 
					            LocalizeTag: "",
 | 
				
			||||||
 | 
					            LocalizeDescTag: ""
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (config.worldState?.affinityBoost) {
 | 
				
			||||||
 | 
					        worldState.GlobalUpgrades.push({
 | 
				
			||||||
 | 
					            _id: { $oid: "5b23106f283a555109666673" },
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
				
			||||||
 | 
					            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
 | 
					            UpgradeType: "GAMEPLAY_KILL_XP_AMOUNT",
 | 
				
			||||||
 | 
					            OperationType: "MULTIPLY",
 | 
				
			||||||
 | 
					            Value: 2,
 | 
				
			||||||
 | 
					            LocalizeTag: "",
 | 
				
			||||||
 | 
					            LocalizeDescTag: ""
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (config.worldState?.resourceBoost) {
 | 
				
			||||||
 | 
					        worldState.GlobalUpgrades.push({
 | 
				
			||||||
 | 
					            _id: { $oid: "5b23106f283a555109666674" },
 | 
				
			||||||
 | 
					            Activation: { $date: { $numberLong: "1740164400000" } },
 | 
				
			||||||
 | 
					            ExpiryDate: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
 | 
					            UpgradeType: "GAMEPLAY_PICKUP_AMOUNT",
 | 
				
			||||||
 | 
					            OperationType: "MULTIPLY",
 | 
				
			||||||
 | 
					            Value: 2,
 | 
				
			||||||
 | 
					            LocalizeTag: "",
 | 
				
			||||||
 | 
					            LocalizeDescTag: ""
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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 = eMissionType[missionIndex].tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Circuit choices cycling every week
 | 
				
			||||||
 | 
					    worldState.EndlessXpChoices.push({
 | 
				
			||||||
 | 
					        Category: "EXC_NORMAL",
 | 
				
			||||||
 | 
					        Choices: [
 | 
				
			||||||
 | 
					            ["Nidus", "Octavia", "Harrow"],
 | 
				
			||||||
 | 
					            ["Gara", "Khora", "Revenant"],
 | 
				
			||||||
 | 
					            ["Garuda", "Baruuk", "Hildryn"],
 | 
				
			||||||
 | 
					            ["Excalibur", "Trinity", "Ember"],
 | 
				
			||||||
 | 
					            ["Loki", "Mag", "Rhino"],
 | 
				
			||||||
 | 
					            ["Ash", "Frost", "Nyx"],
 | 
				
			||||||
 | 
					            ["Saryn", "Vauban", "Nova"],
 | 
				
			||||||
 | 
					            ["Nekros", "Valkyr", "Oberon"],
 | 
				
			||||||
 | 
					            ["Hydroid", "Mirage", "Limbo"],
 | 
				
			||||||
 | 
					            ["Mesa", "Chroma", "Atlas"],
 | 
				
			||||||
 | 
					            ["Ivara", "Inaros", "Titania"]
 | 
				
			||||||
 | 
					        ][week % 12]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    worldState.EndlessXpChoices.push({
 | 
				
			||||||
 | 
					        Category: "EXC_HARD",
 | 
				
			||||||
 | 
					        Choices: [
 | 
				
			||||||
 | 
					            ["Boar", "Gammacor", "Angstrum", "Gorgon", "Anku"],
 | 
				
			||||||
 | 
					            ["Bo", "Latron", "Furis", "Furax", "Strun"],
 | 
				
			||||||
 | 
					            ["Lex", "Magistar", "Boltor", "Bronco", "CeramicDagger"],
 | 
				
			||||||
 | 
					            ["Torid", "DualToxocyst", "DualIchor", "Miter", "Atomos"],
 | 
				
			||||||
 | 
					            ["AckAndBrunt", "Soma", "Vasto", "NamiSolo", "Burston"],
 | 
				
			||||||
 | 
					            ["Zylok", "Sibear", "Dread", "Despair", "Hate"],
 | 
				
			||||||
 | 
					            ["Dera", "Sybaris", "Cestra", "Sicarus", "Okina"],
 | 
				
			||||||
 | 
					            ["Braton", "Lato", "Skana", "Paris", "Kunai"]
 | 
				
			||||||
 | 
					        ][week % 8]
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1999 Calendar Season cycling every week + YearIteration every 4 weeks
 | 
				
			||||||
 | 
					    worldState.KnownCalendarSeasons[0].Activation = { $date: { $numberLong: weekStart.toString() } };
 | 
				
			||||||
 | 
					    worldState.KnownCalendarSeasons[0].Expiry = { $date: { $numberLong: weekEnd.toString() } };
 | 
				
			||||||
 | 
					    worldState.KnownCalendarSeasons[0].Season = ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][week % 4];
 | 
				
			||||||
 | 
					    worldState.KnownCalendarSeasons[0].Days = [
 | 
				
			||||||
 | 
					        static1999WinterDays,
 | 
				
			||||||
 | 
					        static1999SpringDays,
 | 
				
			||||||
 | 
					        static1999SummerDays,
 | 
				
			||||||
 | 
					        static1999FallDays
 | 
				
			||||||
 | 
					    ][week % 4];
 | 
				
			||||||
 | 
					    worldState.KnownCalendarSeasons[0].YearIteration = Math.trunc(week / 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Sentient Anomaly cycling every 30 minutes
 | 
				
			||||||
 | 
					    const halfHour = Math.trunc(Date.now() / (unixTimesInMs.hour / 2));
 | 
				
			||||||
 | 
					    const tmp = {
 | 
				
			||||||
 | 
					        cavabegin: "1690761600",
 | 
				
			||||||
 | 
					        PurchasePlatformLockEnabled: true,
 | 
				
			||||||
 | 
					        tcsn: true,
 | 
				
			||||||
 | 
					        pgr: {
 | 
				
			||||||
 | 
					            ts: "1732572900",
 | 
				
			||||||
 | 
					            en: "CUSTOM DECALS @ ZEVILA",
 | 
				
			||||||
 | 
					            fr: "DECALS CUSTOM @ ZEVILA",
 | 
				
			||||||
 | 
					            it: "DECALCOMANIE PERSONALIZZATE @ ZEVILA",
 | 
				
			||||||
 | 
					            de: "AUFKLEBER NACH WUNSCH @ ZEVILA",
 | 
				
			||||||
 | 
					            es: "CALCOMANÍAS PERSONALIZADAS @ ZEVILA",
 | 
				
			||||||
 | 
					            pt: "DECALQUES PERSONALIZADOS NA ZEVILA",
 | 
				
			||||||
 | 
					            ru: "ПОЛЬЗОВАТЕЛЬСКИЕ НАКЛЕЙКИ @ ЗеВиЛа",
 | 
				
			||||||
 | 
					            pl: "NOWE NAKLEJKI @ ZEVILA",
 | 
				
			||||||
 | 
					            uk: "КОРИСТУВАЦЬКІ ДЕКОЛІ @ ЗІВІЛА",
 | 
				
			||||||
 | 
					            tr: "ÖZEL ÇIKARTMALAR @ ZEVILA",
 | 
				
			||||||
 | 
					            ja: "カスタムデカール @ ゼビラ",
 | 
				
			||||||
 | 
					            zh: "定制贴花认准泽威拉",
 | 
				
			||||||
 | 
					            ko: "커스텀 데칼 @ ZEVILA",
 | 
				
			||||||
 | 
					            tc: "自訂貼花 @ ZEVILA",
 | 
				
			||||||
 | 
					            th: "รูปลอกสั่งทำที่ ZEVILA"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ennnd: true,
 | 
				
			||||||
 | 
					        mbrt: true,
 | 
				
			||||||
 | 
					        sfn: [550, 553, 554, 555][halfHour % 4]
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    worldState.Tmp = JSON.stringify(tmp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return worldState;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										122
									
								
								src/types/worldStateTypes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/types/worldStateTypes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					import { IMongoDate, IOid } from "./commonTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IWorldState {
 | 
				
			||||||
 | 
					    Version: number; // for goals
 | 
				
			||||||
 | 
					    BuildLabel: string;
 | 
				
			||||||
 | 
					    Time: number;
 | 
				
			||||||
 | 
					    Goals: IGoal[];
 | 
				
			||||||
 | 
					    SyndicateMissions: ISyndicateMission[];
 | 
				
			||||||
 | 
					    GlobalUpgrades: IGlobalUpgrade[];
 | 
				
			||||||
 | 
					    Sorties: ISortie[];
 | 
				
			||||||
 | 
					    LiteSorties: ILiteSortie[];
 | 
				
			||||||
 | 
					    NodeOverrides: INodeOverride[];
 | 
				
			||||||
 | 
					    EndlessXpChoices: IEndlessXpChoice[];
 | 
				
			||||||
 | 
					    SeasonInfo: {
 | 
				
			||||||
 | 
					        Activation: IMongoDate;
 | 
				
			||||||
 | 
					        Expiry: IMongoDate;
 | 
				
			||||||
 | 
					        AffiliationTag: string;
 | 
				
			||||||
 | 
					        Season: number;
 | 
				
			||||||
 | 
					        Phase: number;
 | 
				
			||||||
 | 
					        Params: string;
 | 
				
			||||||
 | 
					        ActiveChallenges: ISeasonChallenge[];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    KnownCalendarSeasons: ICalendarSeason[];
 | 
				
			||||||
 | 
					    Tmp?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IGoal {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Count: number;
 | 
				
			||||||
 | 
					    Goal: number;
 | 
				
			||||||
 | 
					    Success: number;
 | 
				
			||||||
 | 
					    Personal: boolean;
 | 
				
			||||||
 | 
					    Desc: string;
 | 
				
			||||||
 | 
					    ToolTip: string;
 | 
				
			||||||
 | 
					    Icon: string;
 | 
				
			||||||
 | 
					    Tag: string;
 | 
				
			||||||
 | 
					    Node: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ISyndicateMission {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Tag: string;
 | 
				
			||||||
 | 
					    Seed: number;
 | 
				
			||||||
 | 
					    Nodes: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IGlobalUpgrade {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    ExpiryDate: IMongoDate;
 | 
				
			||||||
 | 
					    UpgradeType: string;
 | 
				
			||||||
 | 
					    OperationType: string;
 | 
				
			||||||
 | 
					    Value: number;
 | 
				
			||||||
 | 
					    LocalizeTag: string;
 | 
				
			||||||
 | 
					    LocalizeDescTag: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface INodeOverride {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation?: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry?: IMongoDate;
 | 
				
			||||||
 | 
					    Node: string;
 | 
				
			||||||
 | 
					    Hide?: boolean;
 | 
				
			||||||
 | 
					    Seed?: number;
 | 
				
			||||||
 | 
					    LevelOverride?: string;
 | 
				
			||||||
 | 
					    Faction?: string;
 | 
				
			||||||
 | 
					    CustomNpcEncounters?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export 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;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ILiteSortie {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
 | 
				
			||||||
 | 
					    Seed: number;
 | 
				
			||||||
 | 
					    Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"
 | 
				
			||||||
 | 
					    Missions: {
 | 
				
			||||||
 | 
					        missionType: string;
 | 
				
			||||||
 | 
					        node: string;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IEndlessXpChoice {
 | 
				
			||||||
 | 
					    Category: string;
 | 
				
			||||||
 | 
					    Choices: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ISeasonChallenge {
 | 
				
			||||||
 | 
					    _id: IOid;
 | 
				
			||||||
 | 
					    Daily?: boolean;
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Challenge: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ICalendarSeason {
 | 
				
			||||||
 | 
					    Activation: IMongoDate;
 | 
				
			||||||
 | 
					    Expiry: IMongoDate;
 | 
				
			||||||
 | 
					    Season: string; // "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL"
 | 
				
			||||||
 | 
					    Days: {
 | 
				
			||||||
 | 
					        day: number;
 | 
				
			||||||
 | 
					    }[];
 | 
				
			||||||
 | 
					    YearIteration: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user