2025-04-10 12:54:48 -07:00
|
|
|
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
2025-04-23 11:36:32 -07:00
|
|
|
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
|
2025-04-10 12:54:48 -07:00
|
|
|
import { buildConfig } from "@/src/services/buildConfigService";
|
|
|
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
|
|
|
import { config } from "@/src/services/configService";
|
|
|
|
import { CRng } from "@/src/services/rngService";
|
2025-04-23 11:40:45 -07:00
|
|
|
import { eMissionType, ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus";
|
2025-04-23 11:36:32 -07:00
|
|
|
import {
|
|
|
|
ICalendarDay,
|
2025-04-25 11:53:04 -07:00
|
|
|
ICalendarEvent,
|
2025-04-23 11:36:32 -07:00
|
|
|
ICalendarSeason,
|
|
|
|
ILiteSortie,
|
|
|
|
ISeasonChallenge,
|
|
|
|
ISortieMission,
|
|
|
|
IWorldState
|
|
|
|
} from "../types/worldStateTypes";
|
2025-04-10 12:54:48 -07:00
|
|
|
|
|
|
|
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"
|
|
|
|
};
|
|
|
|
|
2025-04-15 20:41:46 -07:00
|
|
|
const eidolonJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyLib",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyCap",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountyExt",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCap",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyTheft",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/ReclamationBountyCache",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapOne",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/CaptureBountyCapTwo",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/SabotageBountySab",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
|
|
|
|
];
|
|
|
|
|
|
|
|
const eidolonNarmerJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
|
|
|
|
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
|
|
|
|
];
|
|
|
|
|
|
|
|
const venusJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobAssassinate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusChaosJobExcavation",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobAssassinate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobExterminate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusCullJobResource",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobRecovery",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobResource",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusIntelJobSpy",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusSpyJobSpy",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobAmbush",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobExcavation",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusTheftJobResource",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobCaches",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobResource",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusHelpingJobSpy",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobDefense",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobRecovery",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusPreservationJobResource",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobAssassinate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
|
|
|
|
];
|
|
|
|
|
|
|
|
const venusNarmerJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
|
|
|
|
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
|
|
|
|
];
|
|
|
|
|
|
|
|
const microplanetJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosGrnSurvivorBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosKeyPiecesBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosExcavateBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
|
|
|
|
];
|
|
|
|
|
|
|
|
const microplanetEndlessJobs = [
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
|
|
|
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
2025-04-11 06:56:31 -07:00
|
|
|
];
|
|
|
|
|
2025-04-10 12:54:48 -07:00
|
|
|
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
|
|
|
|
|
2025-04-15 20:53:27 -07:00
|
|
|
const isBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
|
|
|
|
return Date.now() + 300_000 > date;
|
|
|
|
};
|
|
|
|
|
2025-04-10 12:54:48 -07:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2025-04-22 10:00:49 -07:00
|
|
|
const pushSyndicateMissions = (
|
|
|
|
worldState: IWorldState,
|
|
|
|
day: number,
|
|
|
|
seed: number,
|
|
|
|
idSuffix: string,
|
|
|
|
syndicateTag: string
|
|
|
|
): void => {
|
|
|
|
const nodeOptions: string[] = [];
|
|
|
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
|
|
|
if (
|
2025-04-23 11:40:45 -07:00
|
|
|
!isArchwingMission(value) &&
|
2025-04-25 11:51:54 -07:00
|
|
|
!value.questReq && // Exclude zariman, murmor, and 1999 stuff
|
|
|
|
!value.hidden && // Exclude the index
|
|
|
|
!value.darkSectorData && // Exclude dark sectors
|
2025-04-22 10:00:49 -07:00
|
|
|
value.missionIndex != 10 && // Exclude MT_PVP (for relays)
|
|
|
|
value.missionIndex != 23 && // no junctions
|
2025-04-25 11:51:54 -07:00
|
|
|
value.missionIndex < 28 // no open worlds, railjack, etc
|
2025-04-22 10:00:49 -07:00
|
|
|
) {
|
|
|
|
nodeOptions.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const rng = new CRng(seed);
|
|
|
|
const nodes: string[] = [];
|
|
|
|
for (let i = 0; i != 6; ++i) {
|
|
|
|
const index = rng.randomInt(0, nodeOptions.length - 1);
|
|
|
|
nodes.push(nodeOptions[index]);
|
|
|
|
nodeOptions.splice(index, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
const dayStart = getSortieTime(day);
|
|
|
|
const dayEnd = getSortieTime(day + 1);
|
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + idSuffix },
|
2025-04-22 10:00:49 -07:00
|
|
|
Activation: { $date: { $numberLong: dayStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
|
|
|
Tag: syndicateTag,
|
|
|
|
Seed: seed,
|
|
|
|
Nodes: nodes
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
|
2025-04-15 20:53:27 -07:00
|
|
|
const dayStart = getSortieTime(day);
|
|
|
|
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const dayEnd = getSortieTime(day + 1);
|
|
|
|
if (Date.now() >= dayEnd) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-04-24 11:24:11 -07:00
|
|
|
const seed = new CRng(day).randomInt(0, 0xffff);
|
|
|
|
const rng = new CRng(seed);
|
2025-04-15 20:53:27 -07:00
|
|
|
|
|
|
|
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!) &&
|
2025-04-23 11:40:45 -07:00
|
|
|
!isArchwingMission(value) &&
|
2025-04-15 20:53:27 -07:00
|
|
|
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
2025-04-22 10:00:49 -07:00
|
|
|
value.missionIndex != 10 && // Exclude MT_PVP (for relays)
|
2025-04-15 20:53:27 -07:00
|
|
|
value.missionIndex != 21 && // Exclude MT_PURIFY
|
2025-04-21 22:06:39 -07:00
|
|
|
value.missionIndex != 22 && // Exclude MT_ARENA
|
2025-04-15 20:53:27 -07:00
|
|
|
value.missionIndex != 23 && // Exclude MT_JUNCTION
|
2025-04-23 11:35:29 -07:00
|
|
|
(value.missionIndex != 28 || value.systemIndex == 2) && // MT_LANDSCAPE only on Earth
|
|
|
|
value.missionIndex < 29
|
2025-04-15 20:53:27 -07:00
|
|
|
) {
|
|
|
|
if (!availableMissionIndexes.includes(value.missionIndex)) {
|
|
|
|
availableMissionIndexes.push(value.missionIndex);
|
|
|
|
}
|
|
|
|
nodes.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-23 11:36:32 -07:00
|
|
|
const selectedNodes: ISortieMission[] = [];
|
2025-04-15 20:53:27 -07:00
|
|
|
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") {
|
2025-04-23 11:36:32 -07:00
|
|
|
selectedNodes.push({
|
|
|
|
missionType: "MT_ASSASSINATION",
|
|
|
|
modifierType,
|
|
|
|
node,
|
|
|
|
tileset: sortieTilesets[node as keyof typeof sortieTilesets]
|
|
|
|
});
|
2025-04-15 20:53:27 -07:00
|
|
|
nodes.splice(randomIndex, 1);
|
|
|
|
continue;
|
|
|
|
} else if (sortieBossNode[boss]) {
|
2025-04-23 11:36:32 -07:00
|
|
|
selectedNodes.push({
|
|
|
|
missionType: "MT_ASSASSINATION",
|
|
|
|
modifierType,
|
|
|
|
node: sortieBossNode[boss],
|
|
|
|
tileset: sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets]
|
|
|
|
});
|
2025-04-15 20:53:27 -07:00
|
|
|
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);
|
|
|
|
|
2025-04-23 11:36:32 -07:00
|
|
|
selectedNodes.push({
|
|
|
|
missionType,
|
|
|
|
modifierType,
|
|
|
|
node,
|
|
|
|
tileset: sortieTilesets[node as keyof typeof sortieTilesets]
|
|
|
|
});
|
2025-04-15 20:53:27 -07:00
|
|
|
nodes.splice(randomIndex, 1);
|
|
|
|
missionTypes.add(missionType);
|
|
|
|
}
|
|
|
|
|
2025-04-22 10:00:49 -07:00
|
|
|
worldState.Sorties.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "d4d932c97c0a3acd" },
|
2025-04-15 20:53:27 -07:00
|
|
|
Activation: { $date: { $numberLong: dayStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
|
|
|
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
|
2025-04-24 11:24:11 -07:00
|
|
|
Seed: seed,
|
2025-04-15 20:53:27 -07:00
|
|
|
Boss: boss,
|
|
|
|
Variants: selectedNodes
|
|
|
|
});
|
2025-04-22 10:00:49 -07:00
|
|
|
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
|
|
|
|
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
2025-04-15 20:53:27 -07:00
|
|
|
};
|
|
|
|
|
2025-04-10 12:54:48 -07:00
|
|
|
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;
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(new CRng(day).randomInt(0, 0xffff));
|
2025-04-10 12:54:48 -07:00
|
|
|
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;
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
|
2025-04-10 12:54:48 -07:00
|
|
|
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;
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
|
2025-04-10 12:54:48 -07:00
|
|
|
return {
|
|
|
|
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
|
|
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
|
|
Challenge: rng.randomElement(weeklyHardChallenges)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-04-19 09:06:49 -07:00
|
|
|
const pushWeeklyActs = (worldState: IWorldState, week: number): void => {
|
|
|
|
const weekStart = EPOCH + week * 604800000;
|
|
|
|
const weekEnd = weekStart + 604800000;
|
|
|
|
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 0));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyChallenge(week, 1));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 2));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonWeeklyHardChallenge(week, 3));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push({
|
|
|
|
_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)
|
|
|
|
});
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push({
|
|
|
|
_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)
|
|
|
|
});
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push({
|
|
|
|
_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)
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2025-04-17 08:00:19 -07:00
|
|
|
const birthdays: number[] = [
|
|
|
|
1, // Kaya
|
|
|
|
45, // Lettie
|
|
|
|
74, // Minerva (MinervaVelemirDialogue_rom.dialogue)
|
|
|
|
143, // Amir
|
|
|
|
166, // Flare
|
|
|
|
191, // Aoi
|
|
|
|
306, // Eleanor
|
|
|
|
307, // Arthur
|
|
|
|
338, // Quincy
|
|
|
|
355 // Velimir (MinervaVelemirDialogue_rom.dialogue)
|
|
|
|
];
|
|
|
|
|
|
|
|
const getCalendarSeason = (week: number): ICalendarSeason => {
|
|
|
|
const seasonIndex = week % 4;
|
2025-04-25 11:53:04 -07:00
|
|
|
const seasonDay1 = [1, 91, 182, 274][seasonIndex];
|
|
|
|
const seasonDay91 = seasonDay1 + 90;
|
2025-04-17 08:00:19 -07:00
|
|
|
const eventDays: ICalendarDay[] = [];
|
|
|
|
for (const day of birthdays) {
|
|
|
|
if (day < seasonDay1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (day >= seasonDay91) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
//logger.debug(`birthday on day ${day}`);
|
|
|
|
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
|
|
|
|
}
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(new CRng(week).randomInt(0, 0xffff));
|
2025-04-17 08:00:19 -07:00
|
|
|
const challenges = [
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithMeleeEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithMeleeMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithMeleeHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithAbilitiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithAbilitiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesWithAbilitiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarDestroyPropsEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarDestroyPropsMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarDestroyPropsHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEximusEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEximusMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEximusHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithAbilitiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithAbilitiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithAbilitiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTankHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithMeleeEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithMeleeMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillScaldraEnemiesWithMeleeHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithAbilitiesEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithAbilitiesMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithAbilitiesHard",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithMeleeEasy",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithMeleeMedium",
|
|
|
|
"/Lotus/Types/Challenges/Calendar1999/CalendarKillTechrotEnemiesWithMeleeHard"
|
|
|
|
];
|
|
|
|
const rewardRanges: number[] = [];
|
|
|
|
const upgradeRanges: number[] = [];
|
|
|
|
for (let i = 0; i != 6; ++i) {
|
|
|
|
const chunkDay1 = seasonDay1 + i * 15;
|
|
|
|
const chunkDay13 = chunkDay1 - 1 + 13;
|
|
|
|
let challengeDay: number;
|
|
|
|
do {
|
|
|
|
challengeDay = rng.randomInt(chunkDay1, chunkDay13);
|
|
|
|
} while (birthdays.indexOf(challengeDay) != -1);
|
|
|
|
|
2025-04-25 11:53:04 -07:00
|
|
|
let challengeIndex;
|
|
|
|
let challenge;
|
|
|
|
do {
|
|
|
|
challengeIndex = rng.randomInt(0, challenges.length - 1);
|
|
|
|
challenge = challenges[challengeIndex];
|
|
|
|
} while (i < 2 && !challenge.endsWith("Easy")); // First 2 challenges should be easy
|
2025-04-17 08:00:19 -07:00
|
|
|
challenges.splice(challengeIndex, 1);
|
|
|
|
|
|
|
|
//logger.debug(`challenge on day ${challengeDay}`);
|
|
|
|
eventDays.push({
|
|
|
|
day: challengeDay,
|
|
|
|
events: [{ type: "CET_CHALLENGE", challenge }]
|
|
|
|
});
|
|
|
|
|
|
|
|
rewardRanges.push(challengeDay);
|
|
|
|
if (i == 0 || i == 3 || i == 5) {
|
|
|
|
upgradeRanges.push(challengeDay);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rewardRanges.push(seasonDay91);
|
|
|
|
upgradeRanges.push(seasonDay91);
|
|
|
|
|
|
|
|
const rewards = [
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker",
|
|
|
|
"/Lotus/StoreItems/Types/Recipes/Components/FormaAuraBlueprint",
|
|
|
|
"/Lotus/StoreItems/Types/Recipes/Components/FormaBlueprint",
|
|
|
|
"/Lotus/StoreItems/Types/Recipes/Components/WeaponUtilityUnlockerBlueprint",
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/WeaponMeleeArcaneUnlocker",
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker",
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker",
|
|
|
|
"/Lotus/StoreItems/Upgrades/Mods/FusionBundles/CircuitSilverSteelPathFusionBundle",
|
|
|
|
"/Lotus/StoreItems/Types/BoosterPacks/CalendarRivenPack",
|
|
|
|
"/Lotus/Types/StoreItems/Packages/Calendar/CalendarKuvaBundleSmall",
|
|
|
|
"/Lotus/Types/StoreItems/Packages/Calendar/CalendarKuvaBundleLarge",
|
|
|
|
"/Lotus/StoreItems/Types/BoosterPacks/CalendarArtifactPack",
|
|
|
|
"/Lotus/StoreItems/Types/BoosterPacks/CalendarMajorArtifactPack",
|
|
|
|
"/Lotus/Types/StoreItems/Boosters/AffinityBooster3DayStoreItem",
|
|
|
|
"/Lotus/Types/StoreItems/Boosters/ModDropChanceBooster3DayStoreItem",
|
|
|
|
"/Lotus/Types/StoreItems/Boosters/ResourceDropChance3DayStoreItem",
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/Forma",
|
|
|
|
"/Lotus/StoreItems/Types/Recipes/Components/OrokinCatalystBlueprint",
|
|
|
|
"/Lotus/StoreItems/Types/Recipes/Components/OrokinReactorBlueprint",
|
|
|
|
"/Lotus/StoreItems/Types/Items/MiscItems/WeaponUtilityUnlocker",
|
|
|
|
"/Lotus/Types/StoreItems/Packages/Calendar/CalendarVosforPack",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalOrange",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalGreen",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
|
|
|
|
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalViolet"
|
|
|
|
];
|
|
|
|
for (let i = 0; i != rewardRanges.length - 1; ++i) {
|
2025-04-25 11:53:04 -07:00
|
|
|
const events: ICalendarEvent[] = [];
|
|
|
|
for (let j = 0; j != 2; ++j) {
|
|
|
|
const rewardIndex = rng.randomInt(0, rewards.length - 1);
|
|
|
|
events.push({ type: "CET_REWARD", reward: rewards[rewardIndex] });
|
|
|
|
rewards.splice(rewardIndex, 1);
|
|
|
|
}
|
2025-04-17 08:00:19 -07:00
|
|
|
|
2025-04-25 11:53:04 -07:00
|
|
|
//logger.debug(`trying to fit rewards between day ${rewardRanges[i]} and ${rewardRanges[i + 1]}`);
|
2025-04-17 08:00:19 -07:00
|
|
|
let day: number;
|
|
|
|
do {
|
|
|
|
day = rng.randomInt(rewardRanges[i] + 1, rewardRanges[i + 1] - 1);
|
|
|
|
} while (eventDays.find(x => x.day == day));
|
2025-04-25 11:53:04 -07:00
|
|
|
eventDays.push({ day, events });
|
2025-04-17 08:00:19 -07:00
|
|
|
}
|
|
|
|
|
2025-04-25 11:53:04 -07:00
|
|
|
const upgradesByHexMember = [
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/AttackAndMovementSpeedOnCritMelee",
|
|
|
|
"/Lotus/Upgrades/Calendar/ElectricalDamageOnBulletJump",
|
|
|
|
"/Lotus/Upgrades/Calendar/ElectricDamagePerDistance",
|
|
|
|
"/Lotus/Upgrades/Calendar/ElectricStatusDamageAndChance",
|
|
|
|
"/Lotus/Upgrades/Calendar/OvershieldCap",
|
|
|
|
"/Lotus/Upgrades/Calendar/SpeedBuffsWhenAirborne"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/AbilityStrength",
|
|
|
|
"/Lotus/Upgrades/Calendar/EnergyOrbToAbilityRange",
|
|
|
|
"/Lotus/Upgrades/Calendar/MagnetStatusPull",
|
|
|
|
"/Lotus/Upgrades/Calendar/MagnitizeWithinRangeEveryXCasts",
|
|
|
|
"/Lotus/Upgrades/Calendar/PowerStrengthAndEfficiencyPerEnergySpent",
|
|
|
|
"/Lotus/Upgrades/Calendar/SharedFreeAbilityEveryXCasts"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/EnergyWavesOnCombo",
|
|
|
|
"/Lotus/Upgrades/Calendar/FinisherChancePerComboMultiplier",
|
|
|
|
"/Lotus/Upgrades/Calendar/MeleeAttackSpeed",
|
|
|
|
"/Lotus/Upgrades/Calendar/MeleeCritChance",
|
|
|
|
"/Lotus/Upgrades/Calendar/MeleeSlideFowardMomentumOnEnemyHit",
|
|
|
|
"/Lotus/Upgrades/Calendar/RadialJavelinOnHeavy"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/Armor",
|
|
|
|
"/Lotus/Upgrades/Calendar/CloneActiveCompanionForEnergySpent",
|
|
|
|
"/Lotus/Upgrades/Calendar/CompanionDamage",
|
|
|
|
"/Lotus/Upgrades/Calendar/CompanionsBuffNearbyPlayer",
|
|
|
|
"/Lotus/Upgrades/Calendar/CompanionsRadiationChance",
|
|
|
|
"/Lotus/Upgrades/Calendar/RadiationProcOnTakeDamage",
|
|
|
|
"/Lotus/Upgrades/Calendar/ReviveEnemyAsSpectreOnKill"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/EnergyOrbsGrantShield",
|
|
|
|
"/Lotus/Upgrades/Calendar/EnergyRestoration",
|
|
|
|
"/Lotus/Upgrades/Calendar/ExplodingHealthOrbs",
|
|
|
|
"/Lotus/Upgrades/Calendar/GenerateOmniOrbsOnWeakKill",
|
|
|
|
"/Lotus/Upgrades/Calendar/HealingEffects",
|
|
|
|
"/Lotus/Upgrades/Calendar/OrbsDuplicateOnPickup"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
"/Lotus/Upgrades/Calendar/BlastEveryXShots",
|
|
|
|
"/Lotus/Upgrades/Calendar/GasChanceToPrimaryAndSecondary",
|
|
|
|
"/Lotus/Upgrades/Calendar/GuidingMissilesChance",
|
|
|
|
"/Lotus/Upgrades/Calendar/MagazineCapacity",
|
|
|
|
"/Lotus/Upgrades/Calendar/PunchToPrimary",
|
|
|
|
"/Lotus/Upgrades/Calendar/RefundBulletOnStatusProc",
|
|
|
|
"/Lotus/Upgrades/Calendar/StatusChancePerAmmoSpent"
|
|
|
|
]
|
2025-04-17 08:00:19 -07:00
|
|
|
];
|
|
|
|
for (let i = 0; i != upgradeRanges.length - 1; ++i) {
|
2025-04-25 11:53:04 -07:00
|
|
|
// Pick 3 unique hex members
|
|
|
|
const hexMembersPickedForThisDay: number[] = [];
|
|
|
|
for (let j = 0; j != 3; ++j) {
|
|
|
|
let hexMemberIndex: number;
|
|
|
|
do {
|
|
|
|
hexMemberIndex = rng.randomInt(0, upgradesByHexMember.length - 1);
|
|
|
|
} while (hexMembersPickedForThisDay.indexOf(hexMemberIndex) != -1);
|
|
|
|
hexMembersPickedForThisDay.push(hexMemberIndex);
|
|
|
|
}
|
|
|
|
hexMembersPickedForThisDay.sort(); // Always present them in the same order
|
|
|
|
|
|
|
|
// For each hex member, pick an upgrade that was not yet picked this season.
|
|
|
|
const events: ICalendarEvent[] = [];
|
|
|
|
for (const hexMemberIndex of hexMembersPickedForThisDay) {
|
|
|
|
const upgrades = upgradesByHexMember[hexMemberIndex];
|
|
|
|
const upgradeIndex = rng.randomInt(0, upgrades.length - 1);
|
|
|
|
events.push({ type: "CET_UPGRADE", upgrade: upgrades[upgradeIndex] });
|
|
|
|
upgrades.splice(upgradeIndex, 1);
|
|
|
|
}
|
2025-04-17 08:00:19 -07:00
|
|
|
|
2025-04-25 11:53:04 -07:00
|
|
|
//logger.debug(`trying to fit upgrades between day ${upgradeRanges[i]} and ${upgradeRanges[i + 1]}`);
|
2025-04-17 08:00:19 -07:00
|
|
|
let day: number;
|
|
|
|
do {
|
|
|
|
day = rng.randomInt(upgradeRanges[i] + 1, upgradeRanges[i + 1] - 1);
|
|
|
|
} while (eventDays.find(x => x.day == day));
|
2025-04-25 11:53:04 -07:00
|
|
|
eventDays.push({ day, events });
|
2025-04-17 08:00:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
eventDays.sort((a, b) => a.day - b.day);
|
|
|
|
|
|
|
|
const weekStart = EPOCH + week * 604800000;
|
|
|
|
const weekEnd = weekStart + 604800000;
|
|
|
|
return {
|
|
|
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
|
|
Days: eventDays,
|
2025-04-25 11:53:34 -07:00
|
|
|
Season: (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[seasonIndex],
|
2025-04-17 08:00:19 -07:00
|
|
|
YearIteration: Math.trunc(week / 4),
|
|
|
|
Version: 19,
|
|
|
|
UpgradeAvaliabilityRequirements: ["/Lotus/Upgrades/Calendar/1999UpgradeApplicationRequirement"]
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-04-10 12:54:48 -07:00
|
|
|
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: [],
|
2025-04-15 20:41:46 -07:00
|
|
|
Alerts: [],
|
2025-04-10 12:54:48 -07:00
|
|
|
Sorties: [],
|
|
|
|
LiteSorties: [],
|
2025-04-15 20:41:46 -07:00
|
|
|
GlobalUpgrades: [],
|
2025-04-10 12:54:48 -07:00
|
|
|
EndlessXpChoices: [],
|
|
|
|
SeasonInfo: {
|
|
|
|
Activation: { $date: { $numberLong: "1715796000000" } },
|
|
|
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
|
|
|
AffiliationTag: "RadioLegionIntermission12Syndicate",
|
|
|
|
Season: 14,
|
|
|
|
Phase: 0,
|
|
|
|
Params: "",
|
2025-04-16 06:31:16 -07:00
|
|
|
ActiveChallenges: []
|
2025-04-10 12:54:48 -07:00
|
|
|
},
|
2025-04-17 08:00:19 -07:00
|
|
|
KnownCalendarSeasons: [],
|
2025-04-15 20:41:46 -07:00
|
|
|
...staticWorldState,
|
|
|
|
SyndicateMissions: [...staticWorldState.SyndicateMissions]
|
2025-04-10 12:54:48 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
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"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-16 06:31:16 -07:00
|
|
|
// Nightwave Challenges
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 2));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 1));
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day - 0));
|
|
|
|
if (isBeforeNextExpectedWorldStateRefresh(EPOCH + (day + 1) * 86400000)) {
|
|
|
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(day + 1));
|
|
|
|
}
|
2025-04-19 09:06:49 -07:00
|
|
|
pushWeeklyActs(worldState, week);
|
|
|
|
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
|
|
|
|
pushWeeklyActs(worldState, week + 1);
|
|
|
|
}
|
2025-04-16 06:31:16 -07:00
|
|
|
|
2025-04-10 12:54:48 -07:00
|
|
|
// Elite Sanctuary Onslaught cycling every week
|
2025-04-24 11:24:11 -07:00
|
|
|
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff);
|
2025-04-10 12:54:48 -07:00
|
|
|
|
|
|
|
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
2025-04-15 20:41:46 -07:00
|
|
|
let bountyCycle = Math.trunc(Date.now() / 9000000);
|
|
|
|
let bountyCycleEnd: number | undefined;
|
|
|
|
do {
|
|
|
|
const bountyCycleStart = bountyCycle * 9000000;
|
|
|
|
bountyCycleEnd = bountyCycleStart + 9000000;
|
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000029" },
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
|
|
|
Tag: "ZarimanSyndicate",
|
|
|
|
Seed: bountyCycle,
|
|
|
|
Nodes: []
|
|
|
|
});
|
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000004" },
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
|
|
|
Tag: "EntratiLabSyndicate",
|
|
|
|
Seed: bountyCycle,
|
|
|
|
Nodes: []
|
|
|
|
});
|
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000006" },
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
|
|
|
Tag: "HexSyndicate",
|
|
|
|
Seed: bountyCycle,
|
|
|
|
Nodes: []
|
|
|
|
});
|
|
|
|
|
|
|
|
const table = String.fromCharCode(65 + (bountyCycle % 3));
|
|
|
|
const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3));
|
|
|
|
const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2));
|
|
|
|
|
|
|
|
// TODO: xpAmounts need to be calculated based on the jobType somehow?
|
|
|
|
|
2025-04-24 11:24:11 -07:00
|
|
|
const seed = new CRng(bountyCycle).randomInt(0, 0xffff);
|
|
|
|
|
2025-04-15 20:41:46 -07:00
|
|
|
{
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(seed);
|
2025-04-15 20:41:46 -07:00
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: {
|
|
|
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
|
|
|
|
},
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
|
|
|
Tag: "CetusSyndicate",
|
2025-04-24 11:24:11 -07:00
|
|
|
Seed: seed,
|
2025-04-15 20:41:46 -07:00
|
|
|
Nodes: [],
|
|
|
|
Jobs: [
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
|
|
|
|
masteryReq: 0,
|
|
|
|
minEnemyLevel: 5,
|
|
|
|
maxEnemyLevel: 15,
|
|
|
|
xpAmounts: [430, 430, 430]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
|
|
|
|
masteryReq: 1,
|
|
|
|
minEnemyLevel: 10,
|
|
|
|
maxEnemyLevel: 30,
|
|
|
|
xpAmounts: [620, 620, 620]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
|
|
|
|
masteryReq: 2,
|
|
|
|
minEnemyLevel: 20,
|
|
|
|
maxEnemyLevel: 40,
|
|
|
|
xpAmounts: [670, 670, 670, 990]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
|
|
|
|
masteryReq: 3,
|
|
|
|
minEnemyLevel: 30,
|
|
|
|
maxEnemyLevel: 50,
|
|
|
|
xpAmounts: [570, 570, 570, 570, 1110]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 40,
|
|
|
|
maxEnemyLevel: 60,
|
|
|
|
xpAmounts: [740, 740, 740, 740, 1450]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
|
|
|
|
masteryReq: 10,
|
|
|
|
minEnemyLevel: 100,
|
|
|
|
maxEnemyLevel: 100,
|
|
|
|
xpAmounts: [840, 840, 840, 840, 1660]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(eidolonNarmerJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTable${table}Rewards`,
|
|
|
|
masteryReq: 0,
|
|
|
|
minEnemyLevel: 50,
|
|
|
|
maxEnemyLevel: 70,
|
|
|
|
xpAmounts: [840, 840, 840, 840, 1650]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(seed);
|
2025-04-15 20:41:46 -07:00
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: {
|
|
|
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
|
|
|
|
},
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
|
|
|
Tag: "SolarisSyndicate",
|
2025-04-24 11:24:11 -07:00
|
|
|
Seed: seed,
|
2025-04-15 20:41:46 -07:00
|
|
|
Nodes: [],
|
|
|
|
Jobs: [
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
|
|
|
|
masteryReq: 0,
|
|
|
|
minEnemyLevel: 5,
|
|
|
|
maxEnemyLevel: 15,
|
|
|
|
xpAmounts: [340, 340, 340]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
|
|
|
|
masteryReq: 1,
|
|
|
|
minEnemyLevel: 10,
|
|
|
|
maxEnemyLevel: 30,
|
|
|
|
xpAmounts: [660, 660, 660]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
|
|
|
|
masteryReq: 2,
|
|
|
|
minEnemyLevel: 20,
|
|
|
|
maxEnemyLevel: 40,
|
|
|
|
xpAmounts: [610, 610, 610, 900]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
|
|
|
|
masteryReq: 3,
|
|
|
|
minEnemyLevel: 30,
|
|
|
|
maxEnemyLevel: 50,
|
|
|
|
xpAmounts: [600, 600, 600, 600, 1170]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 40,
|
|
|
|
maxEnemyLevel: 60,
|
|
|
|
xpAmounts: [690, 690, 690, 690, 1350]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusJobs),
|
|
|
|
rewards: `Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
|
|
|
|
masteryReq: 10,
|
|
|
|
minEnemyLevel: 100,
|
|
|
|
maxEnemyLevel: 100,
|
|
|
|
xpAmounts: [840, 840, 840, 840, 1660]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(venusNarmerJobs),
|
|
|
|
rewards: "/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTableBRewards",
|
|
|
|
masteryReq: 0,
|
|
|
|
minEnemyLevel: 50,
|
|
|
|
maxEnemyLevel: 70,
|
|
|
|
xpAmounts: [780, 780, 780, 780, 1540]
|
2025-04-11 06:56:31 -07:00
|
|
|
}
|
2025-04-15 20:41:46 -07:00
|
|
|
]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2025-04-24 11:24:11 -07:00
|
|
|
const rng = new CRng(seed);
|
2025-04-15 20:41:46 -07:00
|
|
|
worldState.SyndicateMissions.push({
|
2025-04-25 18:55:09 -07:00
|
|
|
_id: {
|
|
|
|
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
|
|
|
|
},
|
2025-04-15 20:41:46 -07:00
|
|
|
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
|
|
|
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
|
|
|
Tag: "EntratiSyndicate",
|
2025-04-24 11:24:11 -07:00
|
|
|
Seed: seed,
|
2025-04-15 20:41:46 -07:00
|
|
|
Nodes: [],
|
|
|
|
Jobs: [
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
|
|
|
|
masteryReq: 0,
|
|
|
|
minEnemyLevel: 5,
|
|
|
|
maxEnemyLevel: 15,
|
|
|
|
xpAmounts: [5, 5, 5]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
|
|
|
|
masteryReq: 1,
|
|
|
|
minEnemyLevel: 15,
|
|
|
|
maxEnemyLevel: 25,
|
|
|
|
xpAmounts: [12, 12, 12]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetEndlessJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTable${table}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 25,
|
|
|
|
maxEnemyLevel: 30,
|
|
|
|
endless: true,
|
|
|
|
xpAmounts: [14, 14, 14]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
|
|
|
|
masteryReq: 2,
|
|
|
|
minEnemyLevel: 30,
|
|
|
|
maxEnemyLevel: 40,
|
|
|
|
xpAmounts: [17, 17, 17, 25]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
|
|
|
masteryReq: 3,
|
|
|
|
minEnemyLevel: 40,
|
|
|
|
maxEnemyLevel: 60,
|
|
|
|
xpAmounts: [22, 22, 22, 22, 43]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
jobType: rng.randomElement(microplanetJobs),
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
|
|
|
|
masteryReq: 10,
|
|
|
|
minEnemyLevel: 100,
|
|
|
|
maxEnemyLevel: 100,
|
|
|
|
xpAmounts: [25, 25, 25, 25, 50]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATable${vaultTable}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 30,
|
|
|
|
maxEnemyLevel: 40,
|
|
|
|
xpAmounts: [2, 2, 2, 4],
|
|
|
|
locationTag: "ChamberB",
|
|
|
|
isVault: true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTable${vaultTable}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 40,
|
|
|
|
maxEnemyLevel: 50,
|
|
|
|
xpAmounts: [4, 4, 4, 5],
|
|
|
|
locationTag: "ChamberA",
|
|
|
|
isVault: true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTable${vaultTable}Rewards`,
|
|
|
|
masteryReq: 5,
|
|
|
|
minEnemyLevel: 50,
|
|
|
|
maxEnemyLevel: 60,
|
|
|
|
xpAmounts: [5, 5, 5, 7],
|
|
|
|
locationTag: "ChamberC",
|
|
|
|
isVault: true
|
2025-04-11 06:56:31 -07:00
|
|
|
}
|
2025-04-15 20:41:46 -07:00
|
|
|
]
|
|
|
|
});
|
2025-04-11 06:56:31 -07:00
|
|
|
}
|
2025-04-15 20:53:27 -07:00
|
|
|
} while (isBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
|
2025-04-10 12:54:48 -07:00
|
|
|
|
|
|
|
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: ""
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-22 10:00:49 -07:00
|
|
|
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
|
|
|
pushSortieIfRelevant(worldState, day - 1);
|
|
|
|
pushSortieIfRelevant(worldState, day);
|
2025-04-10 12:54:48 -07:00
|
|
|
|
|
|
|
// Archon Hunt cycling every week
|
2025-04-19 09:06:38 -07:00
|
|
|
worldState.LiteSorties.push(getLiteSortie(week));
|
|
|
|
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
|
|
|
|
worldState.LiteSorties.push(getLiteSortie(week + 1));
|
2025-04-10 12:54:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2025-04-17 08:00:19 -07:00
|
|
|
worldState.KnownCalendarSeasons.push(getCalendarSeason(week));
|
|
|
|
if (isBeforeNextExpectedWorldStateRefresh(weekEnd)) {
|
|
|
|
worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1));
|
|
|
|
}
|
2025-04-10 12:54:48 -07:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
};
|
2025-04-19 09:06:38 -07:00
|
|
|
|
|
|
|
export const idToWeek = (id: string): number => {
|
|
|
|
return (parseInt(id.substring(0, 8), 16) * 1000 - EPOCH) / 604800000;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getLiteSortie = (week: number): ILiteSortie => {
|
|
|
|
const boss = (["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"] as const)[week % 3];
|
|
|
|
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
|
|
|
|
const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
|
|
|
|
|
|
|
|
const nodes: string[] = [];
|
|
|
|
for (const [key, value] of Object.entries(ExportRegions)) {
|
|
|
|
if (
|
|
|
|
value.systemIndex === systemIndex &&
|
|
|
|
value.factionIndex !== undefined &&
|
|
|
|
value.factionIndex < 2 &&
|
2025-04-23 11:40:45 -07:00
|
|
|
!isArchwingMission(value) &&
|
2025-04-19 09:06:38 -07:00
|
|
|
value.missionIndex != 0 // Exclude MT_ASSASSINATION
|
|
|
|
) {
|
|
|
|
nodes.push(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-24 11:24:11 -07:00
|
|
|
const seed = new CRng(week).randomInt(0, 0xffff);
|
|
|
|
const rng = new CRng(seed);
|
2025-04-19 09:06:38 -07:00
|
|
|
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
|
|
|
|
const firstNode = nodes[firstNodeIndex];
|
|
|
|
nodes.splice(firstNodeIndex, 1);
|
|
|
|
|
|
|
|
const weekStart = EPOCH + week * 604800000;
|
|
|
|
const weekEnd = weekStart + 604800000;
|
|
|
|
return {
|
|
|
|
_id: {
|
2025-04-25 18:55:09 -07:00
|
|
|
$oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "5e23a244740a190c"
|
2025-04-19 09:06:38 -07:00
|
|
|
},
|
|
|
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
|
|
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
|
|
|
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
|
2025-04-24 11:24:11 -07:00
|
|
|
Seed: seed,
|
2025-04-19 09:06:38 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
};
|
2025-04-23 11:40:45 -07:00
|
|
|
|
|
|
|
export const isArchwingMission = (node: IRegion): boolean => {
|
|
|
|
if (node.name.indexOf("Archwing") != -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// SettlementNode10
|
|
|
|
if (node.missionIndex == 25) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|