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: dynamic void fissure missions (#2214)

Closes #1512 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2214 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

95136e60
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

7 个文件 +276 -203
Modified src/controllers/dynamic/worldStateController.ts +12 -3
@@ -1,6 +1,15 @@
1 1 import { RequestHandler } from "express";
2 import { getWorldState } from "@/src/services/worldStateService";
2 import { getWorldState, populateFissures } from "@/src/services/worldStateService";
3 import { version_compare } from "@/src/helpers/inventoryHelpers";
3 4
4 export const worldStateController: RequestHandler = (req, res) => {
5 res.json(getWorldState(req.query.buildLabel as string | undefined));
5 export const worldStateController: RequestHandler = async (req, res) => {
6 const buildLabel = req.query.buildLabel as string | undefined;
7 const worldState = getWorldState(buildLabel);
8
9 // Omitting void fissures for versions prior to Dante Unbound to avoid script errors.
10 if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) {
11 await populateFissures(worldState);
12 }
13
14 res.json(worldState);
6 15 };
Modified src/index.ts +6 -0
@@ -22,6 +22,7 @@ import { JSONStringify } from "json-with-bigint";
22 22 import { startWebServer } from "./services/webService";
23 23
24 24 import { validateConfig } from "@/src/services/configWatcherService";
25 import { updateWorldStateCollections } from "./services/worldStateService";
25 26
26 27 // Patch JSON.stringify to work flawlessly with Bigints.
27 28 JSON.stringify = JSONStringify;
@@ -33,6 +34,11 @@ mongoose
33 34 .then(() => {
34 35 logger.info("Connected to MongoDB");
35 36 startWebServer();
37
38 void updateWorldStateCollections();
39 setInterval(() => {
40 void updateWorldStateCollections();
41 }, 60_000);
36 42 })
37 43 .catch(error => {
38 44 if (error instanceof Error) {
Added src/models/worldStateModel.ts +14 -0
@@ -0,0 +1,14 @@
1 import { IFissureDatabase } from "@/src/types/worldStateTypes";
2 import { model, Schema } from "mongoose";
3
4 const fissureSchema = new Schema<IFissureDatabase>({
5 Activation: Date,
6 Expiry: Date,
7 Node: String, // must be unique
8 Modifier: String,
9 Hard: Boolean
10 });
11
12 fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries.
13
14 export const Fissure = model<IFissureDatabase>("Fissure", fissureSchema);
Modified src/services/worldStateService.ts +81 -10
@@ -1,12 +1,13 @@
1 1 import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
2 import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
2 3 import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
3 4 import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
4 5 import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
5 6 import { buildConfig } from "@/src/services/buildConfigService";
6 7 import { unixTimesInMs } from "@/src/constants/timeConstants";
7 8 import { config } from "@/src/services/configService";
8 import { SRng } from "@/src/services/rngService";
9 import { ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
9 import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
10 import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
10 11 import {
11 12 ICalendarDay,
12 13 ICalendarEvent,
@@ -21,8 +22,9 @@ import {
21 22 IWorldState,
22 23 TCircuitGameMode
23 24 } from "../types/worldStateTypes";
24 import { version_compare } from "../helpers/inventoryHelpers";
25 import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
25 26 import { logger } from "../utils/logger";
27 import { Fissure } from "../models/worldStateModel";
26 28
27 29 const sortieBosses = [
28 30 "SORTIE_BOSS_HYENA",
@@ -1110,6 +1112,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1110 1112 Alerts: [],
1111 1113 Sorties: [],
1112 1114 LiteSorties: [],
1115 ActiveMissions: [],
1113 1116 GlobalUpgrades: [],
1114 1117 VoidStorms: [],
1115 1118 EndlessXpChoices: [],
@@ -1118,13 +1121,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1118 1121 SyndicateMissions: [...staticWorldState.SyndicateMissions]
1119 1122 };
1120 1123
1121 // Omit void fissures for versions prior to Dante Unbound to avoid script errors.
1122 if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) {
1123 worldState.ActiveMissions = [];
1124 if (version_compare(buildLabel, "2017.10.12.17.04") < 0) {
1125 // Old versions seem to really get hung up on not being able to load these.
1126 worldState.PVPChallengeInstances = [];
1127 }
1124 // Old versions seem to really get hung up on not being able to load these.
1125 if (buildLabel && version_compare(buildLabel, "2017.10.12.17.04") < 0) {
1126 worldState.PVPChallengeInstances = [];
1128 1127 }
1129 1128
1130 1129 if (config.worldState?.starDays) {
@@ -1364,6 +1363,24 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
1364 1363 return worldState;
1365 1364 };
1366 1365
1366 export const populateFissures = async (worldState: IWorldState): Promise<void> => {
1367 const fissures = await Fissure.find({});
1368 for (const fissure of fissures) {
1369 const meta = ExportRegions[fissure.Node];
1370 worldState.ActiveMissions.push({
1371 _id: toOid(fissure._id),
1372 Region: meta.systemIndex + 1,
1373 Seed: 1337,
1374 Activation: toMongoDate(fissure.Activation),
1375 Expiry: toMongoDate(fissure.Expiry),
1376 Node: fissure.Node,
1377 MissionType: eMissionType[meta.missionIndex].tag,
1378 Modifier: fissure.Modifier,
1379 Hard: fissure.Hard
1380 });
1381 }
1382 };
1383
1367 1384 export const idToBountyCycle = (id: string): number => {
1368 1385 return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000);
1369 1386 };
@@ -1491,3 +1508,57 @@ const nightwaveTagToSeason: Record<string, number> = {
1491 1508 RadioLegionIntermissionSyndicate: 1, // Intermission I
1492 1509 RadioLegionSyndicate: 0 // The Wolf of Saturn Six
1493 1510 };
1511
1512 export const updateWorldStateCollections = async (): Promise<void> => {
1513 const fissures = await Fissure.find();
1514
1515 const activeNodes = new Set<string>();
1516 const tierToFurthestExpiry: Record<string, number> = {
1517 VoidT1: 0,
1518 VoidT2: 0,
1519 VoidT3: 0,
1520 VoidT4: 0,
1521 VoidT5: 0,
1522 VoidT6: 0,
1523 VoidT1Hard: 0,
1524 VoidT2Hard: 0,
1525 VoidT3Hard: 0,
1526 VoidT4Hard: 0,
1527 VoidT5Hard: 0,
1528 VoidT6Hard: 0
1529 };
1530 for (const fissure of fissures) {
1531 activeNodes.add(fissure.Node);
1532
1533 const key = fissure.Modifier + (fissure.Hard ? "Hard" : "");
1534 tierToFurthestExpiry[key] = Math.max(tierToFurthestExpiry[key], fissure.Expiry.getTime());
1535 }
1536
1537 const deadline = Date.now() - 6 * unixTimesInMs.minute;
1538 for (const [tier, expiry] of Object.entries(tierToFurthestExpiry)) {
1539 if (expiry < deadline) {
1540 const numFissures = getRandomInt(1, 3);
1541 for (let i = 0; i != numFissures; ++i) {
1542 const modifier = tier.replace("Hard", "") as
1543 | "VoidT1"
1544 | "VoidT2"
1545 | "VoidT3"
1546 | "VoidT4"
1547 | "VoidT5"
1548 | "VoidT6";
1549 let node: string;
1550 do {
1551 node = getRandomElement(fissureMissions[modifier])!;
1552 } while (activeNodes.has(node));
1553 activeNodes.add(node);
1554 await Fissure.insertOne({
1555 Activation: new Date(),
1556 Expiry: new Date(Date.now() + getRandomInt(60, 120) * unixTimesInMs.minute),
1557 Node: node,
1558 Modifier: modifier,
1559 Hard: tier.indexOf("Hard") != -1 ? true : undefined
1560 });
1561 }
1562 }
1563 }
1564 };
Modified src/types/worldStateTypes.ts +9 -1
@@ -9,8 +9,8 @@ export interface IWorldState {
9 9 Sorties: ISortie[];
10 10 LiteSorties: ILiteSortie[];
11 11 SyndicateMissions: ISyndicateMissionInfo[];
12 GlobalUpgrades: IGlobalUpgrade[];
13 12 ActiveMissions: IFissure[];
13 GlobalUpgrades: IGlobalUpgrade[];
14 14 NodeOverrides: INodeOverride[];
15 15 VoidStorms: IVoidStorm[];
16 16 PVPChallengeInstances: IPVPChallengeInstance[];
@@ -86,6 +86,14 @@ export interface IFissure {
86 86 Hard?: boolean;
87 87 }
88 88
89 export interface IFissureDatabase {
90 Activation: Date;
91 Expiry: Date;
92 Node: string;
93 Modifier: "VoidT1" | "VoidT2" | "VoidT3" | "VoidT4" | "VoidT5" | "VoidT6";
94 Hard?: boolean;
95 }
96
89 97 export interface INodeOverride {
90 98 _id: IOid;
91 99 Activation?: IMongoDate;
Added static/fixed_responses/worldState/fissureMissions.json +154 -0
@@ -0,0 +1,154 @@
1 {
2 "VoidT1": [
3 "SolNode23",
4 "SolNode66",
5 "SolNode45",
6 "SolNode41",
7 "SolNode59",
8 "SolNode39",
9 "SolNode75",
10 "SolNode113",
11 "SolNode85",
12 "SolNode58",
13 "SolNode101",
14 "SolNode109",
15 "SolNode26",
16 "SolNode15",
17 "SolNode61",
18 "SolNode123",
19 "SolNode16",
20 "SolNode79",
21 "SolNode2",
22 "SolNode22",
23 "SolNode68",
24 "SolNode89",
25 "SolNode11",
26 "SolNode46",
27 "SolNode36",
28 "SolNode27",
29 "SolNode14",
30 "SolNode106",
31 "SolNode30",
32 "SolNode107",
33 "SolNode63",
34 "SolNode128"
35 ],
36 "VoidT2": [
37 "SolNode141",
38 "SolNode149",
39 "SolNode10",
40 "SolNode93",
41 "SettlementNode11",
42 "SolNode137",
43 "SolNode132",
44 "SolNode73",
45 "SolNode82",
46 "SolNode25",
47 "SolNode88",
48 "SolNode126",
49 "SolNode135",
50 "SolNode74",
51 "SettlementNode15",
52 "SolNode147",
53 "SolNode67",
54 "SolNode20",
55 "SolNode42",
56 "SolNode18",
57 "SolNode31",
58 "SolNode139",
59 "SettlementNode12",
60 "SolNode100",
61 "SolNode140",
62 "SolNode70",
63 "SettlementNode1",
64 "SettlementNode14",
65 "SolNode50",
66 "SettlementNode2",
67 "SolNode146",
68 "SettlementNode3",
69 "SolNode97",
70 "SolNode125",
71 "SolNode19",
72 "SolNode121",
73 "SolNode96",
74 "SolNode131"
75 ],
76 "VoidT3": [
77 "SolNode62",
78 "SolNode17",
79 "SolNode403",
80 "SolNode6",
81 "SolNode118",
82 "SolNode211",
83 "SolNode217",
84 "SolNode401",
85 "SolNode64",
86 "SolNode405",
87 "SolNode84",
88 "SolNode402",
89 "SolNode408",
90 "SolNode122",
91 "SolNode57",
92 "SolNode216",
93 "SolNode205",
94 "SolNode215",
95 "SolNode404",
96 "SolNode209",
97 "SolNode406",
98 "SolNode204",
99 "SolNode203",
100 "SolNode409",
101 "SolNode400",
102 "SolNode212",
103 "SolNode1",
104 "SolNode412",
105 "SolNode49",
106 "SolNode78",
107 "SolNode410",
108 "SolNode407",
109 "SolNode220"
110 ],
111 "VoidT4": [
112 "SolNode188",
113 "SolNode403",
114 "SolNode189",
115 "SolNode21",
116 "SolNode102",
117 "SolNode171",
118 "SolNode196",
119 "SolNode184",
120 "SolNode185",
121 "SolNode76",
122 "SolNode195",
123 "SolNode164",
124 "SolNode401",
125 "SolNode405",
126 "SolNode56",
127 "SolNode402",
128 "SolNode408",
129 "SolNode4",
130 "SolNode181",
131 "SolNode406",
132 "SolNode162",
133 "SolNode72",
134 "SolNode407",
135 "SolNode177",
136 "SolNode404",
137 "SolNode400",
138 "SolNode409",
139 "SolNode43",
140 "SolNode166",
141 "SolNode172",
142 "SolNode412",
143 "SolNode187",
144 "SolNode38",
145 "SolNode175",
146 "SolNode81",
147 "SolNode48",
148 "SolNode410",
149 "SolNode153",
150 "SolNode173"
151 ],
152 "VoidT5": ["SolNode747", "SolNode743", "SolNode742", "SolNode744", "SolNode745", "SolNode748", "SolNode746", "SolNode741"],
153 "VoidT6": ["SolNode717", "SolNode309", "SolNode718", "SolNode232", "SolNode230", "SolNode310"]
154 }
Modified static/fixed_responses/worldState/worldState.json +0 -189
@@ -327,195 +327,6 @@
327 327 "Nodes": []
328 328 }
329 329 ],
330 "ActiveMissions": [
331 {
332 "_id": { "$oid": "663a7509d93367863785932d" },
333 "Region": 15,
334 "Seed": 80795,
335 "Activation": { "$date": { "$numberLong": "1715107081517" } },
336 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
337 "Node": "SolNode400",
338 "MissionType": "MT_EXTERMINATION",
339 "Modifier": "VoidT3",
340 "Hard": true
341 },
342 {
343 "_id": { "$oid": "663a75f959a5964cadb39879" },
344 "Region": 19,
345 "Seed": 32067,
346 "Activation": { "$date": { "$numberLong": "1715107321237" } },
347 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
348 "Node": "SolNode747",
349 "MissionType": "MT_INTEL",
350 "Modifier": "VoidT5",
351 "Hard": true
352 },
353 {
354 "_id": { "$oid": "663a779d3e347839ff301814" },
355 "Region": 7,
356 "Seed": 51739,
357 "Activation": { "$date": { "$numberLong": "1715107741454" } },
358 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
359 "Node": "SolNode64",
360 "MissionType": "MT_TERRITORY",
361 "Modifier": "VoidT3"
362 },
363 {
364 "_id": { "$oid": "663a77d916c199f4644ee67d" },
365 "Region": 17,
366 "Seed": 61179,
367 "Activation": { "$date": { "$numberLong": "1715107801647" } },
368 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
369 "Node": "SolNode718",
370 "MissionType": "MT_ALCHEMY",
371 "Modifier": "VoidT6"
372 },
373 {
374 "_id": { "$oid": "663a78c98a609b49b8410726" },
375 "Region": 3,
376 "Seed": 9520,
377 "Activation": { "$date": { "$numberLong": "1715108041501" } },
378 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
379 "Node": "SolNode79",
380 "MissionType": "MT_INTEL",
381 "Modifier": "VoidT1",
382 "Hard": true
383 },
384 {
385 "_id": { "$oid": "663a7df15eeabaac79b0a061" },
386 "Region": 6,
387 "Seed": 48861,
388 "Activation": { "$date": { "$numberLong": "1715109361974" } },
389 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
390 "Node": "SolNode67",
391 "MissionType": "MT_INTEL",
392 "Modifier": "VoidT2",
393 "Hard": true
394 },
395 {
396 "_id": { "$oid": "663a7df25eeabaac79b0a062" },
397 "Region": 5,
398 "Seed": 13550,
399 "Activation": { "$date": { "$numberLong": "1715109361974" } },
400 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
401 "Node": "SolNode10",
402 "MissionType": "MT_SABOTAGE",
403 "Modifier": "VoidT2",
404 "Hard": true
405 },
406 {
407 "_id": { "$oid": "663a83cdec0d5181435f1324" },
408 "Region": 19,
409 "Seed": 39392,
410 "Activation": { "$date": { "$numberLong": "1715110861506" } },
411 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
412 "Node": "SolNode742",
413 "MissionType": "MT_DEFENSE",
414 "Modifier": "VoidT5"
415 },
416 {
417 "_id": { "$oid": "663a83cdec0d5181435f1325" },
418 "Region": 19,
419 "Seed": 88668,
420 "Activation": { "$date": { "$numberLong": "1715110861506" } },
421 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
422 "Node": "SolNode743",
423 "MissionType": "MT_MOBILE_DEFENSE",
424 "Modifier": "VoidT5"
425 },
426 {
427 "_id": { "$oid": "663a83cdec0d5181435f1326" },
428 "Region": 19,
429 "Seed": 73823,
430 "Activation": { "$date": { "$numberLong": "1715110861506" } },
431 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
432 "Node": "SolNode741",
433 "MissionType": "MT_ASSAULT",
434 "Modifier": "VoidT5"
435 },
436 {
437 "_id": { "$oid": "663a878d23d1514873170466" },
438 "Region": 9,
439 "Seed": 88696,
440 "Activation": { "$date": { "$numberLong": "1715111821951" } },
441 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
442 "Node": "SolNode4",
443 "MissionType": "MT_EXTERMINATION",
444 "Modifier": "VoidT4",
445 "Hard": true
446 },
447 {
448 "_id": { "$oid": "663a887d4903098c10992fe6" },
449 "Region": 6,
450 "Seed": 66337,
451 "Activation": { "$date": { "$numberLong": "1715112061729" } },
452 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
453 "Node": "SolNode18",
454 "MissionType": "MT_TERRITORY",
455 "Modifier": "VoidT2"
456 },
457 {
458 "_id": { "$oid": "663a887d4903098c10992fe7" },
459 "Region": 10,
460 "Seed": 5135,
461 "Activation": { "$date": { "$numberLong": "1715112061729" } },
462 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
463 "Node": "SolNode149",
464 "MissionType": "MT_DEFENSE",
465 "Modifier": "VoidT2"
466 },
467 {
468 "_id": { "$oid": "663a8931586c301b1fbe63d3" },
469 "Region": 15,
470 "Seed": 32180,
471 "Activation": { "$date": { "$numberLong": "1715112241196" } },
472 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
473 "Node": "SolNode408",
474 "MissionType": "MT_DEFENSE",
475 "Modifier": "VoidT4"
476 },
477 {
478 "_id": { "$oid": "663a8931586c301b1fbe63d4" },
479 "Region": 12,
480 "Seed": 22521,
481 "Activation": { "$date": { "$numberLong": "1715112241196" } },
482 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
483 "Node": "SolNode181",
484 "MissionType": "MT_EXTERMINATION",
485 "Modifier": "VoidT4"
486 },
487 {
488 "_id": { "$oid": "663a8931586c301b1fbe63d5" },
489 "Region": 2,
490 "Seed": 28500,
491 "Activation": { "$date": { "$numberLong": "1715112241196" } },
492 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
493 "Node": "SolNode128",
494 "MissionType": "MT_EXTERMINATION",
495 "Modifier": "VoidT1"
496 },
497 {
498 "_id": { "$oid": "663a8931586c301b1fbe63d6" },
499 "Region": 3,
500 "Seed": 24747,
501 "Activation": { "$date": { "$numberLong": "1715112241196" } },
502 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
503 "Node": "SolNode26",
504 "MissionType": "MT_DEFENSE",
505 "Modifier": "VoidT1"
506 },
507 {
508 "_id": { "$oid": "663a8931586c301b1fbe63d7" },
509 "Region": 17,
510 "Seed": 63914,
511 "Activation": { "$date": { "$numberLong": "1715112241196" } },
512 "Expiry": { "$date": { "$numberLong": "2000000000000" } },
513 "Node": "SolNode717",
514 "MissionType": "MT_SURVIVAL",
515 "Modifier": "VoidT6",
516 "Hard": true
517 }
518 ],
519 330 "NodeOverrides": [
520 331 { "_id": { "$oid": "549b18e9b029cef5991d6aec" }, "Node": "EuropaHUB", "Hide": true },
521 332 { "_id": { "$oid": "54a1737aeb658f6cbccf70ff" }, "Node": "ErisHUB", "Hide": true },