feat: dojo research (#689)

This commit is contained in:
Sainan 2025-01-03 09:06:50 +01:00 committed by GitHub
parent c80dd1bbd0
commit e0ff240d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 154 additions and 17 deletions

View File

@ -3,9 +3,8 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Guild } from "@/src/models/guildModel"; 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 accountId = await getAccountIdForRequest(req);
const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest; const payload = getJSONfromString(String(req.body)) as ICreateGuildRequest;
@ -34,4 +33,6 @@ const createGuildController: RequestHandler = async (req, res) => {
res.json(guild); res.json(guild);
}; };
export { createGuildController }; interface ICreateGuildRequest {
guildName: string;
}

View File

@ -1,5 +1,100 @@
import { RequestHandler } from "express"; 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) => { export const guildTechController: RequestHandler = async (req, res) => {
res.status(500).end(); // This is what I got for a fresh clan. 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 ??= [];
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") {
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,
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<IGuildTechStartFields> &
Partial<IGuildTechContributeFields>;
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));
}; };

View File

@ -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 { model, Schema } from "mongoose";
import { typeCountSchema } from "./inventoryModels/inventoryModel";
import { toMongoDate } from "../helpers/inventoryHelpers";
const dojoComponentSchema = new Schema<IDojoComponentDatabase>({ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
pf: { type: String, required: true }, pf: { type: String, required: true },
@ -10,12 +17,35 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
CompletionTime: Date CompletionTime: Date
}); });
const techProjectSchema = new Schema<ITechProjectDatabase>(
{
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<IGuildDatabase>( const guildSchema = new Schema<IGuildDatabase>(
{ {
Name: { type: String, required: true }, Name: { type: String, required: true },
DojoComponents: [dojoComponentSchema], DojoComponents: [dojoComponentSchema],
DojoCapacity: { type: Number, default: 100 }, DojoCapacity: { type: Number, default: 100 },
DojoEnergy: { type: Number, default: 5 } DojoEnergy: { type: Number, default: 5 },
TechProjects: { type: [techProjectSchema], default: undefined }
}, },
{ id: false } { id: false }
); );

View File

@ -61,7 +61,7 @@ import {
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { EquipmentSelectionSchema } from "./loadoutModel"; import { EquipmentSelectionSchema } from "./loadoutModel";
const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false }); export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
const focusXPSchema = new Schema<IFocusXP>( const focusXPSchema = new Schema<IFocusXP>(
{ {

View File

@ -2,17 +2,22 @@ import { Request } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { Guild } from "@/src/models/guildModel"; import { Guild } from "@/src/models/guildModel";
import { IInventoryDatabaseDocument } from "../types/inventoryTypes/inventoryTypes";
export const getGuildForRequest = async (req: Request) => { export const getGuildForRequest = async (req: Request) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId); 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; const guildId = req.query.guildId as string;
if (!inventory.GuildId || inventory.GuildId.toString() != guildId) { if (!inventory.GuildId || inventory.GuildId.toString() != guildId) {
throw new Error("Account is not in the guild that it has sent a request for"); throw new Error("Account is not in the guild that it has sent a request for");
} }
const guild = await Guild.findOne({ _id: guildId }); const guild = await Guild.findOne({ _id: guildId });
if (!guild) { 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; return guild;
}; };

View File

@ -11,10 +11,7 @@ export interface IGuildDatabase extends IGuild {
DojoComponents?: IDojoComponentDatabase[]; DojoComponents?: IDojoComponentDatabase[];
DojoCapacity: number; DojoCapacity: number;
DojoEnergy: number; DojoEnergy: number;
} TechProjects?: ITechProjectDatabase[];
export interface ICreateGuildRequest {
guildName: string;
} }
export interface IDojoClient { export interface IDojoClient {
@ -49,3 +46,15 @@ export interface IDojoComponentDatabase
pi?: Types.ObjectId; pi?: Types.ObjectId;
CompletionTime?: Date; CompletionTime?: Date;
} }
export interface ITechProjectClient {
ItemType: string;
ReqCredits: number;
ReqItems: IMiscItem[];
State: number; // 0 = pending, 1 = complete
CompletionDate?: IMongoDate;
}
export interface ITechProjectDatabase extends Omit<ITechProjectClient, "CompletionDate"> {
CompletionDate?: Date;
}

View File

@ -460,10 +460,7 @@ export interface IFlavourItem {
ItemType: string; ItemType: string;
} }
export interface IMiscItem { export type IMiscItem = ITypeCount;
ItemCount: number;
ItemType: string;
}
export interface ICrewShipWeapon { export interface ICrewShipWeapon {
PILOT: ICrewShipPilotWeapon; PILOT: ICrewShipPilotWeapon;