feat: recover nightwave challenges
All checks were successful
Build / build (pull_request) Successful in 55s
All checks were successful
Build / build (pull_request) Successful in 55s
Closes #1534
This commit is contained in:
parent
4a2d863c9c
commit
869ee9e2a2
62
src/controllers/api/getPastWeeklyChallengesController.ts
Normal file
62
src/controllers/api/getPastWeeklyChallengesController.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { RequestHandler } from "express";
|
||||||
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
|
import { getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "@/src/services/worldStateService";
|
||||||
|
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
|
import { ISeasonChallenge } from "@/src/types/worldStateTypes";
|
||||||
|
import { ExportChallenges } from "warframe-public-export-plus";
|
||||||
|
|
||||||
|
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
|
||||||
|
const accountId = await getAccountIdForRequest(req);
|
||||||
|
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
|
||||||
|
const worldState = getWorldState(undefined);
|
||||||
|
|
||||||
|
if (worldState.SeasonInfo) {
|
||||||
|
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
|
||||||
|
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
|
||||||
|
const nightwaveSeason = worldState.SeasonInfo.Season;
|
||||||
|
const timeMs = worldState.Time * 1000;
|
||||||
|
const completedChallengesIds = new Set<string>();
|
||||||
|
|
||||||
|
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
|
||||||
|
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
|
||||||
|
if (nightwaveSeason == entryNightwaveSeason) {
|
||||||
|
const meta = Object.entries(ExportChallenges).find(
|
||||||
|
([key]) => key.split("/").pop() === challengeHistory.challenge
|
||||||
|
);
|
||||||
|
if (meta) {
|
||||||
|
const [, challengeMeta] = meta;
|
||||||
|
const challengeProgress = inventory.ChallengeProgress.find(
|
||||||
|
c => c.Name === challengeHistory.challenge
|
||||||
|
);
|
||||||
|
|
||||||
|
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
|
||||||
|
completedChallengesIds.add(challengeHistory.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const PastWeeklyChallenges: ISeasonChallenge[] = [];
|
||||||
|
|
||||||
|
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
|
||||||
|
|
||||||
|
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
|
||||||
|
const tempActs: ISeasonChallenge[] = [];
|
||||||
|
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
|
||||||
|
|
||||||
|
for (const act of tempActs) {
|
||||||
|
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
|
||||||
|
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
|
||||||
|
act.Permanent = true;
|
||||||
|
}
|
||||||
|
PastWeeklyChallenges.push(act);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
week--;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
|
||||||
|
}
|
||||||
|
};
|
@ -66,6 +66,7 @@ import { getGuildLogController } from "@/src/controllers/api/getGuildLogControll
|
|||||||
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
|
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
|
||||||
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
|
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
|
||||||
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
|
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
|
||||||
|
import { getPastWeeklyChallengesController } from "@/src/controllers/api/getPastWeeklyChallengesController";
|
||||||
import { getShipController } from "@/src/controllers/api/getShipController";
|
import { getShipController } from "@/src/controllers/api/getShipController";
|
||||||
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
|
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
|
||||||
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
|
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
|
||||||
@ -195,6 +196,7 @@ apiRouter.get("/getGuildLog.php", getGuildLogController);
|
|||||||
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
|
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
|
||||||
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
|
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
|
||||||
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
|
||||||
|
apiRouter.get("/getPastWeeklyChallenges.php", getPastWeeklyChallengesController)
|
||||||
apiRouter.get("/getShip.php", getShipController);
|
apiRouter.get("/getShip.php", getShipController);
|
||||||
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
|
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
|
||||||
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
|
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
|
||||||
|
@ -180,7 +180,7 @@ const microplanetEndlessJobs: readonly string[] = [
|
|||||||
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
|
||||||
];
|
];
|
||||||
|
|
||||||
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
|
export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
|
||||||
|
|
||||||
const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
|
const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
|
||||||
return nowMs + 300_000 > thenMs;
|
return nowMs + 300_000 > thenMs;
|
||||||
@ -365,10 +365,10 @@ interface IRotatingSeasonChallengePools {
|
|||||||
daily: string[];
|
daily: string[];
|
||||||
weekly: string[];
|
weekly: string[];
|
||||||
hardWeekly: string[];
|
hardWeekly: string[];
|
||||||
hasWeeklyPermanent: boolean;
|
weeklyPermanent: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
|
export const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
|
||||||
const syndicate = ExportSyndicates[syndicateTag];
|
const syndicate = ExportSyndicates[syndicateTag];
|
||||||
return {
|
return {
|
||||||
daily: syndicate.dailyChallenges!,
|
daily: syndicate.dailyChallenges!,
|
||||||
@ -380,7 +380,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
|
|||||||
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
|
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
|
||||||
),
|
),
|
||||||
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
|
weeklyPermanent: syndicate.weeklyChallenges!.filter(x =>
|
||||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
|
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -401,6 +401,7 @@ const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: numb
|
|||||||
const pushSeasonWeeklyChallenge = (
|
const pushSeasonWeeklyChallenge = (
|
||||||
activeChallenges: ISeasonChallenge[],
|
activeChallenges: ISeasonChallenge[],
|
||||||
pool: string[],
|
pool: string[],
|
||||||
|
nightwaveSeason: number,
|
||||||
week: number,
|
week: number,
|
||||||
id: number
|
id: number
|
||||||
): void => {
|
): void => {
|
||||||
@ -413,51 +414,59 @@ const pushSeasonWeeklyChallenge = (
|
|||||||
challenge = rng.randomElement(pool)!;
|
challenge = rng.randomElement(pool)!;
|
||||||
} while (activeChallenges.some(x => x.Challenge == challenge));
|
} while (activeChallenges.some(x => x.Challenge == challenge));
|
||||||
activeChallenges.push({
|
activeChallenges.push({
|
||||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
_id: {
|
||||||
|
$oid:
|
||||||
|
(nightwaveSeason + 1).toString().padStart(4, "0") +
|
||||||
|
"bb2d9d00cb47" +
|
||||||
|
challengeId.toString().padStart(8, "0")
|
||||||
|
},
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
Challenge: challenge
|
Challenge: challenge
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushWeeklyActs = (
|
export const pushWeeklyActs = (
|
||||||
activeChallenges: ISeasonChallenge[],
|
activeChallenges: ISeasonChallenge[],
|
||||||
pools: IRotatingSeasonChallengePools,
|
pools: IRotatingSeasonChallengePools,
|
||||||
week: number
|
week: number,
|
||||||
|
nightwaveStartTimestamp: number,
|
||||||
|
nightwaveSeason: number
|
||||||
): void => {
|
): void => {
|
||||||
const weekStart = EPOCH + week * 604800000;
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 0);
|
||||||
const weekEnd = weekStart + 604800000;
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 1);
|
||||||
|
if (pools.weeklyPermanent.length > 0) {
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0);
|
const weekStart = EPOCH + week * unixTimesInMs.week;
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1);
|
const weekEnd = weekStart + unixTimesInMs.week;
|
||||||
if (pools.hasWeeklyPermanent) {
|
const nightwaveWeekStart = ((): number => {
|
||||||
activeChallenges.push({
|
const nightwaveStartDate = new Date(nightwaveStartTimestamp);
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
const dayOffset = (nightwaveStartDate.getDay() + 6) % 7;
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
nightwaveStartDate.setDate(nightwaveStartDate.getDate() - dayOffset);
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
nightwaveStartDate.setHours(0, 0, 0, 0);
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions"
|
return nightwaveStartDate.getTime();
|
||||||
});
|
})();
|
||||||
activeChallenges.push({
|
const nightwaveWeek = Math.trunc((weekStart - nightwaveWeekStart) / unixTimesInMs.week);
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
|
const weeklyPermanentIndex = (nightwaveWeek * 3) % pools.weeklyPermanent.length;
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
for (let i = 0; i < 3; i++) {
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
activeChallenges.push({
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus"
|
_id: {
|
||||||
});
|
$oid:
|
||||||
activeChallenges.push({
|
(nightwaveSeason + 1).toString().padStart(4, "0") +
|
||||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
|
"b96e9d00cb47" +
|
||||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
(week * 7 + 2 + i).toString().padStart(8, "0")
|
||||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
},
|
||||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
|
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||||
});
|
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2);
|
Challenge: pools.weeklyPermanent[weeklyPermanentIndex + i]
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3);
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2);
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 2);
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3);
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 3);
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4);
|
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 4);
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5);
|
|
||||||
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6);
|
|
||||||
}
|
}
|
||||||
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 5);
|
||||||
|
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 6);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
|
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
|
||||||
@ -1552,11 +1561,13 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
// Nightwave Challenges
|
// Nightwave Challenges
|
||||||
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
|
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
|
||||||
if (nightwaveSyndicateTag) {
|
if (nightwaveSyndicateTag) {
|
||||||
|
const nightwaveStartTimestamp = 1747851300000;
|
||||||
|
const nightwaveSeason = nightwaveTagToSeason[nightwaveSyndicateTag];
|
||||||
worldState.SeasonInfo = {
|
worldState.SeasonInfo = {
|
||||||
Activation: { $date: { $numberLong: "1715796000000" } },
|
Activation: { $date: { $numberLong: nightwaveStartTimestamp.toString() } },
|
||||||
Expiry: { $date: { $numberLong: "2000000000000" } },
|
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||||
AffiliationTag: nightwaveSyndicateTag,
|
AffiliationTag: nightwaveSyndicateTag,
|
||||||
Season: nightwaveTagToSeason[nightwaveSyndicateTag],
|
Season: nightwaveSeason,
|
||||||
Phase: 0,
|
Phase: 0,
|
||||||
Params: "",
|
Params: "",
|
||||||
ActiveChallenges: []
|
ActiveChallenges: []
|
||||||
@ -1568,9 +1579,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
|||||||
if (isBeforeNextExpectedWorldStateRefresh(timeMs, EPOCH + (day + 1) * 86400000)) {
|
if (isBeforeNextExpectedWorldStateRefresh(timeMs, EPOCH + (day + 1) * 86400000)) {
|
||||||
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1));
|
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1));
|
||||||
}
|
}
|
||||||
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week);
|
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week, nightwaveStartTimestamp, nightwaveSeason);
|
||||||
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
|
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
|
||||||
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week + 1);
|
pushWeeklyActs(
|
||||||
|
worldState.SeasonInfo.ActiveChallenges,
|
||||||
|
pools,
|
||||||
|
week + 1,
|
||||||
|
nightwaveStartTimestamp,
|
||||||
|
nightwaveSeason
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +265,7 @@ export interface IEndlessXpChoice {
|
|||||||
export interface ISeasonChallenge {
|
export interface ISeasonChallenge {
|
||||||
_id: IOid;
|
_id: IOid;
|
||||||
Daily?: boolean;
|
Daily?: boolean;
|
||||||
|
Permanent?: boolean; // only for getPastWeeklyChallenges response
|
||||||
Activation: IMongoDate;
|
Activation: IMongoDate;
|
||||||
Expiry: IMongoDate;
|
Expiry: IMongoDate;
|
||||||
Challenge: string;
|
Challenge: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user