diff --git a/.gitignore b/.gitignore index d1ec7373..3087af60 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ yarn.lock /logs # MongoDB VSCode extension playground scripts -/database_scripts \ No newline at end of file +/database_scripts + +# Game data +/static/game_data \ No newline at end of file diff --git a/src/controllers/api/giveKeyChainTriggeredItemsController.ts b/src/controllers/api/giveKeyChainTriggeredItemsController.ts new file mode 100644 index 00000000..7ac8e93a --- /dev/null +++ b/src/controllers/api/giveKeyChainTriggeredItemsController.ts @@ -0,0 +1,87 @@ +import { RequestHandler } from "express"; +import { parseString } from "@/src/helpers/general"; +import { logger } from "@/src/utils/logger"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { IInventoryDatabase, IQuestKeyResponse } from "@/src/types/inventoryTypes/inventoryTypes"; +import { getInventory } from "@/src/services/inventoryService"; +import { updateShipFeature } from "@/src/services/personalRoomsService"; +import quests from "@/static/game_data/quests.json"; +//import { addItemsToInventory, getKeyChainItems } from "@/src/services/gameDataService"; +import { TQuestKeys } from "@/src/types/questTypes"; + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => { + const accountId = parseString(req.query.accountId); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument + const keyChainItemRequest = getJSONfromString(req.body.toString()) as IGiveKeyChainTriggeredItemsRequest; + console.log(keyChainItemRequest); + + const keyChainName = keyChainItemRequest.KeyChain; + const keyChainStage = keyChainItemRequest.ChainStage; + + //const keyChainItems = getKeyChainItems(keyChainName, keyChainStage); + + // logger.debug(`adding key chain items ${keyChainItems} for ${keyChainName} at stage ${keyChainStage}`); + + //give items + + //const inventoryChanges = await addItemsToInventory(accountId, keyChainItems); + //form item changes + res.send({}); + //{ + //MiscItems: [{ ItemType: "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem", ItemCount: 1 }] + //} +}; + +/* +some items are added or removed (not sure) to the wishlist, in that case a +WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"], +is added to the response, need to determine for which items this is the case. +*/ + +// export const buildInventoryChanges = (keyChainItems: Record) => { +// const inventoryChanges = {}; + +// for (const key of Object.keys(keyChainItems)) { +// if (keyChainItems[key].makeWishlistChange) { +// inventoryChanges["WishlistChanges"] = [key]; +// } +// } +// return inventoryChanges; +// }; + +export type IKeyChains = + | "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" + | "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +//TODO: Record is a placeholder for the actual type + +// type test = keyof typeof KeyChainItems; + +// export interface IItems { +// [key in keyof test]: string; +// } + +export interface IGiveKeyChainTriggeredItemsRequest { + KeyChain: TQuestKeys; + ChainStage: number; +} + +//{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0} +//{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]} + +// const addKeyChainItems = async (accountId: string, _inventory: IInventoryDatabase, keyChain: IKeyChains) => { +// const keyChainItems = KeyChainItems[keyChain]; +// if (!keyChainItems) { +// logger.error(`keyChain ${keyChain} not found`); +// return; +// } + +// switch (keyChainItems.InventoryCategory) { +// case "ShipFeatures": +// await addShipFeature(accountId, keyChainItems.MiscItems[0].ItemType); +// break; +// } +// console.log(keyChainItems); +// }; diff --git a/src/controllers/api/setActiveQuestController.ts b/src/controllers/api/setActiveQuestController.ts index a8f06a25..c3dabaec 100644 --- a/src/controllers/api/setActiveQuestController.ts +++ b/src/controllers/api/setActiveQuestController.ts @@ -1,7 +1,15 @@ +import { parseString } from "@/src/helpers/general"; +import { getInventory } from "@/src/services/inventoryService"; +import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; -const setActiveQuestController: RequestHandler = (_req, res) => { - res.sendStatus(200); -}; +// eslint-disable-next-line @typescript-eslint/no-misused-promises +export const setActiveQuestController: RequestHandler = async (req, res) => { + const newActiveQuest = parseString(req.query.quest); -export { setActiveQuestController }; + const accountId = parseString(req.query.accountId); + const inventory = await getInventory(accountId); + inventory.ActiveQuest = newActiveQuest; + logger.debug(`setting active quest to ${newActiveQuest} for account ${accountId}`); + res.send([]); +}; diff --git a/src/controllers/api/unlockShipFeatureController.ts b/src/controllers/api/unlockShipFeatureController.ts new file mode 100644 index 00000000..639775fd --- /dev/null +++ b/src/controllers/api/unlockShipFeatureController.ts @@ -0,0 +1,14 @@ +import { RequestHandler } from "express"; +import inbox from "@/static/fixed_responses/inbox.json"; +import { updateShipFeature, getPersonalRooms } from "@/src/services/personalRoomsService"; +import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes"; +import { parseString } from "@/src/helpers/general"; + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +export const unlockShipFeatureController: RequestHandler = async (req, res) => { + const accountId = parseString(req.query.accountId); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call + const shipFeatureRequest = JSON.parse(req.body.toString() as string) as IUnlockShipFeatureRequest; + await updateShipFeature(accountId, shipFeatureRequest.Feature); + res.send([]); +}; diff --git a/src/controllers/api/updateQuestController.ts b/src/controllers/api/updateQuestController.ts new file mode 100644 index 00000000..a3aab8a3 --- /dev/null +++ b/src/controllers/api/updateQuestController.ts @@ -0,0 +1,123 @@ +import { RequestHandler } from "express"; +import { isEmptyObject, isObject, parseString } from "@/src/helpers/general"; +import { logger } from "@/src/utils/logger"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { IInventoryDatabase, IQuestKeyDatabase, IQuestKeyResponse } from "@/src/types/inventoryTypes/inventoryTypes"; +import { getInventory } from "@/src/services/inventoryService"; +import { ItemType } from "@/src/helpers/customHelpers/addItemHelpers"; + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +export const updateQuestcontroller: RequestHandler = async (req, res) => { + const accountId = parseString(req.query.accountId); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call + const updateQuestRequest = getJSONfromString(req.body.toString()) as IUpdateQuestRequest; + const inventory = await getInventory(accountId); + + const InventoryChanges = {}; + // the array of quest keys should always be 1 + if (updateQuestRequest.QuestKeys.length > 1) { + const errorMessage = `quest keys array should only have 1 item, but has ${updateQuestRequest.QuestKeys.length}`; + logger.error(errorMessage); + throw new Error(errorMessage); + } + updateQuestKeys(inventory, updateQuestRequest.QuestKeys); + + const questKeyName = updateQuestRequest.QuestKeys[0].ItemType; + + const newQuestKey = getNewQuestKey(questKeyName as newQuestKeys); + + //let the client know of the new quest + if (newQuestKey) { + Object.assign(InventoryChanges, addQuestKey(inventory, newQuestKey)); + } + + if (updateQuestRequest.DoQuestReward) { + const questCompletionItems = getQuestCompletionItems(questKeyName as keyof typeof QuestCompletionItems); + + if (!isEmptyObject(questCompletionItems)) { + addInventoryItems(inventory, questCompletionItems); + Object.assign(InventoryChanges, questCompletionItems); + } + } + + const questKeys = (await inventory.save()).QuestKeys[1]; + console.log(questKeys); + + //give next quest key and add to quest keys + //console.log(updateQuestRequest); + + const updateQuestResponse = { MissionRewards: [] }; + + if (!isEmptyObject(InventoryChanges)) { + Object.assign(updateQuestResponse, { InventoryChanges }); + } + + res.send(updateQuestResponse); +}; + +const updateQuestKeys = (inventory: IInventoryDatabase, questKeyUpdate: IUpdateQuestRequest["QuestKeys"]) => { + if (questKeyUpdate.length > 1) { + logger.error(`more than 1 quest key not implemented`); + throw new Error("more than 1 quest key not implemented"); + } + + const questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType); + console.log("quest key index", questKeyIndex); + + if (questKeyIndex < 0) { + const errorMessage = `quest ${questKeyUpdate[0].ItemType} not found`; + logger.error(errorMessage); + throw new Error(errorMessage); + } + + inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0]; + + if (questKeyUpdate[0].Completed) { + inventory.QuestKeys[questKeyIndex].CompletionDate = new Date(); + } +}; + +type IInventoryDatabaseArrayProps = { + [key in keyof IInventoryDatabase]?: IInventoryDatabase[key]; +}; + +const addInventoryItems = (_inventory: IInventoryDatabase, items: IInventoryDatabaseArrayProps) => { + console.log(items); +}; + +const getQuestCompletionItems = (questname: keyof typeof QuestCompletionItems) => { + return QuestCompletionItems[questname]; +}; + +const QuestCompletionItems = { + "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": { + Recipes: [{ ItemType: "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", ItemCount: 1 }] + } +}; +const addQuestKey = (inventory: IInventoryDatabase, questKey: string) => { + inventory.QuestKeys.push({ ItemType: questKey }); + return { QuestKeys: [{ ItemType: questKey }] }; +}; + +const newQuestKeys = { + "/Lotus/Types/Items/Quests/QuestKeys/VorsPrizeQuestKey": "/Lotus/Types/Keys/DuviriQuest/DuviriQuestKeyChain" +}; + +type newQuestKeys = keyof typeof newQuestKeys; + +const getNewQuestKey = (lastQuest: newQuestKeys) => { + return newQuestKeys[lastQuest]; +}; + +export interface IUpdateQuestRequest { + QuestKeys: Omit[]; + PS: string; + questCompletion: boolean; + PlayerShipEvents: any[]; + crossPlaySetting: string; + DoQuestReward: boolean; +} + +// const updateQuestKey = (questKeyToUpdate: IQuestKeyResponse, questKeyUpdate: IQuestKeyResponse) => {}; + +// const addInventoryItemsByQuestKey = (questname: IQuestNames) => {}; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 392952b7..864d83b4 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -468,19 +468,22 @@ const infestedFoundrySchema = new Schema( { _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: [questProgressSchema], unlock: Boolean, Completed: Boolean, - //CustomData: Schema.Types.Mixed, + CustomData: String, CompletionDate: Date, ItemType: String }, @@ -739,6 +742,7 @@ const inventorySchema = new Schema( //Complete Mission\Quests Missions: [Schema.Types.Mixed], QuestKeys: [questKeysSchema], + ActiveQuest: String, //item like DojoKey or Boss missions key LevelKeys: [Schema.Types.Mixed], //Active quests diff --git a/src/routes/api.ts b/src/routes/api.ts index ff6c7286..17c93bae 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -62,6 +62,9 @@ import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryContro import { trainingResultController } from "@/src/controllers/api/trainingResultController"; import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController"; import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController"; +import { updateQuestcontroller } from "@/src/controllers/api/updateQuestController"; +import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController"; +import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController"; import { updateThemeController } from "../controllers/api/updateThemeController"; import { upgradesController } from "@/src/controllers/api/upgradesController"; @@ -100,6 +103,9 @@ apiRouter.get("/surveys.php", surveysController); apiRouter.get("/updateSession.php", updateSessionGetController); // post +apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController); +apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController); +apiRouter.post("/updateQuest.php", updateQuestcontroller); apiRouter.post("/addFriendImage.php", addFriendImageController); apiRouter.post("/artifacts.php", artifactsController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); diff --git a/src/services/gameDataService.ts b/src/services/gameDataService.ts new file mode 100644 index 00000000..f43019da --- /dev/null +++ b/src/services/gameDataService.ts @@ -0,0 +1,130 @@ +// import { addPowerSuit, getInventory } from "@/src/services/inventoryService"; +// import { items } from "@/src/services/itemDataService"; +// import { IMongoDate } from "@/src/types/commonTypes"; +// import { IInventoryDatabase, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; +// import { TQuestKeys } from "@/src/types/questTypes"; +// import { logger } from "@/src/utils/logger"; +// import keys from "@/static/game_data/quests.json"; +// import storeItems from "@/static/game_data/storeItems.json"; + +// export const getKeyChainItems = (keyChainName: TQuestKeys, chainStage: number) => { +// const keyChain = keys[keyChainName].ChainStages; +// if (!keyChain) { +// logger.error(`KeyChain ${keyChain} not found`); +// throw new Error(`KeyChain ${keyChain} not found`); +// } + +// const keyChainStage = keyChain[chainStage]; +// if (!keyChainStage) { +// logger.error(`KeyChainStage ${chainStage} not found`); +// throw new Error(`KeyChainStage ${chainStage} not found`); +// } + +// if (keyChainStage.ItemsToGiveWhenTriggered.length === 0) { +// logger.error(`No items to give for KeyChain ${keyChainName} at stage ${chainStage}`); +// throw new Error(`No items to give for KeyChain ${keyChainName} at stage ${chainStage}`); +// } + +// return keyChainStage.ItemsToGiveWhenTriggered; +// }; + +// interface IChainStage { +// ItemsToGiveWhenTriggered: string[]; +// MessageToSendWhenTriggered: IMessage; +// } + +// export interface IMessage { +// messageId: string; +// sub: string; +// sndr: string; +// msg: string; +// contextInfo: string; +// icon: string; +// date: IMongoDate; +// att: string[]; +// modPacks: string[]; +// countedAtt: string[]; +// attSpecial: string[]; +// transmission: string; +// ordisReactionTransmission: string; +// arg: string[]; +// r: string; +// acceptAction: string; +// declineAction: string; +// highPriority: string; +// gifts: string[]; +// teleportLoc: string; +// RegularCredits: string; +// PremiumCredits: string; +// PrimeTokens: string; +// Coupons: string[]; +// syndicateAttachment: string[]; +// tutorialTag: string; +// url: string; +// urlButtonText: string; +// cinematic: string; +// requiredLevel: string; +// } + +// export interface IDifficulties { +// LOW: number; +// NORMAL: number; +// HIGH: number; +// } + +// export const addItemsToInventory = async (accountId: string, items: string[]) => { +// logger.debug(`adding items ${items} to inventory for ${accountId}`); + +// const inventory = await getInventory(accountId); + +// let inventoryChanges = {}; + +// for (const item of items) { +// //call addItemToInventory and assign the return value to inventoryChanges spread syntax +// inventoryChanges = { ...inventoryChanges, ...addItemToInventory(inventory, item) }; + +// } + +// return { inventoryChanges }; +// }; + +// /* +// certain properties are not valid InventoryChanges, such as accountOwnerId, +// could Omit these properties +// */ +// type TInventoryChanges = { [inventoryKey in keyof IInventoryDatabase]?: ITypeCount[] }; + +// export const addItemToInventory = (inventory: IInventoryDatabase, item: string) => { +// logger.debug(`adding item ${item} to inventory`); + +// const itemCategory = getItemCategory(item); + +// switch (itemCategory) { +// case "Powersuit": +// return await addPowerSuit(inventory, item); +// break; +// default: +// logger.error(`item category ${itemCategory} not found`); +// throw new Error(`item category ${itemCategory} not found`); +// } + +// }; + +// export const getItemCategory = (item: string) => { + +// if (!item.includes("/StoreItems/")) { +// logger.error(`item ${item} is not a store item, currently only store items are supported`); +// throw new Error(`item ${item} is not a store item, currently only store items are supported`); +// } + +// const itemCategory = storeItems[item]. + +// if (!itemCategory) { +// logger.error(`item category not found for ${item}`); +// throw new Error(`item category not found for ${item}`); +// } + +// return itemCategory; +// }; + +// export type TStoreItems = keyof typeof storeItems; diff --git a/src/services/personalRoomsService.ts b/src/services/personalRoomsService.ts index c2d0b629..212c3b0f 100644 --- a/src/services/personalRoomsService.ts +++ b/src/services/personalRoomsService.ts @@ -10,3 +10,9 @@ export const getPersonalRooms = async (accountId: string) => { } return personalRooms; }; + +export const updateShipFeature = async (accountId: string, shipFeature: string) => { + const personalRooms = await getPersonalRooms(accountId); + personalRooms.Ship.Features.push(shipFeature); + await personalRooms.save(); +}; diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 3c55823e..7fc1c990 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -45,7 +45,7 @@ export interface IQuestKeyDatabase { Progress?: IQuestProgress[]; unlock?: boolean; Completed?: boolean; - CustomData?: string; //TODO: check whether this actually exists + CustomData?: string; ItemType: string; CompletionDate?: Date; } @@ -155,6 +155,7 @@ export interface IInventoryResponse { Melee: IEquipmentDatabase[]; Ships: IShipInventory[]; QuestKeys: IQuestKeyResponse[]; + ActiveQuest: string; FlavourItems: IFlavourItem[]; Scoops: IEquipmentDatabase[]; TrainingRetriesLeft: number; diff --git a/src/types/questTypes.ts b/src/types/questTypes.ts new file mode 100644 index 00000000..b14d6d3a --- /dev/null +++ b/src/types/questTypes.ts @@ -0,0 +1,3 @@ +import quests from "@/static/game_data/quests.json"; + +export type TQuestKeys = keyof typeof quests; diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index 288bb99c..9f8b5157 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -99,6 +99,12 @@ export interface IUpdateGlyphRequest { AvatarImage: string; } +export interface IUnlockShipFeatureRequest { + Feature: string; + KeyChain: string; + ChainStage: number; +} + export interface IUpgradesRequest { ItemCategory: TEquipmentKey; ItemId: IOid; diff --git a/tsconfig.json b/tsconfig.json index c2603f08..2d7ee099 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -40,7 +40,7 @@ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ "resolveJsonModule": true /* Enable importing .json files. */, - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + "allowArbitraryExtensions": true /* Enable importing files with any extension, provided a declaration file is present. */, // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */