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: 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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

6 个文件 +205 -90
Modified src/controllers/api/abortDojoComponentController.ts +1 -1
@@ -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();
Modified src/controllers/api/contributeToDojoComponentController.ts +12 -9
@@ -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 }
Modified src/controllers/api/contributeToVaultController.ts +10 -4
@@ -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 }
Modified src/controllers/api/dojoComponentRushController.ts +10 -5
@@ -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(
Modified src/controllers/api/guildTechController.ts +14 -55
@@ -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
Modified src/services/guildService.ts +158 -16
@@ -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 });