feat: identify & repair railjack components (#1664)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled

Closes #911

Reviewed-on: #1664
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-04-16 06:31:00 -07:00 committed by Sainan
parent 51b82df5fd
commit 0ea67ea89a
7 changed files with 158 additions and 37 deletions

View File

@ -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<ICrewShipIdentifySalvageRequest>(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;
}

View File

@ -14,20 +14,23 @@ import {
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { import {
addCrewShipWeaponSkin,
addItem, addItem,
addMiscItems, addMiscItems,
addRecipes, addRecipes,
combineInventoryChanges, combineInventoryChanges,
getInventory, getInventory,
occupySlot,
updateCurrency updateCurrency
} from "@/src/services/inventoryService"; } 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 { IInventoryChanges } from "@/src/types/purchaseTypes";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes"; import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
import { GuildMember } from "@/src/models/guildModel"; import { GuildMember } from "@/src/models/guildModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers"; import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
export const guildTechController: RequestHandler = async (req, res) => { export const guildTechController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -99,6 +102,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
State: 0, State: 0,
ReqCredits: recipe.price, ReqCredits: recipe.price,
ItemType: data.RecipeType, ItemType: data.RecipeType,
ProductCategory: data.TechProductCategory,
CategoryItemId: data.CategoryItemId,
ReqItems: recipe.ingredients ReqItems: recipe.ingredients
}) - 1 }) - 1
]; ];
@ -222,33 +227,44 @@ export const guildTechController: RequestHandler = async (req, res) => {
}); });
} }
} else if (data.Action.split(",")[0] == "Buy") { } 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 purchase = data as IGuildTechBuyRequest;
const quantity = parseInt(data.Action.split(",")[1]); if (purchase.Mode == "Guild") {
const recipeChanges = [ const guild = await getGuildForRequestEx(req, inventory);
{ if (
ItemType: purchase.RecipeType, !hasAccessToDojo(inventory) ||
ItemCount: quantity !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
) {
res.status(400).send("-1").end();
return;
} }
]; const quantity = parseInt(data.Action.split(",")[1]);
addRecipes(inventory, recipeChanges); const recipeChanges = [
const currencyChanges = updateCurrency( {
inventory, ItemType: purchase.RecipeType,
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice, ItemCount: quantity
false }
); ];
await inventory.save(); addRecipes(inventory, recipeChanges);
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. const currencyChanges = updateCurrency(
res.json({ inventory,
inventoryChanges: { ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
...currencyChanges, false
Recipes: recipeChanges );
} 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") { } else if (data.Action == "Fabricate") {
const guild = await getGuildForRequestEx(req, inventory); const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
@ -289,9 +305,18 @@ export const guildTechController: RequestHandler = async (req, res) => {
guild.ActiveDojoColorResearch = data.RecipeType; guild.ActiveDojoColorResearch = data.RecipeType;
await guild.save(); await guild.save();
res.end(); 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 { } else {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`); 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; | IGuildTechContributeRequest;
interface IGuildTechBasicRequest { interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause"; Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush";
Mode: "Guild" | "Personal"; Mode: "Guild" | "Personal";
RecipeType: string; RecipeType: string;
TechProductCategory?: string;
CategoryItemId?: string;
} }
interface IGuildTechBuyRequest { interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
Action: string; Action: string;
Mode: "Guild";
RecipeType: string;
} }
interface IGuildTechContributeRequest { interface IGuildTechContributeRequest {
@ -321,3 +346,30 @@ interface IGuildTechContributeRequest {
VaultCredits: number; VaultCredits: number;
VaultMiscItems: IMiscItem[]; 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;
};

View File

@ -504,6 +504,8 @@ const personalTechProjectSchema = new Schema<IPersonalTechProjectDatabase>({
State: Number, State: Number,
ReqCredits: Number, ReqCredits: Number,
ItemType: String, ItemType: String,
ProductCategory: String,
CategoryItemId: Schema.Types.ObjectId,
ReqItems: { type: [typeCountSchema], default: undefined }, ReqItems: { type: [typeCountSchema], default: undefined },
HasContributions: Boolean, HasContributions: Boolean,
CompletionDate: Date CompletionDate: Date
@ -522,6 +524,9 @@ personalTechProjectSchema.set("toJSON", {
const db = ret as IPersonalTechProjectDatabase; const db = ret as IPersonalTechProjectDatabase;
const client = ret as IPersonalTechProjectClient; const client = ret as IPersonalTechProjectClient;
if (db.CategoryItemId) {
client.CategoryItemId = toOid(db.CategoryItemId);
}
if (db.CompletionDate) { if (db.CompletionDate) {
client.CompletionDate = toMongoDate(db.CompletionDate); client.CompletionDate = toMongoDate(db.CompletionDate);
} }

View File

@ -27,6 +27,7 @@ import { contributeToVaultController } from "@/src/controllers/api/contributeToV
import { createAllianceController } from "@/src/controllers/api/createAllianceController"; import { createAllianceController } from "@/src/controllers/api/createAllianceController";
import { createGuildController } from "@/src/controllers/api/createGuildController"; import { createGuildController } from "@/src/controllers/api/createGuildController";
import { creditsController } from "@/src/controllers/api/creditsController"; import { creditsController } from "@/src/controllers/api/creditsController";
import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController";
import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController"; import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController";
import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController"; import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController";
import { declineAllianceInviteController } from "@/src/controllers/api/declineAllianceInviteController"; import { declineAllianceInviteController } from "@/src/controllers/api/declineAllianceInviteController";
@ -218,6 +219,7 @@ apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentContro
apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/contributeToVault.php", contributeToVaultController);
apiRouter.post("/createAlliance.php", createAllianceController); apiRouter.post("/createAlliance.php", createAllianceController);
apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/createGuild.php", createGuildController);
apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController);
apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController); apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController);
apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController); apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController);
apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController); apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController);

View File

@ -426,7 +426,7 @@ export const addItem = async (
); );
} }
inventoryChanges = { inventoryChanges = {
...addCrewShipWeaponSkin(inventory, typeName), ...addCrewShipWeaponSkin(inventory, typeName, undefined),
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, premiumPurchase) ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, premiumPurchase)
}; };
} }
@ -1107,12 +1107,14 @@ export const addSkin = (
return inventoryChanges; return inventoryChanges;
}; };
const addCrewShipWeaponSkin = ( export const addCrewShipWeaponSkin = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
typeName: string, typeName: string,
upgradeFingerprint: string | undefined,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): 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 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
inventoryChanges.CrewShipWeaponSkins ??= []; inventoryChanges.CrewShipWeaponSkins ??= [];
(inventoryChanges.CrewShipWeaponSkins as IUpgradeClient[]).push( (inventoryChanges.CrewShipWeaponSkins as IUpgradeClient[]).push(
@ -1121,6 +1123,22 @@ const addCrewShipWeaponSkin = (
return inventoryChanges; 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<IUpgradeClient>()
);
return inventoryChanges;
};
const addCrewShip = ( const addCrewShip = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
typeName: string, typeName: string,

View File

@ -947,15 +947,17 @@ export interface IPersonalTechProjectDatabase {
State: number; State: number;
ReqCredits: number; ReqCredits: number;
ItemType: string; ItemType: string;
ProductCategory?: string;
CategoryItemId?: Types.ObjectId;
ReqItems: ITypeCount[]; ReqItems: ITypeCount[];
HasContributions?: boolean; HasContributions?: boolean;
CompletionDate?: Date; CompletionDate?: Date;
} }
export interface IPersonalTechProjectClient extends Omit<IPersonalTechProjectDatabase, "CompletionDate"> { export interface IPersonalTechProjectClient
CompletionDate?: IMongoDate; extends Omit<IPersonalTechProjectDatabase, "CategoryItemId" | "CompletionDate"> {
ProductCategory?: string;
CategoryItemId?: IOid; CategoryItemId?: IOid;
CompletionDate?: IMongoDate;
ItemId: IOid; ItemId: IOid;
} }

View File

@ -43,6 +43,7 @@ export type IInventoryChanges = {
Drones?: IDroneClient[]; Drones?: IDroneClient[];
MiscItems?: IMiscItem[]; MiscItems?: IMiscItem[];
EmailItems?: ITypeCount[]; EmailItems?: ITypeCount[];
CrewShipRawSalvage?: ITypeCount[];
Nemesis?: Partial<INemesisClient>; Nemesis?: Partial<INemesisClient>;
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0 NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0 RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0