返回提交历史
Added
src/controllers/api/feedPrinceController.ts
+62
-0
Added
src/controllers/api/researchMushroomController.ts
+117
-0
Modified
src/models/inventoryModels/inventoryModel.ts
+22
-2
Modified
src/routes/api.ts
+4
-0
Modified
src/services/inventoryService.ts
+17
-9
Modified
src/types/inventoryTypes/inventoryTypes.ts
+11
-0
Modified
static/webui/script.js
+1
-1
XFEstudio/SpaceNinjaServer
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
代码差异
7 个文件
+234
-12
@@ -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
}
@@ -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
};
@@ -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
);
@@ -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);
@@ -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 {
@@ -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
}
@@ -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")) {