From ab11f67f0bfb590d0c99ebf6ddc40abe64019c59 Mon Sep 17 00:00:00 2001 From: Sainan Date: Sun, 16 Mar 2025 04:32:11 -0700 Subject: [PATCH] feat: clan polychrome research (#1177) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1177 --- src/controllers/api/guildTechController.ts | 86 +++++++++++++++------- src/models/guildModel.ts | 1 + src/services/guildService.ts | 15 ++++ src/types/guildTypes.ts | 2 + 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index c51628af9..f9a2e9fb1 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -4,6 +4,7 @@ import { getGuildVault, hasAccessToDojo, hasGuildPermission, + removePigmentsFromGuildMembers, scaleRequiredCount } from "@/src/services/guildService"; import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus"; @@ -28,8 +29,7 @@ export const guildTechController: RequestHandler = async (req, res) => { const inventory = await getInventory(accountId); const guild = await getGuildForRequestEx(req, inventory); const data = JSON.parse(String(req.body)) as TGuildTechRequest; - const action = data.Action.split(",")[0]; - if (action == "Sync") { + if (data.Action == "Sync") { let needSave = false; const techProjects: ITechProjectClient[] = []; if (guild.TechProjects) { @@ -53,18 +53,18 @@ export const guildTechController: RequestHandler = async (req, res) => { await guild.save(); } res.json({ TechProjects: techProjects }); - } else if (action == "Start") { - if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { + } else if (data.Action == "Start") { + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { res.status(400).send("-1").end(); return; } - const recipe = ExportDojoRecipes.research[data.RecipeType!]; + const recipe = ExportDojoRecipes.research[data.RecipeType]; guild.TechProjects ??= []; if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) { const techProject = guild.TechProjects[ guild.TechProjects.push({ - ItemType: data.RecipeType!, + ItemType: data.RecipeType, ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price), ReqItems: recipe.ingredients.map(x => ({ ItemType: x.ItemType, @@ -76,16 +76,20 @@ export const guildTechController: RequestHandler = async (req, res) => { setTechLogState(guild, techProject.ItemType, 5); if (config.noDojoResearchCosts) { processFundedProject(guild, techProject, recipe); + } else { + if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") { + guild.ActiveDojoColorResearch = data.RecipeType; + } } } await guild.save(); res.end(); - } else if (action == "Contribute") { + } else if (data.Action == "Contribute") { if (!hasAccessToDojo(inventory)) { res.status(400).send("-1").end(); return; } - const contributions = data as IGuildTechContributeFields; + const contributions = data; const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!; if (contributions.VaultCredits) { @@ -136,8 +140,12 @@ export const guildTechController: RequestHandler = async (req, res) => { if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) { // This research is now fully funded. - const recipe = ExportDojoRecipes.research[data.RecipeType!]; + const recipe = ExportDojoRecipes.research[data.RecipeType]; processFundedProject(guild, techProject, recipe); + if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") { + guild.ActiveDojoColorResearch = ""; + await removePigmentsFromGuildMembers(guild._id); + } } await guild.save(); @@ -146,12 +154,12 @@ export const guildTechController: RequestHandler = async (req, res) => { InventoryChanges: inventoryChanges, Vault: getGuildVault(guild) }); - } else if (action == "Buy") { + } else if (data.Action.split(",")[0] == "Buy") { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { res.status(400).send("-1").end(); return; } - const purchase = data as IGuildTechBuyFields; + const purchase = data as IGuildTechBuyRequest; const quantity = parseInt(data.Action.split(",")[1]); const recipeChanges = [ { @@ -173,13 +181,12 @@ export const guildTechController: RequestHandler = async (req, res) => { Recipes: recipeChanges } }); - } else if (action == "Fabricate") { + } else if (data.Action == "Fabricate") { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { res.status(400).send("-1").end(); return; } - const payload = data as IGuildTechFabricateRequest; - const recipe = ExportDojoRecipes.fabrications[payload.RecipeType]; + const recipe = ExportDojoRecipes.fabrications[data.RecipeType]; const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false); inventoryChanges.MiscItems = recipe.ingredients.map(x => ({ ItemType: x.ItemType, @@ -190,6 +197,31 @@ export const guildTechController: RequestHandler = async (req, res) => { await inventory.save(); // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. res.json({ inventoryChanges: inventoryChanges }); + } else if (data.Action == "Pause") { + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { + res.status(400).send("-1").end(); + return; + } + const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!; + project.State = -2; + guild.ActiveDojoColorResearch = ""; + await guild.save(); + await removePigmentsFromGuildMembers(guild._id); + res.end(); + } else if (data.Action == "Unpause") { + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { + res.status(400).send("-1").end(); + return; + } + const project = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!; + project.State = 0; + guild.ActiveDojoColorResearch = data.RecipeType; + const entry = guild.TechChanges?.find(x => x.details == data.RecipeType); + if (entry) { + entry.dateTime = new Date(); + } + await guild.save(); + res.end(); } else { throw new Error(`unknown guildTech action: ${data.Action}`); } @@ -233,20 +265,24 @@ const setTechLogState = ( }; type TGuildTechRequest = - | ({ - Action: string; - } & Partial & - Partial) - | IGuildTechFabricateRequest; + | { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" } + | IGuildTechBasicRequest + | IGuildTechContributeRequest; -interface IGuildTechStartFields { +interface IGuildTechBasicRequest { + Action: "Start" | "Fabricate" | "Pause" | "Unpause"; Mode: "Guild"; RecipeType: string; } -type IGuildTechBuyFields = IGuildTechStartFields; +interface IGuildTechBuyRequest { + Action: string; + Mode: "Guild"; + RecipeType: string; +} -interface IGuildTechContributeFields { +interface IGuildTechContributeRequest { + Action: "Contribute"; ResearchId: ""; RecipeType: string; RegularCredits: number; @@ -254,9 +290,3 @@ interface IGuildTechContributeFields { VaultCredits: number; VaultMiscItems: IMiscItem[]; } - -interface IGuildTechFabricateRequest { - Action: "Fabricate"; - Mode: "Guild"; - RecipeType: string; -} diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 00c428ed9..23d28b362 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -152,6 +152,7 @@ const guildSchema = new Schema( VaultShipDecorations: { type: [typeCountSchema], default: undefined }, VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined }, TechProjects: { type: [techProjectSchema], default: undefined }, + ActiveDojoColorResearch: { type: String, default: "" }, Class: { type: Number, default: 0 }, XP: { type: Number, default: 0 }, ClaimedXP: { type: [String], default: undefined }, diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 92f38e3e9..9176e66bf 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -93,6 +93,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s TradeTax: guild.TradeTax, Tier: 1, Vault: getGuildVault(guild), + ActiveDojoColorResearch: guild.ActiveDojoColorResearch, Class: guild.Class, XP: guild.XP, IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), @@ -350,3 +351,17 @@ export const hasGuildPermissionEx = ( const rank = guild.Ranks[member.rank]; return (rank.Permissions & perm) != 0; }; + +export const removePigmentsFromGuildMembers = async (guildId: string | Types.ObjectId): Promise => { + const members = await GuildMember.find({ guildId, status: 0 }, "accountId"); + for (const member of members) { + const inventory = await getInventory(member.accountId.toString(), "MiscItems"); + const index = inventory.MiscItems.findIndex( + x => x.ItemType == "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment" + ); + if (index != -1) { + inventory.MiscItems.splice(index, 1); + await inventory.save(); + } + } +}; diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 93e8ae641..ee10656b8 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -12,6 +12,7 @@ export interface IGuildClient { TradeTax: number; Tier: number; Vault: IGuildVault; + ActiveDojoColorResearch: string; Class: number; XP: number; IsContributor: boolean; @@ -38,6 +39,7 @@ export interface IGuildDatabase { VaultFusionTreasures?: IFusionTreasure[]; TechProjects?: ITechProjectDatabase[]; + ActiveDojoColorResearch: string; Class: number; XP: number;