From 0ea67ea89ade9f48baf499cce2f824c356bc5dad Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 16 Apr 2025 06:31:00 -0700 Subject: [PATCH] feat: identify & repair railjack components (#1664) Closes #911 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1664 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/crewShipIdentifySalvageController.ts | 41 +++++++ src/controllers/api/guildTechController.ts | 114 +++++++++++++----- src/models/inventoryModels/inventoryModel.ts | 5 + src/routes/api.ts | 2 + src/services/inventoryService.ts | 24 +++- src/types/inventoryTypes/inventoryTypes.ts | 8 +- src/types/purchaseTypes.ts | 1 + 7 files changed, 158 insertions(+), 37 deletions(-) create mode 100644 src/controllers/api/crewShipIdentifySalvageController.ts diff --git a/src/controllers/api/crewShipIdentifySalvageController.ts b/src/controllers/api/crewShipIdentifySalvageController.ts new file mode 100644 index 00000000..c40f78c5 --- /dev/null +++ b/src/controllers/api/crewShipIdentifySalvageController.ts @@ -0,0 +1,41 @@ +import { addCrewShipSalvagedWeaponSkin, addCrewShipRawSalvage, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { RequestHandler } from "express"; +import { IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes"; +import { ExportCustoms } from "warframe-public-export-plus"; +import { IFingerprintStat } from "@/src/helpers/rivenHelper"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; + +export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "CrewShipSalvagedWeaponSkins CrewShipRawSalvage"); + const payload = getJSONfromString(String(req.body)); + + const buffs: IFingerprintStat[] = []; + for (const upgrade of ExportCustoms[payload.ItemType].randomisedUpgrades!) { + buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) }); + } + const inventoryChanges: IInventoryChanges = addCrewShipSalvagedWeaponSkin( + inventory, + payload.ItemType, + JSON.stringify({ compat: payload.ItemType, buffs } satisfies IInnateDamageFingerprint) + ); + + inventoryChanges.CrewShipRawSalvage = [ + { + ItemType: payload.ItemType, + ItemCount: -1 + } + ]; + addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage); + + await inventory.save(); + res.json({ + InventoryChanges: inventoryChanges + }); +}; + +interface ICrewShipIdentifySalvageRequest { + ItemType: string; +} diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index 55083d5a..257434fb 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -14,20 +14,23 @@ import { import { ExportDojoRecipes } from "warframe-public-export-plus"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { + addCrewShipWeaponSkin, addItem, addMiscItems, addRecipes, combineInventoryChanges, getInventory, + occupySlot, updateCurrency } from "@/src/services/inventoryService"; -import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { config } from "@/src/services/configService"; import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes"; import { GuildMember } from "@/src/models/guildModel"; import { toMongoDate } from "@/src/helpers/inventoryHelpers"; import { logger } from "@/src/utils/logger"; +import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; export const guildTechController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -99,6 +102,8 @@ export const guildTechController: RequestHandler = async (req, res) => { State: 0, ReqCredits: recipe.price, ItemType: data.RecipeType, + ProductCategory: data.TechProductCategory, + CategoryItemId: data.CategoryItemId, ReqItems: recipe.ingredients }) - 1 ]; @@ -222,33 +227,44 @@ export const guildTechController: RequestHandler = async (req, res) => { }); } } else if (data.Action.split(",")[0] == "Buy") { - const guild = await getGuildForRequestEx(req, inventory); - if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { - res.status(400).send("-1").end(); - return; - } const purchase = data as IGuildTechBuyRequest; - const quantity = parseInt(data.Action.split(",")[1]); - const recipeChanges = [ - { - ItemType: purchase.RecipeType, - ItemCount: quantity + if (purchase.Mode == "Guild") { + const guild = await getGuildForRequestEx(req, inventory); + if ( + !hasAccessToDojo(inventory) || + !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator)) + ) { + res.status(400).send("-1").end(); + return; } - ]; - addRecipes(inventory, recipeChanges); - const currencyChanges = updateCurrency( - inventory, - ExportDojoRecipes.research[purchase.RecipeType].replicatePrice, - false - ); - await inventory.save(); - // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. - res.json({ - inventoryChanges: { - ...currencyChanges, - Recipes: recipeChanges - } - }); + const quantity = parseInt(data.Action.split(",")[1]); + const recipeChanges = [ + { + ItemType: purchase.RecipeType, + ItemCount: quantity + } + ]; + addRecipes(inventory, recipeChanges); + const currencyChanges = updateCurrency( + inventory, + ExportDojoRecipes.research[purchase.RecipeType].replicatePrice, + false + ); + await inventory.save(); + // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. + res.json({ + inventoryChanges: { + ...currencyChanges, + Recipes: recipeChanges + } + }); + } else { + const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!); + await inventory.save(); + res.json({ + inventoryChanges: inventoryChanges + }); + } } else if (data.Action == "Fabricate") { const guild = await getGuildForRequestEx(req, inventory); if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { @@ -289,9 +305,18 @@ export const guildTechController: RequestHandler = async (req, res) => { guild.ActiveDojoColorResearch = data.RecipeType; await guild.save(); res.end(); + } else if (data.Action == "Rush" && data.CategoryItemId) { + const inventoryChanges: IInventoryChanges = { + ...updateCurrency(inventory, 20, true), + ...claimSalvagedComponent(inventory, data.CategoryItemId) + }; + await inventory.save(); + res.json({ + inventoryChanges: inventoryChanges + }); } else { logger.debug(`data provided to ${req.path}: ${String(req.body)}`); - throw new Error(`unknown guildTech action: ${data.Action}`); + throw new Error(`unhandled guildTech request`); } }; @@ -301,15 +326,15 @@ type TGuildTechRequest = | IGuildTechContributeRequest; interface IGuildTechBasicRequest { - Action: "Start" | "Fabricate" | "Pause" | "Unpause"; + Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush"; Mode: "Guild" | "Personal"; RecipeType: string; + TechProductCategory?: string; + CategoryItemId?: string; } -interface IGuildTechBuyRequest { +interface IGuildTechBuyRequest extends Omit { Action: string; - Mode: "Guild"; - RecipeType: string; } interface IGuildTechContributeRequest { @@ -321,3 +346,30 @@ interface IGuildTechContributeRequest { VaultCredits: number; VaultMiscItems: IMiscItem[]; } + +const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => { + // delete personal tech project + const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId)); + if (personalTechProjectIndex != -1) { + inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1); + } + + // find salved part & delete it + const crewShipSalvagedWeaponSkinsIndex = inventory.CrewShipSalvagedWeaponSkins.findIndex(x => x._id.equals(itemId)); + const crewShipWeaponSkin = inventory.CrewShipSalvagedWeaponSkins[crewShipSalvagedWeaponSkinsIndex]; + inventory.CrewShipSalvagedWeaponSkins.splice(crewShipSalvagedWeaponSkinsIndex, 1); + + // add final item + const inventoryChanges = { + ...addCrewShipWeaponSkin(inventory, crewShipWeaponSkin.ItemType, crewShipWeaponSkin.UpgradeFingerprint), + ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false) + }; + + inventoryChanges.RemovedIdItems = [ + { + ItemId: { $oid: itemId } + } + ]; + + return inventoryChanges; +}; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 6b4b4959..97a0022e 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -504,6 +504,8 @@ const personalTechProjectSchema = new Schema({ State: Number, ReqCredits: Number, ItemType: String, + ProductCategory: String, + CategoryItemId: Schema.Types.ObjectId, ReqItems: { type: [typeCountSchema], default: undefined }, HasContributions: Boolean, CompletionDate: Date @@ -522,6 +524,9 @@ personalTechProjectSchema.set("toJSON", { const db = ret as IPersonalTechProjectDatabase; const client = ret as IPersonalTechProjectClient; + if (db.CategoryItemId) { + client.CategoryItemId = toOid(db.CategoryItemId); + } if (db.CompletionDate) { client.CompletionDate = toMongoDate(db.CompletionDate); } diff --git a/src/routes/api.ts b/src/routes/api.ts index 39b3d751..d16ea321 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -27,6 +27,7 @@ import { contributeToVaultController } from "@/src/controllers/api/contributeToV import { createAllianceController } from "@/src/controllers/api/createAllianceController"; import { createGuildController } from "@/src/controllers/api/createGuildController"; import { creditsController } from "@/src/controllers/api/creditsController"; +import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController"; import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController"; import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController"; import { declineAllianceInviteController } from "@/src/controllers/api/declineAllianceInviteController"; @@ -218,6 +219,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/createAlliance.php", createAllianceController); apiRouter.post("/createGuild.php", createGuildController); +apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController); apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController); apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController); apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController); diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 2d17f7ed..d3e86bfd 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -426,7 +426,7 @@ export const addItem = async ( ); } inventoryChanges = { - ...addCrewShipWeaponSkin(inventory, typeName), + ...addCrewShipWeaponSkin(inventory, typeName, undefined), ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, premiumPurchase) }; } @@ -1107,12 +1107,14 @@ export const addSkin = ( return inventoryChanges; }; -const addCrewShipWeaponSkin = ( +export const addCrewShipWeaponSkin = ( inventory: TInventoryDatabaseDocument, typeName: string, + upgradeFingerprint: string | undefined, inventoryChanges: IInventoryChanges = {} ): IInventoryChanges => { - const index = inventory.CrewShipWeaponSkins.push({ ItemType: typeName }) - 1; + const index = + inventory.CrewShipWeaponSkins.push({ ItemType: typeName, UpgradeFingerprint: upgradeFingerprint }) - 1; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition inventoryChanges.CrewShipWeaponSkins ??= []; (inventoryChanges.CrewShipWeaponSkins as IUpgradeClient[]).push( @@ -1121,6 +1123,22 @@ const addCrewShipWeaponSkin = ( return inventoryChanges; }; +export const addCrewShipSalvagedWeaponSkin = ( + inventory: TInventoryDatabaseDocument, + typeName: string, + upgradeFingerprint: string | undefined, + inventoryChanges: IInventoryChanges = {} +): IInventoryChanges => { + const index = + inventory.CrewShipSalvagedWeaponSkins.push({ ItemType: typeName, UpgradeFingerprint: upgradeFingerprint }) - 1; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + inventoryChanges.CrewShipSalvagedWeaponSkins ??= []; + (inventoryChanges.CrewShipSalvagedWeaponSkins as IUpgradeClient[]).push( + inventory.CrewShipSalvagedWeaponSkins[index].toJSON() + ); + return inventoryChanges; +}; + const addCrewShip = ( inventory: TInventoryDatabaseDocument, typeName: string, diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 63207ef0..73156437 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -947,15 +947,17 @@ export interface IPersonalTechProjectDatabase { State: number; ReqCredits: number; ItemType: string; + ProductCategory?: string; + CategoryItemId?: Types.ObjectId; ReqItems: ITypeCount[]; HasContributions?: boolean; CompletionDate?: Date; } -export interface IPersonalTechProjectClient extends Omit { - CompletionDate?: IMongoDate; - ProductCategory?: string; +export interface IPersonalTechProjectClient + extends Omit { CategoryItemId?: IOid; + CompletionDate?: IMongoDate; ItemId: IOid; } diff --git a/src/types/purchaseTypes.ts b/src/types/purchaseTypes.ts index 51459454..1b4c9aca 100644 --- a/src/types/purchaseTypes.ts +++ b/src/types/purchaseTypes.ts @@ -43,6 +43,7 @@ export type IInventoryChanges = { Drones?: IDroneClient[]; MiscItems?: IMiscItem[]; EmailItems?: ITypeCount[]; + CrewShipRawSalvage?: ITypeCount[]; Nemesis?: Partial; NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0 RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0