From edc3171eeefc96aee2d5e1a6a77aee89c79c39a1 Mon Sep 17 00:00:00 2001 From: Ordis <134585663+OrdisPrime@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:24:42 +0100 Subject: [PATCH] feat: Quests 2 (#878) --- package-lock.json | 8 +-- package.json | 2 +- .../giveKeyChainTriggeredItemsController.ts | 32 +++--------- .../giveKeyChainTriggeredMessageController.ts | 36 ++++++++++++++ src/controllers/api/giveQuestKey.ts | 45 +++++++++++++++++ src/controllers/api/updateQuestController.ts | 25 ++++++---- src/models/inboxModel.ts | 11 +++-- src/models/inventoryModels/inventoryModel.ts | 17 ++++--- src/routes/api.ts | 4 ++ src/services/inboxService.ts | 1 - src/services/inventoryService.ts | 15 ++++-- src/services/itemDataService.ts | 29 +++++++++-- src/services/questService.ts | 49 +++++++++++++++---- src/types/inventoryTypes/inventoryTypes.ts | 2 +- src/types/requestTypes.ts | 6 +-- 15 files changed, 210 insertions(+), 72 deletions(-) create mode 100644 src/controllers/api/giveKeyChainTriggeredMessageController.ts create mode 100644 src/controllers/api/giveQuestKey.ts diff --git a/package-lock.json b/package-lock.json index 4190c4c7e..f1582976d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "copyfiles": "^2.4.1", "express": "^5", "mongoose": "^8.9.4", - "warframe-public-export-plus": "^0.5.24", + "warframe-public-export-plus": "^0.5.26", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -4092,9 +4092,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.25", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.25.tgz", - "integrity": "sha512-vYEm6q7zECk+2xi9pb6KEksoNNEqSqcrbEtwuEDCySAzZJwbE8iErFtTA2ZAK81LOG42i8yDMevvldyke/Sr3A==" + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.26.tgz", + "integrity": "sha512-oRcz14nBHcwx5CgCbxw5bhesiJtVlspuHyqzjOwRu63c2Vs+1TgfqbVGdqc9sm3AZL9eJhtay+khc1h7r3BM/A==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index 2aab9a34d..28cac72a9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "copyfiles": "^2.4.1", "express": "^5", "mongoose": "^8.9.4", - "warframe-public-export-plus": "^0.5.24", + "warframe-public-export-plus": "^0.5.26", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/controllers/api/giveKeyChainTriggeredItemsController.ts b/src/controllers/api/giveKeyChainTriggeredItemsController.ts index e8f0dda62..ef1e4700a 100644 --- a/src/controllers/api/giveKeyChainTriggeredItemsController.ts +++ b/src/controllers/api/giveKeyChainTriggeredItemsController.ts @@ -2,40 +2,21 @@ import { RequestHandler } from "express"; import { isEmptyObject, parseString } from "@/src/helpers/general"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { addKeyChainItems, getInventory } from "@/src/services/inventoryService"; +import { IGroup } from "@/src/types/loginTypes"; +import { updateQuestStage } from "@/src/services/questService"; export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => { const accountId = parseString(req.query.accountId); - const keyChainTriggeredItemsRequest = getJSONfromString( - (req.body as string).toString() - ); + const keyChainInfo = getJSONfromString((req.body as string).toString()); const inventory = await getInventory(accountId); - const inventoryChanges = await addKeyChainItems(inventory, keyChainTriggeredItemsRequest); + const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo); if (isEmptyObject(inventoryChanges)) { throw new Error("inventory changes was empty after getting keychain items: should not happen"); } // items were added: update quest stage's i (item was given) - const quest = inventory.QuestKeys.find(quest => quest.ItemType === keyChainTriggeredItemsRequest.KeyChain); - - if (!quest) { - throw new Error(`Quest ${keyChainTriggeredItemsRequest.KeyChain} not found in QuestKeys`); - } - if (!quest.Progress) { - throw new Error(`Progress should always exist when giving keychain triggered items`); - } - - const questStage = quest.Progress[keyChainTriggeredItemsRequest.ChainStage]; - if (questStage) { - questStage.i = true; - } else { - const questStageIndex = quest.Progress.push({ i: true }) - 1; - if (questStageIndex !== keyChainTriggeredItemsRequest.ChainStage) { - throw new Error( - `Quest stage index mismatch: ${questStageIndex} !== ${keyChainTriggeredItemsRequest.ChainStage}` - ); - } - } + updateQuestStage(inventory, keyChainInfo, { i: true }); await inventory.save(); res.send(inventoryChanges); @@ -50,7 +31,8 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, //{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]} }; -export interface IGiveKeyChainTriggeredItemsRequest { +export interface IKeyChainRequest { KeyChain: string; ChainStage: number; + Groups?: IGroup[]; } diff --git a/src/controllers/api/giveKeyChainTriggeredMessageController.ts b/src/controllers/api/giveKeyChainTriggeredMessageController.ts new file mode 100644 index 000000000..244877d96 --- /dev/null +++ b/src/controllers/api/giveKeyChainTriggeredMessageController.ts @@ -0,0 +1,36 @@ +import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { IMessage } from "@/src/models/inboxModel"; +import { createMessage } from "@/src/services/inboxService"; +import { getInventory } from "@/src/services/inventoryService"; +import { getKeyChainMessage } from "@/src/services/itemDataService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { updateQuestStage } from "@/src/services/questService"; +import { RequestHandler } from "express"; + +export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest; + console.log(keyChainInfo); + + const keyChainMessage = getKeyChainMessage(keyChainInfo); + + const message = { + sndr: keyChainMessage.sender, + msg: keyChainMessage.body, + sub: keyChainMessage.title, + att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined, + icon: keyChainMessage.icon ?? "", + transmission: keyChainMessage.transmission ?? "", + highPriority: keyChainMessage.highPriority ?? false, + r: false + } satisfies IMessage; + + const savedMessages = await createMessage(accountId, [message]); + console.log("savedMessages", savedMessages); + + const inventory = await getInventory(accountId, "QuestKeys"); + updateQuestStage(inventory, keyChainInfo, { m: true }); + await inventory.save(); + + res.send(1); +}; diff --git a/src/controllers/api/giveQuestKey.ts b/src/controllers/api/giveQuestKey.ts new file mode 100644 index 000000000..070dea752 --- /dev/null +++ b/src/controllers/api/giveQuestKey.ts @@ -0,0 +1,45 @@ +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { addItem, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IOid } from "@/src/types/commonTypes"; +import { RequestHandler } from "express"; + +export const giveQuestKeyRewardController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const rewardRequest = getJSONfromString((req.body as Buffer).toString()); + + if (Array.isArray(rewardRequest.reward)) { + throw new Error("Multiple rewards not expected"); + } + + const reward = rewardRequest.reward; + const inventory = await getInventory(accountId); + const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount); + await inventory.save(); + res.json(inventoryChanges.InventoryChanges); + //TODO: consider whishlist changes +}; + +export interface IQuestKeyRewardRequest { + reward: IQuestKeyReward; +} + +export interface IQuestKeyReward { + RewardType: string; + CouponType: string; + Icon: string; + ItemType: string; + StoreItemType: string; + ProductCategory: string; + Amount: number; + ScalingMultiplier: number; + Durability: string; + DisplayName: string; + Duration: number; + CouponSku: number; + Syndicate: string; + Milestones: any[]; + ChooseSetIndex: number; + NewSystemReward: boolean; + _id: IOid; +} diff --git a/src/controllers/api/updateQuestController.ts b/src/controllers/api/updateQuestController.ts index dbfdb4999..d5d4a5a72 100644 --- a/src/controllers/api/updateQuestController.ts +++ b/src/controllers/api/updateQuestController.ts @@ -4,7 +4,8 @@ import { logger } from "@/src/utils/logger"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService"; import { getQuestCompletionItems } from "@/src/services/itemDataService"; -import { addItem, combineInventoryChanges, getInventory } from "@/src/services/inventoryService"; +import { addItems, getInventory } from "@/src/services/inventoryService"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; // eslint-disable-next-line @typescript-eslint/no-misused-promises export const updateQuestController: RequestHandler = async (req, res) => { @@ -18,24 +19,28 @@ export const updateQuestController: RequestHandler = async (req, res) => { const inventory = await getInventory(accountId); + const updateQuestResponse: { CustomData?: string; InventoryChanges?: IInventoryChanges; MissionRewards: [] } = { + MissionRewards: [] + }; updateQuestKey(inventory, updateQuestRequest.QuestKeys); if (updateQuestRequest.QuestKeys[0].Completed) { logger.debug(`completed quest ${updateQuestRequest.QuestKeys[0].ItemType} `); const questKeyName = updateQuestRequest.QuestKeys[0].ItemType; const questCompletionItems = getQuestCompletionItems(questKeyName); + logger.debug(`quest completion items`, questCompletionItems); - logger.debug(`quest completion items { ${questCompletionItems.map(item => item.ItemType).join(", ")} }`); + const inventoryChanges = await addItems(inventory, questCompletionItems); + inventory.ActiveQuest = ""; - const inventoryChanges = {}; - for (const item of questCompletionItems) { - const inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount); - combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges); - } - res.json({ MissionRewards: [], inventoryChanges }); - return; + updateQuestResponse.InventoryChanges = inventoryChanges; + } + + //TODO: might need to parse the custom data and add the associated items to inventory + if (updateQuestRequest.QuestKeys[0].CustomData) { + updateQuestResponse.CustomData = updateQuestRequest.QuestKeys[0].CustomData; } await inventory.save(); - res.send({ MissionRewards: [] }); + res.send(updateQuestResponse); }; diff --git a/src/models/inboxModel.ts b/src/models/inboxModel.ts index 9469c3e52..10b1930e5 100644 --- a/src/models/inboxModel.ts +++ b/src/models/inboxModel.ts @@ -12,23 +12,26 @@ export interface IMessageClient extends Omit( { _id: false } ); -const questProgressSchema = new Schema({ - c: Number, - i: Boolean, - m: Boolean, - b: [] -}); +const questProgressSchema = new Schema( + { + c: Number, + i: Boolean, + m: Boolean, + b: [] + }, + { _id: false } +); const questKeysSchema = new Schema( { Progress: { type: [questProgressSchema], default: undefined }, unlock: Boolean, Completed: Boolean, - //CustomData: Schema.Types.Mixed, + CustomData: String, CompletionDate: Date, ItemType: String }, diff --git a/src/routes/api.ts b/src/routes/api.ts index 909d7f0e2..19f7d0e5e 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -82,6 +82,8 @@ import { updateQuestController } from "@/src/controllers/api/updateQuestControll import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController"; import { updateThemeController } from "../controllers/api/updateThemeController"; import { upgradesController } from "@/src/controllers/api/upgradesController"; +import { giveKeyChainTriggeredMessageController } from "@/src/controllers/api/giveKeyChainTriggeredMessageController"; +import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey"; const apiRouter = express.Router(); @@ -138,6 +140,8 @@ apiRouter.post("/getAlliance.php", getAllianceController); apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController); apiRouter.post("/gildWeapon.php", gildWeaponController); apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController); +apiRouter.post("/giveKeyChainTriggeredMessage.php", giveKeyChainTriggeredMessageController); +apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController); apiRouter.post("/guildTech.php", guildTechController); apiRouter.post("/hostSession.php", hostSessionController); apiRouter.post("/infestedFoundry.php", infestedFoundryController); diff --git a/src/services/inboxService.ts b/src/services/inboxService.ts index 81cc443ee..117680543 100644 --- a/src/services/inboxService.ts +++ b/src/services/inboxService.ts @@ -46,7 +46,6 @@ export const createNewEventMessages = async (req: Request) => { prev.eventMessageDate > current.eventMessageDate ? prev : current ); - console.log("latestEventMessage", latestEventMessage); account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate); await account.save(); }; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 791470393..594c28475 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -47,7 +47,7 @@ import { } from "warframe-public-export-plus"; import { createShip } from "./shipService"; import { creditBundles, fusionBundles } from "@/src/services/missionInventoryUpdateService"; -import { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; import { toOid } from "../helpers/inventoryHelpers"; export const createInventory = async ( @@ -499,11 +499,16 @@ export const addItem = async ( export const addItems = async ( inventory: TInventoryDatabaseDocument, - items: ITypeCount[], + items: ITypeCount[] | string[], inventoryChanges: IInventoryChanges = {} ): Promise => { + let inventoryDelta; for (const item of items) { - const inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount); + if (typeof item === "string") { + inventoryDelta = await addItem(inventory, item); + } else { + inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount); + } combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges); } return inventoryChanges; @@ -1074,7 +1079,7 @@ export const updateSyndicate = ( */ export const addKeyChainItems = async ( inventory: TInventoryDatabaseDocument, - keyChainData: IGiveKeyChainTriggeredItemsRequest + keyChainData: IKeyChainRequest ): Promise => { const keyChainItems = getKeyChainItems(keyChainData); @@ -1092,5 +1097,7 @@ export const addKeyChainItems = async ( combineInventoryChanges(inventoryChanges, inventoryChangesDelta.InventoryChanges); } + await addItems(inventory, nonStoreItems); + return inventoryChanges; }; diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index a8e94df03..be4291d6f 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -1,4 +1,4 @@ -import { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; import { getIndexAfter } from "@/src/helpers/stringHelpers"; import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; @@ -142,7 +142,7 @@ export const getString = (key: string, dict: Record): string => return dict[key] ?? key; }; -export const getKeyChainItems = ({ KeyChain, ChainStage }: IGiveKeyChainTriggeredItemsRequest): string[] => { +export const getKeyChainItems = ({ KeyChain, ChainStage }: IKeyChainRequest): string[] => { const chainStages = ExportKeys[KeyChain].chainStages; if (!chainStages) { throw new Error(`KeyChain ${KeyChain} does not contain chain stages`); @@ -154,7 +154,9 @@ export const getKeyChainItems = ({ KeyChain, ChainStage }: IGiveKeyChainTriggere } if (keyChainStage.itemsToGiveWhenTriggered.length === 0) { - throw new Error(`No items to give for KeyChain ${KeyChain} at stage ${ChainStage}`); + throw new Error( + `client requested key chain items in KeyChain ${KeyChain} at stage ${ChainStage}, but they did not exist` + ); } return keyChainStage.itemsToGiveWhenTriggered; @@ -194,3 +196,24 @@ export const getQuestCompletionItems = (questKey: string) => { return items; }; + +export const getKeyChainMessage = ({ KeyChain, ChainStage }: IKeyChainRequest) => { + const chainStages = ExportKeys[KeyChain]?.chainStages; + if (!chainStages) { + throw new Error(`KeyChain ${KeyChain} does not contain chain stages`); + } + + const keyChainStage = chainStages[ChainStage]; + if (!keyChainStage) { + throw new Error(`KeyChainStage ${ChainStage} not found`); + } + + const chainStageMessage = keyChainStage.messageToSendWhenTriggered; + + if (!chainStageMessage) { + throw new Error( + `client requested key chain message in keychain ${KeyChain} at stage ${ChainStage} but they did not exist` + ); + } + return chainStageMessage; +}; diff --git a/src/services/questService.ts b/src/services/questService.ts index 7b600178f..797b15cbf 100644 --- a/src/services/questService.ts +++ b/src/services/questService.ts @@ -1,7 +1,18 @@ -import { IInventoryDatabase, IQuestKeyDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; +import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes"; import { logger } from "@/src/utils/logger"; import { HydratedDocument } from "mongoose"; +export interface IUpdateQuestRequest { + QuestKeys: Omit[]; + PS: string; + questCompletion: boolean; + PlayerShipEvents: unknown[]; + crossPlaySetting: string; + DoQuestReward: boolean; +} + export const updateQuestKey = ( inventory: HydratedDocument, questKeyUpdate: IUpdateQuestRequest["QuestKeys"] @@ -17,6 +28,7 @@ export const updateQuestKey = ( throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); } + console.log(questKeyUpdate[0]); inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0]; if (questKeyUpdate[0].Completed) { @@ -24,11 +36,30 @@ export const updateQuestKey = ( } }; -export interface IUpdateQuestRequest { - QuestKeys: Omit[]; - PS: string; - questCompletion: boolean; - PlayerShipEvents: unknown[]; - crossPlaySetting: string; - DoQuestReward: boolean; -} +export const updateQuestStage = ( + inventory: TInventoryDatabaseDocument, + { KeyChain, ChainStage }: IKeyChainRequest, + questStageUpdate: IQuestStage +): void => { + const quest = inventory.QuestKeys.find(quest => quest.ItemType === KeyChain); + + if (!quest) { + throw new Error(`Quest ${KeyChain} not found in QuestKeys`); + } + + if (!quest.Progress) { + throw new Error(`Progress should always exist when giving keychain triggered items or messages`); + } + + const questStage = quest.Progress[ChainStage]; + + if (!questStage) { + const questStageIndex = quest.Progress.push(questStageUpdate) - 1; + if (questStageIndex !== ChainStage) { + throw new Error(`Quest stage index mismatch: ${questStageIndex} !== ${ChainStage}`); + } + return; + } + + Object.assign(questStage, questStageUpdate); +}; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 066eb8a8b..0657ebadd 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -86,7 +86,7 @@ export interface IQuestKeyDatabase { Progress?: IQuestStage[]; unlock?: boolean; Completed?: boolean; - CustomData?: string; //TODO: check whether this actually exists + CustomData?: string; ItemType: string; CompletionDate?: Date; } diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index d3adf7f1f..543639695 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -11,8 +11,8 @@ import { TSolarMapRegion, TEquipmentKey, IFusionTreasure, - IQuestKeyClient, - IPlayerSkills + IPlayerSkills, + IQuestKeyDatabase } from "./inventoryTypes/inventoryTypes"; export interface IThemeUpdateRequest { @@ -46,7 +46,7 @@ export type IMissionInventoryUpdateRequest = { Consumables?: ITypeCount[]; FusionTreasures?: IFusionTreasure[]; Recipes?: ITypeCount[]; - QuestKeys?: IQuestKeyClient[]; + QuestKeys?: Omit[]; RegularCredits?: number; MissionFailed: boolean; MissionStatus: IMissionStatus;