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: loadouts in U7-U13 (#3062)

This gets saving and loading loadouts working in U13 and below, there may be some cases that cause some appearance options to get reset when swapping between builds (namely anything in the skins field likes to get reset for some reason if set on a newer build like U13 and going back to U7-U8). U14-U15 need their own implementation for loading loadouts to work in them (I haven't found where the client gets the loadout data from yet), so loadouts remain unsupported in those builds for now. Also seems to fix loadouts in U16-U19.5 (by extension of now using legacy oid in places where it's needed), and fixes appearance options in U16. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3062 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: VoltPrime <subsonicjackal@gmail.com> Co-committed-by: VoltPrime <subsonicjackal@gmail.com>

332791bc
VoltPrime <subsonicjackal@gmail.com>
提交于

代码差异

6 个文件 +701 -71
Modified src/controllers/api/inventoryController.ts +286 -3
@@ -5,9 +5,9 @@ import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
5 5 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 import type { ILoadoutDatabase } from "../../types/saveLoadoutTypes.ts";
8 import type { ILoadoutConfigClientLegacy, ILoadoutDatabase, ILoadOutPresets } from "../../types/saveLoadoutTypes.ts";
9 9 import type { IInventoryClient, IShipInventory, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
10 import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
10 import { equipmentKeys, loadoutKeysLegacy } from "../../types/inventoryTypes/inventoryTypes.ts";
11 11 import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
12 12 import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
13 13 import type { ICountedItem } from "warframe-public-export-plus";
@@ -30,7 +30,14 @@ import { getNemesisManifest } from "../../helpers/nemesisHelpers.ts";
30 30 import { getPersonalRooms } from "../../services/personalRoomsService.ts";
31 31 import type { IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
32 32 import { Ship } from "../../models/shipModel.ts";
33 import { toLegacyOid, toOid, toOid2, version_compare } from "../../helpers/inventoryHelpers.ts";
33 import {
34 convertIColorToLegacyColors,
35 convertIColorToLegacyColorsWithAtt,
36 toLegacyOid,
37 toOid,
38 toOid2,
39 version_compare
40 } from "../../helpers/inventoryHelpers.ts";
34 41 import { Inbox } from "../../models/inboxModel.ts";
35 42 import { unixTimesInMs } from "../../constants/timeConstants.ts";
36 43 import { DailyDeal } from "../../models/worldStateModel.ts";
@@ -480,6 +487,22 @@ export const getInventoryResponse = async (
480 487 for (const category of equipmentKeys) {
481 488 for (const item of inventoryResponse[category]) {
482 489 toLegacyOid(item.ItemId);
490 if (version_compare(buildLabel, "2015.05.14.16.29") < 0) {
491 // Appearance config format is different for versions before U16.5
492 for (const config of item.Configs) {
493 if (version_compare(buildLabel, "2015.03.21.08.17") < 0) {
494 config.Customization = {
495 Colors: convertIColorToLegacyColorsWithAtt(config.pricol, config.attcol),
496 Skins: config.Skins ?? []
497 };
498 } else if (version_compare(buildLabel, "2015.03.21.08.17") >= 0) {
499 config.Colors = convertIColorToLegacyColorsWithAtt(
500 config.pricol,
501 config.attcol
502 );
503 }
504 }
505 }
483 506 }
484 507 }
485 508
@@ -588,6 +611,52 @@ export const getInventoryResponse = async (
588 611 if (inventoryResponse.GuildId) {
589 612 toLegacyOid(inventoryResponse.GuildId);
590 613 }
614 for (const item of inventoryResponse.CurrentLoadOutIds) {
615 toLegacyOid(item);
616 }
617 if (
618 version_compare(buildLabel, "2015.03.19.00.00") <= 0 &&
619 inventoryResponse.CurrentLoadOutIds.length > 0 &&
620 inventoryResponse.LoadOutPresets.NORMAL.length > 0
621 ) {
622 if (version_compare(buildLabel, "2014.07.21.18.38") >= 0) {
623 // U14-U15 expect a different response than any other version, where the client is pulling loadout data from has not been found yet
624 // As such, loading loadouts is currently unsupported for these versions
625 logger.warn("Loadouts are currently unsupported in U14-U15, loadouts will be undefined");
626 } else {
627 // U13 and below
628 inventoryResponse.CurrentLoadout = mapLegacyLoadoutConfig(
629 inventory,
630 inventoryResponse.LoadOutPresets,
631 buildLabel
632 );
633 }
634 }
635 for (const category of loadoutKeysLegacy) {
636 for (const item of inventoryResponse.LoadOutPresets[category]) {
637 toLegacyOid(item.ItemId);
638 if (item.s) {
639 if (item.s.ItemId) {
640 toLegacyOid(item.s.ItemId);
641 }
642 }
643 if (item.l) {
644 if (item.l.ItemId) {
645 toLegacyOid(item.l.ItemId);
646 }
647 }
648 if (item.p) {
649 if (item.p.ItemId) {
650 toLegacyOid(item.p.ItemId);
651 }
652 }
653 if (item.m) {
654 if (item.m.ItemId) {
655 toLegacyOid(item.m.ItemId);
656 }
657 }
658 }
659 }
591 660 }
592 661 }
593 662 }
@@ -596,6 +665,220 @@ export const getInventoryResponse = async (
596 665 return inventoryResponse;
597 666 };
598 667
668 const mapLegacyLoadoutConfig = (
669 inventory: TInventoryDatabaseDocument,
670 loadoutPresets: ILoadOutPresets,
671 buildLabel: string
672 ): ILoadoutConfigClientLegacy | undefined => {
673 // Loadout config mapping for U15 and below
674 const normPreset = loadoutPresets.NORMAL.find(x => x.ItemId.$oid == inventory.CurrentLoadOutIds[0].toString());
675 if (normPreset) {
676 const s = normPreset.s?.ItemId?.$oid ? inventory.Suits.id(normPreset.s.ItemId.$oid) : null;
677 const p = normPreset.p?.ItemId?.$oid ? inventory.Pistols.id(normPreset.p.ItemId.$oid) : null;
678 const l = normPreset.l?.ItemId?.$oid ? inventory.LongGuns.id(normPreset.l.ItemId.$oid) : null;
679 const m = normPreset.m?.ItemId?.$oid ? inventory.Melee.id(normPreset.m.ItemId.$oid) : null;
680 const loadoutConfig = {
681 ItemId: { $id: version_compare(buildLabel, "2014.07.21.18.38") < 0 ? "Current" : normPreset.ItemId.$oid },
682 Name: normPreset.n ?? "Default Loadout",
683 Presets: [
684 {
685 ItemId: { $id: s?._id.toString() ?? "ffffffffffffffffffffffff" },
686 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (normPreset.s?.mod ?? 0),
687 CustSlot: normPreset.s?.cus ?? 0,
688 Customization: {
689 Emblem: "",
690 Colors: convertIColorToLegacyColors(s?.Configs[normPreset.s?.cus ?? 0].pricol),
691 Skins: s?.Configs[normPreset.s?.cus ?? 0].Skins ?? []
692 }
693 },
694 {
695 ItemId: { $id: p?._id.toString() ?? "ffffffffffffffffffffffff" },
696 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (normPreset.p?.mod ?? 0),
697 CustSlot: normPreset.p?.cus ?? 0,
698 Customization: {
699 Emblem: "",
700 Colors: convertIColorToLegacyColors(p?.Configs[normPreset.p?.cus ?? 0].pricol),
701 Skins: p?.Configs[normPreset.p?.cus ?? 0].Skins ?? []
702 }
703 },
704 {
705 ItemId: { $id: l?._id.toString() ?? "ffffffffffffffffffffffff" },
706 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (normPreset.l?.mod ?? 0),
707 CustSlot: normPreset.l?.cus ?? 0,
708 Customization: {
709 Emblem: "",
710 Colors: convertIColorToLegacyColors(l?.Configs[normPreset.l?.cus ?? 0].pricol),
711 Skins: l?.Configs[normPreset.l?.cus ?? 0].Skins ?? []
712 }
713 },
714 {
715 ItemId: { $id: m?._id.toString() ?? "ffffffffffffffffffffffff" },
716 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (normPreset.m?.mod ?? 0),
717 CustSlot: normPreset.m?.cus ?? 0,
718 Customization: {
719 Emblem: "",
720 Colors: convertIColorToLegacyColors(m?.Configs[normPreset.m?.cus ?? 0].pricol),
721 Skins: m?.Configs[normPreset.m?.cus ?? 0].Skins ?? []
722 }
723 }
724 ]
725 };
726
727 if (version_compare(buildLabel, "2013.03.18.00.00") >= 0) {
728 if (inventory.CurrentLoadOutIds.length > 1 && loadoutPresets.SENTINEL.length > 0) {
729 const compPreset = loadoutPresets.SENTINEL.find(
730 x => x.ItemId.$oid == inventory.CurrentLoadOutIds[1].toString()
731 );
732 if (compPreset) {
733 const s = compPreset.s?.ItemId?.$oid ? inventory.Sentinels.id(compPreset.s.ItemId.$oid) : null;
734 const l = compPreset.l?.ItemId?.$oid
735 ? inventory.SentinelWeapons.id(compPreset.l.ItemId.$oid)
736 : null;
737 loadoutConfig.Presets.push(
738 {
739 ItemId: { $id: s?._id.toString() ?? "ffffffffffffffffffffffff" },
740 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (compPreset.s?.mod ?? 0),
741 CustSlot: compPreset.s?.cus ?? 0,
742 Customization: {
743 Emblem: "",
744 Colors: convertIColorToLegacyColors(s?.Configs[compPreset.s?.cus ?? 0].pricol),
745 Skins: s?.Configs[compPreset.s?.cus ?? 0].Skins ?? []
746 }
747 },
748 {
749 ItemId: { $id: l?._id.toString() ?? "ffffffffffffffffffffffff" },
750 ModSlot: version_compare(buildLabel, "2013.09.13.00.00") < 0 ? 0 : (compPreset.l?.mod ?? 0),
751 CustSlot: compPreset.l?.cus ?? 0,
752 Customization: {
753 Emblem: "",
754 Colors: convertIColorToLegacyColors(l?.Configs[compPreset.l?.cus ?? 0].pricol),
755 Skins: l?.Configs[compPreset.l?.cus ?? 0].Skins ?? []
756 }
757 }
758 );
759 }
760 } else {
761 logger.warn(
762 `Could not find SENTINEL loadout with id ${inventory.CurrentLoadOutIds[1].toString()}, this part of the loadout will be empty`
763 );
764
765 loadoutConfig.Presets.push(
766 {
767 ItemId: { $id: "ffffffffffffffffffffffff" },
768 ModSlot: 0,
769 CustSlot: 0,
770 Customization: {
771 Emblem: "",
772 Colors: [],
773 Skins: []
774 }
775 },
776 {
777 ItemId: { $id: "ffffffffffffffffffffffff" },
778 ModSlot: 0,
779 CustSlot: 0,
780 Customization: {
781 Emblem: "",
782 Colors: [],
783 Skins: []
784 }
785 }
786 );
787 }
788 }
789
790 if (version_compare(buildLabel, "2014.10.24.08.24") >= 0) {
791 if (inventory.CurrentLoadOutIds.length > 2 && loadoutPresets.ARCHWING.length > 0) {
792 const archPreset = loadoutPresets.ARCHWING.find(
793 x => x.ItemId.$oid == inventory.CurrentLoadOutIds[2].toString()
794 );
795 if (archPreset) {
796 const s = archPreset.s?.ItemId?.$oid ? inventory.SpaceSuits.id(archPreset.s.ItemId.$oid) : null;
797 const l = archPreset.l?.ItemId?.$oid ? inventory.SpaceGuns.id(archPreset.l.ItemId.$oid) : null;
798 const m = archPreset.m?.ItemId?.$oid ? inventory.SpaceMelee.id(archPreset.m.ItemId.$oid) : null;
799 loadoutConfig.Presets.push(
800 {
801 ItemId: { $id: s?._id.toString() ?? "ffffffffffffffffffffffff" },
802 ModSlot: archPreset.s?.mod ?? 0,
803 CustSlot: archPreset.s?.cus ?? 0,
804 Customization: {
805 Emblem: "",
806 Colors: convertIColorToLegacyColors(s?.Configs[archPreset.s?.cus ?? 0].pricol),
807 Skins: s?.Configs[0].Skins ?? []
808 }
809 },
810 {
811 ItemId: { $id: l?._id.toString() ?? "ffffffffffffffffffffffff" },
812 ModSlot: archPreset.l?.mod ?? 0,
813 CustSlot: archPreset.l?.cus ?? 0,
814 Customization: {
815 Emblem: "",
816 Colors: convertIColorToLegacyColors(l?.Configs[archPreset.l?.cus ?? 0].pricol),
817 Skins: l?.Configs[0].Skins ?? []
818 }
819 },
820 {
821 ItemId: { $id: m?._id.toString() ?? "ffffffffffffffffffffffff" },
822 ModSlot: archPreset.m?.mod ?? 0,
823 CustSlot: archPreset.m?.cus ?? 0,
824 Customization: {
825 Emblem: "",
826 Colors: convertIColorToLegacyColors(m?.Configs[archPreset.m?.cus ?? 0].pricol),
827 Skins: m?.Configs[0].Skins ?? []
828 }
829 }
830 );
831 }
832 } else {
833 logger.warn(
834 `Could not find ARCHWING loadout with id ${inventory.CurrentLoadOutIds[2].toString()}, this part of the loadout will be empty`
835 );
836
837 loadoutConfig.Presets.push(
838 {
839 ItemId: { $id: "ffffffffffffffffffffffff" },
840 ModSlot: 0,
841 CustSlot: 0,
842 Customization: {
843 Emblem: "",
844 Colors: [],
845 Skins: []
846 }
847 },
848 {
849 ItemId: { $id: "ffffffffffffffffffffffff" },
850 ModSlot: 0,
851 CustSlot: 0,
852 Customization: {
853 Emblem: "",
854 Colors: [],
855 Skins: []
856 }
857 },
858 {
859 ItemId: { $id: "ffffffffffffffffffffffff" },
860 ModSlot: 0,
861 CustSlot: 0,
862 Customization: {
863 Emblem: "",
864 Colors: [],
865 Skins: []
866 }
867 }
868 );
869 }
870 }
871
872 return loadoutConfig;
873 }
874
875 logger.error(
876 `Could not find NORMAL loadout with id ${inventory.CurrentLoadOutIds[0].toString()}, entire loadout will be undefined`
877 );
878
879 return undefined;
880 };
881
599 882 const getExpRequiredForMr = (rank: number): number => {
600 883 if (rank <= 30) {
601 884 return 2500 * rank * rank;
Modified src/helpers/inventoryHelpers.ts +33 -0
@@ -2,6 +2,7 @@ import type { IMongoDate, IOid, IOidWithLegacySupport } from "../types/commonTyp
2 2 import { Types } from "mongoose";
3 3 import type { TRarity } from "warframe-public-export-plus";
4 4 import type { IFusionTreasure } from "../types/inventoryTypes/inventoryTypes.ts";
5 import type { IColor } from "../types/inventoryTypes/commonInventoryTypes.ts";
5 6
6 7 export const version_compare = (a: string, b: string): number => {
7 8 const a_digits = a
@@ -70,6 +71,38 @@ export const parseFusionTreasure = (name: string, count: number): IFusionTreasur
70 71 };
71 72 };
72 73
74 export const convertIColorToLegacyColors = (colors: IColor | undefined): number[] => {
75 const convertedColors = [colors?.t0 ?? -1, colors?.t1 ?? -1, colors?.t2 ?? -1, colors?.t3 ?? -1, colors?.en ?? -1];
76 return convertedColors;
77 };
78
79 export const convertIColorToLegacyColorsWithAtt = (
80 pricol: IColor | undefined,
81 attcol: IColor | undefined
82 ): number[] => {
83 const convertedColors = [
84 pricol?.t0 ?? -1,
85 pricol?.t1 ?? -1,
86 pricol?.t2 ?? -1,
87 pricol?.t3 ?? -1,
88 pricol?.en ?? -1,
89 attcol?.t0 ?? -1,
90 attcol?.t1 ?? -1,
91 attcol?.t2 ?? -1,
92 attcol?.t3 ?? -1,
93 attcol?.en ?? -1
94 ];
95 return convertedColors;
96 };
97
98 export const convertLegacyColorsToIColor = (colors: number[] | undefined): IColor => {
99 if (colors) {
100 return { t0: colors[0], t1: colors[1], t2: colors[2], t3: colors[3], en: colors[4] };
101 } else {
102 return {};
103 }
104 };
105
73 106 export type TTraitsPool = Record<
74 107 "Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
75 108 { type: string; rarity: TRarity }[]
Modified src/services/saveLoadoutService.ts +351 -65
@@ -1,21 +1,31 @@
1 1 import type {
2 2 IItemEntry,
3 3 ILoadoutClient,
4 ILoadoutConfigClientLegacy,
4 5 ILoadoutEntry,
6 ILoadoutPresetClientLegacy,
5 7 IOperatorConfigEntry,
6 8 ISaveLoadoutRequestNoUpgradeVer
7 9 } from "../types/saveLoadoutTypes.ts";
8 10 import { Loadout } from "../models/inventoryModels/loadoutModel.ts";
9 11 import { addMods, getInventory } from "./inventoryService.ts";
10 import type { IOid } from "../types/commonTypes.ts";
12 import type { IOidWithLegacySupport } from "../types/commonTypes.ts";
11 13 import { Types } from "mongoose";
12 14 import { isEmptyObject } from "../helpers/general.ts";
13 import { version_compare } from "../helpers/inventoryHelpers.ts";
15 import {
16 convertLegacyColorsToIColor,
17 fromDbOid,
18 fromOid,
19 toObjectId,
20 version_compare
21 } from "../helpers/inventoryHelpers.ts";
14 22 import { logger } from "../utils/logger.ts";
15 23 import type { TEquipmentKey } from "../types/inventoryTypes/inventoryTypes.ts";
16 24 import { equipmentKeys } from "../types/inventoryTypes/inventoryTypes.ts";
17 25 import type { IItemConfig, IItemConfigDatabase } from "../types/inventoryTypes/commonInventoryTypes.ts";
18 26 import { importCrewShipMembers, importCrewShipWeapon, importLoadOutConfig } from "./importService.ts";
27 import type { IEquipmentDatabase, IEquipmentSelectionDatabase } from "../types/equipmentTypes.ts";
28 import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
19 29
20 30 //TODO: setup default items on account creation or like originally in giveStartingItems.php
21 31
@@ -65,68 +75,125 @@ export const handleInventoryItemConfigChange = async (
65 75 break;
66 76 }
67 77 case "LoadOuts": {
68 logger.debug("loadout received");
69 const loadout = await Loadout.findOne({ loadoutOwnerId: accountId });
70 if (!loadout) {
71 throw new Error("loadout not found");
72 }
78 if (
79 buildLabel &&
80 version_compare(buildLabel, "2014.04.10.17.47") >= 0 &&
81 version_compare(buildLabel, "2015.03.19.00.00") < 0
82 ) {
83 // U14-U15
84 // const configs = equipment as {
85 // [key: string]: ILoadoutConfigClientLegacy;
86 // };
73 87
74 let newLoadoutId: Types.ObjectId | undefined;
75 for (const [_loadoutSlot, _loadout] of Object.entries(equipment)) {
76 const loadoutSlot = _loadoutSlot as keyof ILoadoutClient;
77 const newLoadout = _loadout as ILoadoutEntry;
88 // logger.debug("legacy loadout received (U14-U15 format)", configs);
78 89
79 // empty loadout slot like: "NORMAL": {}
80 if (isEmptyObject(newLoadout)) {
81 continue;
90 // for (const key in configs) {
91 // const x = configs[key];
92 // await saveLegacyLoadoutPreset(inventory, x.Presets, x.Name, buildLabel);
93 // }
94
95 logger.warn("Loadouts are currently unsupported in U14-U15, only saving mod/appearance configs");
96
97 break;
98 } else {
99 logger.debug("loadout received");
100 const loadout = await Loadout.findOne({ loadoutOwnerId: accountId });
101 if (!loadout) {
102 throw new Error("loadout not found");
82 103 }
83 104
84 // all non-empty entries are one loadout slot
85 for (const [loadoutId, loadoutConfig] of Object.entries(newLoadout)) {
86 if (loadoutConfig.Remove) {
87 loadout[loadoutSlot].pull({ _id: loadoutId });
105 let newLoadoutId: Types.ObjectId | undefined;
106 for (const [_loadoutSlot, _loadout] of Object.entries(equipment)) {
107 const loadoutSlot = _loadoutSlot as keyof ILoadoutClient;
108 const newLoadout = _loadout as ILoadoutEntry;
109
110 // empty loadout slot like: "NORMAL": {}
111 if (isEmptyObject(newLoadout)) {
88 112 continue;
89 113 }
90 114
91 const oldLoadoutConfig = loadout[loadoutSlot].id(loadoutId);
115 // all non-empty entries are one loadout slot
116 for (const [loadoutId, loadoutConfig] of Object.entries(newLoadout)) {
117 if (loadoutConfig.Remove) {
118 loadout[loadoutSlot].pull({ _id: loadoutId });
119 continue;
120 }
92 121
93 const loadoutConfigDatabase = importLoadOutConfig(loadoutConfig);
122 const oldLoadoutConfig = loadout[loadoutSlot].id(loadoutId);
94 123
95 // if no config with this id exists, create a new one
96 if (!oldLoadoutConfig) {
97 //save the new object id and assign it for every ffff return at the end
98 if (loadoutConfigDatabase._id.toString() === "ffffffffffffffffffffffff") {
99 if (!newLoadoutId) {
100 newLoadoutId = new Types.ObjectId();
124 const loadoutConfigDatabase = importLoadOutConfig(loadoutConfig);
125
126 // if no config with this id exists, create a new one
127 if (!oldLoadoutConfig) {
128 //save the new object id and assign it for every ffff return at the end
129 if (loadoutConfigDatabase._id.toString() === "ffffffffffffffffffffffff") {
130 if (!newLoadoutId) {
131 newLoadoutId = new Types.ObjectId();
132 }
133 loadoutConfigDatabase._id = newLoadoutId;
134 loadout[loadoutSlot].push(loadoutConfigDatabase);
135 continue;
101 136 }
102 loadoutConfigDatabase._id = newLoadoutId;
137
103 138 loadout[loadoutSlot].push(loadoutConfigDatabase);
104 139 continue;
105 140 }
106 141
107 loadout[loadoutSlot].push(loadoutConfigDatabase);
108 continue;
109 }
142 const loadoutIndex = loadout[loadoutSlot].indexOf(oldLoadoutConfig);
143 if (loadoutIndex === -1) {
144 throw new Error("loadout index not found");
145 }
110 146
111 const loadoutIndex = loadout[loadoutSlot].indexOf(oldLoadoutConfig);
112 if (loadoutIndex === -1) {
113 throw new Error("loadout index not found");
147 loadout[loadoutSlot][loadoutIndex].overwrite(loadoutConfigDatabase);
114 148 }
149 }
150 await loadout.save();
115 151
116 loadout[loadoutSlot][loadoutIndex].overwrite(loadoutConfigDatabase);
152 //only return an id if a new loadout was added
153 if (newLoadoutId) {
154 return newLoadoutId.toString();
117 155 }
118 156 }
119 await loadout.save();
120 157
121 //only return an id if a new loadout was added
122 if (newLoadoutId) {
123 return newLoadoutId.toString();
158 break;
159 }
160 case "LoadOut": {
161 // U10-U13
162 const config = equipment as ILoadoutConfigClientLegacy;
163 logger.debug("legacy loadout received (U10-U13 format)", config);
164
165 await saveLegacyLoadoutPreset(inventory, config.Presets, config.Name, buildLabel);
166 break;
167 }
168 case "Presets": {
169 // U8 and below
170 const presets = equipment as ILoadoutPresetClientLegacy[];
171 logger.debug("legacy loadout received (U8 format)", presets);
172
173 await saveLegacyLoadoutPreset(inventory, presets, undefined, buildLabel);
174 break;
175 }
176 case "CurrentLoadout": {
177 // U14-U15
178 const id = equipment as string;
179 if (inventory.CurrentLoadOutIds[0]) {
180 inventory.CurrentLoadOutIds[0] = toObjectId(id);
181 }
182 if (inventory.CurrentLoadOutIds[1]) {
183 inventory.CurrentLoadOutIds[1] = toObjectId(id);
184 }
185 if (inventory.CurrentLoadOutIds[2]) {
186 inventory.CurrentLoadOutIds[2] = toObjectId(id);
124 187 }
125 188 break;
126 189 }
127 190 case "CurrentLoadOutIds": {
128 const loadoutIds = equipment as IOid[]; // TODO: Check for more than just an array of oids, I think i remember one instance
129 inventory.CurrentLoadOutIds = loadoutIds;
191 const loadoutIds = equipment as IOidWithLegacySupport[]; // TODO: Check for more than just an array of oids, I think i remember one instance
192 const ids: Types.ObjectId[] = [];
193 loadoutIds.forEach(x => {
194 ids.push(toObjectId(fromOid(x)));
195 });
196 inventory.CurrentLoadOutIds = ids;
130 197 break;
131 198 }
132 199 case "EquippedGear":
@@ -199,32 +266,47 @@ export const handleInventoryItemConfigChange = async (
199 266 for (const [configId, config] of Object.entries(itemConfigEntries)) {
200 267 if (/^[0-9]+$/.test(configId)) {
201 268 const c = config as IItemConfig;
202 if (buildLabel && version_compare(buildLabel, "2014.04.10.17.47") < 0) {
203 if (c.Upgrades) {
204 // U10-U11 store mods in the item config as $id instead of a string, need to convert that here
205 const convertedUpgrades: string[] = [];
206 c.Upgrades.forEach(upgrade => {
207 const upgradeId = upgrade as { $id: string };
208 const rawUpgrade = inventory.RawUpgrades.id(upgradeId.$id);
209 if (rawUpgrade) {
210 const newId = new Types.ObjectId();
211 convertedUpgrades.push(newId.toString());
212 addMods(inventory, [
213 {
269 if (buildLabel && version_compare(buildLabel, "2015.03.21.08.17") <= 0) {
270 const legacyColors = c.Customization?.Colors ?? c.Colors;
271 if (legacyColors) {
272 if (legacyColors.length == 10) {
273 c.pricol = convertLegacyColorsToIColor(legacyColors.splice(0, 5));
274 c.attcol = convertLegacyColorsToIColor(legacyColors);
275 } else {
276 c.pricol = convertLegacyColorsToIColor(legacyColors);
277 }
278 }
279 const legacySkins = c.Customization?.Skins;
280 if (legacySkins) {
281 c.Skins = legacySkins;
282 }
283 if (version_compare(buildLabel, "2014.04.10.17.47") < 0) {
284 if (c.Upgrades) {
285 // U10-U11 store mods in the item config as $id instead of a string, need to convert that here
286 const convertedUpgrades: string[] = [];
287 c.Upgrades.forEach(upgrade => {
288 const upgradeId = upgrade as { $id: string };
289 const rawUpgrade = inventory.RawUpgrades.id(upgradeId.$id);
290 if (rawUpgrade) {
291 const newId = new Types.ObjectId();
292 convertedUpgrades.push(newId.toString());
293 addMods(inventory, [
294 {
295 ItemType: rawUpgrade.ItemType,
296 ItemCount: -1
297 }
298 ]);
299 inventory.Upgrades.push({
300 UpgradeFingerprint: `{"lvl":0}`,
214 301 ItemType: rawUpgrade.ItemType,
215 ItemCount: -1
216 }
217 ]);
218 inventory.Upgrades.push({
219 UpgradeFingerprint: `{"lvl":0}`,
220 ItemType: rawUpgrade.ItemType,
221 _id: newId
222 });
223 } else {
224 convertedUpgrades.push(upgradeId.$id);
225 }
226 });
227 c.Upgrades = convertedUpgrades;
302 _id: newId
303 });
304 } else {
305 convertedUpgrades.push(upgradeId.$id);
306 }
307 });
308 c.Upgrades = convertedUpgrades;
309 }
228 310 }
229 311 }
230 312 inventoryItem.Configs[parseInt(configId)] = c as IItemConfigDatabase;
@@ -264,3 +346,207 @@ export const handleInventoryItemConfigChange = async (
264 346 }
265 347 await inventory.save();
266 348 };
349
350 const saveLegacyLoadoutPreset = async (
351 inventory: TInventoryDatabaseDocument,
352 presets: ILoadoutPresetClientLegacy[],
353 name: string | undefined,
354 buildLabel: string | undefined
355 ): Promise<void> => {
356 const loadout = await Loadout.findOne({ loadoutOwnerId: inventory.accountOwnerId });
357 if (!loadout) {
358 throw new Error("loadout not found");
359 }
360
361 const currentLoadouts = inventory.CurrentLoadOutIds as Types.ObjectId[];
362
363 const s =
364 fromOid(presets[0].ItemId) != "ffffffffffffffffffffffff"
365 ? configureLegacyEquipmentSelection(
366 presets[0],
367 buildLabel,
368 inventory.Suits.id(fromOid(presets[0].ItemId)),
369 loadout.NORMAL.id(currentLoadouts[0])?.s?.cus
370 )
371 : undefined;
372 const p =
373 fromOid(presets[1].ItemId) != "ffffffffffffffffffffffff"
374 ? configureLegacyEquipmentSelection(
375 presets[1],
376 buildLabel,
377 inventory.Pistols.id(fromOid(presets[1].ItemId)),
378 loadout.NORMAL.id(currentLoadouts[0])?.p?.cus
379 )
380 : undefined;
381 const l =
382 fromOid(presets[2].ItemId) != "ffffffffffffffffffffffff"
383 ? configureLegacyEquipmentSelection(
384 presets[2],
385 buildLabel,
386 inventory.LongGuns.id(fromOid(presets[2].ItemId)),
387 loadout.NORMAL.id(currentLoadouts[0])?.l?.cus
388 )
389 : undefined;
390 const m =
391 fromOid(presets[3].ItemId) != "ffffffffffffffffffffffff"
392 ? configureLegacyEquipmentSelection(
393 presets[3],
394 buildLabel,
395 inventory.Melee.id(fromOid(presets[3].ItemId)),
396 loadout.NORMAL.id(currentLoadouts[0])?.m?.cus
397 )
398 : undefined;
399
400 if (loadout.NORMAL.length == 0) {
401 const loadoutId = new Types.ObjectId("000000000000000000000000");
402 loadout.NORMAL.push({
403 n: "Default Loadout",
404 s: s,
405 l: l,
406 p: p,
407 m: m,
408 _id: loadoutId
409 });
410 if (currentLoadouts.length == 0) {
411 currentLoadouts.push(loadoutId);
412 } else {
413 currentLoadouts[0] = loadoutId;
414 }
415 } else {
416 const loadoutId = fromDbOid(currentLoadouts[0]);
417 const preset = loadout.NORMAL.id(loadoutId);
418 if (preset) {
419 preset.n = name ?? preset.n;
420 preset.s = s;
421 preset.p = p;
422 preset.l = l;
423 preset.m = m;
424 } else {
425 logger.warn(
426 `Could not find NORMAL loadout with id ${loadoutId.toString()}, equipment selection will not be saved`
427 );
428 }
429 }
430
431 if (presets.length >= 6) {
432 const s =
433 fromOid(presets[4].ItemId) != "ffffffffffffffffffffffff"
434 ? configureLegacyEquipmentSelection(
435 presets[4],
436 buildLabel,
437 inventory.Sentinels.id(fromOid(presets[4].ItemId)),
438 loadout.SENTINEL.id(currentLoadouts[1])?.s?.cus
439 )
440 : undefined;
441 const l =
442 fromOid(presets[5].ItemId) != "ffffffffffffffffffffffff"
443 ? configureLegacyEquipmentSelection(
444 presets[5],
445 buildLabel,
446 inventory.SentinelWeapons.id(fromOid(presets[5].ItemId)),
447 loadout.SENTINEL.id(currentLoadouts[1])?.l?.cus
448 )
449 : undefined;
450
451 if (loadout.SENTINEL.length == 0) {
452 const loadoutId = new Types.ObjectId("000000000000000000000000");
453 loadout.SENTINEL.push({
454 n: "Default Loadout",
455 s: s,
456 l: l,
457 _id: loadoutId
458 });
459 if (currentLoadouts.length < 2) {
460 currentLoadouts.push(loadoutId);
461 } else {
462 currentLoadouts[1] = loadoutId;
463 }
464 } else {
465 const loadoutId = fromDbOid(currentLoadouts[1]);
466 const preset = loadout.SENTINEL.id(loadoutId);
467 if (preset) {
468 preset.n = name ?? preset.n;
469 preset.s = s;
470 preset.l = l;
471 } else {
472 logger.warn(
473 `Could not find SENTINEL loadout with id ${loadoutId.toString()}, equipment selection will not be saved`
474 );
475 }
476 }
477 }
478
479 if (presets.length == 9) {
480 const s =
481 fromOid(presets[6].ItemId) != "ffffffffffffffffffffffff"
482 ? configureLegacyEquipmentSelection(presets[6], buildLabel, null, 0)
483 : undefined;
484 const l =
485 fromOid(presets[7].ItemId) != "ffffffffffffffffffffffff"
486 ? configureLegacyEquipmentSelection(presets[7], buildLabel, null, 0)
487 : undefined;
488 const m =
489 fromOid(presets[8].ItemId) != "ffffffffffffffffffffffff"
490 ? configureLegacyEquipmentSelection(presets[8], buildLabel, null, 0)
491 : undefined;
492
493 if (loadout.ARCHWING.length == 0) {
494 const loadoutId = new Types.ObjectId("000000000000000000000000");
495 loadout.ARCHWING.push({
496 n: "Default Loadout",
497 s: s,
498 l: l,
499 m: m,
500 _id: loadoutId
501 });
502 if (currentLoadouts.length < 3) {
503 currentLoadouts.push(loadoutId);
504 } else {
505 currentLoadouts[2] = loadoutId;
506 }
507 } else {
508 const loadoutId = fromDbOid(currentLoadouts[2]);
509 const preset = loadout.ARCHWING.id(loadoutId);
510 if (preset) {
511 preset.n = name ?? preset.n;
512 preset.s = s;
513 preset.l = l;
514 preset.m = m;
515 } else {
516 logger.warn(
517 `Could not find ARCHWING loadout with id ${loadoutId.toString()}, equipment selection will not be saved`
518 );
519 }
520 }
521 }
522
523 await loadout.save();
524 };
525
526 const configureLegacyEquipmentSelection = (
527 preset: ILoadoutPresetClientLegacy,
528 buildLabel: string | undefined,
529 item: IEquipmentDatabase | null,
530 appearanceConfig: number | undefined
531 ): IEquipmentSelectionDatabase | undefined => {
532 if (preset.ItemId.$id) {
533 const slotEntry = {
534 ItemId: toObjectId(preset.ItemId.$id),
535 mod: preset.ModSlot ?? 0,
536 cus: preset.CustSlot ?? 0
537 };
538
539 if (item && buildLabel && version_compare(buildLabel, "2013.09.13.00.00") < 0) {
540 // Specific code path for U8 and below for applying cosmetics
541 const config = item.Configs[appearanceConfig ?? 0];
542 if (item.Configs[appearanceConfig ?? 0]) {
543 config.pricol = convertLegacyColorsToIColor(preset.Customization?.Colors);
544 config.Skins = preset.Customization?.Skins;
545 }
546 }
547
548 return slotEntry;
549 } else {
550 return undefined;
551 }
552 };
Modified src/types/inventoryTypes/commonInventoryTypes.ts +9 -0
@@ -54,6 +54,15 @@ export interface IItemConfig {
54 54 AbilityOverride?: IAbilityOverride;
55 55 PvpUpgrades?: string[];
56 56 ugly?: boolean;
57 Colors?: number[]; // U16.0
58 Customization?: IItemConfigCustomizationsLegacy; // U10-U15
59 }
60
61 export interface IItemConfigCustomizationsLegacy {
62 CustomEmblems?: { EmblemId: string }[];
63 Emblem?: string;
64 Colors: number[];
65 Skins: string[];
57 66 }
58 67
59 68 export interface IItemConfigDatabase extends Omit<IItemConfig, "Upgrades"> {
Modified src/types/inventoryTypes/inventoryTypes.ts +4 -1
@@ -13,7 +13,7 @@ import type { IFingerprintStat, RivenFingerprint } from "../../helpers/rivenHelp
13 13 import type { IOrbiterClient } from "../personalRoomsTypes.ts";
14 14 import type { ICountedStoreItem } from "warframe-public-export-plus";
15 15 import type { IEquipmentClient, IEquipmentDatabase, ITraits } from "../equipmentTypes.ts";
16 import type { ILoadOutPresets } from "../saveLoadoutTypes.ts";
16 import type { ILoadoutConfigClientLegacy, ILoadOutPresets } from "../saveLoadoutTypes.ts";
17 17 import type { CalendarSeasonType } from "../worldStateTypes.ts";
18 18
19 19 export type InventoryDatabaseEquipment = {
@@ -195,6 +195,8 @@ export const equipmentKeys = [
195 195 "CrewShipSalvagedWeapons"
196 196 ] as const;
197 197
198 export const loadoutKeysLegacy = ["NORMAL", "NORMAL_PVP", "LUNARO", "ARCHWING", "SENTINEL", "OPERATOR"] as const;
199
198 200 export type TEquipmentKey = (typeof equipmentKeys)[number];
199 201
200 202 export interface IDuviriInfo {
@@ -301,6 +303,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
301 303 FlavourItems: IFlavourItem[];
302 304 LoadOutPresets: ILoadOutPresets;
303 305 CurrentLoadOutIds: IOid[];
306 CurrentLoadout?: ILoadoutConfigClientLegacy; // U8-13
304 307 Missions: IMission[];
305 308 RandomUpgradesIdentified?: number;
306 309 LastRegionPlayed: TSolarMapRegion;
Modified src/types/saveLoadoutTypes.ts +18 -2
@@ -1,8 +1,9 @@
1 import type { IOid } from "./commonTypes.ts";
1 import type { IOid, IOidWithLegacySupport } from "./commonTypes.ts";
2 2 import type {
3 3 ICrewShipCustomization,
4 4 IFlavourItem,
5 5 IItemConfig,
6 IItemConfigCustomizationsLegacy,
6 7 ILotusCustomization,
7 8 IOperatorConfigClient
8 9 } from "./inventoryTypes/commonInventoryTypes.ts";
@@ -15,7 +16,9 @@ import type {
15 16 } from "./equipmentTypes.ts";
16 17
17 18 export interface ISaveLoadoutRequest {
18 LoadOuts: ILoadoutClient;
19 LoadOuts: ILoadoutClient | ILoadoutConfigClientLegacy;
20 LoadOut?: ILoadoutPresetClientLegacy[]; // U10-U13
21 Presets?: ILoadoutPresetClientLegacy[]; // U8
19 22 LongGuns: IItemEntry;
20 23 OperatorAmps: IItemEntry;
21 24 Pistols: IItemEntry;
@@ -42,6 +45,7 @@ export interface ISaveLoadoutRequest {
42 45 OperatorLoadOuts: IOperatorConfigEntry;
43 46 KahlLoadOuts: IOperatorConfigEntry;
44 47 CrewShips: IItemEntry;
48 CurrentLoadout: string; // U14-U15
45 49 CurrentLoadOutIds: IOid[];
46 50 ValidNewLoadoutId: string;
47 51 ActiveCrewShip: IOid;
@@ -148,3 +152,15 @@ export interface ILoadoutConfigDatabase
148 152 h?: IEquipmentSelectionDatabase;
149 153 a?: IEquipmentSelectionDatabase;
150 154 }
155
156 export interface ILoadoutConfigClientLegacy {
157 ItemId: IOidWithLegacySupport;
158 Name: string;
159 Presets: ILoadoutPresetClientLegacy[];
160 }
161 export interface ILoadoutPresetClientLegacy {
162 ItemId: IOidWithLegacySupport;
163 ModSlot?: number;
164 CustSlot?: number;
165 Customization?: IItemConfigCustomizationsLegacy;
166 }