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

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

1 个文件 +136 -140
Modified src/services/worldStateService.ts +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 {