diff --git a/src/controllers/api/contributeGuildClassController.ts b/src/controllers/api/contributeGuildClassController.ts new file mode 100644 index 00000000..6a32ad11 --- /dev/null +++ b/src/controllers/api/contributeGuildClassController.ts @@ -0,0 +1,65 @@ +import { toMongoDate } from "@/src/helpers/inventoryHelpers"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { Guild } from "@/src/models/guildModel"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { RequestHandler } from "express"; +import { Types } from "mongoose"; + +export const contributeGuildClassController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const payload = getJSONfromString(String(req.body)); + const guild = (await Guild.findOne({ _id: payload.GuildId }))!; + + // First contributor initiates ceremony and locks the pending class. + if (!guild.CeremonyContributors) { + guild.CeremonyContributors = []; + guild.CeremonyClass = guildXpToClass(guild.XP); + guild.CeremonyEndo = 0; + for (let i = guild.Class; i != guild.CeremonyClass; ++i) { + guild.CeremonyEndo += (i + 1) * 1000; + } + } + + guild.CeremonyContributors.push(new Types.ObjectId(accountId)); + + // Once required contributor count is hit, the class is committed and there's 72 hours to claim endo. + if (guild.CeremonyContributors.length == payload.RequiredContributors) { + guild.Class = guild.CeremonyClass!; + guild.CeremonyClass = undefined; + guild.CeremonyResetDate = new Date(Date.now() + 72 * 3600_000); + } + + await guild.save(); + + // Either way, endo is given to the contributor. + const inventory = await getInventory(accountId, "FusionPoints"); + inventory.FusionPoints += guild.CeremonyEndo!; + await inventory.save(); + + res.json({ + NumContributors: guild.CeremonyContributors.length, + FusionPointReward: guild.CeremonyEndo, + Class: guild.Class, + CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined + }); +}; + +interface IContributeGuildClassRequest { + GuildId: string; + RequiredContributors: number; +} + +const guildXpToClass = (xp: number): number => { + const cummXp = [ + 0, 11000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 68000, 563000, 665000, 774000, 891000 + ]; + let highest = 0; + for (let i = 0; i != cummXp.length; ++i) { + if (xp < cummXp[i]) { + break; + } + highest = i; + } + return highest; +}; diff --git a/src/controllers/api/contributeToDojoComponentController.ts b/src/controllers/api/contributeToDojoComponentController.ts index 9d1942c1..21be9c82 100644 --- a/src/controllers/api/contributeToDojoComponentController.ts +++ b/src/controllers/api/contributeToDojoComponentController.ts @@ -1,6 +1,11 @@ import { TGuildDatabaseDocument } from "@/src/models/guildModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; -import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService"; +import { + getDojoClient, + getGuildForRequestEx, + processDojoBuildMaterialsGathered, + scaleRequiredCount +} from "@/src/services/guildService"; import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { IDojoContributable } from "@/src/types/guildTypes"; @@ -134,6 +139,7 @@ const processContribution = ( } if (fullyFunded) { component.CompletionTime = new Date(Date.now() + meta.time * 1000); + processDojoBuildMaterialsGathered(guild, meta); } } }; diff --git a/src/controllers/api/getGuildController.ts b/src/controllers/api/getGuildController.ts index 6703d87c..62a27501 100644 --- a/src/controllers/api/getGuildController.ts +++ b/src/controllers/api/getGuildController.ts @@ -2,8 +2,9 @@ import { RequestHandler } from "express"; 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 { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { getGuildVault } from "@/src/services/guildService"; +import { logger } from "@/src/utils/logger"; const getGuildController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -15,6 +16,13 @@ const getGuildController: RequestHandler = async (req, res) => { if (inventory.GuildId) { const guild = await Guild.findOne({ _id: inventory.GuildId }); if (guild) { + if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) { + logger.debug(`ascension ceremony is over`); + guild.CeremonyEndo = undefined; + guild.CeremonyContributors = undefined; + guild.CeremonyResetDate = undefined; + await guild.save(); + } res.json({ _id: toOid(guild._id), Name: guild.Name, @@ -64,7 +72,12 @@ const getGuildController: RequestHandler = async (req, res) => { } ], Tier: 1, - Vault: getGuildVault(guild) + Vault: getGuildVault(guild), + Class: guild.Class, + XP: guild.XP, + IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)), + NumContributors: guild.CeremonyContributors?.length ?? 0, + CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined }); return; } diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index a5a080b1..35dffe2d 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -7,6 +7,7 @@ import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { config } from "@/src/services/configService"; import { ITechProjectDatabase } from "@/src/types/guildTypes"; +import { TGuildDatabaseDocument } from "@/src/models/guildModel"; export const guildTechController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -35,7 +36,7 @@ export const guildTechController: RequestHandler = async (req, res) => { }) - 1 ]; if (config.noDojoResearchCosts) { - processFundedProject(techProject, recipe); + processFundedProject(guild, techProject, recipe); } } await guild.save(); @@ -93,7 +94,7 @@ 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!]; - processFundedProject(techProject, recipe); + processFundedProject(guild, techProject, recipe); } await guild.save(); @@ -131,9 +132,16 @@ export const guildTechController: RequestHandler = async (req, res) => { } }; -const processFundedProject = (techProject: ITechProjectDatabase, recipe: IDojoResearch): void => { +const processFundedProject = ( + guild: TGuildDatabaseDocument, + techProject: ITechProjectDatabase, + recipe: IDojoResearch +): void => { techProject.State = 1; techProject.CompletionDate = new Date(new Date().getTime() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000); + if (recipe.guildXpValue) { + guild.XP += recipe.guildXpValue; + } }; type TGuildTechRequest = { diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index 30f4ed75..ee7bb202 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -1,6 +1,6 @@ import { RequestHandler } from "express"; import { IDojoComponentClient } from "@/src/types/guildTypes"; -import { getDojoClient, getGuildForRequest } from "@/src/services/guildService"; +import { getDojoClient, getGuildForRequest, processDojoBuildMaterialsGathered } from "@/src/services/guildService"; import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; import { config } from "@/src/services/configService"; @@ -35,6 +35,9 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { ]; if (config.noDojoRoomBuildStage) { component.CompletionTime = new Date(Date.now()); + if (room) { + processDojoBuildMaterialsGathered(guild, room); + } } await guild.save(); res.json(await getDojoClient(guild, 0)); diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index 4fdbc223..7afceb98 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -70,7 +70,14 @@ const guildSchema = new Schema( VaultMiscItems: { type: [typeCountSchema], default: undefined }, VaultShipDecorations: { type: [typeCountSchema], default: undefined }, VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined }, - TechProjects: { type: [techProjectSchema], default: undefined } + TechProjects: { type: [techProjectSchema], default: undefined }, + Class: { type: Number, default: 0 }, + XP: { type: Number, default: 0 }, + ClaimedXP: { type: [String], default: undefined }, + CeremonyClass: Number, + CeremonyContributors: { type: [Types.ObjectId], default: undefined }, + CeremonyResetDate: Date, + CeremonyEndo: Number }, { id: false } ); diff --git a/src/routes/api.ts b/src/routes/api.ts index 2ad923d6..bb63982e 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -13,6 +13,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController"; +import { contributeGuildClassController } from "@/src/controllers/api/contributeGuildClassController"; import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController"; import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController"; import { createGuildController } from "@/src/controllers/api/createGuildController"; @@ -153,6 +154,7 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController); +apiRouter.post("/contributeGuildClass.php", contributeGuildClassController); apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController); apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/createGuild.php", createGuildController); diff --git a/src/services/guildService.ts b/src/services/guildService.ts index cd877835..9c89f863 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -12,7 +12,7 @@ import { } from "@/src/types/guildTypes"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers"; import { Types } from "mongoose"; -import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus"; import { logger } from "../utils/logger"; export const getGuildForRequest = async (req: Request): Promise => { @@ -182,3 +182,13 @@ const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoCon guild.VaultPremiumCredits += component.RushPlatinum; } }; + +export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => { + if (build.guildXpValue) { + guild.ClaimedXP ??= []; + if (!guild.ClaimedXP.find(x => x == build.resultType)) { + guild.ClaimedXP.push(build.resultType); + guild.XP += build.guildXpValue; + } + } +}; diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index b85d97e2..9effcfbc 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -5,15 +5,26 @@ import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTyp export interface IGuildDatabase { _id: Types.ObjectId; Name: string; + DojoComponents: IDojoComponentDatabase[]; DojoCapacity: number; DojoEnergy: number; + VaultRegularCredits?: number; VaultPremiumCredits?: number; VaultMiscItems?: IMiscItem[]; VaultShipDecorations?: ITypeCount[]; VaultFusionTreasures?: IFusionTreasure[]; + TechProjects?: ITechProjectDatabase[]; + + Class: number; + XP: number; + ClaimedXP?: string[]; // track rooms and decos that have already granted XP + CeremonyClass?: number; + CeremonyEndo?: number; + CeremonyContributors?: Types.ObjectId[]; + CeremonyResetDate?: Date; } export interface IGuildVault {