forked from OpenWF/SpaceNinjaServer
fix: provide current & upcoming sortie if rollover is imminent (#1666)
Reviewed-on: OpenWF/SpaceNinjaServer#1666 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
parent
c13615c4df
commit
95562a97ad
@ -8,7 +8,7 @@ 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 { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
||||||
import { ISeasonChallenge, IWorldState } from "../types/worldStateTypes";
|
import { ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes";
|
||||||
|
|
||||||
const sortieBosses = [
|
const sortieBosses = [
|
||||||
"SORTIE_BOSS_HYENA",
|
"SORTIE_BOSS_HYENA",
|
||||||
@ -153,6 +153,10 @@ const microplanetEndlessJobs = [
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
const isBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
|
||||||
|
return Date.now() + 300_000 > date;
|
||||||
|
};
|
||||||
|
|
||||||
const getSortieTime = (day: number): number => {
|
const getSortieTime = (day: number): number => {
|
||||||
const dayStart = EPOCH + day * 86400000;
|
const dayStart = EPOCH + day * 86400000;
|
||||||
const date = new Date(dayStart);
|
const date = new Date(dayStart);
|
||||||
@ -167,6 +171,134 @@ const getSortieTime = (day: number): number => {
|
|||||||
return dayStart + (isDst ? 16 : 17) * 3600000;
|
return dayStart + (isDst ? 16 : 17) * 3600000;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||||
|
const dayStart = getSortieTime(day);
|
||||||
|
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dayEnd = getSortieTime(day + 1);
|
||||||
|
if (Date.now() >= dayEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.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
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
|
||||||
);
|
);
|
||||||
@ -220,10 +352,6 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const expiresBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
|
|
||||||
return Date.now() + 300_000 > date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getWorldState = (buildLabel?: string): IWorldState => {
|
export const getWorldState = (buildLabel?: string): IWorldState => {
|
||||||
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
||||||
const week = Math.trunc(day / 7);
|
const week = Math.trunc(day / 7);
|
||||||
@ -565,7 +693,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} while (expiresBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
|
} while (isBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
|
||||||
|
|
||||||
if (config.worldState?.creditBoost) {
|
if (config.worldState?.creditBoost) {
|
||||||
worldState.GlobalUpgrades.push({
|
worldState.GlobalUpgrades.push({
|
||||||
@ -605,140 +733,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sortie cycling every day
|
// Sortie cycling every day
|
||||||
{
|
pushSortieIfRelevant(worldState.Sorties, day - 1);
|
||||||
let genDay;
|
pushSortieIfRelevant(worldState.Sorties, day);
|
||||||
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
|
// Archon Hunt cycling every week
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user