返回提交历史
Modified
src/controllers/api/abortDojoComponentController.ts
+14
-3
Modified
src/controllers/api/contributeToDojoComponentController.ts
+37
-12
Modified
src/controllers/api/createAllianceController.ts
+2
-1
Modified
src/controllers/api/getVaultController.ts
+2
-2
Modified
src/controllers/api/guildTechController.ts
+1
-1
Modified
src/controllers/api/setBadlandBudgetCapsController.ts
+14
-5
Added
src/controllers/api/startGuildRecipeController.ts
+93
-0
Modified
src/models/guildModel.ts
+39
-5
Modified
src/routes/api.ts
+2
-0
Modified
src/services/guildService.ts
+207
-27
Modified
src/services/itemDataService.ts
+31
-0
Modified
src/types/guildTypes.ts
+45
-20
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
代码差异
12 个文件
+487
-76
@@ -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
}
@@ -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;
@@ -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));
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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 },
@@ -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);
@@ -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
@@ -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
@@ -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 {