diff --git a/src/controllers/api/contributeToDojoComponentController.ts b/src/controllers/api/contributeToDojoComponentController.ts index 40c6b59ff..5aa748554 100644 --- a/src/controllers/api/contributeToDojoComponentController.ts +++ b/src/controllers/api/contributeToDojoComponentController.ts @@ -13,12 +13,9 @@ interface IContributeToDojoComponentRequest { ComponentId: string; DecoId?: string; DecoType?: string; - IngredientContributions: { - ItemType: string; - ItemCount: number; - }[]; + IngredientContributions: IMiscItem[]; RegularCredits: number; - VaultIngredientContributions: []; + VaultIngredientContributions: IMiscItem[]; VaultCredits: number; } @@ -37,13 +34,13 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r throw new Error("attempt to contribute to a deco in an unfinished room?!"); } const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!; - await processContribution(guild, request, inventory, inventoryChanges, meta, component); + processContribution(guild, 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)!; - await processContribution(guild, request, inventory, inventoryChanges, meta, deco); + processContribution(guild, request, inventory, inventoryChanges, meta, deco); } } @@ -55,46 +52,76 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r }); }; -const processContribution = async ( +const processContribution = ( guild: TGuildDatabaseDocument, request: IContributeToDojoComponentRequest, inventory: TInventoryDatabaseDocument, inventoryChanges: IInventoryChanges, meta: IDojoRecipe, component: IDojoContributable -): Promise => { +): void => { component.RegularCredits ??= 0; - if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(meta.price)) { - request.RegularCredits = scaleRequiredCount(meta.price) - component.RegularCredits; + if (request.RegularCredits) { + component.RegularCredits += request.RegularCredits; + inventoryChanges.RegularCredits = -request.RegularCredits; + updateCurrency(inventory, request.RegularCredits, false); + } + if (request.VaultCredits) { + component.RegularCredits += request.VaultCredits; + guild.VaultRegularCredits! -= request.VaultCredits; + } + if (component.RegularCredits > scaleRequiredCount(meta.price)) { + guild.VaultRegularCredits ??= 0; + guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price); + component.RegularCredits = scaleRequiredCount(meta.price); } - 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; + if (request.VaultIngredientContributions.length) { + for (const ingredientContribution of request.VaultIngredientContributions) { + 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); } - componentMiscItem.ItemCount += ingredientContribution.ItemCount; - } else { - component.MiscItems.push(ingredientContribution); + const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == ingredientContribution.ItemType)!; + vaultMiscItem.ItemCount -= ingredientContribution.ItemCount; } - miscItemChanges.push({ - ItemType: ingredientContribution.ItemType, - ItemCount: ingredientContribution.ItemCount * -1 - }); } - addMiscItems(inventory, miscItemChanges); - inventoryChanges.MiscItems = miscItemChanges; + if (request.IngredientContributions.length) { + 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; @@ -106,12 +133,6 @@ const processContribution = async ( } } if (fullyFunded) { - if (request.IngredientContributions.length) { - // We've already updated subpaths of MiscItems, we need to allow MongoDB to save this before we remove MiscItems. - await guild.save(); - } - component.RegularCredits = undefined; - component.MiscItems = undefined; component.CompletionTime = new Date(Date.now() + meta.time * 1000); } } diff --git a/src/controllers/api/contributeToVaultController.ts b/src/controllers/api/contributeToVaultController.ts new file mode 100644 index 000000000..dd8b18fae --- /dev/null +++ b/src/controllers/api/contributeToVaultController.ts @@ -0,0 +1,49 @@ +import { getGuildForRequestEx } from "@/src/services/guildService"; +import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; +import { RequestHandler } from "express"; + +export const contributeToVaultController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const guild = await getGuildForRequestEx(req, inventory); + const request = JSON.parse(String(req.body)) as IContributeToVaultRequest; + + if (request.RegularCredits) { + guild.VaultRegularCredits ??= 0; + guild.VaultRegularCredits += request.RegularCredits; + } + if (request.MiscItems.length) { + guild.VaultMiscItems ??= []; + for (const item of request.MiscItems) { + guild.VaultMiscItems.push(item); + addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); + } + } + if (request.ShipDecorations.length) { + guild.VaultShipDecorations ??= []; + for (const item of request.ShipDecorations) { + guild.VaultShipDecorations.push(item); + addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); + } + } + if (request.FusionTreasures.length) { + guild.VaultFusionTreasures ??= []; + for (const item of request.FusionTreasures) { + guild.VaultFusionTreasures.push(item); + addFusionTreasures(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]); + } + } + + await guild.save(); + await inventory.save(); + res.end(); +}; + +interface IContributeToVaultRequest { + RegularCredits: number; + MiscItems: IMiscItem[]; + ShipDecorations: ITypeCount[]; + FusionTreasures: IFusionTreasure[]; +} diff --git a/src/controllers/api/dojoComponentRushController.ts b/src/controllers/api/dojoComponentRushController.ts index a19cfa7ef..f15bce6c5 100644 --- a/src/controllers/api/dojoComponentRushController.ts +++ b/src/controllers/api/dojoComponentRushController.ts @@ -47,4 +47,6 @@ const processContribution = (component: IDojoContributable, meta: IDojoRecipe, p component.CompletionTime = new Date( component.CompletionTime!.getTime() - secondsPerPlatinum * platinumDonated * 1000 ); + component.RushPlatinum ??= 0; + component.RushPlatinum += platinumDonated; }; diff --git a/src/controllers/api/getGuildController.ts b/src/controllers/api/getGuildController.ts index d112f9960..6703d87c0 100644 --- a/src/controllers/api/getGuildController.ts +++ b/src/controllers/api/getGuildController.ts @@ -3,6 +3,7 @@ import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Guild } from "@/src/models/guildModel"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { toOid } from "@/src/helpers/inventoryHelpers"; +import { getGuildVault } from "@/src/services/guildService"; const getGuildController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -62,7 +63,8 @@ const getGuildController: RequestHandler = async (req, res) => { Permissions: 4096 } ], - Tier: 1 + Tier: 1, + Vault: getGuildVault(guild) }); return; } diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index d7cb99adf..a5a080b1d 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -1,5 +1,5 @@ import { RequestHandler } from "express"; -import { getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService"; +import { getGuildForRequestEx, getGuildVault, scaleRequiredCount } from "@/src/services/guildService"; import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService"; @@ -43,10 +43,35 @@ export const guildTechController: RequestHandler = async (req, res) => { } else if (action == "Contribute") { const contributions = data as IGuildTechContributeFields; const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!; + + if (contributions.VaultCredits) { + if (contributions.VaultCredits > techProject.ReqCredits) { + contributions.VaultCredits = techProject.ReqCredits; + } + techProject.ReqCredits -= contributions.VaultCredits; + guild.VaultRegularCredits! -= contributions.VaultCredits; + } + if (contributions.RegularCredits > techProject.ReqCredits) { contributions.RegularCredits = techProject.ReqCredits; } techProject.ReqCredits -= contributions.RegularCredits; + + if (contributions.VaultMiscItems.length) { + for (const miscItem of contributions.VaultMiscItems) { + const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType); + if (reqItem) { + if (miscItem.ItemCount > reqItem.ItemCount) { + miscItem.ItemCount = reqItem.ItemCount; + } + reqItem.ItemCount -= miscItem.ItemCount; + + const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!; + vaultMiscItem.ItemCount -= miscItem.ItemCount; + } + } + } + const miscItemChanges = []; for (const miscItem of contributions.MiscItems) { const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType); @@ -74,7 +99,8 @@ export const guildTechController: RequestHandler = async (req, res) => { await guild.save(); await inventory.save(); res.json({ - InventoryChanges: inventoryChanges + InventoryChanges: inventoryChanges, + Vault: getGuildVault(guild) }); } else if (action == "Buy") { const purchase = data as IGuildTechBuyFields; diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index b3f47e043..4fdbc2232 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -6,7 +6,7 @@ import { IDojoDecoDatabase } from "@/src/types/guildTypes"; import { Document, Model, model, Schema, Types } from "mongoose"; -import { typeCountSchema } from "./inventoryModels/inventoryModel"; +import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel"; import { toMongoDate } from "../helpers/inventoryHelpers"; const dojoDecoSchema = new Schema({ @@ -16,7 +16,8 @@ const dojoDecoSchema = new Schema({ Name: String, RegularCredits: Number, MiscItems: { type: [typeCountSchema], default: undefined }, - CompletionTime: Date + CompletionTime: Date, + RushPlatinum: Number }); const dojoComponentSchema = new Schema({ @@ -30,6 +31,7 @@ const dojoComponentSchema = new Schema({ RegularCredits: Number, MiscItems: { type: [typeCountSchema], default: undefined }, CompletionTime: Date, + RushPlatinum: Number, DestructionTime: Date, Decos: [dojoDecoSchema], DecoCapacity: Number @@ -63,6 +65,11 @@ const guildSchema = new Schema( DojoComponents: { type: [dojoComponentSchema], default: [] }, DojoCapacity: { type: Number, default: 100 }, DojoEnergy: { type: Number, default: 5 }, + VaultRegularCredits: Number, + VaultPremiumCredits: Number, + VaultMiscItems: { type: [typeCountSchema], default: undefined }, + VaultShipDecorations: { type: [typeCountSchema], default: undefined }, + VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined }, TechProjects: { type: [techProjectSchema], default: undefined } }, { id: false } diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 2aa16bb90..04c2acedc 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -552,7 +552,7 @@ questKeysSchema.set("toJSON", { } }); -const fusionTreasuresSchema = new Schema().add(typeCountSchema).add({ Sockets: Number }); +export const fusionTreasuresSchema = new Schema().add(typeCountSchema).add({ Sockets: Number }); const spectreLoadoutsSchema = new Schema( { diff --git a/src/routes/api.ts b/src/routes/api.ts index dcc9fddcb..2ad923d67 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -14,6 +14,7 @@ import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/cla import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController"; import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController"; +import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController"; import { createGuildController } from "@/src/controllers/api/createGuildController"; import { creditsController } from "@/src/controllers/api/creditsController"; import { deleteSessionController } from "@/src/controllers/api/deleteSessionController"; @@ -153,6 +154,7 @@ apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController); apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController); +apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/destroyDojoDeco.php", destroyDojoDecoController); apiRouter.post("/dojoComponentRush.php", dojoComponentRushController); diff --git a/src/services/guildService.ts b/src/services/guildService.ts index bc710c0a0..cd877835e 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -3,7 +3,13 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory } from "@/src/services/inventoryService"; import { Guild, TGuildDatabaseDocument } from "@/src/models/guildModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; -import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes"; +import { + IDojoClient, + IDojoComponentClient, + IDojoContributable, + IDojoDecoClient, + IGuildVault +} from "@/src/types/guildTypes"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; @@ -30,6 +36,16 @@ export const getGuildForRequestEx = async ( return guild; }; +export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => { + return { + DojoRefundRegularCredits: guild.VaultRegularCredits, + DojoRefundMiscItems: guild.VaultMiscItems, + DojoRefundPremiumCredits: guild.VaultPremiumCredits, + ShipDecorations: guild.VaultShipDecorations, + FusionTreasures: guild.VaultFusionTreasures + }; +}; + export const getDojoClient = async ( guild: TGuildDatabaseDocument, status: number, @@ -41,6 +57,7 @@ export const getDojoClient = async ( Tier: 1, FixedContributions: true, DojoRevision: 1, + Vault: getGuildVault(guild), RevisionTime: Math.round(Date.now() / 1000), Energy: guild.DojoEnergy, Capacity: guild.DojoCapacity, @@ -79,15 +96,20 @@ export const getDojoClient = async ( if (dojoComponent.Decos) { clientComponent.Decos = []; for (const deco of dojoComponent.Decos) { - clientComponent.Decos.push({ + const clientDeco: IDojoDecoClient = { 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 - }); + Name: deco.Name + }; + if (deco.CompletionTime) { + clientDeco.CompletionTime = toMongoDate(deco.CompletionTime); + } else { + clientDeco.RegularCredits = deco.RegularCredits; + clientDeco.MiscItems = deco.MiscItems; + } + clientComponent.Decos.push(clientDeco); } } dojo.DojoComponents.push(clientComponent); @@ -118,7 +140,8 @@ export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: Types guild.DojoCapacity -= meta.capacity; guild.DojoEnergy -= meta.energy; } - // TODO: Add resources spent to the clan vault + moveResourcesToVault(guild, component); + component.Decos?.forEach(deco => moveResourcesToVault(guild, deco)); }; export const removeDojoDeco = ( @@ -135,5 +158,27 @@ export const removeDojoDeco = ( if (meta && meta.capacityCost) { component.DecoCapacity! += meta.capacityCost; } - // TODO: Add resources spent to the clan vault + moveResourcesToVault(guild, deco); +}; + +const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => { + if (component.RegularCredits) { + guild.VaultRegularCredits ??= 0; + guild.VaultRegularCredits += component.RegularCredits; + } + if (component.MiscItems) { + guild.VaultMiscItems ??= []; + for (const componentMiscItem of component.MiscItems) { + const vaultMiscItem = guild.VaultMiscItems.find(x => x.ItemType == componentMiscItem.ItemType); + if (vaultMiscItem) { + vaultMiscItem.ItemCount += componentMiscItem.ItemCount; + } else { + guild.VaultMiscItems.push(componentMiscItem); + } + } + } + if (component.RushPlatinum) { + guild.VaultPremiumCredits ??= 0; + guild.VaultPremiumCredits += component.RushPlatinum; + } }; diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 176cfeeac..4802070f6 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -1,6 +1,6 @@ import { Types } from "mongoose"; import { IOid, IMongoDate } from "@/src/types/commonTypes"; -import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; export interface IGuild { Name: string; @@ -11,19 +11,38 @@ export interface IGuildDatabase extends IGuild { DojoComponents: IDojoComponentDatabase[]; DojoCapacity: number; DojoEnergy: number; + VaultRegularCredits?: number; + VaultPremiumCredits?: number; + VaultMiscItems?: IMiscItem[]; + VaultShipDecorations?: ITypeCount[]; + VaultFusionTreasures?: IFusionTreasure[]; TechProjects?: ITechProjectDatabase[]; } +export interface IGuildVault { + DojoRefundRegularCredits?: number; + DojoRefundMiscItems?: IMiscItem[]; + DojoRefundPremiumCredits?: number; + ShipDecorations?: ITypeCount[]; + FusionTreasures?: IFusionTreasure[]; + DecoRecipes?: ITypeCount[]; // Event Trophies +} + export interface IDojoClient { _id: IOid; // ID of the guild Name: string; Tier: number; FixedContributions: boolean; DojoRevision: number; + AllianceId?: IOid; + Vault?: IGuildVault; + Class?: number; // Level RevisionTime: number; Energy: number; Capacity: number; DojoRequestStatus: number; + ContentURL?: string; + GuildEmblem?: boolean; DojoComponents: IDojoComponentClient[]; } @@ -46,7 +65,7 @@ export interface IDojoComponentClient { } export interface IDojoComponentDatabase - extends Omit { + extends Omit { _id: Types.ObjectId; pi?: Types.ObjectId; CompletionTime?: Date; @@ -63,6 +82,7 @@ export interface IDojoDecoClient { RegularCredits?: number; MiscItems?: IMiscItem[]; CompletionTime?: IMongoDate; + RushPlatinum?: number; } export interface IDojoDecoDatabase extends Omit { @@ -70,6 +90,14 @@ export interface IDojoDecoDatabase extends Omit { CompletionDate?: Date; } - -export interface IDojoContributable { - RegularCredits?: number; - MiscItems?: IMiscItem[]; - CompletionTime?: Date; -}