XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: guild solar rail crafting (#4013)

Re #3602 any pending recipe in alliance vault for some reason softlocks game Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/4013 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>

1a8a4056
AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
提交于

代码差异

12 个文件 +487 -76
Modified src/controllers/api/abortDojoComponentController.ts +14 -3
@@ -5,6 +5,7 @@ import {
5 5 hasGuildPermission,
6 6 hasPermissionToDecorateComponent,
7 7 removeDojoDeco,
8 removeVaultPendingRecipe,
8 9 removeDojoRoom
9 10 } from "../../services/guildService.ts";
10 11 import { getInventory } from "../../services/inventoryService.ts";
@@ -18,20 +19,29 @@ export const abortDojoComponentGetController: RequestHandler = async (req, res)
18 19
19 20 export const abortDojoComponentPostController: RequestHandler = async (req, res) => {
20 21 const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
21 res.json(await abortDojoComponent(req, request.ComponentId, request.DecoId));
22 res.json(await abortDojoComponent(req, request.ComponentId, request.DecoId, request.ResultType));
22 23 };
23 24
24 25 const abortDojoComponent = async (
25 26 req: Request,
26 27 componentId: string,
27 decoId?: string
28 decoId?: string,
29 resultType?: string
28 30 ): Promise<{ DojoRequestStatus: number } | IDojoClient> => {
29 31 const account = await getAccountForRequest(req);
30 32 const accountId = account._id.toString();
31 33 const inventory = await getInventory(accountId, "GuildId LevelKeys");
32 34 const guild = await getGuildForRequestEx(req, inventory);
33 35
34 if (decoId) {
36 if (resultType) {
37 if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tactician))) {
38 return { DojoRequestStatus: -1 };
39 }
40
41 removeVaultPendingRecipe(guild, componentId, resultType);
42 await guild.save();
43 return await getDojoClient(guild, 0, undefined, account.BuildLabel);
44 } else if (decoId) {
35 45 if (!hasAccessToDojo(inventory) || !(await hasPermissionToDecorateComponent(guild, accountId, componentId))) {
36 46 return { DojoRequestStatus: -1 };
37 47 }
@@ -54,4 +64,5 @@ interface IAbortDojoComponentRequest {
54 64 DecoType?: string;
55 65 ComponentId: string;
56 66 DecoId?: string;
67 ResultType?: string;
57 68 }
Modified src/controllers/api/contributeToDojoComponentController.ts +37 -12
@@ -18,6 +18,7 @@ import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
18 18 import type { RequestHandler } from "express";
19 19 import type { IDojoBuild } from "warframe-public-export-plus";
20 20 import { ExportDojoRecipes } from "warframe-public-export-plus";
21 import { getRecipe } from "../../services/itemDataService.ts";
21 22
22 23 interface IContributeToDojoComponentRequest {
23 24 ComponentId?: string; // req.query.componentId in U10
@@ -33,6 +34,8 @@ interface IContributeToDojoComponentRequest {
33 34 VaultMiscItems?: IMiscItem[];
34 35 IngredientContributions?: IMiscItem[];
35 36 VaultIngredientContributions?: IMiscItem[];
37
38 ResultType?: string;
36 39 }
37 40
38 41 export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
@@ -56,9 +59,27 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
56 59 request.IngredientContributions ??= request.MiscItems;
57 60 request.VaultIngredientContributions ??= request.VaultMiscItems;
58 61 const component = guild.DojoComponents.id(request.ComponentId)!;
59
60 62 const inventoryChanges: IInventoryChanges = {};
61 if (!component.CompletionTime) {
63 if (request.ResultType) {
64 guild.VaultPendingRecipes ??= [];
65 if (!guild.VaultPendingRecipes.find(r => String(r.ParentRoom) == request.ComponentId)) {
66 throw new Error("attempt to contribute to non-existent vault recipe?!");
67 }
68
69 const cRecipe = guild.VaultPendingRecipes.find(r => String(r.ParentRoom) == request.ComponentId)!;
70 const recipe = getRecipe(cRecipe.RecipeType)!;
71 const meta: IDojoBuild = {
72 name: "",
73 description: "",
74 icon: "",
75 time: recipe.buildTime,
76 price: recipe.buildPrice,
77 ingredients: recipe.ingredients,
78 resultType: recipe.resultType,
79 skipTimePrice: recipe.skipBuildTimePrice
80 };
81 processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, cRecipe, true);
82 } else if (!component.CompletionTime) {
62 83 // Room is in "Collecting Materials" state
63 84 if (request.DecoId) {
64 85 throw new Error("attempt to contribute to a deco in an unfinished room?!");
@@ -92,7 +113,8 @@ const processContribution = (
92 113 inventory: TInventoryDatabaseDocument,
93 114 inventoryChanges: IInventoryChanges,
94 115 meta: IDojoBuild,
95 component: IDojoContributable
116 component: IDojoContributable,
117 disableScaling?: boolean
96 118 ): void => {
97 119 component.RegularCredits ??= 0;
98 120 if (request.RegularCredits) {
@@ -107,10 +129,11 @@ const processContribution = (
107 129 component.RegularCredits += request.VaultCredits;
108 130 guild.VaultRegularCredits! -= request.VaultCredits;
109 131 }
110 if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
132 if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price, disableScaling)) {
111 133 guild.VaultRegularCredits ??= 0;
112 guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
113 component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
134 guild.VaultRegularCredits +=
135 component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price, disableScaling);
136 component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price, disableScaling);
114 137 }
115 138
116 139 component.MiscItems ??= [];
@@ -121,10 +144,11 @@ const processContribution = (
121 144 const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
122 145 if (
123 146 componentMiscItem.ItemCount + ingredientContribution.ItemCount >
124 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
147 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount, disableScaling)
125 148 ) {
126 149 ingredientContribution.ItemCount =
127 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
150 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount, disableScaling) -
151 componentMiscItem.ItemCount;
128 152 }
129 153 componentMiscItem.ItemCount += ingredientContribution.ItemCount;
130 154 } else {
@@ -142,10 +166,11 @@ const processContribution = (
142 166 const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
143 167 if (
144 168 componentMiscItem.ItemCount + ingredientContribution.ItemCount >
145 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
169 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount, disableScaling)
146 170 ) {
147 171 ingredientContribution.ItemCount =
148 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
172 scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount, disableScaling) -
173 componentMiscItem.ItemCount;
149 174 }
150 175 componentMiscItem.ItemCount += ingredientContribution.ItemCount;
151 176 } else {
@@ -162,13 +187,13 @@ const processContribution = (
162 187 inventoryChanges.MiscItems = miscItemChanges;
163 188 }
164 189
165 if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
190 if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price, disableScaling)) {
166 191 let fullyFunded = true;
167 192 for (const ingredient of meta.ingredients) {
168 193 const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
169 194 if (
170 195 !componentMiscItem ||
171 componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
196 componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount, disableScaling)
172 197 ) {
173 198 fullyFunded = false;
174 199 break;
Modified src/controllers/api/createAllianceController.ts +2 -1
@@ -42,7 +42,8 @@ export const createAllianceController: RequestHandler = async (req, res) => {
42 42 GuildPermission.Promoter |
43 43 GuildPermission.Recruiter |
44 44 GuildPermission.Treasurer |
45 GuildPermission.ChatModerator
45 GuildPermission.ChatModerator |
46 GuildPermission.Tactician
46 47 })
47 48 ]);
48 49 res.json(await getAllianceClient(alliance, guild, account.BuildLabel));
Modified src/controllers/api/getVaultController.ts +2 -2
@@ -11,14 +11,14 @@ export const getVaultController: RequestHandler = async (req, res) => {
11 11
12 12 const allianceId = req.query.allianceId as string;
13 13 if (!allianceId) {
14 res.json({ Vault: getGuildVault(guild) });
14 res.json({ Vault: await getGuildVault(guild, account.BuildLabel) });
15 15 return;
16 16 }
17 17
18 18 if (guild.AllianceId?.toString() === allianceId) {
19 19 const alliance = await Alliance.findById(allianceId);
20 20 if (alliance) {
21 res.json({ AllianceVault: getAllianceVault(alliance) });
21 res.json({ AllianceVault: await getAllianceVault(alliance, account.BuildLabel) });
22 22 return;
23 23 }
24 24 }
Modified src/controllers/api/guildTechController.ts +1 -1
@@ -252,7 +252,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
252 252 await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
253 253 res.json({
254 254 InventoryChanges: inventoryChanges,
255 Vault: getGuildVault(guild)
255 Vault: await getGuildVault(guild, account.BuildLabel)
256 256 });
257 257 broadcastGuildUpdate(req, guild._id.toString());
258 258 }
Modified src/controllers/api/setBadlandBudgetCapsController.ts +14 -5
@@ -4,7 +4,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
4 4 import { getInventory } from "../../services/inventoryService.ts";
5 5 import { GuildPermission } from "../../types/guildTypes.ts";
6 6 import { getJSONfromString } from "../../helpers/stringHelpers.ts";
7 import { Guild } from "../../models/guildModel.ts";
7 import { Alliance, Guild } from "../../models/guildModel.ts";
8 8
9 9 export const setBadlandBudgetCapsController: RequestHandler = async (req, res) => {
10 10 const accountId = await getAccountIdForRequest(req);
@@ -15,10 +15,18 @@ export const setBadlandBudgetCapsController: RequestHandler = async (req, res) =
15 15 return;
16 16 }
17 17 const payload = getJSONfromString<ISetBadlandBudgetCapsRequest>(String(req.body));
18 guild.MaxBattlePayReserve = payload.MaxBattlePayReserve;
19 guild.MaxMissionBattlePay = payload.MaxMissionBattlePay;
20 guild.MinMissionBattlePay = payload.MinMissionBattlePay;
21 await guild.save();
18 if (payload.Alliance) {
19 const alliance = (await Alliance.findById(guild.AllianceId))!;
20 alliance.MaxBattlePayReserve = payload.MaxBattlePayReserve;
21 alliance.MaxMissionBattlePay = payload.MaxMissionBattlePay;
22 alliance.MinMissionBattlePay = payload.MinMissionBattlePay;
23 await alliance.save();
24 } else {
25 guild.MaxBattlePayReserve = payload.MaxBattlePayReserve;
26 guild.MaxMissionBattlePay = payload.MaxMissionBattlePay;
27 guild.MinMissionBattlePay = payload.MinMissionBattlePay;
28 await guild.save();
29 }
22 30 res.json(payload);
23 31 };
24 32
@@ -26,4 +34,5 @@ interface ISetBadlandBudgetCapsRequest {
26 34 MaxBattlePayReserve: number;
27 35 MaxMissionBattlePay: number;
28 36 MinMissionBattlePay: number;
37 Alliance?: true;
29 38 }
Added src/controllers/api/startGuildRecipeController.ts +93 -0
@@ -0,0 +1,93 @@
1 import type { RequestHandler } from "express";
2 import { GuildPermission } from "../../types/guildTypes.ts";
3 import {
4 getDojoClient,
5 getGuildForRequestEx,
6 hasAccessToDojo,
7 hasGuildPermission
8 } from "../../services/guildService.ts";
9 import { getAccountForRequest } from "../../services/loginService.ts";
10 import { getInventory } from "../../services/inventoryService.ts";
11 import { getJSONfromString } from "../../helpers/stringHelpers.ts";
12 import { getRecipe } from "../../services/itemDataService.ts";
13 import { Alliance, type TGuildDatabaseDocument } from "../../models/guildModel.ts";
14 import { Types } from "mongoose";
15
16 export const startGuildRecipeController: RequestHandler = async (req, res) => {
17 const account = await getAccountForRequest(req);
18 const inventory = await getInventory(account._id, "GuildId LevelKeys");
19 const guild = await getGuildForRequestEx(req, inventory);
20 const payload = getJSONfromString<IStartGuildRecipeRequest>(String(req.body));
21
22 if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, account._id, GuildPermission.Tactician))) {
23 res.json({ DojoRequestStatus: -1 });
24 return;
25 }
26
27 if (await isRoomCrafting(guild, payload.ParentRoom, payload.Alliance)) {
28 res.json({ DojoRequestStatus: -1 });
29 return;
30 }
31
32 const recipe = getRecipe(payload.Recipe)!;
33 if (payload.Alliance) {
34 res.json({ DojoRequestStatus: -1 });
35 return;
36 // Alliance crafting for some reason softlocks client
37 // const alliance = (await Alliance.findById(guild.AllianceId))!;
38 // const allianceMember = await AllianceMember.findOne({
39 // guildId: guild._id,
40 // Pending: false
41 // });
42
43 // if (!allianceMember || !(allianceMember.Permissions & GuildPermission.Tactician)) {
44 // res.json({ DojoRequestStatus: -1 });
45 // return;
46 // }
47
48 // alliance.VaultPendingRecipes ??= [];
49 // alliance.VaultPendingRecipes.push({
50 // ParentRoom: new Types.ObjectId(payload.ParentRoom),
51 // ParentGuildId: guild._id,
52 // Type: recipe.resultType,
53 // RecipeType: payload.Recipe;
54 // });
55 // await alliance.save();
56 // res.json(await getDojoClient(guild, 0, undefined, account.BuildLabel));
57 } else {
58 guild.VaultPendingRecipes ??= [];
59 guild.VaultPendingRecipes.push({
60 ParentRoom: new Types.ObjectId(payload.ParentRoom),
61 ParentGuildId: guild._id,
62 Type: recipe.resultType,
63 RecipeType: payload.Recipe
64 });
65 await guild.save();
66 res.json(await getDojoClient(guild, 0, undefined, account.BuildLabel));
67 }
68 };
69
70 const isRoomCrafting = async (
71 guild: TGuildDatabaseDocument,
72 ParentRoom: string,
73 isAlliance?: true
74 ): Promise<boolean> => {
75 guild.VaultPendingRecipes ??= [];
76 if (guild.VaultPendingRecipes.find(r => String(r.ParentRoom) == ParentRoom)) {
77 return true;
78 }
79 if (isAlliance) {
80 const alliance = (await Alliance.findById(guild.AllianceId))!;
81 alliance.VaultPendingRecipes ??= [];
82 if (alliance.VaultPendingRecipes.find(r => String(r.ParentRoom) == ParentRoom)) {
83 return true;
84 }
85 }
86 return false;
87 };
88
89 interface IStartGuildRecipeRequest {
90 Recipe: string; // /Lotus/Types/Game/SolarRails/BasicSolarRailBlueprint
91 ParentRoom: string;
92 Alliance?: true;
93 }
Modified src/models/guildModel.ts +39 -5
@@ -13,7 +13,8 @@ import type {
13 13 IDojoLeaderboardEntry,
14 14 IGuildAdDatabase,
15 15 IAllianceDatabase,
16 IAllianceMemberDatabase
16 IAllianceMemberDatabase,
17 IVaultPendingRecipeDatabase
17 18 } from "../types/guildTypes.ts";
18 19 import { GuildPermission } from "../types/guildTypes.ts";
19 20 import type { Document, Model } from "mongoose";
@@ -104,11 +105,11 @@ const guildRankSchema = new Schema<IGuildRank>(
104 105 const defaultRanks: IGuildRank[] = [
105 106 {
106 107 Name: "/Lotus/Language/Game/Rank_Creator",
107 Permissions: GuildPermission.Host | 16351
108 Permissions: GuildPermission.Tactician | GuildPermission.Host | 16351
108 109 },
109 110 {
110 111 Name: "/Lotus/Language/Game/Rank_Warlord",
111 Permissions: GuildPermission.Host | 16351
112 Permissions: GuildPermission.Tactician | GuildPermission.Host | 16351
112 113 },
113 114 {
114 115 Name: "/Lotus/Language/Game/Rank_General",
@@ -199,6 +200,20 @@ goalProgressSchema.set("toJSON", {
199 200 }
200 201 });
201 202
203 const pendingRecipeSchema = new Schema<IVaultPendingRecipeDatabase>(
204 {
205 ParentRoom: Types.ObjectId,
206 ParentGuildId: Types.ObjectId,
207 MiscItems: { type: [typeCountSchema], default: undefined },
208 RegularCredits: Number,
209 RushPlatinum: Number,
210 CompletionTime: Date,
211 Type: String,
212 RecipeType: String
213 },
214 { _id: false }
215 );
216
202 217 const guildSchema = new Schema<IGuildDatabase>(
203 218 {
204 219 // SNS guild cheats
@@ -230,6 +245,8 @@ const guildSchema = new Schema<IGuildDatabase>(
230 245 VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined },
231 246 VaultDecoRecipes: { type: [typeCountSchema], default: undefined },
232 247 VaultRecipes: { type: [typeCountSchema], default: undefined },
248 VaultInventoryItems: { type: [typeCountSchema], default: undefined },
249 VaultPendingRecipes: { type: [pendingRecipeSchema], default: undefined },
233 250 MaxBattlePayReserve: Number,
234 251 MaxMissionBattlePay: Number,
235 252 MinMissionBattlePay: Number,
@@ -318,12 +335,29 @@ const allianceSchema = new Schema<IAllianceDatabase>({
318 335 MOTD: longMOTDSchema,
319 336 LongMOTD: longMOTDSchema,
320 337 Emblem: Boolean,
321 VaultRegularCredits: Number
338 VaultRegularCredits: Number,
339 VaultPremiumCredits: Number,
340 VaultRecipes: { type: [typeCountSchema], default: undefined },
341 VaultMiscItems: { type: [typeCountSchema], default: undefined },
342 VaultInventoryItems: { type: [typeCountSchema], default: undefined },
343 VaultPendingRecipes: { type: [pendingRecipeSchema], default: undefined },
344 MaxBattlePayReserve: Number,
345 MaxMissionBattlePay: Number,
346 MinMissionBattlePay: Number
322 347 });
323 348
324 349 allianceSchema.index({ Name: 1 }, { unique: true });
325 350
326 export const Alliance = model<IAllianceDatabase>("Alliance", allianceSchema);
351 type AllianceModel = Model<IAllianceDatabase>;
352
353 export const Alliance = model<IAllianceDatabase, AllianceModel>("Alliance", allianceSchema);
354
355 // eslint-disable-next-line @typescript-eslint/no-empty-object-type
356 export type TAllianceDatabaseDocument = Document<unknown, {}, IAllianceDatabase> &
357 IAllianceDatabase & {
358 _id: Types.ObjectId;
359 __v: number;
360 };
327 361
328 362 const allianceMemberSchema = new Schema<IAllianceMemberDatabase>({
329 363 allianceId: { type: Schema.Types.ObjectId, required: true },
Modified src/routes/api.ts +2 -0
@@ -161,6 +161,7 @@ import { shipDecorationsController } from "../controllers/api/shipDecorationsCon
161 161 import { signupAndroidController } from "../controllers/api/signupAndroidController.ts";
162 162 import { startCollectibleEntryController } from "../controllers/api/startCollectibleEntryController.ts";
163 163 import { startDojoRecipeController } from "../controllers/api/startDojoRecipeController.ts";
164 import { startGuildRecipeController } from "../controllers/api/startGuildRecipeController.ts";
164 165 import { startLibraryDailyTaskController } from "../controllers/api/startLibraryDailyTaskController.ts";
165 166 import { startLibraryPersonalTargetController } from "../controllers/api/startLibraryPersonalTargetController.ts";
166 167 import { startRecipeController } from "../controllers/api/startRecipeController.ts";
@@ -396,6 +397,7 @@ apiRouter.post("/setWeaponSkillTree.php", setWeaponSkillTreeController);
396 397 apiRouter.post("/shipDecorations.php", shipDecorationsController);
397 398 apiRouter.post("/startCollectibleEntry.php", startCollectibleEntryController);
398 399 apiRouter.post("/startDojoRecipe.php", startDojoRecipeController);
400 apiRouter.post("/startGuildRecipe.php", startGuildRecipeController);
399 401 apiRouter.post("/startRecipe.php", startRecipeController);
400 402 apiRouter.post("/stepSequencers.php", stepSequencersController);
401 403 apiRouter.post("/syncRetroSettings.php", syncRetroSettingsController);
Modified src/services/guildService.ts +207 -27
@@ -1,12 +1,11 @@
1 1 import type { Request } from "express";
2 2 import type { TAccountDocument } from "./loginService.ts";
3 3 import { addLevelKeys, addRecipes, combineInventoryChanges, getInventory } from "./inventoryService.ts";
4 import type { TGuildDatabaseDocument } from "../models/guildModel.ts";
4 import type { TAllianceDatabaseDocument, TGuildDatabaseDocument } from "../models/guildModel.ts";
5 5 import { Alliance, AllianceMember, Guild, GuildAd, GuildMember } from "../models/guildModel.ts";
6 6 import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
7 7 import type {
8 8 IAllianceClient,
9 IAllianceDatabase,
10 9 IAllianceMemberClient,
11 10 IDojoClient,
12 11 IDojoComponentClient,
@@ -18,8 +17,10 @@ import type {
18 17 IGuildClient,
19 18 IGuildMemberClient,
20 19 IGuildMemberDatabase,
21 IGuildVault,
22 ITechProjectDatabase
20 IGuildVaultClient,
21 ITechProjectDatabase,
22 IVaultDatabase,
23 IVaultPendingRecipeClient
23 24 } from "../types/guildTypes.ts";
24 25 import { GuildPermission } from "../types/guildTypes.ts";
25 26 import { toMongoDate, toOid2, version_compare } from "../helpers/inventoryHelpers.ts";
@@ -37,6 +38,7 @@ import { createMessage } from "./inboxService.ts";
37 38 import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService.ts";
38 39 import type { ITypeCount } from "../types/commonTypes.ts";
39 40 import gameToBuildVersion from "../constants/gameToBuildVersion.ts";
41 import { getItemCategoryByUniqueName } from "./itemDataService.ts";
40 42
41 43 export const getGuildForRequest = async (
42 44 req: Request,
@@ -142,7 +144,7 @@ export const getGuildClient = async (
142 144 Ranks: ranks,
143 145 Tier: guild.Tier,
144 146 Emblem: guild.Emblem,
145 Vault: getGuildVault(guild),
147 Vault: await getGuildVault(guild, account.BuildLabel),
146 148 ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
147 149 Class: guild.Class,
148 150 XP: guild.XP,
@@ -161,8 +163,18 @@ export const getGuildClient = async (
161 163 };
162 164 };
163 165
164 export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
165 return {
166 export const getGuildVault = async (guild: TGuildDatabaseDocument, buildLabel?: string): Promise<IGuildVaultClient> => {
167 const { vault, needsSave } = getGuildVaultEx(guild, buildLabel);
168 if (needsSave) await guild.save();
169 return vault;
170 };
171
172 export const getGuildVaultEx = (
173 guild: TGuildDatabaseDocument,
174 buildLabel?: string,
175 needsSave: boolean = false
176 ): { vault: IGuildVaultClient; needsSave: boolean } => {
177 const vault: IGuildVaultClient = {
166 178 DojoRefundRegularCredits: guild.VaultRegularCredits,
167 179 DojoRefundMiscItems: guild.VaultMiscItems,
168 180 DojoRefundPremiumCredits: guild.VaultPremiumCredits,
@@ -175,12 +187,150 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
175 187 MaxMissionBattlePay: guild.MaxMissionBattlePay,
176 188 MinMissionBattlePay: guild.MinMissionBattlePay
177 189 };
190
191 {
192 const pendingRecipes: Record<string, IVaultPendingRecipeClient[]> = {};
193 const toRemove: Map<string, Set<string>> = new Map();
194 guild.VaultPendingRecipes ??= [];
195 guild.VaultPendingRecipes.forEach(recipe => {
196 const category = getItemCategoryByUniqueName(recipe.Type);
197 if (!category) return;
198
199 if (recipe.CompletionTime && Date.now() >= recipe.CompletionTime.getTime()) {
200 addVaultInventoryItems(guild, [{ ItemType: recipe.Type, ItemCount: 1 }]);
201 const key = recipe.ParentRoom.toString();
202 if (!toRemove.has(key)) toRemove.set(key, new Set());
203 toRemove.get(key)!.add(recipe.Type);
204 return;
205 }
206
207 pendingRecipes[category] ??= [];
208 pendingRecipes[category].push({
209 ParentGuildId: toOid2(recipe.ParentGuildId, buildLabel),
210 ParentRoom: toOid2(recipe.ParentRoom, buildLabel),
211 MiscItems: recipe.MiscItems,
212 RegularCredits: recipe.RegularCredits,
213 RushPlatinum: recipe.RushPlatinum,
214 Type: recipe.Type,
215 ...(recipe.CompletionTime && {
216 CompletionTime: toMongoDate(recipe.CompletionTime),
217 TimeRemaining: Math.trunc((recipe.CompletionTime.getTime() - Date.now()) / 1000)
218 })
219 });
220 });
221
222 toRemove.forEach((recipeSet, key) => {
223 recipeSet.forEach(type => {
224 logger.debug(`removing completed recipe ${type} in ${key}`);
225 guild.VaultPendingRecipes ??= [];
226 guild.VaultPendingRecipes.splice(
227 guild.VaultPendingRecipes.findIndex(x => x.Type == type && x.ParentRoom.equals(key)),
228 1
229 );
230 });
231 needsSave = true;
232 });
233
234 if (Object.keys(pendingRecipes).length > 0) vault.PendingRecipes = pendingRecipes;
235 }
236
237 {
238 const inventoryItems: Record<string, ITypeCount[]> = {};
239 for (const item of guild.VaultInventoryItems ?? []) {
240 const productCategory = getItemCategoryByUniqueName(item.ItemType);
241 if (!productCategory) continue;
242 inventoryItems[productCategory] ??= [];
243 inventoryItems[productCategory].push(item);
244 }
245 if (Object.keys(inventoryItems).length > 0) vault.InventoryItems = inventoryItems;
246 }
247
248 return { vault, needsSave };
178 249 };
179 250
180 export const getAllianceVault = (alliance: IAllianceDatabase): IGuildVault => {
181 return {
182 DojoRefundRegularCredits: alliance.VaultRegularCredits
251 export const getAllianceVault = async (
252 alliance: TAllianceDatabaseDocument,
253 buildLabel?: string
254 ): Promise<IGuildVaultClient> => {
255 const { vault, needsSave } = getAllianceVaultEx(alliance, buildLabel);
256 if (needsSave) await alliance.save();
257 return vault;
258 };
259
260 export const getAllianceVaultEx = (
261 alliance: TAllianceDatabaseDocument,
262 buildLabel?: string,
263 needsSave: boolean = false
264 ): { vault: IGuildVaultClient; needsSave: boolean } => {
265 const vault: IGuildVaultClient = {
266 DojoRefundRegularCredits: alliance.VaultRegularCredits,
267 DojoRefundMiscItems: alliance.VaultMiscItems,
268 DojoRefundPremiumCredits: alliance.VaultPremiumCredits,
269
270 MaxBattlePayReserve: alliance.MaxBattlePayReserve,
271 MaxMissionBattlePay: alliance.MaxMissionBattlePay,
272 MinMissionBattlePay: alliance.MinMissionBattlePay
183 273 };
274
275 // it just softlocks game for some reason
276 {
277 const pendingRecipes: Record<string, IVaultPendingRecipeClient[]> = {};
278 const toRemove: Map<string, Set<string>> = new Map();
279 alliance.VaultPendingRecipes ??= [];
280 alliance.VaultPendingRecipes.forEach(recipe => {
281 const category = getItemCategoryByUniqueName(recipe.Type);
282 if (!category) return;
283
284 if (recipe.CompletionTime && Date.now() >= recipe.CompletionTime.getTime()) {
285 addVaultInventoryItems(alliance, [{ ItemType: recipe.Type, ItemCount: 1 }]);
286 const key = recipe.ParentRoom.toString();
287 if (!toRemove.has(key)) toRemove.set(key, new Set());
288 toRemove.get(key)!.add(recipe.Type);
289 return;
290 }
291
292 pendingRecipes[category] ??= [];
293 pendingRecipes[category].push({
294 ParentGuildId: toOid2(recipe.ParentGuildId, buildLabel),
295 ParentRoom: toOid2(recipe.ParentRoom, buildLabel),
296 MiscItems: recipe.MiscItems,
297 RegularCredits: recipe.RegularCredits,
298 RushPlatinum: recipe.RushPlatinum,
299 Type: recipe.Type,
300 ...(recipe.CompletionTime && {
301 CompletionTime: toMongoDate(recipe.CompletionTime),
302 TimeRemaining: Math.trunc((recipe.CompletionTime.getTime() - Date.now()) / 1000)
303 })
304 });
305 });
306
307 toRemove.forEach((recipeSet, key) => {
308 recipeSet.forEach(type => {
309 logger.debug(`removing completed recipe ${type} in ${key}`);
310 alliance.VaultPendingRecipes ??= [];
311 alliance.VaultPendingRecipes.splice(
312 alliance.VaultPendingRecipes.findIndex(x => x.Type == type && x.ParentRoom.equals(key)),
313 1
314 );
315 });
316 needsSave = true;
317 });
318
319 if (Object.keys(pendingRecipes).length > 0) vault.PendingRecipes = pendingRecipes;
320 }
321
322 {
323 const inventoryItems: Record<string, ITypeCount[]> = {};
324 for (const item of alliance.VaultInventoryItems ?? []) {
325 const productCategory = getItemCategoryByUniqueName(item.ItemType);
326 if (!productCategory) continue;
327 inventoryItems[productCategory] ??= [];
328 inventoryItems[productCategory].push(item);
329 }
330 if (Object.keys(inventoryItems).length > 0) vault.InventoryItems = inventoryItems;
331 }
332
333 return { vault, needsSave };
184 334 };
185 335
186 336 export const getDojoClient = async (
@@ -189,6 +339,8 @@ export const getDojoClient = async (
189 339 componentId?: Types.ObjectId | string,
190 340 buildLabel?: string
191 341 ): Promise<IDojoClient> => {
342 const v = getGuildVaultEx(guild, buildLabel);
343 let needSave = v.needsSave;
192 344 const dojo: IDojoClient = {
193 345 _id: toOid2(guild._id, buildLabel),
194 346 Name: guild.Name,
@@ -199,7 +351,7 @@ export const getDojoClient = async (
199 351 CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined,
200 352 FixedContributions: true,
201 353 DojoRevision: 1,
202 Vault: getGuildVault(guild),
354 Vault: v.vault,
203 355 RevisionTime: Math.round(Date.now() / 1000),
204 356 Energy: guild.DojoEnergy,
205 357 Capacity: guild.DojoCapacity,
@@ -208,7 +360,6 @@ export const getDojoClient = async (
208 360 };
209 361 const roomsToRemove: Types.ObjectId[] = [];
210 362 const decosToRemoveNoRefund: { componentId: Types.ObjectId; decoId: Types.ObjectId }[] = [];
211 let needSave = false;
212 363 for (const dojoComponent of guild.DojoComponents) {
213 364 if (!componentId || dojoComponent._id.equals(componentId)) {
214 365 const clientComponent: IDojoComponentClient = {
@@ -346,7 +497,11 @@ export const getDojoClient = async (
346 497 };
347 498
348 499 const guildTierScalingFactors = [0.01, 0.03, 0.1, 0.3, 1];
349 export const scaleRequiredCount = (tier: number, count: number): number => {
500 export const scaleRequiredCount = (tier: number, count: number, disableScaling?: boolean): number => {
501 if (disableScaling) {
502 return count;
503 }
504
350 505 return Math.max(1, Math.trunc(count * guildTierScalingFactors[tier - 1]));
351 506 };
352 507
@@ -402,6 +557,19 @@ export const removeDojoDeco = (
402 557 refundDojoDeco(guild, component, deco);
403 558 };
404 559
560 export const removeVaultPendingRecipe = (
561 vault: IVaultDatabase,
562 componentId: Types.ObjectId | string,
563 recipeType: string
564 ): void => {
565 vault.VaultPendingRecipes ??= [];
566 const recipe = vault.VaultPendingRecipes.splice(
567 vault.VaultPendingRecipes.findIndex(x => x.Type == recipeType && x.ParentRoom.equals(componentId)),
568 1
569 )[0];
570 moveResourcesToVault(vault, recipe);
571 };
572
405 573 export const refundDojoDeco = (
406 574 guild: TGuildDatabaseDocument,
407 575 component: IDojoComponentDatabase,
@@ -435,32 +603,32 @@ export const refundDojoDeco = (
435 603 moveResourcesToVault(guild, deco); // Refund resources spent on construction
436 604 };
437 605
438 const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => {
606 const moveResourcesToVault = (vault: IVaultDatabase, component: IDojoContributable): void => {
439 607 if (component.RegularCredits) {
440 guild.VaultRegularCredits ??= 0;
441 guild.VaultRegularCredits += component.RegularCredits;
608 vault.VaultRegularCredits ??= 0;
609 vault.VaultRegularCredits += component.RegularCredits;
442 610 }
443 611 if (component.MiscItems) {
444 addVaultMiscItems(guild, component.MiscItems);
612 addVaultMiscItems(vault, component.MiscItems);
445 613 }
446 614 if (component.RushPlatinum) {
447 guild.VaultPremiumCredits ??= 0;
448 guild.VaultPremiumCredits += component.RushPlatinum;
615 vault.VaultPremiumCredits ??= 0;
616 vault.VaultPremiumCredits += component.RushPlatinum;
449 617 }
450 618 };
451 619
452 export const getVaultMiscItemCount = (guild: TGuildDatabaseDocument, itemType: string): number => {
453 return guild.VaultMiscItems?.find(x => x.ItemType == itemType)?.ItemCount ?? 0;
620 export const getVaultMiscItemCount = (vault: IVaultDatabase, itemType: string): number => {
621 return vault.VaultMiscItems?.find(x => x.ItemType == itemType)?.ItemCount ?? 0;
454 622 };
455 623
456 export const addVaultMiscItems = (guild: TGuildDatabaseDocument, miscItems: ITypeCount[]): void => {
457 guild.VaultMiscItems ??= [];
624 export const addVaultMiscItems = (vault: IVaultDatabase, miscItems: ITypeCount[]): void => {
625 vault.VaultMiscItems ??= [];
458 626 for (const item of miscItems) {
459 const vaultItem = guild.VaultMiscItems.find(x => x.ItemType == item.ItemType);
627 const vaultItem = vault.VaultMiscItems.find(x => x.ItemType == item.ItemType);
460 628 if (vaultItem) {
461 629 vaultItem.ItemCount += item.ItemCount;
462 630 } else {
463 guild.VaultMiscItems.push(item);
631 vault.VaultMiscItems.push(item);
464 632 }
465 633 }
466 634 };
@@ -491,6 +659,18 @@ export const addVaultFusionTreasures = (guild: TGuildDatabaseDocument, fusionTre
491 659 }
492 660 };
493 661
662 export const addVaultInventoryItems = (vault: IVaultDatabase, miscItems: ITypeCount[]): void => {
663 vault.VaultInventoryItems ??= [];
664 for (const item of miscItems) {
665 const vaultItem = vault.VaultInventoryItems.find(x => x.ItemType == item.ItemType);
666 if (vaultItem) {
667 vaultItem.ItemCount += item.ItemCount;
668 } else {
669 vault.VaultInventoryItems.push(item);
670 }
671 }
672 };
673
494 674 export const addGuildMemberMiscItemContribution = (guildMember: IGuildMemberDatabase, item: ITypeCount): void => {
495 675 guildMember.MiscItemsContributed ??= [];
496 676 const miscItemContribution = guildMember.MiscItemsContributed.find(x => x.ItemType == item.ItemType);
@@ -865,7 +1045,7 @@ export const deleteAlliance = async (allianceId: Types.ObjectId): Promise<void>
865 1045 };
866 1046
867 1047 export const getAllianceClient = async (
868 alliance: IAllianceDatabase,
1048 alliance: TAllianceDatabaseDocument,
869 1049 guild: TGuildDatabaseDocument,
870 1050 buildLabel: string | undefined
871 1051 ): Promise<IAllianceClient> => {
@@ -890,7 +1070,7 @@ export const getAllianceClient = async (
890 1070 MOTD: alliance.MOTD,
891 1071 LongMOTD: alliance.LongMOTD,
892 1072 Clans: clans,
893 AllianceVault: getAllianceVault(alliance)
1073 AllianceVault: await getAllianceVault(alliance, buildLabel)
894 1074 };
895 1075 };
896 1076
Modified src/services/itemDataService.ts +31 -0
@@ -474,6 +474,34 @@ export const supplementalRecipes: Record<string, IRecipe> = {
474 474 }
475 475 ],
476 476 tradable: false
477 },
478 "/Lotus/Types/Game/SolarRails/BasicSolarRailBlueprint": {
479 resultType: "/Lotus/Types/Game/SolarRails/BasicSolarRail",
480 buildPrice: 500000,
481 buildTime: 86400,
482 skipBuildTimePrice: 0,
483 consumeOnUse: false,
484 num: 1,
485 codexSecret: false,
486 ingredients: [
487 {
488 ItemType: "/Lotus/Types/Items/MiscItems/Gallium",
489 ItemCount: 25
490 },
491 {
492 ItemType: "/Lotus/Types/Items/MiscItems/Rubedo",
493 ItemCount: 15000
494 },
495 {
496 ItemType: "/Lotus/Types/Items/MiscItems/Plastids",
497 ItemCount: 15000
498 },
499 {
500 ItemType: "/Lotus/Types/Items/MiscItems/ControlModule",
501 ItemCount: 25
502 }
503 ],
504 tradable: false
477 505 }
478 506 };
479 507
@@ -1460,6 +1488,9 @@ export const getItemCategoryByUniqueName = (uniqueName: string): string | undefi
1460 1488 if (uniqueName in ExportWeapons) {
1461 1489 return ExportWeapons[uniqueName].productCategory;
1462 1490 }
1491 if (uniqueName == "/Lotus/Types/Game/SolarRails/BasicSolarRail") {
1492 return "SolarRails";
1493 }
1463 1494 return undefined;
1464 1495 };
1465 1496
Modified src/types/guildTypes.ts +45 -20
@@ -24,7 +24,7 @@ export interface IGuildClient {
24 24 Ranks: IGuildRank[];
25 25 Tier: number;
26 26 Emblem?: boolean;
27 Vault: IGuildVault;
27 Vault: IGuildVaultClient;
28 28 ActiveDojoColorResearch: string;
29 29 Class: number;
30 30 XP: number;
@@ -48,7 +48,7 @@ export interface IGuildCheats {
48 48 fastClanAscension?: boolean;
49 49 }
50 50
51 export interface IGuildDatabase extends IGuildCheats {
51 export interface IGuildDatabase extends IGuildCheats, IVaultDatabase {
52 52 _id: Types.ObjectId;
53 53 Name: string;
54 54 MOTD: string;
@@ -65,18 +65,6 @@ export interface IGuildDatabase extends IGuildCheats {
65 65 DojoCapacity: number;
66 66 DojoEnergy: number;
67 67
68 VaultRegularCredits?: number;
69 VaultPremiumCredits?: number;
70 VaultMiscItems?: IMiscItem[];
71 VaultShipDecorations?: ITypeCount[];
72 VaultFusionTreasures?: IFusionTreasure[];
73 VaultDecoRecipes?: ITypeCount[];
74 VaultRecipes?: ITypeCount[];
75
76 MaxBattlePayReserve?: number;
77 MaxMissionBattlePay?: number;
78 MinMissionBattlePay?: number;
79
80 68 TechProjects?: ITechProjectDatabase[];
81 69 ActiveDojoColorResearch: string;
82 70
@@ -113,6 +101,7 @@ export enum GuildPermission {
113 101 Decorator = 1024, // Create and destroy decos
114 102 Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault.
115 103 Tech = 128, // Queue research
104 Tactician = 256, // Start Solar Rail Craft & Deploy Solar Rails
116 105 ChatModerator = 512, // (Clans & Alliances)
117 106 Herald = 2048, // Change MOTD
118 107 Fabricator = 4096 // Replicate research
@@ -148,7 +137,22 @@ export interface IGuildMemberClient extends IFriendInfo {
148 137 ShipDecorationsContributed?: ITypeCount[];
149 138 }
150 139
151 export interface IGuildVault {
140 export interface IVaultDatabase {
141 VaultRegularCredits?: number;
142 VaultPremiumCredits?: number;
143 VaultMiscItems?: IMiscItem[];
144 VaultShipDecorations?: ITypeCount[];
145 VaultFusionTreasures?: IFusionTreasure[];
146 VaultDecoRecipes?: ITypeCount[];
147 VaultRecipes?: ITypeCount[];
148 VaultInventoryItems?: ITypeCount[];
149 VaultPendingRecipes?: IVaultPendingRecipeDatabase[];
150
151 MaxBattlePayReserve?: number;
152 MaxMissionBattlePay?: number;
153 MinMissionBattlePay?: number;
154 }
155 export interface IGuildVaultClient {
152 156 DojoRefundRegularCredits?: number;
153 157 DojoRefundMiscItems?: IMiscItem[];
154 158 DojoRefundPremiumCredits?: number;
@@ -160,7 +164,29 @@ export interface IGuildVault {
160 164 MaxBattlePayReserve?: number;
161 165 MaxMissionBattlePay?: number;
162 166 MinMissionBattlePay?: number;
163 // InventoryItems?: Record<"SolarRails", ITypeCount[]>;
167 InventoryItems?: Record<string, ITypeCount[]>;
168 PendingRecipes?: Record<string, IVaultPendingRecipeClient[]>;
169 }
170
171 export interface IVaultPendingRecipeDatabase {
172 ParentRoom: Types.ObjectId;
173 ParentGuildId: Types.ObjectId;
174 MiscItems?: IMiscItem[];
175 RegularCredits?: number;
176 RushPlatinum?: number;
177 CompletionTime?: Date;
178 Type: string;
179 RecipeType: string;
180 TimeRemaining?: number;
181 }
182
183 export interface IVaultPendingRecipeClient extends Omit<
184 IVaultPendingRecipeDatabase,
185 "CompletionTime" | "ParentRoom" | "ParentGuildId" | "RecipeType"
186 > {
187 ParentRoom: IOidWithLegacySupport;
188 ParentGuildId: IOidWithLegacySupport;
189 CompletionTime?: IMongoDate;
164 190 }
165 191
166 192 export interface IDojoClient {
@@ -171,7 +197,7 @@ export interface IDojoClient {
171 197 FixedContributions: boolean;
172 198 DojoRevision: number;
173 199 AllianceId?: IOidWithLegacySupport;
174 Vault?: IGuildVault;
200 Vault?: IGuildVaultClient;
175 201 Class?: number; // Level
176 202 RevisionTime: number;
177 203 Energy: number;
@@ -341,16 +367,15 @@ export interface IAllianceClient {
341 367 CrossPlatformEnabled?: boolean;
342 368 Clans: IAllianceMemberClient[];
343 369 OriginalPlatform?: number;
344 AllianceVault?: IGuildVault;
370 AllianceVault?: IGuildVaultClient;
345 371 }
346 372
347 export interface IAllianceDatabase {
373 export interface IAllianceDatabase extends IVaultDatabase {
348 374 _id: Types.ObjectId;
349 375 Name: string;
350 376 MOTD?: ILongMOTD;
351 377 LongMOTD?: ILongMOTD;
352 378 Emblem?: boolean;
353 VaultRegularCredits?: number;
354 379 }
355 380
356 381 export interface IAllianceMemberClient {