From db0aaffb4491b09b4be476373db287e6f27b8b15 Mon Sep 17 00:00:00 2001 From: Nicat <52883148+holmityd@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:29:09 +0400 Subject: [PATCH] Mission rewards save (#46) --- .gitignore | 1 + .../api/missionInventoryUpdateController.ts | 57 +++++++++- src/controllers/stats/viewController.ts | 7 ++ src/models/inventoryModel.ts | 12 ++- src/routes/stats.ts | 4 + src/services/inventoryService.ts | 101 +++++++++++++++++- src/services/purchaseService.ts | 39 +++++-- src/types/inventoryTypes/SuitTypes.ts | 8 +- src/types/inventoryTypes/inventoryTypes.ts | 12 ++- src/types/inventoryTypes/weaponTypes.ts | 2 + src/types/missionInventoryUpdateType.ts | 64 +++++++++++ 11 files changed, 292 insertions(+), 15 deletions(-) create mode 100644 src/controllers/stats/viewController.ts create mode 100644 src/types/missionInventoryUpdateType.ts diff --git a/.gitignore b/.gitignore index ee9cbba67..705566745 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /.env /static/data/*.bin yarn.lock +/tmp \ No newline at end of file diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 54b7e5484..bde878afb 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -1,6 +1,61 @@ import { RequestHandler } from "express"; +import { missionInventoryUpdate } from "@/src/services/inventoryService"; +import { MissionInventoryUpdate } from "@/src/types/missionInventoryUpdateType"; +/* +- [ ] crossPlaySetting +- [ ] rewardsMultiplier +- [ ] ActiveBoosters +- [x] LongGuns +- [x] Pistols +- [x] Suits +- [x] Melee +- [x] RawUpgrades +- [x] MiscItems +- [x] RegularCredits +- [ ] RandomUpgradesIdentified +- [ ] MissionFailed +- [ ] MissionStatus +- [ ] CurrentLoadOutIds +- [ ] AliveTime +- [ ] MissionTime +- [ ] Missions +- [ ] CompletedAlerts +- [ ] LastRegionPlayed +- [ ] GameModeId +- [ ] hosts +- [x] ChallengeProgress +- [ ] SeasonChallengeHistory +- [ ] PS +- [ ] ActiveDojoColorResearch +- [ ] RewardInfo +- [ ] ReceivedCeremonyMsg +- [ ] LastCeremonyResetDate +- [ ] MissionPTS +- [ ] RepHash +- [ ] EndOfMatchUpload +- [ ] ObjectiveReached +- [ ] FpsAvg +- [ ] FpsMin +- [ ] FpsMax +- [ ] FpsSamples +*/ -const missionInventoryUpdateController: RequestHandler = (_req, res) => { +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const missionInventoryUpdateController: RequestHandler = async (req, res) => { + const [data] = String(req.body).split("\n"); + const id = req.query.accountId as string; + + // TODO - salt check + + try { + const parsedData = JSON.parse(data) as MissionInventoryUpdate; + if (typeof parsedData !== "object" || parsedData === null) throw new Error("Invalid data format"); + await missionInventoryUpdate(parsedData, id); + } catch (err) { + console.error("Error parsing JSON data:", err); + } + + // TODO - get original response res.json({}); }; diff --git a/src/controllers/stats/viewController.ts b/src/controllers/stats/viewController.ts new file mode 100644 index 000000000..4e988cccf --- /dev/null +++ b/src/controllers/stats/viewController.ts @@ -0,0 +1,7 @@ +import { RequestHandler } from "express"; + +const viewController: RequestHandler = (_req, res) => { + res.json({}); +}; + +export { viewController }; diff --git a/src/models/inventoryModel.ts b/src/models/inventoryModel.ts index 552cc1f31..f73dd1fb5 100644 --- a/src/models/inventoryModel.ts +++ b/src/models/inventoryModel.ts @@ -1,5 +1,5 @@ import { Model, Schema, Types, model } from "mongoose"; -import { FlavourItem, IInventoryDatabase } from "../types/inventoryTypes/inventoryTypes"; +import { FlavourItem, RawUpgrade, MiscItem, IInventoryDatabase, Booster } from "../types/inventoryTypes/inventoryTypes"; import { Oid } from "../types/commonTypes"; import { ISuitDatabase, ISuitDocument } from "@/src/types/inventoryTypes/SuitTypes"; import { IWeaponDatabase } from "@/src/types/inventoryTypes/weaponTypes"; @@ -66,6 +66,11 @@ const WeaponSchema = new Schema({ UnlockLevel: Number }); +const BoosterSchema = new Schema({ + ExpiryDate: Number, + ItemType: String +}); + WeaponSchema.set("toJSON", { transform(_document, returnedObject) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call @@ -213,7 +218,7 @@ const inventorySchema = new Schema({ LoreFragmentScans: [Schema.Types.Mixed], EquippedEmotes: [String], PendingTrades: [Schema.Types.Mixed], - Boosters: [Schema.Types.Mixed], + Boosters: [BoosterSchema], ActiveDojoColorResearch: String, SentientSpawnChanceBoosters: Schema.Types.Mixed, Affiliations: [Schema.Types.Mixed], @@ -334,6 +339,9 @@ type InventoryDocumentProps = { Pistols: Types.DocumentArray; Melee: Types.DocumentArray; FlavourItems: Types.DocumentArray; + RawUpgrades: Types.DocumentArray; + MiscItems: Types.DocumentArray; + Boosters: Types.DocumentArray; }; type InventoryModelType = Model; diff --git a/src/routes/stats.ts b/src/routes/stats.ts index 96b68e132..a0df739f3 100644 --- a/src/routes/stats.ts +++ b/src/routes/stats.ts @@ -1,7 +1,11 @@ +import { viewController } from "../controllers/api/viewController"; import { uploadController } from "@/src/controllers/stats/uploadController"; + import express from "express"; const statsRouter = express.Router(); +statsRouter.get("/view.php", viewController); statsRouter.post("/upload.php", uploadController); + export { statsRouter }; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 41b22959c..199004972 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -5,7 +5,13 @@ import { Types } from "mongoose"; import { ISuitResponse } from "@/src/types/inventoryTypes/SuitTypes"; import { SlotType } from "@/src/types/purchaseTypes"; import { IWeaponResponse } from "@/src/types/inventoryTypes/weaponTypes"; -import { FlavourItem } from "@/src/types/inventoryTypes/inventoryTypes"; +import { ChallengeProgress, FlavourItem, IInventoryDatabaseDocument } from "@/src/types/inventoryTypes/inventoryTypes"; +import { + MissionInventoryUpdate, + MissionInventoryUpdateCard, + MissionInventoryUpdateGear, + MissionInventoryUpdateItem +} from "../types/missionInventoryUpdateType"; const createInventory = async (accountOwnerId: Types.ObjectId) => { try { @@ -106,4 +112,97 @@ export const addCustomization = async (customizatonName: string, accountId: stri return changedInventory.FlavourItems[flavourItemIndex].toJSON(); //mongoose bug forces as FlavourItem }; +const addGearExpByCategory = ( + inventory: IInventoryDatabaseDocument, + gearArray: MissionInventoryUpdateGear[] | undefined, + categoryName: "Pistols" | "LongGuns" | "Melee" | "Suits" +) => { + const category = inventory[categoryName]; + + gearArray?.forEach(({ ItemId, XP }) => { + const itemIndex = category.findIndex(i => i._id?.equals(ItemId.$oid)); + const item = category[itemIndex]; + + if (itemIndex !== -1 && item.XP != undefined) { + item.XP += XP; + inventory.markModified(`${categoryName}.${itemIndex}.XP`); + } + }); +}; + +const addItemsByCategory = ( + inventory: IInventoryDatabaseDocument, + itemsArray: (MissionInventoryUpdateItem | MissionInventoryUpdateCard)[] | undefined, + categoryName: "RawUpgrades" | "MiscItems" +) => { + const category = inventory[categoryName]; + + itemsArray?.forEach(({ ItemCount, ItemType }) => { + const itemIndex = category.findIndex(i => i.ItemType === ItemType); + + if (itemIndex !== -1) { + category[itemIndex].ItemCount += ItemCount; + inventory.markModified(`${categoryName}.${itemIndex}.ItemCount`); + } else { + category.push({ ItemCount, ItemType }); + } + }); +}; + +const addChallenges = (inventory: IInventoryDatabaseDocument, itemsArray: ChallengeProgress[] | undefined) => { + const category = inventory.ChallengeProgress; + + itemsArray?.forEach(({ Name, Progress }) => { + const itemIndex = category.findIndex(i => i.Name === Name); + + if (itemIndex !== -1) { + category[itemIndex].Progress += Progress; + inventory.markModified(`ChallengeProgress.${itemIndex}.ItemCount`); + } else { + category.push({ Name, Progress }); + } + }); +}; + +const gearKeys = ["Suits", "Pistols", "LongGuns", "Melee"] as const; +type GearKeysType = (typeof gearKeys)[number]; + +export const missionInventoryUpdate = async (data: MissionInventoryUpdate, accountId: string): Promise => { + const { RawUpgrades, MiscItems, RegularCredits, ChallengeProgress } = data; + const inventory = await getInventory(accountId); + + // TODO - multipliers logic + // credits + inventory.RegularCredits += RegularCredits || 0; + + // gear exp + gearKeys.forEach((key: GearKeysType) => addGearExpByCategory(inventory, data[key], key)); + + // other + addItemsByCategory(inventory, RawUpgrades, "RawUpgrades"); // TODO - check mods fusion level + addItemsByCategory(inventory, MiscItems, "MiscItems"); + addChallenges(inventory, ChallengeProgress); + + await inventory.save(); +}; + +export const addBooster = async (ItemType: string, time: number, accountId: string): Promise => { + const currentTime = Math.floor(Date.now() / 1000) - 129600; // booster time getting more without 129600, probably defence logic, idk + + const inventory = await getInventory(accountId); + const { Boosters } = inventory; + + const itemIndex = Boosters.findIndex(i => i.ItemType === ItemType); + + if (itemIndex !== -1) { + const existingBooster = Boosters[itemIndex]; + existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + time; + inventory.markModified(`Boosters.${itemIndex}.ExpiryDate`); + } else { + Boosters.push({ ItemType, ExpiryDate: currentTime + time }) - 1; + } + + await inventory.save(); +}; + export { createInventory, addPowerSuit }; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 16716573d..8b88cd860 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -1,13 +1,7 @@ import { getWeaponType } from "@/src/helpers/purchaseHelpers"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; -import { - addCustomization, - addPowerSuit, - addWeapon, - updateCurrency, - updateSlots -} from "@/src/services/inventoryService"; -import { IPurchaseRequest, IPurchaseResponse, SlotType } from "@/src/types/purchaseTypes"; +import { addBooster, addCustomization, addPowerSuit, addWeapon, updateSlots } from "@/src/services/inventoryService"; +import { IPurchaseRequest, SlotType } from "@/src/types/purchaseTypes"; export const getStoreItemCategory = (storeItem: string) => { const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/"); @@ -41,6 +35,9 @@ export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountI case "Types": purchaseResponse = await handleTypesPurchase(internalName, accountId); break; + case "Boosters": + purchaseResponse = await handleBoostersPurchase(internalName, accountId); + break; default: throw new Error(`unknown store category: ${storeCategory} not implemented or new`); @@ -114,3 +111,29 @@ const handleSuitCustomizationsPurchase = async (customizationName: string, accou } }; }; + +const boosterCollection = [ + "/Lotus/Types/Boosters/ResourceAmountBooster", + "/Lotus/Types/Boosters/AffinityBooster", + "/Lotus/Types/Boosters/ResourceDropChanceBooster", + "/Lotus/Types/Boosters/CreditBooster" +]; + +const handleBoostersPurchase = async (boosterStoreName: string, accountId: string) => { + const match = boosterStoreName.match(/(\d+)Day/); + if (!match) return; + + const extractedDigit = Number(match[1]); + const ItemType = boosterCollection.find(i => + boosterStoreName.includes(i.split("/").pop()!.replace("Booster", "")) + )!; + const ExpiryDate = extractedDigit * 86400; + + await addBooster(ItemType, ExpiryDate, accountId); + + return { + InventoryChanges: { + Boosters: [{ ItemType, ExpiryDate }] + } + }; +}; diff --git a/src/types/inventoryTypes/SuitTypes.ts b/src/types/inventoryTypes/SuitTypes.ts index 187d5ab19..7dc8ca5f6 100644 --- a/src/types/inventoryTypes/SuitTypes.ts +++ b/src/types/inventoryTypes/SuitTypes.ts @@ -1,8 +1,11 @@ import { Oid } from "@/src/types/commonTypes"; import { AbilityOverride, Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; -import { Document } from "mongoose"; +import { Document, Types } from "mongoose"; -export interface ISuitDocument extends ISuitResponse, Document {} +// export interface ISuitDocument extends ISuitResponse, Document {} +export interface ISuitDocument extends Document, ISuitResponse { + _id: Types.ObjectId; +} export interface ISuitResponse extends ISuitDatabase { ItemId: Oid; @@ -20,6 +23,7 @@ export interface ISuitDatabase { ModSlotPurchases?: number; FocusLens?: string; UnlockLevel?: number; + _id: Types.ObjectId; } export interface SuitConfig { diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 938ac6517..b175dc648 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -377,6 +377,16 @@ export interface FlavourItem { ItemType: string; } +export interface RawUpgrade { + ItemCount: number; + ItemType: string; +} + +export interface MiscItem { + ItemCount: number; + ItemType: string; +} + export interface CrewshipWeapon { PILOT: Pilot; PORT_GUNS: PortGuns; @@ -919,7 +929,7 @@ export interface Progress { export interface RawUpgrade { ItemCount: number; - LastAdded: Oid; + LastAdded?: Oid; ItemType: string; } diff --git a/src/types/inventoryTypes/weaponTypes.ts b/src/types/inventoryTypes/weaponTypes.ts index 3c561e2d0..7fe6bf404 100644 --- a/src/types/inventoryTypes/weaponTypes.ts +++ b/src/types/inventoryTypes/weaponTypes.ts @@ -1,5 +1,6 @@ import { Oid } from "@/src/types/commonTypes"; import { Color, Polarity } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { Types } from "mongoose"; export interface IWeaponResponse extends IWeaponDatabase { ItemId: Oid; @@ -20,6 +21,7 @@ export interface IWeaponDatabase { ItemName?: string; ModularParts?: string[]; UnlockLevel?: number; + _id?: Types.ObjectId; } export interface WeaponConfig { diff --git a/src/types/missionInventoryUpdateType.ts b/src/types/missionInventoryUpdateType.ts new file mode 100644 index 000000000..dcc88da05 --- /dev/null +++ b/src/types/missionInventoryUpdateType.ts @@ -0,0 +1,64 @@ +interface MongooseId{ + $oid: string; +} + +interface ExpireDate{ + $date: { + $numberLong: string; + } +} + +export interface MissionInventoryUpdateGear{ + ItemType: string; + ItemName: string; + ItemId: MongooseId; + XP: number; + UpgradeVer: number; + Features: number; + Polarized: number; + CustomizationSlotPurchases: number; + ModSlotPurchases: number; + FocusLens: string; + Expiry: ExpireDate; + Polarity: any[]; + Configs: any[]; + ModularParts: any[]; + SkillTree: string; + UpgradeType: string; + UpgradeFingerprint: string; + OffensiveUpgrade: string; + DefensiveUpgrade: string; + UpgradesExpiry: ExpireDate; + ArchonCrystalUpgrades: any[]; +} + +export interface MissionInventoryUpdateItem{ + ItemCount: number; + ItemType: string; +} + +export interface MissionInventoryUpdateCard extends MissionInventoryUpdateItem{ + ItemId: MongooseId; + UpgradeFingerprint: string; + PendingRerollFingerprint: string; + LastAdded: MongooseId; +} + +interface MissionInventoryUpdateChallange{ + Name: string; + Progress: number; + Completed: any[]; +} + +export interface MissionInventoryUpdate{ + rewardsMultiplier?: number; + ActiveBoosters?: any[]; + LongGuns?: MissionInventoryUpdateGear[]; + Pistols?: MissionInventoryUpdateGear[]; + Suits?: MissionInventoryUpdateGear[]; + Melee?: MissionInventoryUpdateGear[]; + RawUpgrades?: MissionInventoryUpdateCard[]; + MiscItems?: MissionInventoryUpdateItem[]; + RegularCredits?: number; + ChallengeProgress?: MissionInventoryUpdateChallange[]; +} \ No newline at end of file