返回提交历史
Modified
src/controllers/api/fusionTreasuresController.ts
+57
-30
Modified
src/controllers/api/inventoryController.ts
+42
-2
Modified
src/controllers/api/sellController.ts
+12
-4
Modified
src/models/inventoryModels/inventoryModel.ts
+11
-2
Modified
src/services/importService.ts
+6
-2
Modified
src/services/inventoryService.ts
+46
-16
Modified
src/services/missionInventoryUpdateService.ts
+18
-2
Modified
src/types/inventoryTypes/inventoryTypes.ts
+19
-1
Modified
src/types/requestTypes.ts
+2
-1
XFEstudio/SpaceNinjaServer
feat: initial support for legacy format of fusion treasures (#3894)
Closes #3890 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3894 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
5a1cfc75
代码差异
9 个文件
+213
-60
@@ -1,9 +1,9 @@
1
1
import type { RequestHandler } from "express";
2
2
import { ExportResources } from "warframe-public-export-plus";
3
3
import { getAccountIdForRequest } from "../../services/loginService.ts";
4
import { addFusionTreasures, addMiscItems, getInventory } from "../../services/inventoryService.ts";
5
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
6
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
4
import { addFusionTreasures, addMiscItem, addMiscItems, getInventory } from "../../services/inventoryService.ts";
5
import type { IFusionTreasureClientLegacy, IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
6
import { fromOid, parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
7
7
8
8
interface IFusionTreasureRequest {
9
9
oldTreasureName: string;
@@ -12,33 +12,60 @@ interface IFusionTreasureRequest {
12
12
13
13
export const fusionTreasuresController: RequestHandler = async (req, res) => {
14
14
const accountId = await getAccountIdForRequest(req);
15
const inventory = await getInventory(accountId);
16
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest;
17
18
// Swap treasures
19
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
20
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
21
const fusionTreasureChanges = [oldTreasure, newTreasure];
22
addFusionTreasures(inventory, fusionTreasureChanges);
23
24
// Remove consumed stars
25
const miscItemChanges: IMiscItem[] = [];
26
const filledSockets = newTreasure.Sockets & ~oldTreasure.Sockets;
27
for (let i = 0; filledSockets >> i; ++i) {
28
if ((filledSockets >> i) & 1) {
29
//console.log("Socket", i, "has been filled with", ExportResources[oldTreasure.ItemType].sockets![i]);
30
miscItemChanges.push({
31
ItemType: ExportResources[oldTreasure.ItemType].sockets![i],
32
ItemCount: -1
33
});
15
const inventory = await getInventory(accountId, "HybridFusionTreasures MiscItems");
16
const request = JSON.parse(String(req.body)) as IFusionTreasureRequest | IFusionTreasureClientLegacy;
17
if ("ItemId" in request) {
18
const fusex = inventory.HybridFusionTreasures.id(fromOid(request.ItemId))!;
19
const newSockets = convertLegacySocketsToBitset(request.Sockets);
20
const filledSockets = newSockets & ~fusex.Sockets;
21
22
// Update treasure
23
fusex.Sockets = newSockets;
24
25
// Remove consumed stars
26
for (let i = 0; filledSockets >> i; ++i) {
27
if ((filledSockets >> i) & 1) {
28
//console.log("Socket", i, "has been filled with", ExportResources[fusex.ItemType].sockets![i]);
29
addMiscItem(inventory, ExportResources[fusex.ItemType].sockets![i], -1);
30
}
31
}
32
33
await inventory.save();
34
res.end();
35
} else {
36
// Swap treasures
37
const oldTreasure = parseFusionTreasure(request.oldTreasureName, -1);
38
const newTreasure = parseFusionTreasure(request.newTreasureName, 1);
39
const fusionTreasureChanges = [oldTreasure, newTreasure];
40
addFusionTreasures(inventory, fusionTreasureChanges);
41
42
// Remove consumed stars
43
const miscItemChanges: IMiscItem[] = [];
44
const filledSockets = newTreasure.Sockets & ~oldTreasure.Sockets;
45
for (let i = 0; filledSockets >> i; ++i) {
46
if ((filledSockets >> i) & 1) {
47
//console.log("Socket", i, "has been filled with", ExportResources[oldTreasure.ItemType].sockets![i]);
48
miscItemChanges.push({
49
ItemType: ExportResources[oldTreasure.ItemType].sockets![i],
50
ItemCount: -1
51
});
52
}
34
53
}
54
addMiscItems(inventory, miscItemChanges);
55
56
await inventory.save();
57
// The response itself is the inventory changes for this endpoint.
58
res.json({
59
MiscItems: miscItemChanges,
60
FusionTreasures: fusionTreasureChanges
61
});
62
}
63
};
64
65
const convertLegacySocketsToBitset = (Sockets: Record<string, string>): number => {
66
let bitset = 0;
67
for (const key in Sockets) {
68
bitset |= 1 << (key.charCodeAt(0) - 0x61);
35
69
}
36
addMiscItems(inventory, miscItemChanges);
37
38
await inventory.save();
39
// The response itself is the inventory changes for this endpoint.
40
res.json({
41
MiscItems: miscItemChanges,
42
FusionTreasures: fusionTreasureChanges
43
});
70
return bitset;
44
71
};
@@ -6,7 +6,13 @@ import { config } from "../../services/configService.ts";
6
6
import allDialogue from "../../../static/fixed_responses/allDialogue.json" with { type: "json" };
7
7
import allPopups from "../../../static/fixed_responses/allPopups.json" with { type: "json" };
8
8
import type { ILoadoutConfigClientLegacy, ILoadoutDatabase, ILoadOutPresets } from "../../types/saveLoadoutTypes.ts";
9
import type { IInventoryClient, IShipInventory, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
9
import type {
10
IFusionTreasure,
11
IFusionTreasureClientLegacy,
12
IInventoryClient,
13
IShipInventory,
14
IUpgradeClient
15
} from "../../types/inventoryTypes/inventoryTypes.ts";
10
16
import {
11
17
accountCheatBooleans,
12
18
accountCheatNumbers,
@@ -16,7 +22,7 @@ import {
16
22
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
17
23
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
18
24
import type { ICountedItem } from "warframe-public-export-plus";
19
import { ExportArcanes, ExportWarframes, ExportWeapons } from "warframe-public-export-plus";
25
import { ExportArcanes, ExportResources, ExportWarframes, ExportWeapons } from "warframe-public-export-plus";
20
26
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
21
27
import {
22
28
addEmailItem,
@@ -552,6 +558,40 @@ export const getInventoryResponse = async (
552
558
}
553
559
}
554
560
561
// Convert fusion treasures to client format
562
inventoryResponse.FusionTreasures = [];
563
if (!buildLabel || version_compare(buildLabel, gameToBuildVersion["25.7.0"]) >= 0) {
564
for (const treasure of inventory.HybridFusionTreasures) {
565
const entry = (inventoryResponse.FusionTreasures as IFusionTreasure[]).find(
566
x => x.ItemType == treasure.ItemType && x.Sockets == treasure.Sockets
567
);
568
if (entry) {
569
++entry.ItemCount;
570
} else {
571
(inventoryResponse.FusionTreasures as IFusionTreasure[]).push({
572
ItemType: treasure.ItemType,
573
ItemCount: 1,
574
Sockets: treasure.Sockets
575
});
576
}
577
}
578
} else {
579
for (const treasure of inventory.HybridFusionTreasures) {
580
const meta = ExportResources[treasure.ItemType];
581
const sockets: Record<string, string> = {};
582
for (let i = 0; i != meta.sockets!.length; ++i) {
583
if ((treasure.Sockets >> i) & 1) {
584
sockets[String.fromCharCode(0x61 + i)] = meta.sockets![i];
585
}
586
}
587
(inventoryResponse.FusionTreasures as IFusionTreasureClientLegacy[]).push({
588
ItemId: toOid2(treasure._id, buildLabel),
589
ItemType: treasure.ItemType,
590
Sockets: sockets
591
});
592
}
593
}
594
555
595
if (buildLabel) {
556
596
// Fix nemesis for older versions
557
597
if (
@@ -49,9 +49,13 @@ export const sellController: RequestHandler = async (req, res) => {
49
49
requiredFields.add("MiscItems");
50
50
}
51
51
for (const key of Object.keys(payload.Items)) {
52
requiredFields.add(key as keyof TInventoryDatabaseDocument);
53
if (key == "LongGuns") {
54
requiredFields.add("Melee");
52
if (key == "FusionTreasures") {
53
requiredFields.add("HybridFusionTreasures");
54
} else {
55
requiredFields.add(key as keyof TInventoryDatabaseDocument);
56
if (key == "LongGuns") {
57
requiredFields.add("Melee");
58
}
55
59
}
56
60
}
57
61
if (requiredFields.has("Upgrades")) {
@@ -336,7 +340,11 @@ export const sellController: RequestHandler = async (req, res) => {
336
340
break;
337
341
case "FusionTreasures":
338
342
payload.Items.FusionTreasures.forEach(sellItem => {
339
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
343
if (sellItem.String.length == 24 && !sellItem.String.includes("/")) {
344
inventory.HybridFusionTreasures.pull({ _id: sellItem.String });
345
} else {
346
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
347
}
340
348
});
341
349
break;
342
350
case "WeaponSkins": // SNS specific field, only used by webui
@@ -99,7 +99,8 @@ import type {
99
99
IChallengeInstanceStateClient,
100
100
IParam,
101
101
IDialogueCounter,
102
ISketch
102
ISketch,
103
IHybridFusionTreasure
103
104
} from "../../types/inventoryTypes/inventoryTypes.ts";
104
105
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
105
106
import type { IOid, ITypeCount } from "../../types/commonTypes.ts";
@@ -710,6 +711,11 @@ questKeysSchema.set("toJSON", {
710
711
711
712
export const fusionTreasuresSchema = new Schema<IFusionTreasure>().add(typeCountSchema).add({ Sockets: Number });
712
713
714
const hybridFusionTreasuresSchema = new Schema<IHybridFusionTreasure>({
715
ItemType: String,
716
Sockets: Number
717
});
718
713
719
const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
714
720
{
715
721
invasionId: Schema.Types.ObjectId,
@@ -1805,7 +1811,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1805
1811
WeaponSkins: [weaponSkinsSchema],
1806
1812
1807
1813
//Ayatan Item
1808
FusionTreasures: [fusionTreasuresSchema],
1814
FusionTreasures: { type: [fusionTreasuresSchema], default: undefined },
1815
HybridFusionTreasures: [hybridFusionTreasuresSchema],
1809
1816
//only used for Maroo apparently - { "node": "TreasureTutorial", "state": "TS_COMPLETED" }
1810
1817
TauntHistory: { type: [tauntSchema], default: undefined },
1811
1818
@@ -2031,6 +2038,7 @@ inventorySchema.set("toJSON", {
2031
2038
delete returnedObject.DeathSquadPoints;
2032
2039
delete returnedObject.NemesisTaxedCredits;
2033
2040
delete returnedObject.duviriSeedRefresh;
2041
delete returnedObject.HybridFusionTreasures;
2034
2042
2035
2043
const inventoryDatabase = returnedObject as Partial<IInventoryDatabase>;
2036
2044
const inventoryResponse = returnedObject as IInventoryClient;
@@ -2096,6 +2104,7 @@ export type InventoryDocumentProps = {
2096
2104
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
2097
2105
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
2098
2106
KubrowPetPrints: Types.DocumentArray<IKubrowPetPrintDatabase>;
2107
HybridFusionTreasures: Types.DocumentArray<IHybridFusionTreasure>;
2099
2108
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
2100
2109
2101
2110
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -34,7 +34,8 @@ import type {
34
34
IPeriodicMissionCompletionResponse,
35
35
IPeriodicMissionCompletionDatabase,
36
36
INemesisBaseClient,
37
INemesisBaseDatabase
37
INemesisBaseDatabase,
38
IFusionTreasure
38
39
} from "../types/inventoryTypes/inventoryTypes.ts";
39
40
import { equipmentKeys } from "../types/inventoryTypes/inventoryTypes.ts";
40
41
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
@@ -87,6 +88,7 @@ import type {
87
88
import { fromMongoDate, fromOid } from "../helpers/inventoryHelpers.ts";
88
89
import { getRecipe } from "./itemDataService.ts";
89
90
import { logger } from "../utils/logger.ts";
91
import { addFusionTreasures, prepareFusionTreasuresForMigration } from "./inventoryService.ts";
90
92
91
93
const convertOptionalDate = (value: IMongoDateWithLegacySupport | undefined): Date | undefined => {
92
94
return value ? fromMongoDate(value) : undefined;
@@ -463,7 +465,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
463
465
db.Affiliations = client.Affiliations;
464
466
}
465
467
if (client.FusionTreasures !== undefined) {
466
db.FusionTreasures = client.FusionTreasures;
468
db.HybridFusionTreasures.splice(0, db.HybridFusionTreasures.length);
469
prepareFusionTreasuresForMigration(client.FusionTreasures as IFusionTreasure[]);
470
addFusionTreasures(db, client.FusionTreasures as IFusionTreasure[]);
467
471
}
468
472
if (client.FocusUpgrades !== undefined) {
469
473
db.FocusUpgrades = client.FocusUpgrades;
@@ -2405,24 +2405,34 @@ export const addMods = (inventory: TInventoryDatabaseDocument, itemsArray: IRawU
2405
2405
});
2406
2406
};
2407
2407
2408
export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, itemsArray: IFusionTreasure[]): void => {
2409
const { FusionTreasures } = inventory;
2410
itemsArray.forEach(({ ItemType, ItemCount, Sockets }) => {
2411
const itemIndex = FusionTreasures.findIndex(i => i.ItemType == ItemType && (i.Sockets || 0) == (Sockets || 0));
2412
2413
if (itemIndex !== -1) {
2414
FusionTreasures[itemIndex].ItemCount += ItemCount;
2415
if (FusionTreasures[itemIndex].ItemCount == 0) {
2416
FusionTreasures.splice(itemIndex, 1);
2417
} else if (FusionTreasures[itemIndex].ItemCount <= 0) {
2418
throw new Error(
2419
`Cannot remove ${ItemCount * -1}x ${ItemType} from FusionTreasures, would be left with ${FusionTreasures[itemIndex].ItemCount}`
2420
);
2408
export const addFusionTreasures = (inventory: TInventoryDatabaseDocument, changes: IFusionTreasure[]): void => {
2409
for (const change of changes) {
2410
if (change.ItemCount != 0) {
2411
if (change.ItemCount > 0) {
2412
if (change.ItemCount > 100) {
2413
throw new Error(
2414
`unexpected acquisition quantity of FusionTreasures: got ${change.ItemCount}, expected 0..100`
2415
);
2416
}
2417
for (let i = 0; i != change.ItemCount; ++i) {
2418
inventory.HybridFusionTreasures.push({
2419
ItemType: change.ItemType,
2420
Sockets: change.Sockets || 0
2421
});
2422
}
2423
} else {
2424
for (let i = 0; i != change.ItemCount; --i) {
2425
const fusex = inventory.HybridFusionTreasures.find(
2426
x => x.ItemType == change.ItemType && (x.Sockets || 0) == (change.Sockets || 0)
2427
);
2428
if (!fusex) {
2429
throw new Error(`Not enough ${change.ItemType} with Sockets=${change.Sockets || 0} to remove`);
2430
}
2431
inventory.HybridFusionTreasures.pull({ _id: fusex._id });
2432
}
2421
2433
}
2422
} else {
2423
FusionTreasures.push({ ItemCount, ItemType, Sockets });
2424
2434
}
2425
});
2435
}
2426
2436
};
2427
2437
2428
2438
export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focusXpPlus: number[]): void => {
@@ -2863,6 +2873,26 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
2863
2873
logger.debug(`migrated ${inventory.KubrowPetEggs.length} KubrowPetEggs to MiscItems`);
2864
2874
inventory.KubrowPetEggs = undefined;
2865
2875
}
2876
2877
// Migrate fusion treasures to hybrid format
2878
if (inventory.FusionTreasures) {
2879
prepareFusionTreasuresForMigration(inventory.FusionTreasures);
2880
addFusionTreasures(inventory, inventory.FusionTreasures);
2881
inventory.FusionTreasures = undefined;
2882
}
2883
};
2884
2885
export const prepareFusionTreasuresForMigration = (fusionTreasures: IFusionTreasure[]): void => {
2886
for (const treasure of fusionTreasures) {
2887
if (treasure.ItemCount < 0) {
2888
treasure.ItemCount = 0;
2889
} else if (treasure.ItemCount > 100) {
2890
logger.debug(
2891
`reducing ${treasure.ItemCount} owned ${treasure.ItemType} to 100 for migration to hybrid format`
2892
);
2893
treasure.ItemCount = 100;
2894
}
2895
}
2866
2896
};
2867
2897
2868
2898
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
@@ -28,7 +28,6 @@ import {
28
28
addEmailItem,
29
29
addFocusXpIncreases,
30
30
addFusionPoints,
31
addFusionTreasures,
32
31
addItem,
33
32
addKahlProgress,
34
33
addLevelKeys,
@@ -400,7 +399,24 @@ export const addMissionInventoryUpdates = async (
400
399
);
401
400
break;
402
401
case "FusionTreasures":
403
addFusionTreasures(inventory, value);
402
for (const treasure of value) {
403
if ("ItemId" in treasure) {
404
inventory.HybridFusionTreasures.push({
405
ItemType: treasure.ItemType,
406
Sockets: 0
407
});
408
} else {
409
if (treasure.Sockets) {
410
throw new Error(`Filled ayatan treasures do not spawn in missions`);
411
}
412
for (let i = 0; i < treasure.ItemCount; ++i) {
413
inventory.HybridFusionTreasures.push({
414
ItemType: treasure.ItemType,
415
Sockets: 0
416
});
417
}
418
}
419
}
404
420
break;
405
421
case "CrewShipRawSalvage":
406
422
addCrewShipRawSalvage(inventory, value);
@@ -153,6 +153,7 @@ export interface IInventoryDatabase
153
153
| "FocusLoadouts"
154
154
| "ChallengeInstanceStates"
155
155
| "PeriodicMissionCompletions"
156
| "FusionTreasures"
156
157
| TEquipmentKey
157
158
>,
158
159
InventoryDatabaseEquipment,
@@ -207,6 +208,8 @@ export interface IInventoryDatabase
207
208
FocusLoadouts?: IFocusLoadoutDatabase[];
208
209
ChallengeInstanceStates?: IChallengeInstanceStateDatabase[];
209
210
PeriodicMissionCompletions: IPeriodicMissionCompletionDatabase[];
211
FusionTreasures?: IFusionTreasure[];
212
HybridFusionTreasures: IHybridFusionTreasure[]; // SNS-specific in-database format to support both modern & legacy clients
210
213
}
211
214
212
215
export interface IQuestKeyDatabase {
@@ -418,7 +421,7 @@ export interface IInventoryClient
418
421
Cards?: IUpgradeClient[]; // U8
419
422
EquippedGear: string[];
420
423
DeathMarks: string[];
421
FusionTreasures: IFusionTreasure[];
424
FusionTreasures: IFusionTreasure[] | IFusionTreasureClientLegacy[];
422
425
//WebFlags: IWebFlags;
423
426
CompletedAlerts: string[];
424
427
Consumables: ITypeCount[];
@@ -787,12 +790,27 @@ export interface IFocusXP {
787
790
788
791
export type TFocusPolarity = keyof IFocusXP;
789
792
793
// SNS-specific in-database format to support both modern & legacy clients
794
export interface IHybridFusionTreasure {
795
_id: Types.ObjectId;
796
ItemType: string;
797
Sockets: number;
798
}
799
800
// >= U25.7
790
801
export interface IFusionTreasure {
791
802
ItemCount: number;
792
803
ItemType: string;
793
804
Sockets: number;
794
805
}
795
806
807
// < U25.7
808
export interface IFusionTreasureClientLegacy {
809
ItemId: IOidWithLegacySupport;
810
ItemType: string;
811
Sockets: Record<string, string>; // Record<"a" | "b" | "c" | "d", "/Lotus/Types/Items/FusionTreasures/OroFusexOrnamentA" | "/Lotus/Types/Items/FusionTreasures/OroFusexOrnamentB">;
812
}
813
796
814
export interface IHelminthFoodRecord {
797
815
ItemType: string;
798
816
Date: number;
@@ -10,6 +10,7 @@ import type {
10
10
TSolarMapRegion,
11
11
TEquipmentKey,
12
12
IFusionTreasure,
13
IFusionTreasureClientLegacy,
13
14
ICustomMarkers,
14
15
IPlayerSkills,
15
16
IQuestKeyDatabase,
@@ -73,7 +74,7 @@ export type IMissionInventoryUpdateRequest = {
73
74
KeyToRemove?: string;
74
75
ActiveBoosters?: IBooster[];
75
76
RawUpgrades?: IRawUpgrade[];
76
FusionTreasures?: IFusionTreasure[];
77
FusionTreasures?: IFusionTreasure[] | IFusionTreasureClientLegacy[];
77
78
QuestKeys?: Omit<IQuestKeyDatabase, "CompletionDate">[];
78
79
RegularCredits?: number;
79
80
MissionFailed: boolean;