SpaceNinjaServer/src/services/worldStateService.ts
2024-07-05 08:45:51 +02:00

746 lines
30 KiB
TypeScript

import { WorldState } from "@/src/models/worldStateModel";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import {
IActiveChallenge,
IActiveMission,
IJob,
ILiteSortie,
ISortie,
ISyndicateMission,
IVoidStorm,
IWorldStateDocument
} from "@/src/types/worldStateTypes";
import { getRandomNodes, getCurrentRotation, getRandomRotation } from "@/src/helpers/worldstateHelpers";
import { ExportRegions, ExportNightwave } from "warframe-public-export-plus";
import { logger } from "@/src/utils/logger";
import {
factionSyndicates,
neutralJobsSyndicates,
neutralSyndicates,
restSyndicates,
CertusNormalJobs,
CertusNarmerJobs,
EntratiNormalJobs,
missionIndexToMissionTypes,
validFissureMissionIndex,
omniaNodes,
endStates,
modifierTypes,
voidTiers,
FortunaNarmerJobs,
FortunaNormalJobs,
liteSortiesMissionIndex,
EntratiEndlessJobs,
normalCircuitRotations,
hardCircuitRotations,
liteSortiesBosses
} from "@/src/constants/worldStateConstants";
export const createWorldState = () => {
let ws = new WorldState() as IWorldStateDocument;
ws = updateSyndicateMissions(ws);
ws = updateVoidFissures(ws);
ws = updateSorties(ws);
ws = updateCircuit(ws);
ws = updateNightWave(ws);
ws = updateNodeOverrides(ws);
return ws;
};
export const getWorldState = async () => {
let ws = await WorldState.findOne();
if (!ws) {
ws = createWorldState();
}
return ws as IWorldStateDocument;
};
export const worldStateRunner = async () => {
await getWorldState();
setInterval(async () => {
try {
logger.info("Update worldState");
let ws = await getWorldState();
ws = updateSyndicateMissions(ws);
ws = updateVoidFissures(ws);
ws = updateSorties(ws);
ws = updateCircuit(ws);
ws = updateNightWave(ws);
ws = updateNodeOverrides(ws);
await ws.save();
} catch (error) {
logger.error("Failed to update worldState:", error);
}
}, unixTimesInMs.minute);
};
const updateSyndicateMissions = (ws: IWorldStateDocument) => {
const currentDate = Date.now();
const oneDayIntervalStart =
Math.floor(currentDate / unixTimesInMs.day) * unixTimesInMs.day + 16 * unixTimesInMs.hour;
const oneDayIntervalEnd = oneDayIntervalStart + unixTimesInMs.day;
const neutralJobsIntervalStart = Math.floor(currentDate / (2.5 * unixTimesInMs.hour)) * (2.5 * unixTimesInMs.hour);
const neutralJobsIntervalEnd = neutralJobsIntervalStart + 2.5 * unixTimesInMs.hour;
const neutralSeed = Math.floor(Math.random() * 99999 + 1);
try {
const syndicateArray = ws.SyndicateMissions || [];
const existingTags = syndicateArray.map(syndicate => syndicate.Tag);
const createNewSyndicateEntry = (tag: string): ISyndicateMission => {
switch (true) {
case factionSyndicates.includes(tag):
return {
Tag: tag,
Seed: Math.floor(Math.random() * 99999 + 1),
Nodes: getRandomNodes(7),
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd
};
case neutralJobsSyndicates.includes(tag):
return {
Tag: tag,
Seed: neutralSeed,
Nodes: [],
Activation: neutralJobsIntervalStart,
Expiry: neutralJobsIntervalEnd,
Jobs: getJobs(tag)
};
case neutralSyndicates.includes(tag):
return {
Tag: tag,
Seed: neutralSeed,
Nodes: [],
Activation: neutralJobsIntervalStart,
Expiry: neutralJobsIntervalEnd
};
case restSyndicates.includes(tag):
return {
Tag: tag,
Seed: Math.floor(Math.random() * 99999 + 1),
Nodes: [],
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd
};
default:
throw new Error(`Unhandled syndicate tag: ${tag}`);
}
};
[...factionSyndicates, ...neutralJobsSyndicates, ...neutralSyndicates, ...restSyndicates].forEach(tag => {
if (!existingTags.includes(tag)) {
syndicateArray.push(createNewSyndicateEntry(tag));
} else {
const syndicateIndex = existingTags.indexOf(tag);
const shouldUpdate = currentDate >= syndicateArray[syndicateIndex].Expiry;
if (shouldUpdate) {
syndicateArray[syndicateIndex] = {
...syndicateArray[syndicateIndex],
Tag: tag,
Seed:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralSeed
: Math.floor(Math.random() * 99999 + 1),
Nodes:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? []
: getRandomNodes(7),
Activation:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralJobsIntervalStart
: oneDayIntervalStart,
Expiry:
neutralJobsSyndicates.includes(tag) || neutralSyndicates.includes(tag)
? neutralJobsIntervalEnd
: oneDayIntervalEnd,
Jobs: neutralJobsSyndicates.includes(tag) ? getJobs(tag) : undefined
};
}
}
});
ws.SyndicateMissions = syndicateArray;
return ws;
} catch (error) {
throw new Error(`Error while updating Syndicates ${error}`);
}
};
const getJobs = (tag: string): IJob[] => {
const rotation = getCurrentRotation();
switch (tag) {
case "CetusSyndicate":
return [
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${rotation}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [410, 410, 410]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${rotation}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
maxEnemyLevel: 30,
xpAmounts: [750, 750, 750]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${rotation}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
maxEnemyLevel: 40,
xpAmounts: [580, 580, 580, 850]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${rotation}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
maxEnemyLevel: 50,
xpAmounts: [580, 580, 580, 580, 1130]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${rotation}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [710, 710, 710, 710, 1390]
},
{
jobType: CertusNormalJobs[Math.floor(Math.random() * CertusNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${rotation}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [840, 840, 840, 840, 1660]
},
{
jobType: CertusNarmerJobs[Math.floor(Math.random() * CertusNarmerJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/NarmerTable${rotation}Rewards`,
masteryReq: 0,
minEnemyLevel: 50,
maxEnemyLevel: 70,
xpAmounts: [820, 820, 820, 820, 1610]
}
];
case "SolarisSyndicate":
return [
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${rotation}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [410, 410, 410]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${rotation}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
maxEnemyLevel: 30,
xpAmounts: [750, 750, 750]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${rotation}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
maxEnemyLevel: 40,
xpAmounts: [580, 580, 580, 850]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${rotation}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
maxEnemyLevel: 50,
xpAmounts: [580, 580, 580, 580, 1130]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${rotation}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [710, 710, 710, 710, 1390]
},
{
jobType: FortunaNormalJobs[Math.floor(Math.random() * FortunaNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${rotation}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [840, 840, 840, 840, 1660]
},
{
jobType: FortunaNarmerJobs[Math.floor(Math.random() * FortunaNarmerJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusNarmerTable${rotation}Rewards`,
masteryReq: 0,
minEnemyLevel: 50,
maxEnemyLevel: 70,
xpAmounts: [820, 820, 820, 820, 1610]
}
];
case "EntratiSyndicate":
return [
{
jobType: EntratiNormalJobs[Math.floor(Math.random() * EntratiNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${getRandomRotation()}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
maxEnemyLevel: 15,
xpAmounts: [5, 5, 5]
},
{
jobType: EntratiNormalJobs[Math.floor(Math.random() * EntratiNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${getRandomRotation()}Rewards`,
masteryReq: 1,
minEnemyLevel: 15,
maxEnemyLevel: 25,
xpAmounts: [9, 9, 9]
},
{
jobType: EntratiEndlessJobs[Math.floor(Math.random() * EntratiEndlessJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTable${getRandomRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 25,
maxEnemyLevel: 30,
endless: true,
xpAmounts: [14, 14, 14]
},
{
jobType: EntratiNormalJobs[Math.floor(Math.random() * EntratiNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${getRandomRotation()}Rewards`,
masteryReq: 2,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [19, 19, 19, 29]
},
{
jobType: EntratiNormalJobs[Math.floor(Math.random() * EntratiNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETable${getRandomRotation()}Rewards`,
masteryReq: 3,
minEnemyLevel: 40,
maxEnemyLevel: 60,
xpAmounts: [21, 21, 21, 21, 41]
},
{
jobType: EntratiNormalJobs[Math.floor(Math.random() * EntratiNormalJobs.length)],
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETable${getRandomRotation()}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
maxEnemyLevel: 100,
xpAmounts: [25, 25, 25, 25, 50]
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierATable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [2, 2, 2, 4],
locationTag: "ChamberB",
isVault: true
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierBTable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
maxEnemyLevel: 50,
xpAmounts: [4, 4, 4, 5],
locationTag: "ChamberA",
isVault: true
},
{
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/VaultBountyTierCTable${getCurrentRotation()}Rewards`,
masteryReq: 5,
minEnemyLevel: 50,
maxEnemyLevel: 60,
xpAmounts: [5, 5, 5, 7],
locationTag: "ChamberC",
isVault: true
}
];
default:
throw new Error(`Error while updating Syndicates: Unknown Jobs syndicate ${tag}`);
}
};
const updateVoidFissures = (ws: IWorldStateDocument) => {
const curDate = Date.now();
try {
const voidFissures = ws.ActiveMissions;
const voidStorms = ws.VoidStorms;
const voidFissuresByTier: { [key: string]: IActiveMission[] } = {
VoidT1: [],
VoidT2: [],
VoidT3: [],
VoidT4: [],
VoidT5: [],
VoidT6: []
};
const voidStormsByTier: { [key: string]: IVoidStorm[] } = {
VoidT1: [],
VoidT2: [],
VoidT3: [],
VoidT4: [],
VoidT5: [],
VoidT6: []
};
if (voidFissures) {
voidFissures.forEach(mission => {
const tier = mission.Modifier;
if (tier) {
if (!voidFissuresByTier[tier]) {
voidFissuresByTier[tier] = [];
}
voidFissuresByTier[tier].push(mission);
}
});
}
if (voidStorms) {
voidStorms.forEach(mission => {
const tier = mission.ActiveMissionTier;
if (tier) {
if (!voidStormsByTier[tier]) {
voidStormsByTier[tier] = [];
}
voidStormsByTier[tier].push(mission);
}
});
}
voidTiers.forEach(voidTier => {
if (voidFissuresByTier[voidTier].length < 3) {
const nodeData = getRandomFissureNode(false, voidTier == "VoidT6");
if (!nodeData.missionIndex) nodeData.missionIndex = 1;
const node = {
Region: nodeData.systemIndex,
Seed: Math.floor(Math.random() * 99999 + 1),
Activation: curDate,
Expiry: curDate + Math.floor(Math.random() * unixTimesInMs.hour),
Node: nodeData.nodeKey,
MissionType: missionIndexToMissionTypes[nodeData.missionIndex],
Modifier: voidTier,
Hard: Math.random() < 0.1
} as IActiveMission;
voidFissures?.push(node);
}
if (voidStormsByTier[voidTier].length < 2) {
const nodeData = getRandomFissureNode(true, voidTier == "VoidT6");
const node = {
Activation: curDate,
Expiry: curDate + Math.floor(Math.random() * unixTimesInMs.hour),
Node: nodeData.nodeKey,
ActiveMissionTier: voidTier
} as IVoidStorm;
voidStorms?.push(node);
}
});
return ws;
} catch (error) {
throw new Error(`Error while updating VoidFissures: ${error}`);
}
};
const getRandomFissureNode = (isRailJack: boolean, isOmnia: boolean) => {
const validNodes = Object.entries(ExportRegions)
.map(([key, node]) => ({ ...node, nodeKey: key }))
.filter(node => {
if (node.missionIndex && node.missionName) {
return (
validFissureMissionIndex.includes(node.missionIndex) &&
(!node.missionName.includes("Archwing") || !node.missionName.includes("Railjack"))
);
} else return false;
});
if (isRailJack) {
const railJackNodes = Object.keys(ExportRegions).filter(key => key.includes("CrewBattleNode"));
const randomKey = railJackNodes[Math.floor(Math.random() * railJackNodes.length)];
return { nodeKey: randomKey };
}
if (isOmnia) {
const validOmniaNodes = validNodes.filter(node => omniaNodes.includes(node.nodeKey));
const randomNode = validOmniaNodes[Math.floor(Math.random() * validOmniaNodes.length)];
return {
nodeKey: randomNode.nodeKey,
systemIndex: randomNode.systemIndex,
missionIndex: randomNode.missionIndex
};
}
const randomNode = validNodes[Math.floor(Math.random() * validNodes.length)];
return {
nodeKey: randomNode.nodeKey,
systemIndex: randomNode.systemIndex,
missionIndex: randomNode.missionIndex
};
};
const updateSorties = (ws: IWorldStateDocument) => {
const currentDate = Date.now();
const oneDayIntervalStart =
Math.floor(currentDate / unixTimesInMs.day) * unixTimesInMs.day + 16 * unixTimesInMs.hour;
const oneDayIntervalEnd = oneDayIntervalStart + unixTimesInMs.day;
const oneWeekIntervalStart =
Math.floor(currentDate / unixTimesInMs.week) * unixTimesInMs.week + 16 * unixTimesInMs.hour;
const oneWeekIntervalEnd = oneWeekIntervalStart + unixTimesInMs.week;
type node = {
systemIndex: number;
missionIndex: number;
nodeKey: string;
};
const nodes = Object.entries(ExportRegions).map(([key, node]) => {
return {
systemIndex: node.systemIndex,
missionIndex: node.missionIndex,
nodeKey: key
} as node;
});
try {
const liteSorties: ILiteSortie[] = ws?.LiteSorties;
const sorties: ISortie[] = ws?.Sorties;
[...liteSorties, ...sorties].forEach((sortie, index, array) => {
if (currentDate >= sortie.Expiry) array.splice(index, 1);
});
if (liteSorties.length < 1) {
const liteSortiesBoss = liteSortiesBosses[Math.floor(Math.random() * liteSortiesBosses.length)];
const liteSortiesSystemIndex = nodes.filter(node => {
switch (liteSortiesBoss) {
case "SORTIE_BOSS_AMAR":
return node.systemIndex === 3;
case "SORTIE_BOSS_NIRA":
return node.systemIndex === 4;
case "SORTIE_BOSS_PAAZUL":
return node.systemIndex === 0;
default:
throw new Error(`Unknown liteSortiesBoss: ${liteSortiesBoss}`);
}
});
const filteredLiteSortiesNodes = liteSortiesMissionIndex.map(missionIndexArray =>
liteSortiesSystemIndex.filter(node => missionIndexArray.includes(node.missionIndex))
);
const selectedLiteSortiesNodes = filteredLiteSortiesNodes.map(
filteredNodes => filteredNodes[Math.floor(Math.random() * filteredNodes.length)]
);
const sortie = {
Activation: oneWeekIntervalStart,
Expiry: oneWeekIntervalEnd,
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: Math.floor(Math.random() * 99999 + 1),
Boss: liteSortiesBoss,
Missions: selectedLiteSortiesNodes.map(node => ({
missionType: missionIndexToMissionTypes[node.missionIndex],
node: node.nodeKey
}))
};
liteSorties.push(sortie);
}
if (sorties.length < 1) {
const randomState = endStates[Math.floor(Math.random() * endStates.length)];
const selectedSortieNodes = Array.from({ length: 3 }, () => {
const randomIndex = Math.floor(Math.random() * randomState.regions.length);
const filteredNodes = nodes.filter(
node =>
randomState.regions[randomIndex].systemIndex === node.systemIndex &&
randomState.regions[randomIndex].missionIndex.includes(node.missionIndex)
);
return filteredNodes[Math.floor(Math.random() * filteredNodes.length)];
});
const sortie: ISortie = {
Activation: oneDayIntervalStart,
Expiry: oneDayIntervalEnd,
ExtraDrops: [],
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: Math.floor(Math.random() * 99999 + 1),
Boss: randomState.bossName,
Variants: selectedSortieNodes.map(node => ({
missionType: missionIndexToMissionTypes[node.missionIndex],
modifierType: modifierTypes[Math.floor(Math.random() * modifierTypes.length)],
node: node.nodeKey,
tileset: "CorpusShipTileset" // needs more info about tilesets used in nodes
})),
Twitter: true
};
sorties.push(sortie);
}
return ws;
} catch (error) {
throw new Error(`Error while updating Sorties ${error}`);
}
};
const updateCircuit = (ws: IWorldStateDocument) => {
try {
const curWeek = Math.floor(Date.now() / unixTimesInMs.week);
const normalIndex = curWeek % normalCircuitRotations.length;
const hardIndex = curWeek % hardCircuitRotations.length;
ws.EndlessXpChoices = [
{ Category: "EXC_NORMAL", Choices: normalCircuitRotations[normalIndex] },
{ Category: "EXC_HARD", Choices: hardCircuitRotations[hardIndex] }
];
return ws;
} catch (error) {
throw new Error(`Error while updating Circuit ${error}`);
}
};
const updateNightWave = (ws: IWorldStateDocument) => {
const currentDate = Date.now();
const oneDayIntervalStart =
Math.floor(currentDate / unixTimesInMs.day) * unixTimesInMs.day + 16 * unixTimesInMs.hour;
const oneDayIntervalEnd = oneDayIntervalStart + unixTimesInMs.day;
const oneWeekIntervalStart =
Math.floor(currentDate / unixTimesInMs.week) * unixTimesInMs.week + 16 * unixTimesInMs.hour;
const oneWeekIntervalEnd = oneWeekIntervalStart + unixTimesInMs.week;
try {
let season = ws.SeasonInfo;
if (!season)
season = {
Activation: 1715796000000,
Expiry: 9999999999999,
AffiliationTag: "RadioLegionIntermission10Syndicate",
Season: 12,
Phase: 0,
Params: "",
ActiveChallenges: [],
UsedChallenges: []
};
const activeChallenges = season.ActiveChallenges.filter(challenge => currentDate < challenge.Expiry);
const usedChallenges = season.UsedChallenges;
const exportChallenges = Object.keys(ExportNightwave.challenges);
const filterChallenges = (prefix: string): string[] =>
exportChallenges.filter(challenge => challenge.startsWith(prefix));
const dailyChallenges = filterChallenges("/Lotus/Types/Challenges/Seasons/Daily/");
const weeklyChallenges = filterChallenges("/Lotus/Types/Challenges/Seasons/Weekly/");
const weeklyHardChallenges = filterChallenges("/Lotus/Types/Challenges/Seasons/WeeklyHard/");
let dailyCount = 0,
weeklyCount = 0,
weeklyHardCount = 0;
activeChallenges.forEach(challenge => {
if (challenge.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")) dailyCount++;
else if (challenge.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/")) weeklyCount++;
else if (challenge.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")) weeklyHardCount++;
});
const addChallenges = (
count: number,
limit: number,
intervalStart: number,
intervalEnd: number,
challengesArray: string[],
isDaily = false
) => {
while (count < limit) {
challengesArray = challengesArray.filter(challenge => !usedChallenges.includes(challenge));
const uniqueName = challengesArray[Math.floor(Math.random() * challengesArray.length)];
const challenge: IActiveChallenge = {
Activation: intervalStart,
Expiry: intervalEnd,
Challenge: uniqueName
};
if (isDaily) {
challenge.Daily = true;
} else {
usedChallenges.push(uniqueName);
}
activeChallenges.push(challenge);
count++;
}
};
addChallenges(dailyCount, 3, oneDayIntervalStart, oneDayIntervalEnd, dailyChallenges, true);
addChallenges(weeklyCount, 5, oneWeekIntervalStart, oneWeekIntervalEnd, weeklyChallenges);
addChallenges(weeklyHardCount, 2, oneWeekIntervalStart, oneWeekIntervalEnd, weeklyHardChallenges);
season = {
Activation: season.Activation || 1715796000000,
Expiry: season.Expiry || 9999999999999,
AffiliationTag: season.AffiliationTag || "RadioLegionIntermission10Syndicate",
Season: season.Season || 12,
Phase: season.Phase || 0,
Params: season.Params || "",
ActiveChallenges: activeChallenges,
UsedChallenges: usedChallenges
};
ws.SeasonInfo = season;
return ws;
} catch (error) {
throw new Error(`Error while updating NightWave ${error}`);
}
};
const updateNodeOverrides = (ws: IWorldStateDocument) => {
try {
const curWeek = Math.floor(Date.now() / unixTimesInMs.week);
let overrides = ws.NodeOverrides;
if (overrides == undefined || overrides.length < 1) {
overrides = [
{ Node: "EuropaHUB", Hide: true },
{ Node: "ErisHUB", Hide: true },
{ Node: "VenusHUB", Hide: true },
{ Node: "SolNode802", Seed: curWeek }, // Elite sanctuary onslaught
{
Node: "EarthHUB",
Hide: false,
LevelOverride: "/Lotus/Levels/Proc/Hub/RelayStationHubTwoB"
},
{
Node: "MercuryHUB",
Hide: true,
LevelOverride: "/Lotus/Levels/Proc/Hub/RelayStationHubHydroid"
}
];
} else {
const solNodeIndex = overrides.findIndex(node => node.Node === "SolNode802");
if (solNodeIndex !== -1) {
if (overrides[solNodeIndex].Seed !== curWeek) overrides[solNodeIndex].Seed = curWeek;
} else {
overrides.push({ Node: "SolNode802", Seed: curWeek });
}
}
ws.NodeOverrides = overrides;
return ws;
} catch (error) {
throw new Error(`Error while updating NodeOverrides ${error}`);
}
};