返回提交历史
Added
src/controllers/api/getPastWeeklyChallengesController.ts
+62
-0
Modified
src/routes/api.ts
+2
-0
Modified
src/services/worldStateService.ts
+57
-42
Modified
src/types/worldStateTypes.ts
+1
-0
XFEstudio/XFESpaceNinjaServer
feat: recover nightwave challenges (#2593)
Closes #1534 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2593 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
67975263
代码差异
4 个文件
+122
-42
@@ -0,0 +1,62 @@
1
import { RequestHandler } from "express";
2
import { getAccountIdForRequest } from "@/src/services/loginService";
3
import { getInventory } from "@/src/services/inventoryService";
4
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "@/src/services/worldStateService";
5
import { unixTimesInMs } from "@/src/constants/timeConstants";
6
import { ISeasonChallenge } from "@/src/types/worldStateTypes";
7
import { ExportChallenges } from "warframe-public-export-plus";
8
9
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
10
const accountId = await getAccountIdForRequest(req);
11
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
12
const worldState = getWorldState(undefined);
13
14
if (worldState.SeasonInfo) {
15
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
16
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
17
const nightwaveSeason = worldState.SeasonInfo.Season;
18
const timeMs = worldState.Time * 1000;
19
const completedChallengesIds = new Set<string>();
20
21
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
22
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
23
if (nightwaveSeason == entryNightwaveSeason) {
24
const meta = Object.entries(ExportChallenges).find(
25
([key]) => key.split("/").pop() === challengeHistory.challenge
26
);
27
if (meta) {
28
const [, challengeMeta] = meta;
29
const challengeProgress = inventory.ChallengeProgress.find(
30
c => c.Name === challengeHistory.challenge
31
);
32
33
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
34
completedChallengesIds.add(challengeHistory.id);
35
}
36
}
37
}
38
});
39
40
const PastWeeklyChallenges: ISeasonChallenge[] = [];
41
42
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
43
44
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
45
const tempActs: ISeasonChallenge[] = [];
46
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
47
48
for (const act of tempActs) {
49
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
50
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
51
act.Permanent = true;
52
}
53
PastWeeklyChallenges.push(act);
54
}
55
}
56
57
week--;
58
}
59
60
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
61
}
62
};
@@ -66,6 +66,7 @@ import { getGuildLogController } from "@/src/controllers/api/getGuildLogControll
66
66
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
67
67
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
68
68
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
69
import { getPastWeeklyChallengesController } from "@/src/controllers/api/getPastWeeklyChallengesController";
69
70
import { getShipController } from "@/src/controllers/api/getShipController";
70
71
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
71
72
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
@@ -195,6 +196,7 @@ apiRouter.get("/getGuildLog.php", getGuildLogController);
195
196
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
196
197
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
197
198
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
199
apiRouter.get("/getPastWeeklyChallenges.php", getPastWeeklyChallengesController)
198
200
apiRouter.get("/getShip.php", getShipController);
199
201
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
200
202
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
@@ -180,7 +180,7 @@ const microplanetEndlessJobs: readonly string[] = [
180
180
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
181
181
];
182
182
183
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
183
export const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
184
184
185
185
const isBeforeNextExpectedWorldStateRefresh = (nowMs: number, thenMs: number): boolean => {
186
186
return nowMs + 300_000 > thenMs;
@@ -365,10 +365,10 @@ interface IRotatingSeasonChallengePools {
365
365
daily: string[];
366
366
weekly: string[];
367
367
hardWeekly: string[];
368
hasWeeklyPermanent: boolean;
368
weeklyPermanent: string[];
369
369
}
370
370
371
const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
371
export const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallengePools => {
372
372
const syndicate = ExportSyndicates[syndicateTag];
373
373
return {
374
374
daily: syndicate.dailyChallenges!,
@@ -380,7 +380,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
380
380
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
381
381
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
382
382
),
383
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
383
weeklyPermanent: syndicate.weeklyChallenges!.filter(x =>
384
384
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
385
385
)
386
386
};
@@ -401,6 +401,7 @@ const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: numb
401
401
const pushSeasonWeeklyChallenge = (
402
402
activeChallenges: ISeasonChallenge[],
403
403
pool: string[],
404
nightwaveSeason: number,
404
405
week: number,
405
406
id: number
406
407
): void => {
@@ -413,51 +414,57 @@ const pushSeasonWeeklyChallenge = (
413
414
challenge = rng.randomElement(pool)!;
414
415
} while (activeChallenges.some(x => x.Challenge == challenge));
415
416
activeChallenges.push({
416
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
417
_id: {
418
$oid:
419
(nightwaveSeason + 1).toString().padStart(4, "0") +
420
"bb2d9d00cb47" +
421
challengeId.toString().padStart(8, "0")
422
},
417
423
Activation: { $date: { $numberLong: weekStart.toString() } },
418
424
Expiry: { $date: { $numberLong: weekEnd.toString() } },
419
425
Challenge: challenge
420
426
});
421
427
};
422
428
423
const pushWeeklyActs = (
429
export const pushWeeklyActs = (
424
430
activeChallenges: ISeasonChallenge[],
425
431
pools: IRotatingSeasonChallengePools,
426
week: number
432
week: number,
433
nightwaveStartTimestamp: number,
434
nightwaveSeason: number
427
435
): void => {
428
const weekStart = EPOCH + week * 604800000;
429
const weekEnd = weekStart + 604800000;
430
431
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0);
432
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1);
433
if (pools.hasWeeklyPermanent) {
434
activeChallenges.push({
435
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
436
Activation: { $date: { $numberLong: weekStart.toString() } },
437
Expiry: { $date: { $numberLong: weekEnd.toString() } },
438
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions"
439
});
440
activeChallenges.push({
441
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
442
Activation: { $date: { $numberLong: weekStart.toString() } },
443
Expiry: { $date: { $numberLong: weekEnd.toString() } },
444
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus"
445
});
446
activeChallenges.push({
447
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
448
Activation: { $date: { $numberLong: weekStart.toString() } },
449
Expiry: { $date: { $numberLong: weekEnd.toString() } },
450
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
451
});
452
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2);
453
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3);
436
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 0);
437
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 1);
438
if (pools.weeklyPermanent.length > 0) {
439
const weekStart = EPOCH + week * unixTimesInMs.week;
440
const weekEnd = weekStart + unixTimesInMs.week;
441
const nightwaveWeekStart = ((): number => {
442
let ts = nightwaveStartTimestamp - EPOCH;
443
ts -= ts % unixTimesInMs.week;
444
return EPOCH + ts;
445
})();
446
const nightwaveWeek = Math.trunc((weekStart - nightwaveWeekStart) / unixTimesInMs.week);
447
const weeklyPermanentIndex = (nightwaveWeek * 3) % pools.weeklyPermanent.length;
448
for (let i = 0; i < 3; i++) {
449
activeChallenges.push({
450
_id: {
451
$oid:
452
(nightwaveSeason + 1).toString().padStart(4, "0") +
453
"b96e9d00cb47" +
454
(week * 7 + 2 + i).toString().padStart(8, "0")
455
},
456
Activation: { $date: { $numberLong: weekStart.toString() } },
457
Expiry: { $date: { $numberLong: weekEnd.toString() } },
458
Challenge: pools.weeklyPermanent[weeklyPermanentIndex + i]
459
});
460
}
454
461
} else {
455
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2);
456
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3);
457
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4);
458
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5);
459
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6);
462
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 2);
463
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 3);
464
pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, nightwaveSeason, week, 4);
460
465
}
466
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 5);
467
pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, nightwaveSeason, week, 6);
461
468
};
462
469
463
470
const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => {
@@ -1552,11 +1559,13 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1552
1559
// Nightwave Challenges
1553
1560
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
1554
1561
if (nightwaveSyndicateTag) {
1562
const nightwaveStartTimestamp = 1747851300000;
1563
const nightwaveSeason = nightwaveTagToSeason[nightwaveSyndicateTag];
1555
1564
worldState.SeasonInfo = {
1556
Activation: { $date: { $numberLong: "1715796000000" } },
1565
Activation: { $date: { $numberLong: nightwaveStartTimestamp.toString() } },
1557
1566
Expiry: { $date: { $numberLong: "2000000000000" } },
1558
1567
AffiliationTag: nightwaveSyndicateTag,
1559
Season: nightwaveTagToSeason[nightwaveSyndicateTag],
1568
Season: nightwaveSeason,
1560
1569
Phase: 0,
1561
1570
Params: "",
1562
1571
ActiveChallenges: []
@@ -1568,9 +1577,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1568
1577
if (isBeforeNextExpectedWorldStateRefresh(timeMs, EPOCH + (day + 1) * 86400000)) {
1569
1578
worldState.SeasonInfo.ActiveChallenges.push(getSeasonDailyChallenge(pools, day + 1));
1570
1579
}
1571
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week);
1580
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week, nightwaveStartTimestamp, nightwaveSeason);
1572
1581
if (isBeforeNextExpectedWorldStateRefresh(timeMs, weekEnd)) {
1573
pushWeeklyActs(worldState.SeasonInfo.ActiveChallenges, pools, week + 1);
1582
pushWeeklyActs(
1583
worldState.SeasonInfo.ActiveChallenges,
1584
pools,
1585
week + 1,
1586
nightwaveStartTimestamp,
1587
nightwaveSeason
1588
);
1574
1589
}
1575
1590
}
1576
1591
@@ -265,6 +265,7 @@ export interface IEndlessXpChoice {
265
265
export interface ISeasonChallenge {
266
266
_id: IOid;
267
267
Daily?: boolean;
268
Permanent?: boolean; // only for getPastWeeklyChallenges response
268
269
Activation: IMongoDate;
269
270
Expiry: IMongoDate;
270
271
Challenge: string;