From ca4fce8a46a6dd6e6da9b3be144380ccd2ed652d Mon Sep 17 00:00:00 2001 From: Sainan Date: Tue, 4 Mar 2025 19:11:04 +0100 Subject: [PATCH] feat: place decos & contribute resources for their construction --- .../contributeToDojoComponentController.ts | 149 +++++++++++------- .../api/placeDecoInComponentController.ts | 38 +++++ .../api/startDojoRecipeController.ts | 3 +- src/models/guildModel.ts | 18 ++- src/routes/api.ts | 2 + src/services/guildService.ts | 16 +- src/types/guildTypes.ts | 25 ++- 7 files changed, 181 insertions(+), 70 deletions(-) create mode 100644 src/controllers/api/placeDecoInComponentController.ts diff --git a/src/controllers/api/contributeToDojoComponentController.ts b/src/controllers/api/contributeToDojoComponentController.ts index 7496ea0b..201c2050 100644 --- a/src/controllers/api/contributeToDojoComponentController.ts +++ b/src/controllers/api/contributeToDojoComponentController.ts @@ -1,10 +1,24 @@ +import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService"; import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; -import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { ExportDojoRecipes, IDojoRecipe } from "warframe-public-export-plus"; + +interface IContributeToDojoComponentRequest { + ComponentId: string; + DecoId?: string; + DecoType?: string; + IngredientContributions: { + ItemType: string; + ItemCount: number; + }[]; + RegularCredits: number; + VaultIngredientContributions: []; + VaultCredits: number; +} export const contributeToDojoComponentController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -13,60 +27,21 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r // Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in. const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest; const component = guild.DojoComponents.id(request.ComponentId)!; + const inventoryChanges: IInventoryChanges = {}; - if (!component.CompletionTime) { - const componentMeta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!; - - component.RegularCredits ??= 0; - if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(componentMeta.price)) { - request.RegularCredits = scaleRequiredCount(componentMeta.price) - component.RegularCredits; + // Room is in "Collecting Materials" state + if (request.DecoId) { + throw new Error("attempt to contribute to a deco in an unfinished room?!"); } - component.RegularCredits += request.RegularCredits; - inventoryChanges.RegularCredits = -request.RegularCredits; - updateCurrency(inventory, request.RegularCredits, false); - - component.MiscItems ??= []; - const miscItemChanges: IMiscItem[] = []; - for (const ingredientContribution of request.IngredientContributions) { - const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType); - if (componentMiscItem) { - const ingredientMeta = componentMeta.ingredients.find( - x => x.ItemType == ingredientContribution.ItemType - )!; - if ( - componentMiscItem.ItemCount + ingredientContribution.ItemCount > - scaleRequiredCount(ingredientMeta.ItemCount) - ) { - ingredientContribution.ItemCount = - scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount; - } - componentMiscItem.ItemCount += ingredientContribution.ItemCount; - } else { - component.MiscItems.push(ingredientContribution); - } - miscItemChanges.push({ - ItemType: ingredientContribution.ItemType, - ItemCount: ingredientContribution.ItemCount * -1 - }); - } - addMiscItems(inventory, miscItemChanges); - inventoryChanges.MiscItems = miscItemChanges; - - if (component.RegularCredits >= scaleRequiredCount(componentMeta.price)) { - let fullyFunded = true; - for (const ingredient of componentMeta.ingredients) { - const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType); - if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) { - fullyFunded = false; - break; - } - } - if (fullyFunded) { - component.RegularCredits = undefined; - component.MiscItems = undefined; - component.CompletionTime = new Date(Date.now() + componentMeta.time * 1000); - } + const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!; + processContribution(request, inventory, inventoryChanges, meta, component); + } else { + // Room is past "Collecting Materials" + if (request.DecoId) { + const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!; + const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!; + processContribution(request, inventory, inventoryChanges, meta, deco); } } @@ -78,13 +53,65 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r }); }; -export interface IContributeToDojoComponentRequest { - ComponentId: string; - IngredientContributions: { - ItemType: string; - ItemCount: number; - }[]; - RegularCredits: number; - VaultIngredientContributions: []; - VaultCredits: number; +interface IDojoContributable { + RegularCredits?: number; + MiscItems?: IMiscItem[]; + CompletionTime?: Date; } + +const processContribution = ( + request: IContributeToDojoComponentRequest, + inventory: TInventoryDatabaseDocument, + inventoryChanges: IInventoryChanges, + meta: IDojoRecipe, + component: IDojoContributable +): void => { + component.RegularCredits ??= 0; + if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(meta.price)) { + request.RegularCredits = scaleRequiredCount(meta.price) - component.RegularCredits; + } + component.RegularCredits += request.RegularCredits; + inventoryChanges.RegularCredits = -request.RegularCredits; + updateCurrency(inventory, request.RegularCredits, false); + + component.MiscItems ??= []; + const miscItemChanges: IMiscItem[] = []; + for (const ingredientContribution of request.IngredientContributions) { + const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType); + if (componentMiscItem) { + const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!; + if ( + componentMiscItem.ItemCount + ingredientContribution.ItemCount > + scaleRequiredCount(ingredientMeta.ItemCount) + ) { + ingredientContribution.ItemCount = + scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount; + } + componentMiscItem.ItemCount += ingredientContribution.ItemCount; + } else { + component.MiscItems.push(ingredientContribution); + } + miscItemChanges.push({ + ItemType: ingredientContribution.ItemType, + ItemCount: ingredientContribution.ItemCount * -1 + }); + } + addMiscItems(inventory, miscItemChanges); + inventoryChanges.MiscItems = miscItemChanges; + + if (component.RegularCredits >= scaleRequiredCount(meta.price)) { + let fullyFunded = true; + for (const ingredient of meta.ingredients) { + const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType); + if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) { + fullyFunded = false; + break; + } + } + if (fullyFunded) { + component.RegularCredits = undefined; + component.MiscItems = undefined; + component.CompletionTime = new Date(Date.now() + meta.time * 1000); + } + } +}; diff --git a/src/controllers/api/placeDecoInComponentController.ts b/src/controllers/api/placeDecoInComponentController.ts new file mode 100644 index 00000000..7ace69ca --- /dev/null +++ b/src/controllers/api/placeDecoInComponentController.ts @@ -0,0 +1,38 @@ +import { getDojoClient, getGuildForRequest } from "@/src/services/guildService"; +import { RequestHandler } from "express"; +import { Types } from "mongoose"; +import { ExportDojoRecipes } from "warframe-public-export-plus"; + +export const placeDecoInComponentController: RequestHandler = async (req, res) => { + const guild = await getGuildForRequest(req); + const request = JSON.parse(String(req.body)) as IPlaceDecoInComponentRequest; + // At this point, we know that a member of the guild is making this request. Assuming they are allowed to place decorations. + const component = guild.DojoComponents.id(request.ComponentId)!; + + if (component.DecoCapacity === undefined) { + component.DecoCapacity = Object.values(ExportDojoRecipes.rooms).find( + x => x.resultType == component.pf + )!.decoCapacity; + } + + component.Decos ??= []; + component.Decos.push({ + _id: new Types.ObjectId(), + Type: request.Type, + Pos: request.Pos, + Rot: request.Rot, + Name: request.Name + }); + + await guild.save(); + res.json(getDojoClient(guild, 0)); // TODO: This response format seems incorrect. +}; + +interface IPlaceDecoInComponentRequest { + ComponentId: string; + Revision: number; + Type: string; + Pos: number[]; + Rot: number[]; + Name?: string; +} diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index 96a1e99c..0a0dfc66 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -29,7 +29,8 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { ppf: request.PlacedComponent.ppf, pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid), op: request.PlacedComponent.op, - pp: request.PlacedComponent.pp + pp: request.PlacedComponent.pp, + DecoCapacity: room?.decoCapacity }) - 1 ]; if (config.noDojoRoomBuildStage) { diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 417eeb7e..b3f47e04 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -2,12 +2,23 @@ import { IGuildDatabase, IDojoComponentDatabase, ITechProjectDatabase, - ITechProjectClient + ITechProjectClient, + IDojoDecoDatabase } from "@/src/types/guildTypes"; import { Document, Model, model, Schema, Types } from "mongoose"; import { typeCountSchema } from "./inventoryModels/inventoryModel"; import { toMongoDate } from "../helpers/inventoryHelpers"; +const dojoDecoSchema = new Schema({ + Type: String, + Pos: [Number], + Rot: [Number], + Name: String, + RegularCredits: Number, + MiscItems: { type: [typeCountSchema], default: undefined }, + CompletionTime: Date +}); + const dojoComponentSchema = new Schema({ pf: { type: String, required: true }, ppf: String, @@ -18,7 +29,10 @@ const dojoComponentSchema = new Schema({ Message: String, RegularCredits: Number, MiscItems: { type: [typeCountSchema], default: undefined }, - CompletionTime: Date + CompletionTime: Date, + DestructionTime: Date, + Decos: [dojoDecoSchema], + DecoCapacity: Number }); const techProjectSchema = new Schema( diff --git a/src/routes/api.ts b/src/routes/api.ts index 3437d665..15ed59ee 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -59,6 +59,7 @@ import { missionInventoryUpdateController } from "@/src/controllers/api/missionI import { modularWeaponCraftingController } from "@/src/controllers/api/modularWeaponCraftingController"; import { modularWeaponSaleController } from "@/src/controllers/api/modularWeaponSaleController"; import { nameWeaponController } from "@/src/controllers/api/nameWeaponController"; +import { placeDecoInComponentController } from "@/src/controllers/api/placeDecoInComponentController"; import { playerSkillsController } from "@/src/controllers/api/playerSkillsController"; import { projectionManagerController } from "@/src/controllers/api/projectionManagerController"; import { purchaseController } from "@/src/controllers/api/purchaseController"; @@ -175,6 +176,7 @@ apiRouter.post("/login.php", loginController); apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController); apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController); apiRouter.post("/nameWeapon.php", nameWeaponController); +apiRouter.post("/placeDecoInComponent.php", placeDecoInComponentController); apiRouter.post("/playerSkills.php", playerSkillsController); apiRouter.post("/projectionManager.php", projectionManagerController); apiRouter.post("/purchase.php", purchaseController); diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 5a4658e1..aabc51ef 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -53,7 +53,7 @@ export const getDojoClient = ( ppf: dojoComponent.ppf, Name: dojoComponent.Name, Message: dojoComponent.Message, - DecoCapacity: 600 + DecoCapacity: dojoComponent.DecoCapacity ?? 600 }; if (dojoComponent.pi) { clientComponent.pi = toOid(dojoComponent.pi); @@ -66,6 +66,20 @@ export const getDojoClient = ( clientComponent.RegularCredits = dojoComponent.RegularCredits; clientComponent.MiscItems = dojoComponent.MiscItems; } + if (dojoComponent.Decos) { + clientComponent.Decos = []; + for (const deco of dojoComponent.Decos) { + clientComponent.Decos.push({ + id: toOid(deco._id), + Type: deco.Type, + Pos: deco.Pos, + Rot: deco.Rot, + CompletionTime: deco.CompletionTime ? toMongoDate(deco.CompletionTime) : undefined, + RegularCredits: deco.RegularCredits, + MiscItems: deco.MiscItems + }); + } + } dojo.DojoComponents.push(clientComponent); } }); diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 4b909708..50ff178a 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -41,18 +41,33 @@ export interface IDojoComponentClient { CompletionTime?: IMongoDate; RushPlatinum?: number; DestructionTime?: IMongoDate; + Decos?: IDojoDecoClient[]; DecoCapacity?: number; } export interface IDojoComponentDatabase - extends Omit< - IDojoComponentClient, - "id" | "pi" | "CompletionTime" | "RushPlatinum" | "DestructionTime" | "DecoCapacity" - > { + extends Omit { _id: Types.ObjectId; pi?: Types.ObjectId; CompletionTime?: Date; - //DestructionTime?: Date; + DestructionTime?: Date; + Decos?: IDojoDecoDatabase[]; +} + +export interface IDojoDecoClient { + id: IOid; + Type: string; + Pos: number[]; + Rot: number[]; + Name?: string; // for teleporters + RegularCredits?: number; + MiscItems?: IMiscItem[]; + CompletionTime?: IMongoDate; +} + +export interface IDojoDecoDatabase extends Omit { + _id: Types.ObjectId; + CompletionTime?: Date; } export interface ITechProjectClient {