XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 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
AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
提交于

代码差异

4 个文件 +122 -42
Added src/controllers/api/getPastWeeklyChallengesController.ts +62 -0
@@ -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 };
Modified src/routes/api.ts +2 -0
@@ -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);
Modified src/services/worldStateService.ts +57 -42
@@ -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
Modified src/types/worldStateTypes.ts +1 -0
@@ -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;