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

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

代码差异

9 个文件 +213 -60
Modified src/controllers/api/fusionTreasuresController.ts +57 -30
@@ -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 };
Modified src/controllers/api/inventoryController.ts +42 -2
@@ -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 (
Modified src/controllers/api/sellController.ts +12 -4
@@ -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
Modified src/models/inventoryModels/inventoryModel.ts +11 -2
@@ -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
Modified src/services/importService.ts +6 -2
@@ -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;
Modified src/services/inventoryService.ts +46 -16
@@ -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 => {
Modified src/services/missionInventoryUpdateService.ts +18 -2
@@ -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);
Modified src/types/inventoryTypes/inventoryTypes.ts +19 -1
@@ -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;
Modified src/types/requestTypes.ts +2 -1
@@ -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;