返回提交历史
Modified
src/controllers/dynamic/worldStateController.ts
+12
-3
Modified
src/index.ts
+6
-0
Added
src/models/worldStateModel.ts
+14
-0
Modified
src/services/worldStateService.ts
+81
-10
Modified
src/types/worldStateTypes.ts
+9
-1
Added
static/fixed_responses/worldState/fissureMissions.json
+154
-0
Modified
static/fixed_responses/worldState/worldState.json
+0
-189
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
代码差异
7 个文件
+276
-203
@@ -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
};
@@ -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) {
@@ -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);
@@ -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
};
@@ -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;
@@ -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
}
@@ -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 },