返回提交历史
Modified
src/services/worldStateService.ts
+136
-140
XFEstudio/SpaceNinjaServer
fix: provide current & upcoming sortie if rollover is imminent (#1666)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1666 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
95562a97
代码差异
1 个文件
+136
-140
@@ -8,7 +8,7 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
8
8
import { config } from "@/src/services/configService";
9
9
import { CRng } from "@/src/services/rngService";
10
10
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
11
import { ISeasonChallenge, IWorldState } from "../types/worldStateTypes";
11
import { ISeasonChallenge, ISortie, IWorldState } from "../types/worldStateTypes";
12
12
13
13
const sortieBosses = [
14
14
"SORTIE_BOSS_HYENA",
@@ -153,6 +153,10 @@ const microplanetEndlessJobs = [
153
153
154
154
const EPOCH = 1734307200 * 1000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be winter in 1999 iteration 0
155
155
156
const isBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
157
return Date.now() + 300_000 > date;
158
};
159
156
160
const getSortieTime = (day: number): number => {
157
161
const dayStart = EPOCH + day * 86400000;
158
162
const date = new Date(dayStart);
@@ -167,6 +171,134 @@ const getSortieTime = (day: number): number => {
167
171
return dayStart + (isDst ? 16 : 17) * 3600000;
168
172
};
169
173
174
const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
175
const dayStart = getSortieTime(day);
176
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
177
return;
178
}
179
const dayEnd = getSortieTime(day + 1);
180
if (Date.now() >= dayEnd) {
181
return;
182
}
183
184
const rng = new CRng(day);
185
186
const boss = rng.randomElement(sortieBosses);
187
188
const modifiers = [
189
"SORTIE_MODIFIER_LOW_ENERGY",
190
"SORTIE_MODIFIER_IMPACT",
191
"SORTIE_MODIFIER_SLASH",
192
"SORTIE_MODIFIER_PUNCTURE",
193
"SORTIE_MODIFIER_EXIMUS",
194
"SORTIE_MODIFIER_MAGNETIC",
195
"SORTIE_MODIFIER_CORROSIVE",
196
"SORTIE_MODIFIER_VIRAL",
197
"SORTIE_MODIFIER_ELECTRICITY",
198
"SORTIE_MODIFIER_RADIATION",
199
"SORTIE_MODIFIER_GAS",
200
"SORTIE_MODIFIER_FIRE",
201
"SORTIE_MODIFIER_EXPLOSION",
202
"SORTIE_MODIFIER_FREEZE",
203
"SORTIE_MODIFIER_TOXIN",
204
"SORTIE_MODIFIER_POISON",
205
"SORTIE_MODIFIER_HAZARD_RADIATION",
206
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
207
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
208
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
209
"SORTIE_MODIFIER_HAZARD_ICE",
210
"SORTIE_MODIFIER_HAZARD_COLD",
211
"SORTIE_MODIFIER_SECONDARY_ONLY",
212
"SORTIE_MODIFIER_SHOTGUN_ONLY",
213
"SORTIE_MODIFIER_SNIPER_ONLY",
214
"SORTIE_MODIFIER_RIFLE_ONLY",
215
"SORTIE_MODIFIER_MELEE_ONLY",
216
"SORTIE_MODIFIER_BOW_ONLY"
217
];
218
219
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
220
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
221
222
const nodes: string[] = [];
223
const availableMissionIndexes: number[] = [];
224
for (const [key, value] of Object.entries(ExportRegions)) {
225
if (
226
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
227
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
228
value.name.indexOf("Archwing") == -1 &&
229
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
230
value.missionIndex != 5 && // Exclude MT_CAPTURE
231
value.missionIndex != 21 && // Exclude MT_PURIFY
232
value.missionIndex != 23 && // Exclude MT_JUNCTION
233
value.missionIndex <= 28
234
) {
235
if (!availableMissionIndexes.includes(value.missionIndex)) {
236
availableMissionIndexes.push(value.missionIndex);
237
}
238
nodes.push(key);
239
}
240
}
241
242
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
243
const missionTypes = new Set();
244
245
for (let i = 0; i < 3; i++) {
246
const randomIndex = rng.randomInt(0, nodes.length - 1);
247
const node = nodes[randomIndex];
248
let missionIndex = ExportRegions[node].missionIndex;
249
250
if (
251
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
252
missionIndex != 28 &&
253
rng.randomInt(0, 2) == 2
254
) {
255
missionIndex = rng.randomElement(availableMissionIndexes);
256
}
257
258
if (i == 2 && rng.randomInt(0, 2) == 2) {
259
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
260
const modifierType = rng.randomElement(filteredModifiers);
261
262
if (boss == "SORTIE_BOSS_PHORID") {
263
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
264
nodes.splice(randomIndex, 1);
265
continue;
266
} else if (sortieBossNode[boss]) {
267
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
268
continue;
269
}
270
}
271
272
const missionType = eMissionType[missionIndex].tag;
273
274
if (missionTypes.has(missionType)) {
275
i--;
276
continue;
277
}
278
279
const filteredModifiers =
280
missionType === "MT_TERRITORY"
281
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
282
: modifiers;
283
284
const modifierType = rng.randomElement(filteredModifiers);
285
286
selectedNodes.push({ missionType, modifierType, node });
287
nodes.splice(randomIndex, 1);
288
missionTypes.add(missionType);
289
}
290
291
out.push({
292
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
293
Activation: { $date: { $numberLong: dayStart.toString() } },
294
Expiry: { $date: { $numberLong: dayEnd.toString() } },
295
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
296
Seed: day,
297
Boss: boss,
298
Variants: selectedNodes
299
});
300
};
301
170
302
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
171
303
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
172
304
);
@@ -220,10 +352,6 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
220
352
};
221
353
};
222
354
223
const expiresBeforeNextExpectedWorldStateRefresh = (date: number): boolean => {
224
return Date.now() + 300_000 > date;
225
};
226
227
355
export const getWorldState = (buildLabel?: string): IWorldState => {
228
356
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
229
357
const week = Math.trunc(day / 7);
@@ -565,7 +693,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
565
693
]
566
694
});
567
695
}
568
} while (expiresBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
696
} while (isBeforeNextExpectedWorldStateRefresh(bountyCycleEnd) && ++bountyCycle);
569
697
570
698
if (config.worldState?.creditBoost) {
571
699
worldState.GlobalUpgrades.push({
@@ -605,140 +733,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
605
733
}
606
734
607
735
// Sortie cycling every day
608
{
609
let genDay;
610
let dayStart;
611
let dayEnd;
612
const sortieRolloverToday = getSortieTime(day);
613
if (Date.now() < sortieRolloverToday) {
614
// Early in the day, generate sortie for `day - 1`, expiring at `sortieRolloverToday`.
615
genDay = day - 1;
616
dayStart = getSortieTime(genDay);
617
dayEnd = sortieRolloverToday;
618
} else {
619
// Late in the day, generate sortie for `day`, expiring at `getSortieTime(day + 1)`.
620
genDay = day;
621
dayStart = sortieRolloverToday;
622
dayEnd = getSortieTime(day + 1);
623
}
624
625
const rng = new CRng(genDay);
626
627
const boss = rng.randomElement(sortieBosses);
628
629
const modifiers = [
630
"SORTIE_MODIFIER_LOW_ENERGY",
631
"SORTIE_MODIFIER_IMPACT",
632
"SORTIE_MODIFIER_SLASH",
633
"SORTIE_MODIFIER_PUNCTURE",
634
"SORTIE_MODIFIER_EXIMUS",
635
"SORTIE_MODIFIER_MAGNETIC",
636
"SORTIE_MODIFIER_CORROSIVE",
637
"SORTIE_MODIFIER_VIRAL",
638
"SORTIE_MODIFIER_ELECTRICITY",
639
"SORTIE_MODIFIER_RADIATION",
640
"SORTIE_MODIFIER_GAS",
641
"SORTIE_MODIFIER_FIRE",
642
"SORTIE_MODIFIER_EXPLOSION",
643
"SORTIE_MODIFIER_FREEZE",
644
"SORTIE_MODIFIER_TOXIN",
645
"SORTIE_MODIFIER_POISON",
646
"SORTIE_MODIFIER_HAZARD_RADIATION",
647
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
648
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
649
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
650
"SORTIE_MODIFIER_HAZARD_ICE",
651
"SORTIE_MODIFIER_HAZARD_COLD",
652
"SORTIE_MODIFIER_SECONDARY_ONLY",
653
"SORTIE_MODIFIER_SHOTGUN_ONLY",
654
"SORTIE_MODIFIER_SNIPER_ONLY",
655
"SORTIE_MODIFIER_RIFLE_ONLY",
656
"SORTIE_MODIFIER_MELEE_ONLY",
657
"SORTIE_MODIFIER_BOW_ONLY"
658
];
659
660
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
661
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
662
663
const nodes: string[] = [];
664
const availableMissionIndexes: number[] = [];
665
for (const [key, value] of Object.entries(ExportRegions)) {
666
if (
667
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
668
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
669
value.name.indexOf("Archwing") == -1 &&
670
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
671
value.missionIndex != 5 && // Exclude MT_CAPTURE
672
value.missionIndex != 21 && // Exclude MT_PURIFY
673
value.missionIndex != 23 && // Exclude MT_JUNCTION
674
value.missionIndex <= 28
675
) {
676
if (!availableMissionIndexes.includes(value.missionIndex)) {
677
availableMissionIndexes.push(value.missionIndex);
678
}
679
nodes.push(key);
680
}
681
}
682
683
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
684
const missionTypes = new Set();
685
686
for (let i = 0; i < 3; i++) {
687
const randomIndex = rng.randomInt(0, nodes.length - 1);
688
const node = nodes[randomIndex];
689
let missionIndex = ExportRegions[node].missionIndex;
690
691
if (
692
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
693
missionIndex != 28 &&
694
rng.randomInt(0, 2) == 2
695
) {
696
missionIndex = rng.randomElement(availableMissionIndexes);
697
}
698
699
if (i == 2 && rng.randomInt(0, 2) == 2) {
700
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
701
const modifierType = rng.randomElement(filteredModifiers);
702
703
if (boss == "SORTIE_BOSS_PHORID") {
704
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
705
nodes.splice(randomIndex, 1);
706
continue;
707
} else if (sortieBossNode[boss]) {
708
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
709
continue;
710
}
711
}
712
713
const missionType = eMissionType[missionIndex].tag;
714
715
if (missionTypes.has(missionType)) {
716
i--;
717
continue;
718
}
719
720
const filteredModifiers =
721
missionType === "MT_TERRITORY"
722
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
723
: modifiers;
724
725
const modifierType = rng.randomElement(filteredModifiers);
726
727
selectedNodes.push({ missionType, modifierType, node });
728
nodes.splice(randomIndex, 1);
729
missionTypes.add(missionType);
730
}
731
732
worldState.Sorties.push({
733
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
734
Activation: { $date: { $numberLong: dayStart.toString() } },
735
Expiry: { $date: { $numberLong: dayEnd.toString() } },
736
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
737
Seed: genDay,
738
Boss: boss,
739
Variants: selectedNodes
740
});
741
}
736
pushSortieIfRelevant(worldState.Sorties, day - 1);
737
pushSortieIfRelevant(worldState.Sorties, day);
742
738
743
739
// Archon Hunt cycling every week
744
740
{