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: getProfileViewingData for clans (#1412)

Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1412 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

404c7476
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

2 个文件 +227 -95
Modified src/controllers/dynamic/getProfileViewingDataController.ts +219 -95
@@ -1,5 +1,5 @@
1 1 import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
2 import { Guild } from "@/src/models/guildModel";
2 import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
3 3 import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
4 4 import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
5 5 import { Account } from "@/src/models/loginModel";
@@ -19,99 +19,155 @@ import {
19 19 } from "@/src/types/inventoryTypes/inventoryTypes";
20 20 import { RequestHandler } from "express";
21 21 import { catBreadHash } from "../api/inventoryController";
22 import { ExportCustoms } from "warframe-public-export-plus";
22 import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
23 import { IStatsClient } from "@/src/types/statTypes";
24 import { toStoreItem } from "@/src/services/itemDataService";
23 25
24 26 export const getProfileViewingDataController: RequestHandler = async (req, res) => {
25 if (!req.query.playerId) {
26 res.status(400).end();
27 return;
28 }
29 const account = await Account.findById(req.query.playerId as string, "DisplayName");
30 if (!account) {
31 res.status(400).send("No account or guild ID specified");
32 return;
33 }
34 const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
35 const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
27 if (req.query.playerId) {
28 const account = await Account.findById(req.query.playerId as string, "DisplayName");
29 if (!account) {
30 res.status(409).send("Could not find requested account");
31 return;
32 }
33 const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
36 34
37 const result: IPlayerProfileViewingDataResult = {
38 AccountId: toOid(account._id),
39 DisplayName: account.DisplayName,
40 PlayerLevel: inventory.PlayerLevel,
41 LoadOutInventory: {
42 WeaponSkins: [],
43 XPInfo: inventory.XPInfo
44 },
45 PlayerSkills: inventory.PlayerSkills,
46 ChallengeProgress: inventory.ChallengeProgress,
47 DeathMarks: inventory.DeathMarks,
48 Harvestable: inventory.Harvestable,
49 DeathSquadable: inventory.DeathSquadable,
50 Created: toMongoDate(inventory.Created),
51 MigratedToConsole: false,
52 Missions: inventory.Missions,
53 Affiliations: inventory.Affiliations,
54 DailyFocus: inventory.DailyFocus,
55 Wishlist: inventory.Wishlist,
56 Alignment: inventory.Alignment
57 };
58 if (inventory.CurrentLoadOutIds.length) {
59 result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
60 result.LoadOutPreset.ItemId = undefined;
61 const skins = new Set<string>();
62 if (result.LoadOutPreset.s) {
63 result.LoadOutInventory.Suits = [
64 inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
65 ];
66 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
35 const result: IPlayerProfileViewingDataResult = {
36 AccountId: toOid(account._id),
37 DisplayName: account.DisplayName,
38 PlayerLevel: inventory.PlayerLevel,
39 LoadOutInventory: {
40 WeaponSkins: [],
41 XPInfo: inventory.XPInfo
42 },
43 PlayerSkills: inventory.PlayerSkills,
44 ChallengeProgress: inventory.ChallengeProgress,
45 DeathMarks: inventory.DeathMarks,
46 Harvestable: inventory.Harvestable,
47 DeathSquadable: inventory.DeathSquadable,
48 Created: toMongoDate(inventory.Created),
49 MigratedToConsole: false,
50 Missions: inventory.Missions,
51 Affiliations: inventory.Affiliations,
52 DailyFocus: inventory.DailyFocus,
53 Wishlist: inventory.Wishlist,
54 Alignment: inventory.Alignment
55 };
56 await populateLoadout(inventory, result);
57 if (inventory.GuildId) {
58 const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
59 populateGuild(guild, result);
67 60 }
68 if (result.LoadOutPreset.p) {
69 result.LoadOutInventory.Pistols = [
70 inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
71 ];
72 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
61 for (const key of allDailyAffiliationKeys) {
62 result[key] = inventory[key];
73 63 }
74 if (result.LoadOutPreset.l) {
75 result.LoadOutInventory.LongGuns = [
76 inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
77 ];
78 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
64
65 const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
66 delete stats._id;
67 delete stats.__v;
68 delete stats.accountOwnerId;
69
70 res.json({
71 Results: [result],
72 TechProjects: [],
73 XpComponents: [],
74 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
75 Stats: stats
76 });
77 } else if (req.query.guildId) {
78 const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
79 if (!guild) {
80 res.status(409).send("Could not find guild");
81 return;
79 82 }
80 if (result.LoadOutPreset.m) {
81 result.LoadOutInventory.Melee = [
82 inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
83 ];
84 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
83 const members = await GuildMember.find({ guildId: guild._id, status: 0 });
84 const results: IPlayerProfileViewingDataResult[] = [];
85 for (let i = 0; i != Math.min(4, members.length); ++i) {
86 const member = members[i];
87 const [account, inventory] = await Promise.all([
88 Account.findById(member.accountId, "DisplayName"),
89 Inventory.findOne(
90 { accountOwnerId: member.accountId },
91 "DisplayName PlayerLevel XPInfo LoadOutPresets CurrentLoadOutIds WeaponSkins Suits Pistols LongGuns Melee"
92 )
93 ]);
94 const result: IPlayerProfileViewingDataResult = {
95 AccountId: toOid(account!._id),
96 DisplayName: account!.DisplayName,
97 PlayerLevel: inventory!.PlayerLevel,
98 LoadOutInventory: {
99 WeaponSkins: [],
100 XPInfo: inventory!.XPInfo
101 }
102 };
103 await populateLoadout(inventory!, result);
104 results.push(result);
85 105 }
86 for (const skin of skins) {
87 result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
106 populateGuild(guild, results[0]);
107
108 const combinedStats: IStatsClient = {};
109 const statsArr = await Stats.find({ accountOwnerId: { $in: members.map(x => x.accountId) } }).lean(); // need this as POJO so Object.entries works as expected
110 for (const stats of statsArr) {
111 for (const [key, value] of Object.entries(stats)) {
112 if (typeof value == "number" && key != "__v") {
113 (combinedStats[key as keyof IStatsClient] as number | undefined) ??= 0;
114 (combinedStats[key as keyof IStatsClient] as number) += value;
115 }
116 }
117 for (const arrayName of ["Weapons", "Enemies", "Scans", "Missions", "PVP"] as const) {
118 if (stats[arrayName]) {
119 combinedStats[arrayName] ??= [];
120 for (const entry of stats[arrayName]) {
121 const combinedEntry = combinedStats[arrayName].find(x => x.type == entry.type);
122 if (combinedEntry) {
123 for (const [key, value] of Object.entries(entry)) {
124 if (typeof value == "number") {
125 (combinedEntry[key as keyof typeof combinedEntry] as unknown as
126 | number
127 | undefined) ??= 0;
128 (combinedEntry[key as keyof typeof combinedEntry] as unknown as number) += value;
129 }
130 }
131 } else {
132 // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
133 combinedStats[arrayName].push(entry as any);
134 }
135 }
136 }
137 }
88 138 }
89 }
90 if (inventory.GuildId) {
91 const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class"))!;
92 result.GuildId = toOid(inventory.GuildId);
93 result.GuildName = guild.Name;
94 result.GuildTier = guild.Tier;
95 result.GuildXp = guild.XP;
96 result.GuildClass = guild.Class;
97 result.GuildEmblem = false;
98 }
99 for (const key of allDailyAffiliationKeys) {
100 result[key] = inventory[key];
101 }
102 139
103 const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
104 delete stats._id;
105 delete stats.__v;
106 delete stats.accountOwnerId;
140 const xpComponents: IXPComponentClient[] = [];
141 if (guild.ClaimedXP) {
142 for (const componentName of guild.ClaimedXP) {
143 if (componentName.endsWith(".level")) {
144 const [key] = Object.entries(ExportDojoRecipes.rooms).find(
145 ([_key, value]) => value.resultType == componentName
146 )!;
147 xpComponents.push({
148 StoreTypeName: toStoreItem(key)
149 });
150 } else {
151 const [key] = Object.entries(ExportDojoRecipes.decos).find(
152 ([_key, value]) => value.resultType == componentName
153 )!;
154 xpComponents.push({
155 StoreTypeName: toStoreItem(key)
156 });
157 }
158 }
159 }
107 160
108 res.json({
109 Results: [result],
110 TechProjects: [],
111 XpComponents: [],
112 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
113 Stats: stats
114 });
161 res.json({
162 Results: results,
163 TechProjects: guild.TechProjects,
164 XpComponents: xpComponents,
165 //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
166 Stats: combinedStats
167 });
168 } else {
169 res.sendStatus(400);
170 }
115 171 };
116 172
117 173 interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
@@ -133,20 +189,40 @@ interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
133 189 GuildXp?: number;
134 190 GuildClass?: number;
135 191 GuildEmblem?: boolean;
136 PlayerSkills: IPlayerSkills;
137 ChallengeProgress: IChallengeProgress[];
138 DeathMarks: string[];
139 Harvestable: boolean;
140 DeathSquadable: boolean;
141 Created: IMongoDate;
142 MigratedToConsole: boolean;
143 Missions: IMission[];
144 Affiliations: IAffiliation[];
145 DailyFocus: number;
146 Wishlist: string[];
192 PlayerSkills?: IPlayerSkills;
193 ChallengeProgress?: IChallengeProgress[];
194 DeathMarks?: string[];
195 Harvestable?: boolean;
196 DeathSquadable?: boolean;
197 Created?: IMongoDate;
198 MigratedToConsole?: boolean;
199 Missions?: IMission[];
200 Affiliations?: IAffiliation[];
201 DailyFocus?: number;
202 Wishlist?: string[];
147 203 Alignment?: IAlignment;
148 204 }
149 205
206 interface IXPComponentClient {
207 _id?: IOid;
208 StoreTypeName: string;
209 TypeName?: string;
210 PurchaseQuantity?: number;
211 ProductCategory?: "Recipes";
212 Rarity?: "COMMON";
213 RegularPrice?: number;
214 PremiumPrice?: number;
215 SellingPrice?: number;
216 DateAddedToManifest?: number;
217 PrimeSellingPrice?: number;
218 GuildXp?: number;
219 ResultPrefab?: string;
220 ResultDecoration?: string;
221 ShowInMarket?: boolean;
222 ShowInInventory?: boolean;
223 locTags?: Record<string, string>;
224 }
225
150 226 let skinLookupTable: Record<number, string> | undefined;
151 227
152 228 const resolveAndCollectSkins = (
@@ -181,3 +257,51 @@ const resolveAndCollectSkins = (
181 257 }
182 258 }
183 259 };
260
261 const populateLoadout = async (
262 inventory: TInventoryDatabaseDocument,
263 result: IPlayerProfileViewingDataResult
264 ): Promise<void> => {
265 if (inventory.CurrentLoadOutIds.length) {
266 const loadout = (await Loadout.findById(inventory.LoadOutPresets, "NORMAL"))!;
267 result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON<ILoadoutConfigClient>();
268 result.LoadOutPreset.ItemId = undefined;
269 const skins = new Set<string>();
270 if (result.LoadOutPreset.s) {
271 result.LoadOutInventory.Suits = [
272 inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON<IEquipmentClient>()
273 ];
274 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]);
275 }
276 if (result.LoadOutPreset.p) {
277 result.LoadOutInventory.Pistols = [
278 inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON<IEquipmentClient>()
279 ];
280 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]);
281 }
282 if (result.LoadOutPreset.l) {
283 result.LoadOutInventory.LongGuns = [
284 inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON<IEquipmentClient>()
285 ];
286 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]);
287 }
288 if (result.LoadOutPreset.m) {
289 result.LoadOutInventory.Melee = [
290 inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON<IEquipmentClient>()
291 ];
292 resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]);
293 }
294 for (const skin of skins) {
295 result.LoadOutInventory.WeaponSkins.push({ ItemType: skin });
296 }
297 }
298 };
299
300 const populateGuild = (guild: TGuildDatabaseDocument, result: IPlayerProfileViewingDataResult): void => {
301 result.GuildId = toOid(guild._id);
302 result.GuildName = guild.Name;
303 result.GuildTier = guild.Tier;
304 result.GuildXp = guild.XP;
305 result.GuildClass = guild.Class;
306 result.GuildEmblem = guild.Emblem;
307 };
Modified src/types/statTypes.ts +8 -0
@@ -31,6 +31,14 @@ export interface IStatsClient {
31 31 CaliberChicksScore?: number;
32 32 OlliesCrashCourseScore?: number;
33 33 DojoObstacleScore?: number;
34
35 // not in schema
36 PVP?: {
37 suitDeaths?: number;
38 suitKills?: number;
39 weaponKills?: number;
40 type: string;
41 }[];
34 42 }
35 43
36 44 export interface IStatsDatabase extends IStatsClient {