From be59a631db81fe6871c1c31794e50e83c6a8b46c Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 01:37:07 +0100 Subject: [PATCH 1/6] chore: move ICreateGuildRequest to createGuildController --- src/controllers/api/createGuildController.ts | 7 ++++--- src/types/guildTypes.ts | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 2e73d7a2..2c698995 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -3,9 +3,8 @@ import { getAccountIdForRequest } from "@/src/services/loginService"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Guild } from "@/src/models/guildModel"; -import { ICreateGuildRequest } from "@/src/types/guildTypes"; -const createGuildController: RequestHandler = async (req, res) => { +export const createGuildController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest; @@ -34,4 +33,6 @@ const createGuildController: RequestHandler = async (req, res) => { res.json(guild); }; -export { createGuildController }; +interface ICreateGuildRequest { + guildName: string; +} diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 54f87c98..edd7aa0c 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -13,10 +13,6 @@ export interface IGuildDatabase extends IGuild { DojoEnergy: number; } -export interface ICreateGuildRequest { - guildName: string; -} - export interface IDojoClient { _id: IOid; // ID of the guild Name: string; -- 2.47.2 From 9042d8b265debe792bdb093167121fcd6c69b25b Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 02:28:20 +0100 Subject: [PATCH 2/6] feat: dojo research --- src/controllers/api/guildTechController.ts | 91 +++++++++++++++++++- src/models/guildModel.ts | 34 +++++++- src/models/inventoryModels/inventoryModel.ts | 2 +- src/services/guildService.ts | 5 ++ src/types/guildTypes.ts | 13 +++ 5 files changed, 140 insertions(+), 5 deletions(-) diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index 9e4243da..951291c0 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -1,5 +1,92 @@ import { RequestHandler } from "express"; +import { getGuildForRequestEx } from "@/src/services/guildService"; +import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService"; +import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; -export const guildTechController: RequestHandler = (_req, res) => { - res.status(500).end(); // This is what I got for a fresh clan. +export const guildTechController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const guild = await getGuildForRequestEx(req, inventory); + const data = JSON.parse(String(req.body)) as TGuildTechRequest; + if (data.Action == "Sync") { + res.json({ + TechProjects: guild.toJSON().TechProjects + }); + } else if (data.Action == "Start") { + const recipe = ExportDojoRecipes.research[data.RecipeType!]; + guild.TechProjects ??= []; + guild.TechProjects.push({ + ItemType: data.RecipeType!, + ReqCredits: scaleRequiredCount(recipe.price), + ReqItems: recipe.ingredients.map(x => ({ + ItemType: x.ItemType, + ItemCount: scaleRequiredCount(x.ItemCount) + })), + State: 0 + }); + await guild.save(); + res.end(); + } else if (data.Action == "Contribute") { + const contributions = data as IGuildTechContributeFields; + const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!; + techProject.ReqCredits -= contributions.RegularCredits; + const miscItemChanges = []; + for (const miscItem of contributions.MiscItems) { + const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType); + if (reqItem) { + reqItem.ItemCount -= miscItem.ItemCount; + miscItemChanges.push({ + ItemType: miscItem.ItemType, + ItemCount: miscItem.ItemCount * -1 + }); + } + } + addMiscItems(inventory, miscItemChanges); + const inventoryChanges: IInventoryChanges = { + ...updateCurrency(inventory, contributions.RegularCredits, false), + MiscItems: miscItemChanges + }; + + if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) { + // This research is now fully funded. + techProject.State = 1; + const recipe = ExportDojoRecipes.research[data.RecipeType!]; + techProject.CompletionDate = new Date(new Date().getTime() + recipe.time * 1000); + } + + await guild.save(); + await inventory.save(); + res.json({ + InventoryChanges: inventoryChanges + }); + } else { + throw new Error(`unknown guildTech action: ${data.Action}`); + } +}; + +type TGuildTechRequest = { + Action: string; +} & Partial & + Partial; + +interface IGuildTechStartFields { + Mode: "Guild"; + RecipeType: string; +} + +interface IGuildTechContributeFields { + ResearchId: ""; + RecipeType: string; + RegularCredits: number; + MiscItems: IMiscItem[]; + VaultCredits: number; + VaultMiscItems: IMiscItem[]; +} + +const scaleRequiredCount = (count: number): number => { + // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans. + return Math.max(1, Math.trunc(count / 100)); }; diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 22269ec8..ab87e719 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -1,5 +1,12 @@ -import { IGuildDatabase, IDojoComponentDatabase } from "@/src/types/guildTypes"; +import { + IGuildDatabase, + IDojoComponentDatabase, + ITechProjectDatabase, + ITechProjectClient +} from "@/src/types/guildTypes"; import { model, Schema } from "mongoose"; +import { typeCountSchema } from "./inventoryModels/inventoryModel"; +import { toMongoDate } from "../helpers/inventoryHelpers"; const dojoComponentSchema = new Schema({ pf: { type: String, required: true }, @@ -10,12 +17,35 @@ const dojoComponentSchema = new Schema({ CompletionTime: Date }); +const techProjectSchema = new Schema( + { + ItemType: String, + ReqCredits: Number, + ReqItems: [typeCountSchema], + State: Number, + CompletionDate: Date + }, + { _id: false } +); + +techProjectSchema.set("toJSON", { + virtuals: true, + transform(_doc, obj) { + const db = obj as ITechProjectDatabase; + const client = obj as ITechProjectClient; + if (db.CompletionDate) { + client.CompletionDate = toMongoDate(db.CompletionDate); + } + } +}); + const guildSchema = new Schema( { Name: { type: String, required: true }, DojoComponents: [dojoComponentSchema], DojoCapacity: { type: Number, default: 100 }, - DojoEnergy: { type: Number, default: 5 } + DojoEnergy: { type: Number, default: 5 }, + TechProjects: { type: [techProjectSchema], default: undefined } }, { id: false } ); diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 5e5a1680..868a2491 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -61,7 +61,7 @@ import { import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { EquipmentSelectionSchema } from "./loadoutModel"; -const typeCountSchema = new Schema({ ItemType: String, ItemCount: Number }, { _id: false }); +export const typeCountSchema = new Schema({ ItemType: String, ItemCount: Number }, { _id: false }); const focusXPSchema = new Schema( { diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 35b697fc..250408b9 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -2,10 +2,15 @@ import { Request } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { getInventory } from "@/src/services/inventoryService"; import { Guild } from "@/src/models/guildModel"; +import { IInventoryDatabaseDocument } from "../types/inventoryTypes/inventoryTypes"; export const getGuildForRequest = async (req: Request) => { const accountId = await getAccountIdForRequest(req); const inventory = await getInventory(accountId); + return await getGuildForRequestEx(req, inventory); +}; + +export const getGuildForRequestEx = async (req: Request, inventory: IInventoryDatabaseDocument) => { const guildId = req.query.guildId as string; if (!inventory.GuildId || inventory.GuildId.toString() != guildId) { throw new Error("Account is not in the guild that it has sent a request for"); diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index edd7aa0c..1778582f 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -11,6 +11,7 @@ export interface IGuildDatabase extends IGuild { DojoComponents?: IDojoComponentDatabase[]; DojoCapacity: number; DojoEnergy: number; + TechProjects?: ITechProjectDatabase[]; } export interface IDojoClient { @@ -45,3 +46,15 @@ export interface IDojoComponentDatabase pi?: Types.ObjectId; CompletionTime?: Date; } + +export interface ITechProjectClient { + ItemType: string; + ReqCredits: number; + ReqItems: IMiscItem[]; + State: number; // 0 = pending, 1 = complete + CompletionDate?: IMongoDate; +} + +export interface ITechProjectDatabase extends Omit { + CompletionDate?: Date; +} -- 2.47.2 From bf6c6a522a7c180a286457a95b9d2b5906edb6bd Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 02:34:24 +0100 Subject: [PATCH 3/6] fix typo --- src/services/guildService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 250408b9..dda01ef8 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -17,7 +17,7 @@ export const getGuildForRequestEx = async (req: Request, inventory: IInventoryDa } const guild = await Guild.findOne({ _id: guildId }); if (!guild) { - throw new Error("Account thinks it is a in guild that doesn't exist"); + throw new Error("Account thinks it is in a guild that doesn't exist"); } return guild; }; -- 2.47.2 From 5cad6fe7d9f025f052987f345719f4d752e35944 Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 02:36:38 +0100 Subject: [PATCH 4/6] chore: change IMiscItem to be an alias of ITypeCount --- src/types/inventoryTypes/inventoryTypes.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 94a46650..53c4d121 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -460,10 +460,7 @@ export interface IFlavourItem { ItemType: string; } -export interface IMiscItem { - ItemCount: number; - ItemType: string; -} +export type IMiscItem = ITypeCount; export interface ICrewShipWeapon { PILOT: ICrewShipPilotWeapon; -- 2.47.2 From f37e6dc668fcde6907797d2cb430416a0a2092d5 Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 02:38:46 +0100 Subject: [PATCH 5/6] avoid double-starting research --- src/controllers/api/guildTechController.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index 951291c0..ff7f78f3 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -18,15 +18,17 @@ export const guildTechController: RequestHandler = async (req, res) => { } else if (data.Action == "Start") { const recipe = ExportDojoRecipes.research[data.RecipeType!]; guild.TechProjects ??= []; - guild.TechProjects.push({ - ItemType: data.RecipeType!, - ReqCredits: scaleRequiredCount(recipe.price), - ReqItems: recipe.ingredients.map(x => ({ - ItemType: x.ItemType, - ItemCount: scaleRequiredCount(x.ItemCount) - })), - State: 0 - }); + if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) { + guild.TechProjects.push({ + ItemType: data.RecipeType!, + ReqCredits: scaleRequiredCount(recipe.price), + ReqItems: recipe.ingredients.map(x => ({ + ItemType: x.ItemType, + ItemCount: scaleRequiredCount(x.ItemCount) + })), + State: 0 + }); + } await guild.save(); res.end(); } else if (data.Action == "Contribute") { -- 2.47.2 From 3a84781945a33bb817d700c502bc03893a90091a Mon Sep 17 00:00:00 2001 From: Sainan Date: Fri, 3 Jan 2025 02:43:24 +0100 Subject: [PATCH 6/6] avoid over-contributing to research --- src/controllers/api/guildTechController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index ff7f78f3..7d27267b 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -34,11 +34,17 @@ export const guildTechController: RequestHandler = async (req, res) => { } else if (data.Action == "Contribute") { const contributions = data as IGuildTechContributeFields; const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!; + if (contributions.RegularCredits > techProject.ReqCredits) { + contributions.RegularCredits = techProject.ReqCredits; + } techProject.ReqCredits -= contributions.RegularCredits; const miscItemChanges = []; for (const miscItem of contributions.MiscItems) { 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; miscItemChanges.push({ ItemType: miscItem.ItemType, -- 2.47.2