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: nightcap syndicate (#2934)

Closes #2928 Closes #2931 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2934 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>

482101cc
AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
提交于

代码差异

7 个文件 +234 -12
Added src/controllers/api/feedPrinceController.ts +62 -0
@@ -0,0 +1,62 @@
1 import type { RequestHandler } from "express";
2 import { getAccountIdForRequest } from "../../services/loginService.ts";
3 import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
4 import { getJSONfromString } from "../../helpers/stringHelpers.ts";
5 import { logger } from "../../utils/logger.ts";
6 import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
7
8 export const feedPrinceController: RequestHandler = async (req, res) => {
9 const accountId = await getAccountIdForRequest(req);
10 const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted");
11 const payload = getJSONfromString<IFeedPrinceRequest>(String(req.body));
12
13 switch (payload.Mode) {
14 case "r": {
15 inventory.NokkoColony ??= {
16 FeedLevel: 0,
17 JournalEntries: []
18 };
19 const InventoryChanges: IInventoryChanges = {};
20 inventory.NokkoColony.FeedLevel += payload.Amount;
21 if (
22 (!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) ||
23 (!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60)
24 ) {
25 res.json({
26 FeedSucceeded: false,
27 FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount,
28 InventoryChanges
29 } satisfies IFeedPrinceResponse);
30 } else {
31 addMiscItem(
32 inventory,
33 "/Lotus/Types/Items/MiscItems/MushroomFood",
34 payload.Amount * -1,
35 InventoryChanges
36 );
37 await inventory.save();
38 res.json({
39 FeedSucceeded: true,
40 FeedLevel: inventory.NokkoColony.FeedLevel,
41 InventoryChanges
42 } satisfies IFeedPrinceResponse);
43 }
44 break;
45 }
46
47 default:
48 logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
49 throw new Error(`unknown feedPrince mode: ${payload.Mode}`);
50 }
51 };
52
53 interface IFeedPrinceRequest {
54 Mode: string; // r
55 Amount: number;
56 }
57
58 interface IFeedPrinceResponse {
59 FeedSucceeded: boolean;
60 FeedLevel: number;
61 InventoryChanges: IInventoryChanges;
62 }
Added src/controllers/api/researchMushroomController.ts +117 -0
@@ -0,0 +1,117 @@
1 import type { RequestHandler } from "express";
2 import { getAccountIdForRequest } from "../../services/loginService.ts";
3 import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
4 import { getJSONfromString } from "../../helpers/stringHelpers.ts";
5 import { logger } from "../../utils/logger.ts";
6 import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts";
7 import type { IAffiliationMods } from "../../types/purchaseTypes.ts";
8
9 export const researchMushroomController: RequestHandler = async (req, res) => {
10 const accountId = await getAccountIdForRequest(req);
11 const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations");
12 const payload = getJSONfromString<IResearchMushroom>(String(req.body));
13 switch (payload.Mode) {
14 case "r": {
15 const InventoryChanges = {};
16 const AffiliationMods: IAffiliationMods[] = [];
17
18 addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges);
19 if (payload.Convert) {
20 addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges);
21 }
22
23 inventory.NokkoColony ??= {
24 FeedLevel: 0,
25 JournalEntries: []
26 };
27
28 let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem);
29 if (!journalEntry) {
30 journalEntry = { EntryType: payload.MushroomItem, Progress: 0 };
31 inventory.NokkoColony.JournalEntries.push(journalEntry);
32 }
33
34 let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate");
35 if (!syndicate) {
36 syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 };
37 inventory.Affiliations.push(syndicate);
38 }
39 const completedBefore = inventory.NokkoColony.JournalEntries.filter(
40 entry => getJournalRank(entry) === 3
41 ).length;
42 const PrevRank = syndicateTitleThresholds.reduce(
43 (rank, threshold, i) => (completedBefore >= threshold ? i : rank),
44 0
45 );
46
47 if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount;
48
49 const completedAfter = inventory.NokkoColony.JournalEntries.filter(
50 entry => getJournalRank(entry) === 3
51 ).length;
52 const NewRank = syndicateTitleThresholds.reduce(
53 (rank, threshold, i) => (completedAfter >= threshold ? i : rank),
54 0
55 );
56
57 if (NewRank > (syndicate.Title ?? 0)) {
58 syndicate.Title = NewRank;
59 AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank });
60 }
61
62 await inventory.save();
63 res.json({
64 PrevRank,
65 NewRank,
66 Progress: journalEntry.Progress,
67 InventoryChanges,
68 AffiliationMods
69 });
70 break;
71 }
72
73 default:
74 logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
75 throw new Error(`unknown researchMushroom mode: ${payload.Mode}`);
76 }
77 };
78
79 interface IResearchMushroom {
80 Mode: string; // r
81 MushroomItem: string;
82 Amount: number;
83 Convert: boolean;
84 }
85
86 const journalEntriesRank: Record<string, number> = {
87 "/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1,
88 "/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4,
89 "/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3,
90 "/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4,
91 "/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4,
92 "/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3,
93 "/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5,
94 "/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3,
95 "/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4,
96 "/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3,
97 "/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3,
98 "/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4,
99 "/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3,
100 "/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3,
101 "/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4,
102 "/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5
103 };
104
105 const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16];
106
107 const getJournalRank = (journalEntry: IJournalEntry): number => {
108 const k = journalEntriesRank[journalEntry.EntryType];
109 if (!k) return 0;
110
111 const thresholds = [k * 1, k * 3, k * 6];
112
113 if (journalEntry.Progress >= thresholds[2]) return 3;
114 if (journalEntry.Progress >= thresholds[1]) return 2;
115 if (journalEntry.Progress >= thresholds[0]) return 1;
116 return 0;
117 };
Modified src/models/inventoryModels/inventoryModel.ts +22 -2
@@ -88,7 +88,9 @@ import type {
88 88 IGoalProgressDatabase,
89 89 IGoalProgressClient,
90 90 IKubrowPetPrintClient,
91 IKubrowPetPrintDatabase
91 IKubrowPetPrintDatabase,
92 INokkoColony,
93 IJournalEntry
92 94 } from "../../types/inventoryTypes/inventoryTypes.ts";
93 95 import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
94 96 import type { IOid, ITypeCount } from "../../types/commonTypes.ts";
@@ -1424,6 +1426,22 @@ const hubNpcCustomizationSchema = new Schema<IHubNpcCustomization>(
1424 1426 { _id: false }
1425 1427 );
1426 1428
1429 const journalEntrySchema = new Schema<IJournalEntry>(
1430 {
1431 EntryType: String,
1432 Progress: Number
1433 },
1434 { _id: false }
1435 );
1436
1437 const nokkoColonySchema = new Schema<INokkoColony>(
1438 {
1439 FeedLevel: Number,
1440 JournalEntries: [journalEntrySchema]
1441 },
1442 { _id: false }
1443 );
1444
1427 1445 const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1428 1446 {
1429 1447 accountOwnerId: Schema.Types.ObjectId,
@@ -1840,7 +1858,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1840 1858
1841 1859 ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
1842 1860
1843 SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
1861 SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
1862
1863 NokkoColony: { type: nokkoColonySchema, default: undefined }
1844 1864 },
1845 1865 { timestamps: { createdAt: "Created", updatedAt: false } }
1846 1866 );
Modified src/routes/api.ts +4 -0
@@ -50,6 +50,7 @@ import { dronesController } from "../controllers/api/dronesController.ts";
50 50 import { endlessXpController } from "../controllers/api/endlessXpController.ts";
51 51 import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts";
52 52 import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts";
53 import { feedPrinceController } from "../controllers/api/feedPrinceController.ts";
53 54 import { findSessionsController } from "../controllers/api/findSessionsController.ts";
54 55 import { fishmongerController } from "../controllers/api/fishmongerController.ts";
55 56 import { focusController } from "../controllers/api/focusController.ts";
@@ -116,6 +117,7 @@ import { removeFromGuildController } from "../controllers/api/removeFromGuildCon
116 117 import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts";
117 118 import { renamePetController } from "../controllers/api/renamePetController.ts";
118 119 import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts";
120 import { researchMushroomController } from "../controllers/api/researchMushroomController.ts";
119 121 import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts";
120 122 import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts";
121 123 import { saveDialogueController } from "../controllers/api/saveDialogueController.ts";
@@ -271,6 +273,7 @@ apiRouter.post("/drones.php", dronesController);
271 273 apiRouter.post("/endlessXp.php", endlessXpController);
272 274 apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController);
273 275 apiRouter.post("/evolveWeapon.php", evolveWeaponController);
276 apiRouter.post("/feedPrince.php", feedPrinceController);
274 277 apiRouter.post("/findSessions.php", findSessionsController);
275 278 apiRouter.post("/fishmonger.php", fishmongerController);
276 279 apiRouter.post("/focus.php", focusController);
@@ -318,6 +321,7 @@ apiRouter.post("/removeFromGuild.php", removeFromGuildController);
318 321 apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
319 322 apiRouter.post("/renamePet.php", renamePetController);
320 323 apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
324 apiRouter.post("/researchMushroom.php", researchMushroomController);
321 325 apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
322 326 apiRouter.post("/saveDialogue.php", saveDialogueController);
323 327 apiRouter.post("/saveLoadout.php", saveLoadoutController);
Modified src/services/inventoryService.ts +17 -9
@@ -1391,7 +1391,10 @@ export const addStanding = (
1391 1391
1392 1392 // TODO: AffiliationMods support (Nightwave).
1393 1393 export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => {
1394 const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations");
1394 const inventory = await getInventory(
1395 accountId,
1396 "NodeIntrosCompleted MiscItems ShipDecorations FlavourItems WeaponSkins"
1397 );
1395 1398
1396 1399 // Make it an array for easier parsing.
1397 1400 if (typeof data.NodeIntrosCompleted === "string") {
@@ -1400,6 +1403,12 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
1400 1403
1401 1404 const inventoryChanges: IInventoryChanges = {};
1402 1405 for (const node of data.NodeIntrosCompleted) {
1406 if (inventory.NodeIntrosCompleted.indexOf(node) != -1) {
1407 continue;
1408 }
1409 inventory.NodeIntrosCompleted.push(node);
1410 logger.debug(`completed dialogue/cutscene for ${node}`);
1411
1403 1412 if (node == "TC2025") {
1404 1413 inventoryChanges.ShipDecorations = [
1405 1414 {
@@ -1420,16 +1429,15 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
1420 1429 await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges);
1421 1430 } else if (node == "ClearedFiveLoops") {
1422 1431 await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges);
1432 } else if (node == "NokkoVisions_AllCompleted") {
1433 addCustomization(
1434 inventory,
1435 "/Lotus/Types/StoreItems/AvatarImages/Warframes/NokkoBabySecretGlyph",
1436 inventoryChanges
1437 );
1438 addSkin(inventory, "/Lotus/Upgrades/Skins/Clan/NokkoBabySecretEmblemItem", inventoryChanges);
1423 1439 }
1424 1440 }
1425
1426 // Combine the two arrays into one.
1427 data.NodeIntrosCompleted = inventory.NodeIntrosCompleted.concat(data.NodeIntrosCompleted);
1428
1429 // Remove duplicate entries.
1430 const nodes = [...new Set(data.NodeIntrosCompleted)];
1431
1432 inventory.NodeIntrosCompleted = nodes;
1433 1441 await inventory.save();
1434 1442
1435 1443 return {
Modified src/types/inventoryTypes/inventoryTypes.ts +11 -0
@@ -436,6 +436,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
436 436 Ship?: IOrbiterClient; // U22 and below, response only
437 437 ClaimedJunctionChallengeRewards?: string[]; // U39
438 438 SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
439 NokkoColony?: INokkoColony; // Field Guide
439 440 }
440 441
441 442 export interface IAffiliation {
@@ -1220,3 +1221,13 @@ export interface IHubNpcCustomization {
1220 1221 Pattern: string;
1221 1222 Tag: string;
1222 1223 }
1224
1225 export interface IJournalEntry {
1226 EntryType: string;
1227 Progress: number;
1228 }
1229
1230 export interface INokkoColony {
1231 FeedLevel: number;
1232 JournalEntries: IJournalEntry[];
1233 }
Modified static/webui/script.js +1 -1
@@ -559,7 +559,7 @@ function fetchItemList() {
559 559 });
560 560 } else if (type == "Syndicates") {
561 561 items.forEach(item => {
562 if (item.uniqueName === "ConclaveSyndicate") {
562 if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) {
563 563 return;
564 564 }
565 565 if (item.uniqueName.startsWith("RadioLegion")) {