feat: clan polychrome research (#1177)

Reviewed-on: OpenWF/SpaceNinjaServer#1177
This commit is contained in:
Sainan 2025-03-16 04:32:11 -07:00
parent 56a372ee6f
commit ab11f67f0b
4 changed files with 76 additions and 28 deletions

View File

@ -4,6 +4,7 @@ import {
getGuildVault, getGuildVault,
hasAccessToDojo, hasAccessToDojo,
hasGuildPermission, hasGuildPermission,
removePigmentsFromGuildMembers,
scaleRequiredCount scaleRequiredCount
} from "@/src/services/guildService"; } from "@/src/services/guildService";
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus"; 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 inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory); const guild = await getGuildForRequestEx(req, inventory);
const data = JSON.parse(String(req.body)) as TGuildTechRequest; const data = JSON.parse(String(req.body)) as TGuildTechRequest;
const action = data.Action.split(",")[0]; if (data.Action == "Sync") {
if (action == "Sync") {
let needSave = false; let needSave = false;
const techProjects: ITechProjectClient[] = []; const techProjects: ITechProjectClient[] = [];
if (guild.TechProjects) { if (guild.TechProjects) {
@ -53,18 +53,18 @@ export const guildTechController: RequestHandler = async (req, res) => {
await guild.save(); await guild.save();
} }
res.json({ TechProjects: techProjects }); res.json({ TechProjects: techProjects });
} else if (action == "Start") { } else if (data.Action == "Start") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const recipe = ExportDojoRecipes.research[data.RecipeType!]; const recipe = ExportDojoRecipes.research[data.RecipeType];
guild.TechProjects ??= []; guild.TechProjects ??= [];
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) { if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
const techProject = const techProject =
guild.TechProjects[ guild.TechProjects[
guild.TechProjects.push({ guild.TechProjects.push({
ItemType: data.RecipeType!, ItemType: data.RecipeType,
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price), ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
ReqItems: recipe.ingredients.map(x => ({ ReqItems: recipe.ingredients.map(x => ({
ItemType: x.ItemType, ItemType: x.ItemType,
@ -76,16 +76,20 @@ export const guildTechController: RequestHandler = async (req, res) => {
setTechLogState(guild, techProject.ItemType, 5); setTechLogState(guild, techProject.ItemType, 5);
if (config.noDojoResearchCosts) { if (config.noDojoResearchCosts) {
processFundedProject(guild, techProject, recipe); processFundedProject(guild, techProject, recipe);
} else {
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = data.RecipeType;
}
} }
} }
await guild.save(); await guild.save();
res.end(); res.end();
} else if (action == "Contribute") { } else if (data.Action == "Contribute") {
if (!hasAccessToDojo(inventory)) { if (!hasAccessToDojo(inventory)) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const contributions = data as IGuildTechContributeFields; const contributions = data;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!; const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
if (contributions.VaultCredits) { 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)) { if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
// This research is now fully funded. // This research is now fully funded.
const recipe = ExportDojoRecipes.research[data.RecipeType!]; const recipe = ExportDojoRecipes.research[data.RecipeType];
processFundedProject(guild, techProject, recipe); processFundedProject(guild, techProject, recipe);
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
guild.ActiveDojoColorResearch = "";
await removePigmentsFromGuildMembers(guild._id);
}
} }
await guild.save(); await guild.save();
@ -146,12 +154,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
Vault: getGuildVault(guild) Vault: getGuildVault(guild)
}); });
} else if (action == "Buy") { } else if (data.Action.split(",")[0] == "Buy") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const purchase = data as IGuildTechBuyFields; const purchase = data as IGuildTechBuyRequest;
const quantity = parseInt(data.Action.split(",")[1]); const quantity = parseInt(data.Action.split(",")[1]);
const recipeChanges = [ const recipeChanges = [
{ {
@ -173,13 +181,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
Recipes: recipeChanges Recipes: recipeChanges
} }
}); });
} else if (action == "Fabricate") { } else if (data.Action == "Fabricate") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) { if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end(); res.status(400).send("-1").end();
return; return;
} }
const payload = data as IGuildTechFabricateRequest; const recipe = ExportDojoRecipes.fabrications[data.RecipeType];
const recipe = ExportDojoRecipes.fabrications[payload.RecipeType];
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false); const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false);
inventoryChanges.MiscItems = recipe.ingredients.map(x => ({ inventoryChanges.MiscItems = recipe.ingredients.map(x => ({
ItemType: x.ItemType, ItemType: x.ItemType,
@ -190,6 +197,31 @@ export const guildTechController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`. // Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
res.json({ inventoryChanges: 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 { } else {
throw new Error(`unknown guildTech action: ${data.Action}`); throw new Error(`unknown guildTech action: ${data.Action}`);
} }
@ -233,20 +265,24 @@ const setTechLogState = (
}; };
type TGuildTechRequest = type TGuildTechRequest =
| ({ | { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
Action: string; | IGuildTechBasicRequest
} & Partial<IGuildTechStartFields> & | IGuildTechContributeRequest;
Partial<IGuildTechContributeFields>)
| IGuildTechFabricateRequest;
interface IGuildTechStartFields { interface IGuildTechBasicRequest {
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
Mode: "Guild"; Mode: "Guild";
RecipeType: string; RecipeType: string;
} }
type IGuildTechBuyFields = IGuildTechStartFields; interface IGuildTechBuyRequest {
Action: string;
Mode: "Guild";
RecipeType: string;
}
interface IGuildTechContributeFields { interface IGuildTechContributeRequest {
Action: "Contribute";
ResearchId: ""; ResearchId: "";
RecipeType: string; RecipeType: string;
RegularCredits: number; RegularCredits: number;
@ -254,9 +290,3 @@ interface IGuildTechContributeFields {
VaultCredits: number; VaultCredits: number;
VaultMiscItems: IMiscItem[]; VaultMiscItems: IMiscItem[];
} }
interface IGuildTechFabricateRequest {
Action: "Fabricate";
Mode: "Guild";
RecipeType: string;
}

View File

@ -152,6 +152,7 @@ const guildSchema = new Schema<IGuildDatabase>(
VaultShipDecorations: { type: [typeCountSchema], default: undefined }, VaultShipDecorations: { type: [typeCountSchema], default: undefined },
VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined }, VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined },
TechProjects: { type: [techProjectSchema], default: undefined }, TechProjects: { type: [techProjectSchema], default: undefined },
ActiveDojoColorResearch: { type: String, default: "" },
Class: { type: Number, default: 0 }, Class: { type: Number, default: 0 },
XP: { type: Number, default: 0 }, XP: { type: Number, default: 0 },
ClaimedXP: { type: [String], default: undefined }, ClaimedXP: { type: [String], default: undefined },

View File

@ -93,6 +93,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
TradeTax: guild.TradeTax, TradeTax: guild.TradeTax,
Tier: 1, Tier: 1,
Vault: getGuildVault(guild), Vault: getGuildVault(guild),
ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
Class: guild.Class, Class: guild.Class,
XP: guild.XP, XP: guild.XP,
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)),
@ -350,3 +351,17 @@ export const hasGuildPermissionEx = (
const rank = guild.Ranks[member.rank]; const rank = guild.Ranks[member.rank];
return (rank.Permissions & perm) != 0; return (rank.Permissions & perm) != 0;
}; };
export const removePigmentsFromGuildMembers = async (guildId: string | Types.ObjectId): Promise<void> => {
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();
}
}
};

View File

@ -12,6 +12,7 @@ export interface IGuildClient {
TradeTax: number; TradeTax: number;
Tier: number; Tier: number;
Vault: IGuildVault; Vault: IGuildVault;
ActiveDojoColorResearch: string;
Class: number; Class: number;
XP: number; XP: number;
IsContributor: boolean; IsContributor: boolean;
@ -38,6 +39,7 @@ export interface IGuildDatabase {
VaultFusionTreasures?: IFusionTreasure[]; VaultFusionTreasures?: IFusionTreasure[];
TechProjects?: ITechProjectDatabase[]; TechProjects?: ITechProjectDatabase[];
ActiveDojoColorResearch: string;
Class: number; Class: number;
XP: number; XP: number;