Foundry 1 - Preliminary (#127)

This commit is contained in:
OrdisPrime 2024-01-25 14:49:45 +01:00 committed by GitHub
parent 4a102b9d3b
commit 8b50189fcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 363 additions and 125 deletions

View File

@ -3,11 +3,13 @@ const secondsPerMinute = 60;
const minutesPerHour = 60; const minutesPerHour = 60;
const hoursPerDay = 24; const hoursPerDay = 24;
const unixSecond = millisecondsPerSecond;
const unixMinute = secondsPerMinute * millisecondsPerSecond; const unixMinute = secondsPerMinute * millisecondsPerSecond;
const unixHour = unixMinute * minutesPerHour; const unixHour = unixMinute * minutesPerHour;
const unixDay = hoursPerDay * unixHour; const unixDay = hoursPerDay * unixHour;
export const unixTimesInMs = { export const unixTimesInMs = {
second: unixSecond,
minute: unixMinute, minute: unixMinute,
hour: unixHour, hour: unixHour,
day: unixDay day: unixDay

View File

@ -0,0 +1,64 @@
//this is a controller for the claimCompletedRecipe route
//it will claim a recipe for the user
import { Request, RequestHandler, Response } from "express";
import { logger } from "@/src/utils/logger";
import { getItemByBlueprint, getItemCategoryByUniqueName } from "@/src/services/itemDataService";
import { IOid } from "@/src/types/commonTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { IInventoryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
export interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString(req.body.toString()) as IClaimCompletedRecipeRequest;
const accountId = req.query.accountId as string;
if (!accountId) throw new Error("no account id");
console.log(claimCompletedRecipeRequest);
const inventory = await getInventory(accountId);
const pendingRecipe = inventory.PendingRecipes.find(
recipe => recipe._id?.toString() === claimCompletedRecipeRequest.RecipeIds[0].$oid
);
console.log(pendingRecipe);
if (!pendingRecipe) {
logger.error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
}
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// logger.error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
//get completed Items
const completedItemName = getItemByBlueprint(pendingRecipe.ItemType)?.uniqueName;
if (!completedItemName) {
logger.error(`no completed item found for recipe ${pendingRecipe._id}`);
throw new Error(`no completed item found for recipe ${pendingRecipe._id}`);
}
const itemCategory = getItemCategoryByUniqueName(completedItemName) as keyof typeof inventory;
console.log(itemCategory);
//TODO: remove all Schema.Mixed for inventory[itemCategory] not to be any
//add item
//inventory[itemCategory].
//add additional item components like mods or weapons for a sentinel.
//const additionalItemComponents = itemComponents[uniqueName]
//add these items to inventory
//return changes as InventoryChanges
//remove pending recipe
inventory.PendingRecipes.pull(pendingRecipe._id);
// await inventory.save();
logger.debug("Claiming Completed Recipe", { completedItemName });
res.json({ InventoryChanges: {} });
};

View File

@ -24,6 +24,7 @@ const inventoryController: RequestHandler = async (request: Request, response: R
return; return;
} }
//TODO: make a function that converts from database representation to client
const inventoryJSON = inventory.toJSON(); const inventoryJSON = inventory.toJSON();
const inventoryResponse = toInventoryResponse(inventoryJSON); const inventoryResponse = toInventoryResponse(inventoryJSON);

View File

@ -0,0 +1,21 @@
import { parseString } from "@/src/helpers/general";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { startRecipe } from "@/src/services/recipeService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
interface IStartRecipeRequest {
RecipeName: string;
Ids: string[];
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
export const startRecipeController: RequestHandler = async (req, res) => {
const startRecipeRequest = getJSONfromString(req.body.toString()) as IStartRecipeRequest;
logger.debug("StartRecipe Request", { startRecipeRequest });
const accountId = parseString(req.query.accountId);
const newRecipeId = await startRecipe(startRecipeRequest.RecipeName, accountId);
res.json(newRecipeId);
};

View File

@ -1,5 +1,5 @@
import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers"; import { ItemType, toAddItemRequest } from "@/src/helpers/customHelpers/addItemHelpers";
import { getWeaponType } from "@/src/helpers/purchaseHelpers"; import { getWeaponType } from "@/src/services/itemDataService";
import { addPowerSuit, addWeapon } from "@/src/services/inventoryService"; import { addPowerSuit, addWeapon } from "@/src/services/inventoryService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";

View File

@ -1,5 +1,5 @@
import { isString, parseString } from "@/src/helpers/general"; import { isString, parseString } from "@/src/helpers/general";
import { items } from "@/static/data/items"; import { items } from "@/src/services/itemDataService";
export enum ItemType { export enum ItemType {
Powersuit = "Powersuit", Powersuit = "Powersuit",
@ -23,20 +23,20 @@ interface IAddItemRequest {
InternalName: string; InternalName: string;
accountId: string; accountId: string;
} }
export const isInternalName = (internalName: string): boolean => { export const isInternalItemName = (internalName: string): boolean => {
const item = items.find(i => i.uniqueName === internalName); const item = items.find(i => i.uniqueName === internalName);
return Boolean(item); return Boolean(item);
}; };
const parseInternalName = (internalName: unknown): string => { const parseInternalItemName = (internalName: unknown): string => {
if (!isString(internalName) || !isInternalName(internalName)) { if (!isString(internalName) || !isInternalItemName(internalName)) {
throw new Error("incorrect internal name"); throw new Error("incorrect internal name");
} }
return internalName; return internalName;
}; };
const toAddItemRequest = (body: unknown): IAddItemRequest => { export const toAddItemRequest = (body: unknown): IAddItemRequest => {
if (!body || typeof body !== "object") { if (!body || typeof body !== "object") {
throw new Error("incorrect or missing add item request data"); throw new Error("incorrect or missing add item request data");
} }
@ -44,12 +44,10 @@ const toAddItemRequest = (body: unknown): IAddItemRequest => {
if ("type" in body && "internalName" in body && "accountId" in body) { if ("type" in body && "internalName" in body && "accountId" in body) {
return { return {
type: parseItemType(body.type), type: parseItemType(body.type),
InternalName: parseInternalName(body.internalName), InternalName: parseInternalItemName(body.internalName),
accountId: parseString(body.accountId) accountId: parseString(body.accountId)
}; };
} }
throw new Error("malformed add item request"); throw new Error("malformed add item request");
}; };
export { toAddItemRequest };

View File

@ -1,8 +1,7 @@
import { parseBoolean, parseNumber, parseString } from "@/src/helpers/general"; import { parseBoolean, parseNumber, parseString } from "@/src/helpers/general";
import { WeaponTypeInternal } from "@/src/services/inventoryService"; import { weapons } from "@/src/services/itemDataService";
import { slotPurchaseNameToSlotName } from "@/src/services/purchaseService"; import { slotPurchaseNameToSlotName } from "@/src/services/purchaseService";
import { IPurchaseRequest, SlotPurchaseName } from "@/src/types/purchaseTypes"; import { IPurchaseRequest, SlotPurchaseName } from "@/src/types/purchaseTypes";
import { weapons } from "@/static/data/items";
export const toPurchaseRequest = (purchaseRequest: unknown): IPurchaseRequest => { export const toPurchaseRequest = (purchaseRequest: unknown): IPurchaseRequest => {
if (!purchaseRequest || typeof purchaseRequest !== "object") { if (!purchaseRequest || typeof purchaseRequest !== "object") {
@ -41,22 +40,6 @@ export const toPurchaseRequest = (purchaseRequest: unknown): IPurchaseRequest =>
throw new Error("invalid purchaseRequest"); throw new Error("invalid purchaseRequest");
}; };
export const getWeaponType = (weaponName: string) => {
const weaponInfo = weapons.find(i => i.uniqueName === weaponName);
if (!weaponInfo) {
throw new Error(`unknown weapon ${weaponName}`);
}
const weaponType = weaponInfo.productCategory as WeaponTypeInternal;
if (!weaponType) {
throw new Error(`unknown weapon category for item ${weaponName}`);
}
return weaponType;
};
export const isSlotPurchaseName = (slotPurchaseName: string): slotPurchaseName is SlotPurchaseName => { export const isSlotPurchaseName = (slotPurchaseName: string): slotPurchaseName is SlotPurchaseName => {
return slotPurchaseName in slotPurchaseNameToSlotName; return slotPurchaseName in slotPurchaseNameToSlotName;
}; };

View File

@ -1,4 +1,4 @@
export const getJSONfromString = (str: string): any => { export const getJSONfromString = (str: string) => {
const jsonSubstring = str.substring(0, str.lastIndexOf("}") + 1); const jsonSubstring = str.substring(0, str.lastIndexOf("}") + 1);
return JSON.parse(jsonSubstring); return JSON.parse(jsonSubstring);
}; };
@ -16,3 +16,11 @@ export const getSubstringFromKeywordToKeyword = (str: string, keywordBegin: stri
const endIndex = str.indexOf(keywordEnd); const endIndex = str.indexOf(keywordEnd);
return str.substring(beginIndex, endIndex + 1); return str.substring(beginIndex, endIndex + 1);
}; };
export const getIndexAfter = (str: string, searchWord: string) => {
const index = str.indexOf(searchWord);
if (index === -1) {
return -1;
}
return index + searchWord.length;
};

View File

@ -1,4 +1,4 @@
import { Model, Schema, Types, model } from "mongoose"; import { HydratedDocument, Model, Schema, Types, model } from "mongoose";
import { import {
IFlavourItem, IFlavourItem,
IRawUpgrade, IRawUpgrade,
@ -10,7 +10,9 @@ import {
ISlots, ISlots,
IGenericItem, IGenericItem,
IMailbox, IMailbox,
IDuviriInfo IDuviriInfo,
IPendingRecipe as IPendingRecipeDatabase,
IPendingRecipeResponse
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IMongoDate, IOid } from "../../types/commonTypes"; import { IMongoDate, IOid } from "../../types/commonTypes";
import { ISuitDatabase } from "@/src/types/inventoryTypes/SuitTypes"; import { ISuitDatabase } from "@/src/types/inventoryTypes/SuitTypes";
@ -25,6 +27,29 @@ import {
} from "@/src/types/inventoryTypes/commonInventoryTypes"; } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid } from "@/src/helpers/inventoryHelpers";
const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
{
ItemType: String,
CompletionDate: Date
},
{ id: false }
);
pendingRecipeSchema.virtual("ItemId").get(function () {
return { $oid: this._id.toString() };
});
pendingRecipeSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
(returnedObject as IPendingRecipeResponse).CompletionDate = {
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
};
}
});
const polaritySchema = new Schema<IPolarity>({ const polaritySchema = new Schema<IPolarity>({
Slot: Number, Slot: Number,
Value: String Value: String
@ -296,7 +321,6 @@ DuviriInfoSchema.set("toJSON", {
}); });
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
accountOwnerId: Schema.Types.ObjectId, accountOwnerId: Schema.Types.ObjectId,
SubscribedToEmails: Number, SubscribedToEmails: Number,
Created: Schema.Types.Mixed, Created: Schema.Types.Mixed,
@ -325,7 +349,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
MechBin: slotsBinSchema, MechBin: slotsBinSchema,
CrewMemberBin: slotsBinSchema, CrewMemberBin: slotsBinSchema,
//How many trades do you have left //How many trades do you have left
TradesRemaining: Number, TradesRemaining: Number,
//How many Gift do you have left*(gift spends the trade) //How many Gift do you have left*(gift spends the trade)
@ -351,10 +374,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
DailyAffiliationZariman: Number, DailyAffiliationZariman: Number,
DailyAffiliationKahl: Number, DailyAffiliationKahl: Number,
//Daily Focus limit //Daily Focus limit
DailyFocus: Number, DailyFocus: Number,
//you not used Focus //you not used Focus
FocusXP: Schema.Types.Mixed, FocusXP: Schema.Types.Mixed,
//Curent active like Active school focuses is = "Zenurik" //Curent active like Active school focuses is = "Zenurik"
FocusAbility: String, FocusAbility: String,
@ -441,24 +463,21 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//Railjack/Components(https://warframe.fandom.com/wiki/Railjack/Components) //Railjack/Components(https://warframe.fandom.com/wiki/Railjack/Components)
CrewShipRawSalvage: [Schema.Types.Mixed], CrewShipRawSalvage: [Schema.Types.Mixed],
//Default RailJack //Default RailJack
CrewShips: [Schema.Types.Mixed], CrewShips: [Schema.Types.Mixed],
CrewShipAmmo: [Schema.Types.Mixed], CrewShipAmmo: [Schema.Types.Mixed],
CrewShipWeapons: [Schema.Types.Mixed], CrewShipWeapons: [Schema.Types.Mixed],
CrewShipWeaponSkins: [Schema.Types.Mixed], CrewShipWeaponSkins: [Schema.Types.Mixed],
//NPC Crew and weapon //NPC Crew and weapon
CrewMembers: [Schema.Types.Mixed], CrewMembers: [Schema.Types.Mixed],
CrewShipSalvagedWeaponSkins: [Schema.Types.Mixed], CrewShipSalvagedWeaponSkins: [Schema.Types.Mixed],
CrewShipSalvagedWeapons: [Schema.Types.Mixed], CrewShipSalvagedWeapons: [Schema.Types.Mixed],
//Complete Mission\Quests //Complete Mission\Quests
Missions: [Schema.Types.Mixed], Missions: [Schema.Types.Mixed],
QuestKeys: [Schema.Types.Mixed], QuestKeys: [Schema.Types.Mixed],
//item like DojoKey or Boss missions key //item like DojoKey or Boss missions key
LevelKeys: [Schema.Types.Mixed], LevelKeys: [Schema.Types.Mixed],
//Active quests //Active quests
Quests: [Schema.Types.Mixed], Quests: [Schema.Types.Mixed],
@ -478,25 +497,22 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//Retries rank up(3 time) //Retries rank up(3 time)
TrainingRetriesLeft: Number, TrainingRetriesLeft: Number,
//you saw last played Region when you opened the star map //you saw last played Region when you opened the star map
LastRegionPlayed: String, LastRegionPlayed: String,
//Blueprint //Blueprint
Recipes: [Schema.Types.Mixed], Recipes: [Schema.Types.Mixed],
//Crafting Blueprint(Item Name + CompletionDate) //Crafting Blueprint(Item Name + CompletionDate)
PendingRecipes: [Schema.Types.Mixed], PendingRecipes: [pendingRecipeSchema],
//warframe\Weapon skins //warframe\Weapon skins
WeaponSkins: [Schema.Types.Mixed], WeaponSkins: [Schema.Types.Mixed],
//Ayatan Item //Ayatan Item
FusionTreasures: [Schema.Types.Mixed], FusionTreasures: [Schema.Types.Mixed],
//"node": "TreasureTutorial", "state": "TS_COMPLETED" //"node": "TreasureTutorial", "state": "TS_COMPLETED"
TauntHistory: [Schema.Types.Mixed], TauntHistory: [Schema.Types.Mixed],
//noShow2FA,VisitPrimeVault etc //noShow2FA,VisitPrimeVault etc
WebFlags: Schema.Types.Mixed, WebFlags: Schema.Types.Mixed,
//Id CompletedAlerts //Id CompletedAlerts
@ -508,7 +524,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//Alert->Kuva Siphon //Alert->Kuva Siphon
PeriodicMissionCompletions: [Schema.Types.Mixed], PeriodicMissionCompletions: [Schema.Types.Mixed],
//Codex->LoreFragment //Codex->LoreFragment
LoreFragmentScans: [Schema.Types.Mixed], LoreFragmentScans: [Schema.Types.Mixed],
@ -520,7 +535,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
ActiveDojoColorResearch: String, ActiveDojoColorResearch: String,
SentientSpawnChanceBoosters: Schema.Types.Mixed, SentientSpawnChanceBoosters: Schema.Types.Mixed,
QualifyingInvasions: [Schema.Types.Mixed], QualifyingInvasions: [Schema.Types.Mixed],
FactionScores: [Number], FactionScores: [Number],
@ -530,11 +545,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//If you want change Spectre Gear id //If you want change Spectre Gear id
PendingSpectreLoadouts: [Schema.Types.Mixed], PendingSpectreLoadouts: [Schema.Types.Mixed],
//New quest Email spam //New quest Email spam
//example:"ItemType": "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestEmailItem", //example:"ItemType": "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestEmailItem",
EmailItems: [Schema.Types.Mixed], EmailItems: [Schema.Types.Mixed],
//Profile->Wishlist //Profile->Wishlist
Wishlist: [String], Wishlist: [String],
@ -561,7 +574,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258, //Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
PersonalGoalProgress: [Schema.Types.Mixed], PersonalGoalProgress: [Schema.Types.Mixed],
//Setting interface Style //Setting interface Style
ThemeStyle: String, ThemeStyle: String,
ThemeBackground: String, ThemeBackground: String,
@ -579,7 +592,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//Night Wave Challenge //Night Wave Challenge
SeasonChallengeHistory: [Schema.Types.Mixed], SeasonChallengeHistory: [Schema.Types.Mixed],
//Cephalon Simaris Entries Example:"TargetType"+"Scans"(1-10)+"Completed": true|false //Cephalon Simaris Entries Example:"TargetType"+"Scans"(1-10)+"Completed": true|false
LibraryPersonalProgress: [Schema.Types.Mixed], LibraryPersonalProgress: [Schema.Types.Mixed],
//Cephalon Simaris Daily Task //Cephalon Simaris Daily Task
@ -587,23 +599,23 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//https://warframe.fandom.com/wiki/Invasion //https://warframe.fandom.com/wiki/Invasion
InvasionChainProgress: [Schema.Types.Mixed], InvasionChainProgress: [Schema.Types.Mixed],
//https://warframe.fandom.com/wiki/Parazon //https://warframe.fandom.com/wiki/Parazon
DataKnives: [GenericItemSchema], DataKnives: [GenericItemSchema],
//CorpusLich or GrineerLich //CorpusLich or GrineerLich
NemesisAbandonedRewards: [String], NemesisAbandonedRewards: [String],
//CorpusLich\KuvaLich //CorpusLich\KuvaLich
NemesisHistory: [Schema.Types.Mixed], NemesisHistory: [Schema.Types.Mixed],
LastNemesisAllySpawnTime: Schema.Types.Mixed, LastNemesisAllySpawnTime: Schema.Types.Mixed,
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social) //TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: Schema.Types.Mixed, Settings: Schema.Types.Mixed,
//Railjack craft //Railjack craft
//https://warframe.fandom.com/wiki/Rising_Tide //https://warframe.fandom.com/wiki/Rising_Tide
PersonalTechProjects: [Schema.Types.Mixed], PersonalTechProjects: [Schema.Types.Mixed],
//Modulars lvl and exp(Railjack|Duviri) //Modulars lvl and exp(Railjack|Duviri)
//https://warframe.fandom.com/wiki/Intrinsics //https://warframe.fandom.com/wiki/Intrinsics
PlayerSkills: Schema.Types.Mixed, PlayerSkills: Schema.Types.Mixed,
@ -611,7 +623,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//TradeBannedUntil data //TradeBannedUntil data
TradeBannedUntil: Schema.Types.Mixed, TradeBannedUntil: Schema.Types.Mixed,
//https://warframe.fandom.com/wiki/Helminth //https://warframe.fandom.com/wiki/Helminth
InfestedFoundry: Schema.Types.Mixed, InfestedFoundry: Schema.Types.Mixed,
NextRefill: Schema.Types.Mixed, NextRefill: Schema.Types.Mixed,
@ -624,7 +635,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
//https://warframe.fandom.com/wiki/Incarnon //https://warframe.fandom.com/wiki/Incarnon
EvolutionProgress: [Schema.Types.Mixed], EvolutionProgress: [Schema.Types.Mixed],
//Unknown and system //Unknown and system
DuviriInfo: DuviriInfoSchema, DuviriInfo: DuviriInfoSchema,
Mailbox: MailboxSchema, Mailbox: MailboxSchema,
@ -650,7 +660,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>({
CollectibleSeries: [Schema.Types.Mixed], CollectibleSeries: [Schema.Types.Mixed],
HasResetAccount: Boolean, HasResetAccount: Boolean,
//Discount Coupon //Discount Coupon
PendingCoupon: Schema.Types.Mixed, PendingCoupon: Schema.Types.Mixed,
//Like BossAladV,BossCaptainVor come for you on missions % chance //Like BossAladV,BossCaptainVor come for you on missions % chance
DeathMarks: [String], DeathMarks: [String],
@ -685,13 +695,14 @@ type InventoryDocumentProps = {
MiscItems: Types.DocumentArray<IMiscItem>; MiscItems: Types.DocumentArray<IMiscItem>;
Boosters: Types.DocumentArray<IBooster>; Boosters: Types.DocumentArray<IBooster>;
OperatorLoadOuts: Types.DocumentArray<IOperatorConfigClient>; OperatorLoadOuts: Types.DocumentArray<IOperatorConfigClient>;
AdultOperatorLoadOuts: Types.DocumentArray<IOperatorConfigClient>; AdultOperatorLoadOuts: Types.DocumentArray<IOperatorConfigClient>; //TODO: this should still contain _id
MechSuits: Types.DocumentArray<ISuitDatabase>; MechSuits: Types.DocumentArray<ISuitDatabase>;
Scoops: Types.DocumentArray<IGenericItem>; Scoops: Types.DocumentArray<IGenericItem>;
DataKnives: Types.DocumentArray<IGenericItem>; DataKnives: Types.DocumentArray<IGenericItem>;
DrifterMelee: Types.DocumentArray<IGenericItem>; DrifterMelee: Types.DocumentArray<IGenericItem>;
Sentinels: Types.DocumentArray<IWeaponDatabase>; Sentinels: Types.DocumentArray<IWeaponDatabase>;
Horses: Types.DocumentArray<IGenericItem>; Horses: Types.DocumentArray<IGenericItem>;
PendingRecipes: Types.DocumentArray<IPendingRecipeDatabase>;
}; };
type InventoryModelType = Model<IInventoryDatabase, {}, InventoryDocumentProps>; type InventoryModelType = Model<IInventoryDatabase, {}, InventoryDocumentProps>;

View File

@ -35,6 +35,8 @@ import express from "express";
import { setBootLocationController } from "@/src/controllers/api/setBootLocationController"; import { setBootLocationController } from "@/src/controllers/api/setBootLocationController";
import { focusController } from "@/src/controllers/api/focusController"; import { focusController } from "@/src/controllers/api/focusController";
import { inventorySlotsController } from "@/src/controllers/api/inventorySlotsController"; import { inventorySlotsController } from "@/src/controllers/api/inventorySlotsController";
import { startRecipeController } from "@/src/controllers/api/startRecipeController";
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
const apiRouter = express.Router(); const apiRouter = express.Router();
@ -62,6 +64,9 @@ apiRouter.get("/logout.php", logoutController);
apiRouter.get("/setBootLocation.php", setBootLocationController); apiRouter.get("/setBootLocation.php", setBootLocationController);
// post // post
// eslint-disable-next-line @typescript-eslint/no-misused-promises
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/startRecipe.php", startRecipeController);
apiRouter.post("/inventorySlots.php", inventorySlotsController); apiRouter.post("/inventorySlots.php", inventorySlotsController);
apiRouter.post("/focus.php", focusController); apiRouter.post("/focus.php", focusController);
apiRouter.post("/artifacts.php", artifactsController); apiRouter.post("/artifacts.php", artifactsController);

View File

@ -17,6 +17,7 @@ import {
import { IGenericUpdate } from "../types/genericUpdate"; import { IGenericUpdate } from "../types/genericUpdate";
import { IArtifactsRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IArtifactsRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
export const createInventory = async (accountOwnerId: Types.ObjectId, loadOutPresetId: Types.ObjectId) => { export const createInventory = async (accountOwnerId: Types.ObjectId, loadOutPresetId: Types.ObjectId) => {
try { try {
@ -145,8 +146,6 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string) =>
return data; return data;
}; };
export type WeaponTypeInternal = "LongGuns" | "Pistols" | "Melee";
export const addWeapon = async ( export const addWeapon = async (
weaponType: WeaponTypeInternal, weaponType: WeaponTypeInternal,
weaponName: string, weaponName: string,

View File

@ -0,0 +1,117 @@
import { getIndexAfter } from "@/src/helpers/stringHelpers";
import { logger } from "@/src/utils/logger";
import Items, { Buildable, Category, Item, Warframe, Weapon } from "warframe-items";
type MinWeapon = Omit<Weapon, "patchlogs">;
type MinItem = Omit<Item, "patchlogs">;
export const weapons: MinWeapon[] = (new Items({ category: ["Primary", "Secondary", "Melee"] }) as Weapon[]).map(
item => {
const next = { ...item };
delete next.patchlogs;
return next;
}
);
export type WeaponTypeInternal = "LongGuns" | "Pistols" | "Melee";
export const items: MinItem[] = new Items({ category: ["All"] }).map(item => {
const next = { ...item };
delete next.patchlogs;
return next;
});
export const getWeaponType = (weaponName: string) => {
const weaponInfo = weapons.find(i => i.uniqueName === weaponName);
if (!weaponInfo) {
throw new Error(`unknown weapon ${weaponName}`);
}
const weaponType = weaponInfo.productCategory as WeaponTypeInternal;
if (!weaponType) {
logger.error(`unknown weapon category for item ${weaponName}`);
throw new Error(`unknown weapon category for item ${weaponName}`);
}
return weaponType;
};
const getNamesObj = (category: Category) =>
new Items({ category: [category] }).reduce<{ [index: string]: string }>((acc, item) => {
acc[item.name!.replace("'S", "'s")] = item.uniqueName!;
return acc;
}, {});
export const modNames = getNamesObj("Mods");
export const resourceNames = getNamesObj("Resources");
export const miscNames = getNamesObj("Misc");
export const relicNames = getNamesObj("Relics");
export const skinNames = getNamesObj("Skins");
export const arcaneNames = getNamesObj("Arcanes");
export const gearNames = getNamesObj("Gear");
//logger.debug(`gear names`, { gearNames });
export const craftNames = Object.fromEntries(
(
new Items({
category: [
"Warframes",
"Gear",
"Melee",
"Primary",
"Secondary",
"Sentinels",
"Misc",
"Arch-Gun",
"Arch-Melee"
]
}) as Warframe[]
)
.flatMap(item => item.components || [])
.filter(item => item.drops && item.drops[0])
.map(item => [item.drops![0].type, item.uniqueName])
);
export const blueprintNames = Object.fromEntries(
Object.keys(craftNames)
.filter(name => name.includes("Blueprint"))
.map(name => [name, craftNames[name]])
);
const buildables = items.filter(item => !!(item as Buildable).components);
export const getItemByBlueprint = (uniqueName: string): (MinItem & Buildable) | undefined => {
const item = buildables.find(
item => (item as Buildable).components?.find(component => component.uniqueName === uniqueName)
);
return item;
};
export const getItemCategoryByUniqueName = (uniqueName: string) => {
//Lotus/Types/Items/MiscItems/PolymerBundle
let splitWord = "Items/";
if (!uniqueName.includes("/Items/")) {
splitWord = "/Types/";
}
const index = getIndexAfter(uniqueName, splitWord);
if (index === -1) {
logger.error(`error parsing item category ${uniqueName}`);
throw new Error(`error parsing item category ${uniqueName}`);
}
const category = uniqueName.substring(index).split("/")[0];
return category;
};
export const getItemByUniqueName = (uniqueName: string) => {
const item = items.find(item => item.uniqueName === uniqueName);
return item;
};
export const getItemByName = (name: string) => {
const item = items.find(item => item.name === name);
return item;
};

View File

@ -1,7 +1,14 @@
import { IMissionRewardResponse, IReward, IInventoryFieldType, inventoryFields } from "@/src/types/missionTypes"; import { IMissionRewardResponse, IReward, IInventoryFieldType, inventoryFields } from "@/src/types/missionTypes";
import missionsDropTable from "@/static/json/missions-drop-table.json"; import missionsDropTable from "@/static/json/missions-drop-table.json";
import { modNames, relicNames, miscNames, resourceNames, gearNames, blueprintNames } from "@/static/data/items"; import {
modNames,
relicNames,
miscNames,
resourceNames,
gearNames,
blueprintNames
} from "@/src/services/itemDataService";
import { IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IMissionInventoryUpdateRequest } from "../types/requestTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";

View File

@ -1,4 +1,5 @@
import { getWeaponType, parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers"; import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
import { getWeaponType } from "@/src/services/itemDataService";
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers"; import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
import { import {
addBooster, addBooster,

View File

@ -0,0 +1,74 @@
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { getInventory } from "@/src/services/inventoryService";
import { getItemByBlueprint, getItemCategoryByUniqueName } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger";
import { Types } from "mongoose";
export interface IResource {
uniqueName: string;
count: number;
}
// export const updateResources = async (accountId: string, components: IResource[]) => {
// const inventory = await getInventory(accountId);
// for (const component of components) {
// const category = getItemCategoryByUniqueName(component.uniqueName) as keyof typeof inventory;
// //validate category
// console.log(component.uniqueName);
// console.log("cate", category);
// const invItem = inventory[category];
// console.log("invItem", invItem);
// inventory["MiscItems"];
// }
// };
export const startRecipe = async (recipeName: string, accountId: string) => {
const recipe = getItemByBlueprint(recipeName);
if (!recipe) {
logger.error(`unknown recipe ${recipeName}`);
throw new Error(`unknown recipe ${recipeName}`);
}
const componentsNeeded = recipe.components?.map(component => ({
uniqueName: component.uniqueName,
count: component.itemCount
}));
if (!componentsNeeded) {
logger.error(`recipe ${recipeName} has no components`);
throw new Error(`recipe ${recipeName} has no components`);
}
//TODO: consume components used
//await updateResources(accountId, componentsNeeded);
//might be redundant
if (recipe.consumeOnBuild) {
//consume
}
if (!recipe.buildTime) {
logger.error(`recipe ${recipeName} has no build time`);
throw new Error(`recipe ${recipeName} has no build time`);
}
//buildtime is in seconds
const completionDate = new Date(Date.now() + recipe.buildTime * unixTimesInMs.second);
const inventory = await getInventory(accountId);
inventory.PendingRecipes.push({
ItemType: recipeName,
CompletionDate: completionDate,
_id: new Types.ObjectId()
});
const newInventory = await inventory.save();
return {
RecipeId: { $oid: newInventory.PendingRecipes[newInventory.PendingRecipes.length - 1]._id?.toString() }
};
};

View File

@ -14,11 +14,13 @@ import { IOperatorLoadOutSigcol, IWeaponDatabase } from "@/src/types/inventoryTy
//Document extends will be deleted soon. TODO: delete and migrate uses to ... //Document extends will be deleted soon. TODO: delete and migrate uses to ...
export interface IInventoryDatabaseDocument extends IInventoryDatabase, Document {} export interface IInventoryDatabaseDocument extends IInventoryDatabase, Document {}
export interface IInventoryDatabase extends Omit<IInventoryResponse, "TrainingDate" | "LoadOutPresets" | "Mailbox"> { export interface IInventoryDatabase
extends Omit<IInventoryResponse, "TrainingDate" | "LoadOutPresets" | "Mailbox" | "PendingRecipes"> {
accountOwnerId: Types.ObjectId; accountOwnerId: Types.ObjectId;
TrainingDate: Date; // TrainingDate changed from IMongoDate to Date TrainingDate: Date; // TrainingDate changed from IMongoDate to Date
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
Mailbox: Types.ObjectId; // Mailbox changed from IMailbox to Types.ObjectId Mailbox: Types.ObjectId; // Mailbox changed from IMailbox to Types.ObjectId
PendingRecipes: IPendingRecipe[];
} }
export interface IInventoryResponseDocument extends IInventoryResponse, Document {} export interface IInventoryResponseDocument extends IInventoryResponse, Document {}
@ -41,6 +43,11 @@ export interface IMailbox {
LastInboxId: IOid; LastInboxId: IOid;
} }
//TODO: perhaps split response and database into their own files
export interface IPendingRecipeResponse extends Omit<IPendingRecipe, "CompletionDate"> {
CompletionDate: IMongoDate;
}
export interface IInventoryResponse { export interface IInventoryResponse {
Horses: IGenericItem[]; Horses: IGenericItem[];
DrifterMelee: IGenericItem[]; DrifterMelee: IGenericItem[];
@ -96,7 +103,7 @@ export interface IInventoryResponse {
XPInfo: IEmailItem[]; XPInfo: IEmailItem[];
Recipes: IConsumable[]; Recipes: IConsumable[];
WeaponSkins: IWeaponSkin[]; WeaponSkins: IWeaponSkin[];
PendingRecipes: IPendingRecipe[]; PendingRecipes: IPendingRecipeResponse[];
TrainingDate: IMongoDate; TrainingDate: IMongoDate;
PlayerLevel: number; PlayerLevel: number;
Upgrades: ICrewShipSalvagedWeaponSkin[]; Upgrades: ICrewShipSalvagedWeaponSkin[];
@ -816,7 +823,7 @@ export interface IPendingCoupon {
export interface IPendingRecipe { export interface IPendingRecipe {
ItemType: string; ItemType: string;
CompletionDate: IMongoDate; CompletionDate: Date;
ItemId: IOid; ItemId: IOid;
} }

View File

@ -1,60 +0,0 @@
import Items, { Category, Item, Warframe, Weapon } from "warframe-items";
type MinWeapon = Omit<Weapon, "patchlogs">;
type MinItem = Omit<Item, "patchlogs">;
export const weapons: MinWeapon[] = (new Items({ category: ["Primary", "Secondary", "Melee"] }) as Weapon[]).map(
item => {
const next = { ...item };
delete next.patchlogs;
return next;
}
);
export const items: MinItem[] = new Items({ category: ["All"] }).map(item => {
const next = { ...item };
delete next.patchlogs;
return next;
});
const getNamesObj = (category: Category) =>
new Items({ category: [category] }).reduce((acc, item) => {
acc[item.name!.replace("'S", "'s")] = item.uniqueName!;
return acc;
}, {} as ImportAssertions);
export const modNames = getNamesObj("Mods");
export const resourceNames = getNamesObj("Resources");
export const miscNames = getNamesObj("Misc");
export const relicNames = getNamesObj("Relics");
export const skinNames = getNamesObj("Skins");
export const arcaneNames = getNamesObj("Arcanes");
export const gearNames = getNamesObj("Gear");
export const craftNames: ImportAssertions = Object.fromEntries(
(
new Items({
category: [
"Warframes",
"Gear",
"Melee",
"Primary",
"Secondary",
"Sentinels",
"Misc",
"Arch-Gun",
"Arch-Melee"
]
}) as Warframe[]
)
.flatMap(item => item.components || [])
.filter(item => item.drops && item.drops[0])
.map(item => [item.drops![0].type, item.uniqueName])
);
craftNames["Forma Blueprint"] = "/Lotus/Types/Recipes/Components/FormaBlueprint";
export const blueprintNames: ImportAssertions = Object.fromEntries(
Object.keys(craftNames)
.filter(name => name.includes("Blueprint"))
.map(name => [name, craftNames[name]])
);