返回提交历史
Modified
src/controllers/api/abortDojoComponentController.ts
+1
-1
Modified
src/controllers/api/contributeToDojoComponentController.ts
+12
-9
Modified
src/controllers/api/contributeToVaultController.ts
+10
-4
Modified
src/controllers/api/dojoComponentRushController.ts
+10
-5
Modified
src/controllers/api/guildTechController.ts
+14
-55
Modified
src/services/guildService.ts
+158
-16
XFEstudio/XFESpaceNinjaServer
feat: clan tiers (#1378)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1378 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
516f822e
代码差异
6 个文件
+205
-90
@@ -32,7 +32,7 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
32
32
if (request.DecoId) {
33
33
removeDojoDeco(guild, request.ComponentId, request.DecoId);
34
34
} else {
35
removeDojoRoom(guild, request.ComponentId);
35
await removeDojoRoom(guild, request.ComponentId);
36
36
}
37
37
38
38
await guild.save();
@@ -94,10 +94,10 @@ const processContribution = (
94
94
component.RegularCredits += request.VaultCredits;
95
95
guild.VaultRegularCredits! -= request.VaultCredits;
96
96
}
97
if (component.RegularCredits > scaleRequiredCount(meta.price)) {
97
if (component.RegularCredits > scaleRequiredCount(guild.Tier, meta.price)) {
98
98
guild.VaultRegularCredits ??= 0;
99
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(meta.price);
100
component.RegularCredits = scaleRequiredCount(meta.price);
99
guild.VaultRegularCredits += component.RegularCredits - scaleRequiredCount(guild.Tier, meta.price);
100
component.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
101
101
}
102
102
103
103
component.MiscItems ??= [];
@@ -108,10 +108,10 @@ const processContribution = (
108
108
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
109
109
if (
110
110
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
111
scaleRequiredCount(ingredientMeta.ItemCount)
111
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
112
112
) {
113
113
ingredientContribution.ItemCount =
114
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
114
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
115
115
}
116
116
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
117
117
} else {
@@ -129,10 +129,10 @@ const processContribution = (
129
129
const ingredientMeta = meta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
130
130
if (
131
131
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
132
scaleRequiredCount(ingredientMeta.ItemCount)
132
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount)
133
133
) {
134
134
ingredientContribution.ItemCount =
135
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
135
scaleRequiredCount(guild.Tier, ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
136
136
}
137
137
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
138
138
} else {
@@ -150,11 +150,14 @@ const processContribution = (
150
150
inventoryChanges.MiscItems = miscItemChanges;
151
151
}
152
152
153
if (component.RegularCredits >= scaleRequiredCount(meta.price)) {
153
if (component.RegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
154
154
let fullyFunded = true;
155
155
for (const ingredient of meta.ingredients) {
156
156
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
157
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
157
if (
158
!componentMiscItem ||
159
componentMiscItem.ItemCount < scaleRequiredCount(guild.Tier, ingredient.ItemCount)
160
) {
158
161
fullyFunded = false;
159
162
break;
160
163
}
@@ -1,5 +1,5 @@
1
1
import { GuildMember } from "@/src/models/guildModel";
2
import { getGuildForRequestEx } from "@/src/services/guildService";
2
import { addVaultMiscItems, getGuildForRequestEx } from "@/src/services/guildService";
3
3
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
4
4
import { getAccountIdForRequest } from "@/src/services/loginService";
5
5
import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
@@ -23,11 +23,17 @@ export const contributeToVaultController: RequestHandler = async (req, res) => {
23
23
guildMember.RegularCreditsContributed += request.RegularCredits;
24
24
}
25
25
if (request.MiscItems.length) {
26
guild.VaultMiscItems ??= [];
26
addVaultMiscItems(guild, request.MiscItems);
27
27
28
guildMember.MiscItemsContributed ??= [];
28
29
for (const item of request.MiscItems) {
29
guild.VaultMiscItems.push(item);
30
guildMember.MiscItemsContributed.push(item);
30
const miscItemContribution = guildMember.MiscItemsContributed.find(x => x.ItemType == item.ItemType);
31
if (miscItemContribution) {
32
miscItemContribution.ItemCount += item.ItemCount;
33
} else {
34
guildMember.MiscItemsContributed.push(item);
35
}
36
31
37
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
32
38
}
33
39
}
@@ -1,4 +1,4 @@
1
import { GuildMember } from "@/src/models/guildModel";
1
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
2
2
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
3
3
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
4
4
import { getAccountIdForRequest } from "@/src/services/loginService";
@@ -36,10 +36,10 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
36
36
if (request.DecoId) {
37
37
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
38
38
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
39
processContribution(deco, meta, platinumDonated);
39
processContribution(guild, deco, meta, platinumDonated);
40
40
} else {
41
41
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
42
processContribution(component, meta, platinumDonated);
42
processContribution(guild, component, meta, platinumDonated);
43
43
44
44
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
45
45
if (entry) {
@@ -61,8 +61,13 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
61
61
});
62
62
};
63
63
64
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
65
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
64
const processContribution = (
65
guild: TGuildDatabaseDocument,
66
component: IDojoContributable,
67
meta: IDojoBuild,
68
platinumDonated: number
69
): void => {
70
const fullPlatinumCost = scaleRequiredCount(guild.Tier, meta.skipTimePrice);
66
71
const fullDurationSeconds = meta.time;
67
72
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
68
73
component.CompletionTime = new Date(
@@ -4,10 +4,13 @@ import {
4
4
getGuildVault,
5
5
hasAccessToDojo,
6
6
hasGuildPermission,
7
processFundedGuildTechProject,
8
processGuildTechProjectContributionsUpdate,
7
9
removePigmentsFromGuildMembers,
8
scaleRequiredCount
10
scaleRequiredCount,
11
setGuildTechLogState
9
12
} from "@/src/services/guildService";
10
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
13
import { ExportDojoRecipes } from "warframe-public-export-plus";
11
14
import { getAccountIdForRequest } from "@/src/services/loginService";
12
15
import {
13
16
addItem,
@@ -20,8 +23,8 @@ import {
20
23
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
21
24
import { IInventoryChanges } from "@/src/types/purchaseTypes";
22
25
import { config } from "@/src/services/configService";
23
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
24
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
26
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
27
import { GuildMember } from "@/src/models/guildModel";
25
28
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
26
29
import { logger } from "@/src/utils/logger";
27
30
@@ -44,7 +47,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
44
47
if (project.CompletionDate) {
45
48
techProject.CompletionDate = toMongoDate(project.CompletionDate);
46
49
if (Date.now() >= project.CompletionDate.getTime()) {
47
needSave ||= setTechLogState(guild, project.ItemType, 4, project.CompletionDate);
50
needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
48
51
}
49
52
}
50
53
techProjects.push(techProject);
@@ -66,17 +69,17 @@ export const guildTechController: RequestHandler = async (req, res) => {
66
69
guild.TechProjects[
67
70
guild.TechProjects.push({
68
71
ItemType: data.RecipeType,
69
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(recipe.price),
72
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
70
73
ReqItems: recipe.ingredients.map(x => ({
71
74
ItemType: x.ItemType,
72
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(x.ItemCount)
75
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
73
76
})),
74
77
State: 0
75
78
}) - 1
76
79
];
77
setTechLogState(guild, techProject.ItemType, 5);
80
setGuildTechLogState(guild, techProject.ItemType, 5);
78
81
if (config.noDojoResearchCosts) {
79
processFundedProject(guild, techProject, recipe);
82
processFundedGuildTechProject(guild, techProject, recipe);
80
83
} else {
81
84
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
82
85
guild.ActiveDojoColorResearch = data.RecipeType;
@@ -151,15 +154,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
151
154
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
152
155
inventoryChanges.MiscItems = miscItemChanges;
153
156
154
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
155
// This research is now fully funded.
156
const recipe = ExportDojoRecipes.research[data.RecipeType];
157
processFundedProject(guild, techProject, recipe);
158
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
159
guild.ActiveDojoColorResearch = "";
160
await removePigmentsFromGuildMembers(guild._id);
161
}
162
}
157
// Check if research is fully funded now.
158
await processGuildTechProjectContributionsUpdate(guild, techProject);
163
159
164
160
await guild.save();
165
161
await inventory.save();
@@ -238,43 +234,6 @@ export const guildTechController: RequestHandler = async (req, res) => {
238
234
}
239
235
};
240
236
241
const processFundedProject = (
242
guild: TGuildDatabaseDocument,
243
techProject: ITechProjectDatabase,
244
recipe: IDojoResearch
245
): void => {
246
techProject.State = 1;
247
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
248
if (recipe.guildXpValue) {
249
guild.XP += recipe.guildXpValue;
250
}
251
setTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
252
};
253
254
const setTechLogState = (
255
guild: TGuildDatabaseDocument,
256
type: string,
257
state: number,
258
dateTime: Date | undefined = undefined
259
): boolean => {
260
guild.TechChanges ??= [];
261
const entry = guild.TechChanges.find(x => x.details == type);
262
if (entry) {
263
if (entry.entryType == state) {
264
return false;
265
}
266
entry.dateTime = dateTime;
267
entry.entryType = state;
268
} else {
269
guild.TechChanges.push({
270
dateTime: dateTime,
271
entryType: state,
272
details: type
273
});
274
}
275
return true;
276
};
277
278
237
type TGuildTechRequest =
279
238
| { Action: "Sync" | "SomethingElseThatWeMightNotKnowAbout" }
280
239
| IGuildTechBasicRequest
@@ -13,16 +13,18 @@ import {
13
13
IGuildClient,
14
14
IGuildMemberClient,
15
15
IGuildMemberDatabase,
16
IGuildVault
16
IGuildVault,
17
ITechProjectDatabase
17
18
} from "@/src/types/guildTypes";
18
19
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
19
20
import { Types } from "mongoose";
20
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
21
import { ExportDojoRecipes, IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
21
22
import { logger } from "../utils/logger";
22
23
import { config } from "./configService";
23
24
import { Account } from "../models/loginModel";
24
25
import { getRandomInt } from "./rngService";
25
26
import { Inbox } from "../models/inboxModel";
27
import { ITypeCount } from "../types/inventoryTypes/inventoryTypes";
26
28
27
29
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
28
30
const accountId = await getAccountIdForRequest(req);
@@ -152,6 +154,27 @@ export const getDojoClient = async (
152
154
entry.entryType = 1;
153
155
needSave = true;
154
156
}
157
158
let newTier: number | undefined;
159
switch (dojoComponent.pf) {
160
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksShadow.level":
161
newTier = 2;
162
break;
163
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksStorm.level":
164
newTier = 3;
165
break;
166
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMountain.level":
167
newTier = 4;
168
break;
169
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMoon.level":
170
newTier = 5;
171
break;
172
}
173
if (newTier) {
174
logger.debug(`clan finished building barracks, updating to tier ${newTier}`);
175
await setGuildTier(guild, newTier);
176
needSave = true;
177
}
155
178
}
156
179
if (dojoComponent.DestructionTime) {
157
180
if (Date.now() >= dojoComponent.DestructionTime.getTime()) {
@@ -189,22 +212,26 @@ export const getDojoClient = async (
189
212
if (roomsToRemove.length) {
190
213
logger.debug(`removing now-destroyed rooms`, roomsToRemove);
191
214
for (const id of roomsToRemove) {
192
removeDojoRoom(guild, id);
215
await removeDojoRoom(guild, id);
193
216
}
194
217
needSave = true;
195
218
}
196
219
if (needSave) {
197
220
await guild.save();
198
221
}
222
dojo.Tier = guild.Tier;
199
223
return dojo;
200
224
};
201
225
202
export const scaleRequiredCount = (count: number): number => {
203
// The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
204
return Math.max(1, Math.trunc(count / 100));
226
const guildTierScalingFactors = [0.01, 0.03, 0.1, 0.3, 1];
227
export const scaleRequiredCount = (tier: number, count: number): number => {
228
return Math.max(1, Math.trunc(count * guildTierScalingFactors[tier - 1]));
205
229
};
206
230
207
export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: Types.ObjectId | string): void => {
231
export const removeDojoRoom = async (
232
guild: TGuildDatabaseDocument,
233
componentId: Types.ObjectId | string
234
): Promise<void> => {
208
235
const component = guild.DojoComponents.splice(
209
236
guild.DojoComponents.findIndex(x => x._id.equals(componentId)),
210
237
1
@@ -223,6 +250,21 @@ export const removeDojoRoom = (guild: TGuildDatabaseDocument, componentId: Types
223
250
guild.RoomChanges.splice(index, 1);
224
251
}
225
252
}
253
254
switch (component.pf) {
255
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksShadow.level":
256
await setGuildTier(guild, 1);
257
break;
258
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksStorm.level":
259
await setGuildTier(guild, 2);
260
break;
261
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMountain.level":
262
await setGuildTier(guild, 3);
263
break;
264
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMoon.level":
265
await setGuildTier(guild, 4);
266
break;
267
}
226
268
};
227
269
228
270
export const removeDojoDeco = (
@@ -248,15 +290,7 @@ const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoCon
248
290
guild.VaultRegularCredits += component.RegularCredits;
249
291
}
250
292
if (component.MiscItems) {
251
guild.VaultMiscItems ??= [];
252
for (const componentMiscItem of component.MiscItems) {
253
const vaultMiscItem = guild.VaultMiscItems.find(x => x.ItemType == componentMiscItem.ItemType);
254
if (vaultMiscItem) {
255
vaultMiscItem.ItemCount += componentMiscItem.ItemCount;
256
} else {
257
guild.VaultMiscItems.push(componentMiscItem);
258
}
259
}
293
addVaultMiscItems(guild, component.MiscItems);
260
294
}
261
295
if (component.RushPlatinum) {
262
296
guild.VaultPremiumCredits ??= 0;
@@ -264,6 +298,18 @@ const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoCon
264
298
}
265
299
};
266
300
301
export const addVaultMiscItems = (guild: TGuildDatabaseDocument, miscItems: ITypeCount[]): void => {
302
guild.VaultMiscItems ??= [];
303
for (const miscItem of miscItems) {
304
const vaultMiscItem = guild.VaultMiscItems.find(x => x.ItemType == miscItem.ItemType);
305
if (vaultMiscItem) {
306
vaultMiscItem.ItemCount += miscItem.ItemCount;
307
} else {
308
guild.VaultMiscItems.push(miscItem);
309
}
310
}
311
};
312
267
313
export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => {
268
314
if (build.guildXpValue) {
269
315
guild.ClaimedXP ??= [];
@@ -362,6 +408,102 @@ export const removePigmentsFromGuildMembers = async (guildId: string | Types.Obj
362
408
}
363
409
};
364
410
411
export const processGuildTechProjectContributionsUpdate = async (
412
guild: TGuildDatabaseDocument,
413
techProject: ITechProjectDatabase
414
): Promise<void> => {
415
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
416
// This research is now fully funded.
417
418
if (
419
techProject.State == 0 &&
420
techProject.ItemType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/"
421
) {
422
guild.ActiveDojoColorResearch = "";
423
await removePigmentsFromGuildMembers(guild._id);
424
}
425
426
const recipe = ExportDojoRecipes.research[techProject.ItemType];
427
processFundedGuildTechProject(guild, techProject, recipe);
428
}
429
};
430
431
export const processFundedGuildTechProject = (
432
guild: TGuildDatabaseDocument,
433
techProject: ITechProjectDatabase,
434
recipe: IDojoResearch
435
): void => {
436
techProject.State = 1;
437
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
438
if (recipe.guildXpValue) {
439
guild.XP += recipe.guildXpValue;
440
}
441
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
442
};
443
444
export const setGuildTechLogState = (
445
guild: TGuildDatabaseDocument,
446
type: string,
447
state: number,
448
dateTime: Date | undefined = undefined
449
): boolean => {
450
guild.TechChanges ??= [];
451
const entry = guild.TechChanges.find(x => x.details == type);
452
if (entry) {
453
if (entry.entryType == state) {
454
return false;
455
}
456
entry.dateTime = dateTime;
457
entry.entryType = state;
458
} else {
459
guild.TechChanges.push({
460
dateTime: dateTime,
461
entryType: state,
462
details: type
463
});
464
}
465
return true;
466
};
467
468
const setGuildTier = async (guild: TGuildDatabaseDocument, newTier: number): Promise<void> => {
469
const oldTier = guild.Tier;
470
guild.Tier = newTier;
471
if (guild.TechProjects) {
472
for (const project of guild.TechProjects) {
473
if (project.State == 1) {
474
continue;
475
}
476
477
const meta = ExportDojoRecipes.research[project.ItemType];
478
479
{
480
const numContributed = scaleRequiredCount(oldTier, meta.price) - project.ReqCredits;
481
project.ReqCredits = scaleRequiredCount(newTier, meta.price) - numContributed;
482
if (project.ReqCredits < 0) {
483
guild.VaultRegularCredits ??= 0;
484
guild.VaultRegularCredits += project.ReqCredits * -1;
485
project.ReqCredits = 0;
486
}
487
}
488
489
for (let i = 0; i != project.ReqItems.length; ++i) {
490
const numContributed =
491
scaleRequiredCount(oldTier, meta.ingredients[i].ItemCount) - project.ReqItems[i].ItemCount;
492
project.ReqItems[i].ItemCount =
493
scaleRequiredCount(newTier, meta.ingredients[i].ItemCount) - numContributed;
494
if (project.ReqItems[i].ItemCount < 0) {
495
project.ReqItems[i].ItemCount *= -1;
496
addVaultMiscItems(guild, [project.ReqItems[i]]);
497
project.ReqItems[i].ItemCount = 0;
498
}
499
}
500
501
// Check if research is fully funded now due to lowered requirements.
502
await processGuildTechProjectContributionsUpdate(guild, project);
503
}
504
}
505
};
506
365
507
export const deleteGuild = async (guildId: Types.ObjectId): Promise<void> => {
366
508
await Guild.deleteOne({ _id: guildId });
367
509
await GuildMember.deleteMany({ guildId });