feat: sortie rotation
All checks were successful
Build / build (22) (pull_request) Successful in 36s
Build / build (20) (pull_request) Successful in 1m11s
Build / build (18) (pull_request) Successful in 1m8s

Closes #1179
This commit is contained in:
AMelonInsideLemon 2025-04-04 01:49:52 +02:00
parent abeb17ce44
commit 1ad7b3373d
3 changed files with 261 additions and 17 deletions

View File

@ -10,6 +10,14 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { CRng } from "@/src/services/rngService"; import { CRng } from "@/src/services/rngService";
import { ExportNightwave, ExportRegions } from "warframe-public-export-plus"; import { ExportNightwave, ExportRegions } from "warframe-public-export-plus";
import {
missionTags,
sortieBosses,
sortieBossNode,
sortieBossToFaction,
sortieFactionToFactionIndexes,
sortieFactionToSystemIndexes
} from "@/src/helpers/worlstateHelper";
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0 const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
@ -27,6 +35,7 @@ export const worldStateController: RequestHandler = (req, res) => {
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000), Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
Goals: [], Goals: [],
GlobalUpgrades: [], GlobalUpgrades: [],
Sorties: [],
LiteSorties: [], LiteSorties: [],
EndlessXpChoices: [], EndlessXpChoices: [],
SeasonInfo: { SeasonInfo: {
@ -154,6 +163,129 @@ export const worldStateController: RequestHandler = (req, res) => {
}); });
} }
// Sortie cycling every day
{
const dayStart = EPOCH + day * 86400000;
const dayEnd = dayStart + 86400000;
const rng = new CRng(day);
const boss = rng.randomElement(sortieBosses);
const modifiers = [
"SORTIE_MODIFIER_LOW_ENERGY",
"SORTIE_MODIFIER_IMPACT",
"SORTIE_MODIFIER_SLASH",
"SORTIE_MODIFIER_PUNCTURE",
"SORTIE_MODIFIER_EXIMUS",
"SORTIE_MODIFIER_MAGNETIC",
"SORTIE_MODIFIER_CORROSIVE",
"SORTIE_MODIFIER_VIRAL",
"SORTIE_MODIFIER_ELECTRICITY",
"SORTIE_MODIFIER_RADIATION",
"SORTIE_MODIFIER_GAS",
"SORTIE_MODIFIER_FIRE",
"SORTIE_MODIFIER_EXPLOSION",
"SORTIE_MODIFIER_FREEZE",
"SORTIE_MODIFIER_TOXIN",
"SORTIE_MODIFIER_POISON",
"SORTIE_MODIFIER_HAZARD_RADIATION",
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
"SORTIE_MODIFIER_HAZARD_ICE",
"SORTIE_MODIFIER_HAZARD_COLD",
"SORTIE_MODIFIER_SECONDARY_ONLY",
"SORTIE_MODIFIER_SHOTGUN_ONLY",
"SORTIE_MODIFIER_SNIPER_ONLY",
"SORTIE_MODIFIER_RIFLE_ONLY",
"SORTIE_MODIFIER_MELEE_ONLY",
"SORTIE_MODIFIER_BOW_ONLY"
];
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
const nodes: string[] = [];
const availableMissionIndexes: number[] = [];
for (const [key, value] of Object.entries(ExportRegions)) {
if (
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
value.name.indexOf("Archwing") == -1 &&
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
value.missionIndex != 5 && // Exclude MT_CAPTURE
value.missionIndex != 21 && // Exclude MT_PURIFY
value.missionIndex != 23 && // Exclude MT_JUNCTION
value.missionIndex <= 28
) {
if (!availableMissionIndexes.includes(value.missionIndex)) {
availableMissionIndexes.push(value.missionIndex);
}
nodes.push(key);
}
}
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
const missionTypes = new Set();
for (let i = 0; i < 3; i++) {
const randomIndex = rng.randomInt(0, nodes.length - 1);
const node = nodes[randomIndex];
let missionIndex = ExportRegions[node].missionIndex;
if (
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
missionIndex != 28 &&
rng.randomInt(0, 2) == 2
) {
missionIndex = rng.randomElement(availableMissionIndexes);
}
if (i == 2 && rng.randomInt(0, 2) == 2) {
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
const modifierType = rng.randomElement(filteredModifiers);
if (boss == "SORTIE_BOSS_PHORID") {
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
nodes.splice(randomIndex, 1);
continue;
} else if (sortieBossNode[boss]) {
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
continue;
}
}
const missionType = missionTags[missionIndex];
if (missionTypes.has(missionType)) {
i--;
continue;
}
const filteredModifiers =
missionType === "MT_TERRITORY"
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
: modifiers;
const modifierType = rng.randomElement(filteredModifiers);
selectedNodes.push({ missionType, modifierType, node });
nodes.splice(randomIndex, 1);
missionTypes.add(missionType);
}
worldState.Sorties.push({
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: day,
Boss: boss,
Variants: selectedNodes
});
}
// Archon Hunt cycling every week // Archon Hunt cycling every week
{ {
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3]; const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
@ -298,6 +430,7 @@ interface IWorldState {
Goals: IGoal[]; Goals: IGoal[];
SyndicateMissions: ISyndicateMission[]; SyndicateMissions: ISyndicateMission[];
GlobalUpgrades: IGlobalUpgrade[]; GlobalUpgrades: IGlobalUpgrade[];
Sorties: ISortie[];
LiteSorties: ILiteSortie[]; LiteSorties: ILiteSortie[];
NodeOverrides: INodeOverride[]; NodeOverrides: INodeOverride[];
EndlessXpChoices: IEndlessXpChoice[]; EndlessXpChoices: IEndlessXpChoice[];
@ -361,6 +494,20 @@ interface INodeOverride {
CustomNpcEncounters?: string; CustomNpcEncounters?: string;
} }
interface ISortie {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards";
Seed: number;
Boss: string;
Variants: {
missionType: string;
modifierType: string;
node: string;
}[];
}
interface ILiteSortie { interface ILiteSortie {
_id: IOid; _id: IOid;
Activation: IMongoDate; Activation: IMongoDate;

View File

@ -0,0 +1,114 @@
export const missionTags = [
"MT_ASSASSINATION",
"MT_EXTERMINATION",
"MT_SURVIVAL",
"MT_RESCUE",
"MT_SABOTAGE",
"MT_CAPTURE",
"MT_COUNTER_INTEL",
"MT_INTEL",
"MT_DEFENSE",
"MT_MOBILE_DEFENSE",
"MT_PVP",
"MT_MASTERY",
"MT_RECOVERY",
"MT_TERRITORY",
"MT_RETRIEVAL",
"MT_HIVE",
"MT_SALVAGE",
"MT_EXCAVATE",
"MT_RAID",
"MT_PURGE",
"MT_GENERIC",
"MT_PURIFY",
"MT_ARENA",
"MT_JUNCTION",
"MT_PURSUIT",
"MT_RACE",
"MT_ASSAULT",
"MT_EVACUATION",
"MT_LANDSCAPE",
"MT_RESOURCE_THEFT",
"MT_ENDLESS_EXTERMINATION",
"MT_ENDLESS_DUVIRI",
"MT_RAILJACK",
"MT_ARTIFACT",
"MT_CORRUPTION",
"MT_VOID_CASCADE",
"MT_ARMAGEDDON",
"MT_VAULTS",
"MT_ALCHEMY",
"MT_ASCENSION",
"MT_ENDLESS_CAPTURE",
"MT_OFFERING",
"MT_PVPVE"
];
export const sortieBosses = [
"SORTIE_BOSS_HYENA",
"SORTIE_BOSS_KELA",
"SORTIE_BOSS_VOR",
"SORTIE_BOSS_RUK",
"SORTIE_BOSS_HEK",
"SORTIE_BOSS_KRIL",
"SORTIE_BOSS_TYL",
"SORTIE_BOSS_JACKAL",
"SORTIE_BOSS_ALAD",
"SORTIE_BOSS_AMBULAS",
"SORTIE_BOSS_NEF",
"SORTIE_BOSS_RAPTOR",
"SORTIE_BOSS_PHORID",
"SORTIE_BOSS_LEPHANTIS",
"SORTIE_BOSS_INFALAD",
"SORTIE_BOSS_CORRUPTED_VOR"
];
export const sortieBossToFaction: Record<string, string> = {
SORTIE_BOSS_HYENA: "FC_CORPUS",
SORTIE_BOSS_KELA: "FC_GRINEER",
SORTIE_BOSS_VOR: "FC_GRINEER",
SORTIE_BOSS_RUK: "FC_GRINEER",
SORTIE_BOSS_HEK: "FC_GRINEER",
SORTIE_BOSS_KRIL: "FC_GRINEER",
SORTIE_BOSS_TYL: "FC_GRINEER",
SORTIE_BOSS_JACKAL: "FC_CORPUS",
SORTIE_BOSS_ALAD: "FC_CORPUS",
SORTIE_BOSS_AMBULAS: "FC_CORPUS",
SORTIE_BOSS_NEF: "FC_CORPUS",
SORTIE_BOSS_RAPTOR: "FC_CORPUS",
SORTIE_BOSS_PHORID: "FC_INFESTATION",
SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION",
SORTIE_BOSS_INFALAD: "FC_INFESTATION",
SORTIE_BOSS_CORRUPTED_VOR: "FC_CORRUPTED"
};
export const sortieFactionToSystemIndexes: Record<string, number[]> = {
FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18],
FC_CORPUS: [1, 4, 7, 8, 12, 15],
FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15],
FC_CORRUPTED: [14]
};
export const sortieFactionToFactionIndexes: Record<string, number[]> = {
FC_GRINEER: [0],
FC_CORPUS: [1],
FC_INFESTATION: [0, 1, 2],
FC_CORRUPTED: [3]
};
export const sortieBossNode: Record<string, string> = {
SORTIE_BOSS_HYENA: "SolNode127",
SORTIE_BOSS_KELA: "SolNode193",
SORTIE_BOSS_VOR: "SolNode108",
SORTIE_BOSS_RUK: "SolNode32",
SORTIE_BOSS_HEK: "SolNode24",
SORTIE_BOSS_KRIL: "SolNode99",
SORTIE_BOSS_TYL: "SolNode105",
SORTIE_BOSS_JACKAL: "SolNode104",
SORTIE_BOSS_ALAD: "SolNode53",
SORTIE_BOSS_AMBULAS: "SolNode51",
SORTIE_BOSS_NEF: "SettlementNode20",
SORTIE_BOSS_RAPTOR: "SolNode210",
SORTIE_BOSS_LEPHANTIS: "SolNode712",
SORTIE_BOSS_INFALAD: "SolNode705"
};

View File

@ -62,23 +62,6 @@
} }
} }
], ],
"Sorties": [
{
"_id": { "$oid": "663a4c7d4d932c97c0a3acd7" },
"Activation": { "$date": { "$numberLong": "1715097600000" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Reward": "/Lotus/Types/Game/MissionDecks/SortieRewards",
"Seed": 24491,
"Boss": "SORTIE_BOSS_TYL",
"ExtraDrops": [],
"Variants": [
{ "missionType": "MT_TERRITORY", "modifierType": "SORTIE_MODIFIER_ARMOR", "node": "SolNode122", "tileset": "GrineerOceanTileset" },
{ "missionType": "MT_MOBILE_DEFENSE", "modifierType": "SORTIE_MODIFIER_LOW_ENERGY", "node": "SolNode184", "tileset": "GrineerGalleonTileset" },
{ "missionType": "MT_LANDSCAPE", "modifierType": "SORTIE_MODIFIER_EXIMUS", "node": "SolNode228", "tileset": "EidolonTileset" }
],
"Twitter": true
}
],
"SyndicateMissions": [ "SyndicateMissions": [
{ {
"_id": { "$oid": "663a4fc5ba6f84724fa48049" }, "_id": { "$oid": "663a4fc5ba6f84724fa48049" },