diff --git a/src/app.ts b/src/app.ts index a17ac9b4..160a93c8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -32,6 +32,7 @@ app.use(requestLogger); app.use("/api", apiRouter); app.use("/", cacheRouter); app.use("/custom", customRouter); +app.use("/dynamic", dynamicController); app.use("/:id/dynamic", dynamicController); app.use("/pay", payRouter); app.use("/stats", statsRouter); diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index 040557c3..36b1221d 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -297,7 +297,7 @@ const resourceGetParent = (resourceName: string): string | undefined => { }; // This is FNV1a-32 except operating under modulus 2^31 because JavaScript is stinky and likes producing negative integers out of nowhere. -const catBreadHash = (name: string): number => { +export const catBreadHash = (name: string): number => { let hash = 2166136261; for (let i = 0; i != name.length; ++i) { hash = (hash ^ name.charCodeAt(i)) & 0x7fffffff; diff --git a/src/controllers/dynamic/getProfileViewingDataController.ts b/src/controllers/dynamic/getProfileViewingDataController.ts new file mode 100644 index 00000000..9e9c764f --- /dev/null +++ b/src/controllers/dynamic/getProfileViewingDataController.ts @@ -0,0 +1,183 @@ +import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; +import { Guild } from "@/src/models/guildModel"; +import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; +import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; +import { Account } from "@/src/models/loginModel"; +import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel"; +import { allDailyAffiliationKeys } from "@/src/services/inventoryService"; +import { IMongoDate, IOid } from "@/src/types/commonTypes"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { + IAffiliation, + IAlignment, + IChallengeProgress, + IDailyAffiliations, + ILoadoutConfigClient, + IMission, + IPlayerSkills, + ITypeXPItem +} from "@/src/types/inventoryTypes/inventoryTypes"; +import { RequestHandler } from "express"; +import { catBreadHash } from "../api/inventoryController"; +import { ExportCustoms } from "warframe-public-export-plus"; + +export const getProfileViewingDataController: RequestHandler = async (req, res) => { + if (!req.query.playerId) { + res.status(400).end(); + return; + } + const account = await Account.findOne({ _id: req.query.playerId as string }, "DisplayName"); + if (!account) { + res.status(400).send("No account or guild ID specified"); + return; + } + const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!; + const loadout = (await Loadout.findOne({ _id: inventory.LoadOutPresets }, "NORMAL"))!; + + const result: IPlayerProfileViewingDataResult = { + AccountId: toOid(account._id), + DisplayName: account.DisplayName, + PlayerLevel: inventory.PlayerLevel, + LoadOutInventory: { + WeaponSkins: [], + XPInfo: inventory.XPInfo + }, + PlayerSkills: inventory.PlayerSkills, + ChallengeProgress: inventory.ChallengeProgress, + DeathMarks: inventory.DeathMarks, + Harvestable: inventory.Harvestable, + DeathSquadable: inventory.DeathSquadable, + Created: toMongoDate(inventory.Created), + MigratedToConsole: false, + Missions: inventory.Missions, + Affiliations: inventory.Affiliations, + DailyFocus: inventory.DailyFocus, + Wishlist: inventory.Wishlist, + Alignment: inventory.Alignment + }; + if (inventory.CurrentLoadOutIds.length) { + result.LoadOutPreset = loadout.NORMAL.id(inventory.CurrentLoadOutIds[0].$oid)!.toJSON(); + result.LoadOutPreset.ItemId = undefined; + const skins = new Set(); + if (result.LoadOutPreset.s) { + result.LoadOutInventory.Suits = [ + inventory.Suits.id(result.LoadOutPreset.s.ItemId.$oid)!.toJSON() + ]; + resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Suits[0]); + } + if (result.LoadOutPreset.p) { + result.LoadOutInventory.Pistols = [ + inventory.Pistols.id(result.LoadOutPreset.p.ItemId.$oid)!.toJSON() + ]; + resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Pistols[0]); + } + if (result.LoadOutPreset.l) { + result.LoadOutInventory.LongGuns = [ + inventory.LongGuns.id(result.LoadOutPreset.l.ItemId.$oid)!.toJSON() + ]; + resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.LongGuns[0]); + } + if (result.LoadOutPreset.m) { + result.LoadOutInventory.Melee = [ + inventory.Melee.id(result.LoadOutPreset.m.ItemId.$oid)!.toJSON() + ]; + resolveAndCollectSkins(inventory, skins, result.LoadOutInventory.Melee[0]); + } + for (const skin of skins) { + result.LoadOutInventory.WeaponSkins.push({ ItemType: skin }); + } + } + if (inventory.GuildId) { + const guild = (await Guild.findOne({ _id: inventory.GuildId }, "Name Tier XP Class"))!; + result.GuildId = toOid(inventory.GuildId); + result.GuildName = guild.Name; + result.GuildTier = guild.Tier; + result.GuildXp = guild.XP; + result.GuildClass = guild.Class; + result.GuildEmblem = false; + } + for (const key of allDailyAffiliationKeys) { + result[key] = inventory[key]; + } + + const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON>(); + delete stats._id; + delete stats.__v; + delete stats.accountOwnerId; + + res.json({ + Results: [result], + TechProjects: [], + XpComponents: [], + //XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for + Stats: stats + }); +}; + +interface IPlayerProfileViewingDataResult extends Partial { + AccountId: IOid; + DisplayName: string; + PlayerLevel: number; + LoadOutPreset?: Omit & { ItemId?: IOid }; + LoadOutInventory: { + WeaponSkins: { ItemType: string }[]; + Suits?: IEquipmentClient[]; + Pistols?: IEquipmentClient[]; + LongGuns?: IEquipmentClient[]; + Melee?: IEquipmentClient[]; + XPInfo: ITypeXPItem[]; + }; + GuildId?: IOid; + GuildName?: string; + GuildTier?: number; + GuildXp?: number; + GuildClass?: number; + GuildEmblem?: boolean; + PlayerSkills: IPlayerSkills; + ChallengeProgress: IChallengeProgress[]; + DeathMarks: string[]; + Harvestable: boolean; + DeathSquadable: boolean; + Created: IMongoDate; + MigratedToConsole: boolean; + Missions: IMission[]; + Affiliations: IAffiliation[]; + DailyFocus: number; + Wishlist: string[]; + Alignment?: IAlignment; +} + +let skinLookupTable: Record | undefined; + +const resolveAndCollectSkins = ( + inventory: TInventoryDatabaseDocument, + skins: Set, + item: IEquipmentClient +): void => { + for (const config of item.Configs) { + if (config.Skins) { + for (let i = 0; i != config.Skins.length; ++i) { + // Resolve oids to type names + if (config.Skins[i].length == 24) { + if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") { + if (!skinLookupTable) { + skinLookupTable = {}; + for (const key of Object.keys(ExportCustoms)) { + skinLookupTable[catBreadHash(key)] = key; + } + } + config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)]; + } else { + const skinItem = inventory.WeaponSkins.id(config.Skins[i]); + config.Skins[i] = skinItem ? skinItem.ItemType : ""; + } + } + + // Collect type names + if (config.Skins[i]) { + skins.add(config.Skins[i]); + } + } + } + } +}; diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 30057c76..d0f7c501 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -153,6 +153,7 @@ const guildSchema = new Schema( LongMOTD: { type: longMOTDSchema, default: undefined }, Ranks: { type: [guildRankSchema], default: defaultRanks }, TradeTax: { type: Number, default: 0 }, + Tier: { type: Number, default: 1 }, DojoComponents: { type: [dojoComponentSchema], default: [] }, DojoCapacity: { type: Number, default: 100 }, DojoEnergy: { type: Number, default: 5 }, diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 238779f6..cdfc4dca 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -468,7 +468,6 @@ const seasonChallengeHistorySchema = new Schema( //TODO: check whether this is complete const playerSkillsSchema = new Schema( { - LPP_NONE: { type: Number, default: 0 }, LPP_SPACE: { type: Number, default: 0 }, LPS_PILOTING: { type: Number, default: 0 }, LPS_GUNNERY: { type: Number, default: 0 }, diff --git a/src/routes/dynamic.ts b/src/routes/dynamic.ts index 0e808d48..14185e22 100644 --- a/src/routes/dynamic.ts +++ b/src/routes/dynamic.ts @@ -1,10 +1,12 @@ -import { aggregateSessionsController } from "@/src/controllers/dynamic/aggregateSessionsController"; -import { worldStateController } from "@/src/controllers/dynamic/worldStateController"; import express from "express"; +import { aggregateSessionsController } from "@/src/controllers/dynamic/aggregateSessionsController"; +import { getProfileViewingDataController } from "@/src/controllers/dynamic/getProfileViewingDataController"; +import { worldStateController } from "@/src/controllers/dynamic/worldStateController"; const dynamicController = express.Router(); -dynamicController.get("/worldState.php", worldStateController); dynamicController.get("/aggregateSessions.php", aggregateSessionsController); +dynamicController.get("/getProfileViewingData.php", getProfileViewingDataController); +dynamicController.get("/worldState.php", worldStateController); export { dynamicController }; diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 9176e66b..5b00b622 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -91,7 +91,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s Members: members, Ranks: guild.Ranks, TradeTax: guild.TradeTax, - Tier: 1, + Tier: guild.Tier, Vault: getGuildVault(guild), ActiveDojoColorResearch: guild.ActiveDojoColorResearch, Class: guild.Class, diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index f563e281..7d10b57d 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -27,6 +27,7 @@ export interface IGuildDatabase { LongMOTD?: ILongMOTD; Ranks: IGuildRank[]; TradeTax: number; + Tier: number; DojoComponents: IDojoComponentDatabase[]; DojoCapacity: number; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index af7898d9..fb0d3071 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -928,7 +928,6 @@ export interface IPersonalTechProject { } export interface IPlayerSkills { - LPP_NONE: number; LPP_SPACE: number; LPS_PILOTING: number; LPS_GUNNERY: number;