返回提交历史
Modified
config.json.example
+1
-0
Modified
package-lock.json
+4
-4
Modified
package.json
+1
-1
Added
src/controllers/api/contributeGuildClassController.ts
+66
-0
Modified
src/controllers/api/contributeToDojoComponentController.ts
+9
-3
Modified
src/controllers/api/dojoComponentRushController.ts
+2
-2
Modified
src/controllers/api/getGuildController.ts
+15
-2
Modified
src/controllers/api/guildTechController.ts
+11
-3
Modified
src/controllers/api/startDojoRecipeController.ts
+4
-1
Modified
src/models/guildModel.ts
+8
-1
Modified
src/routes/api.ts
+2
-0
Modified
src/services/configService.ts
+1
-0
Modified
src/services/guildService.ts
+11
-1
Modified
src/types/guildTypes.ts
+12
-4
Modified
static/webui/index.html
+4
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/fr.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
XFEstudio/SpaceNinjaServer
feat: clan xp (#1100)
Closes #690 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1100
f7c2c744
代码差异
18 个文件
+154
-22
@@ -34,5 +34,6 @@
34
34
"fastDojoRoomDestruction": true,
35
35
"noDojoResearchCosts": true,
36
36
"noDojoResearchTime": true,
37
"fastClanAscension": true,
37
38
"spoofMasteryRank": -1
38
39
}
@@ -12,7 +12,7 @@
12
12
"copyfiles": "^2.4.1",
13
13
"express": "^5",
14
14
"mongoose": "^8.11.0",
15
"warframe-public-export-plus": "^0.5.39",
15
"warframe-public-export-plus": "^0.5.40",
16
16
"warframe-riven-info": "^0.1.2",
17
17
"winston": "^3.17.0",
18
18
"winston-daily-rotate-file": "^5.0.0"
@@ -4083,9 +4083,9 @@
4083
4083
}
4084
4084
},
4085
4085
"node_modules/warframe-public-export-plus": {
4086
"version": "0.5.39",
4087
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.39.tgz",
4088
"integrity": "sha512-sEGZedtW4I/M2ceoDs6MQ5eHD7sJgv1KRNLt8BWByXLuDa7qTR3Y9px5TGxqt/rBHKGUyPO1LUxu4bDGZi6yXw=="
4086
"version": "0.5.40",
4087
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.40.tgz",
4088
"integrity": "sha512-/qr46LE/KqDdEkW4z52EG0vZP0Z8U26FscFJ2G5K5ewbQdlSVxtf5fpOnzRkAO7jWWKfgoqx7l5WUgaLSPDj0g=="
4089
4089
},
4090
4090
"node_modules/warframe-riven-info": {
4091
4091
"version": "0.1.2",
@@ -17,7 +17,7 @@
17
17
"copyfiles": "^2.4.1",
18
18
"express": "^5",
19
19
"mongoose": "^8.11.0",
20
"warframe-public-export-plus": "^0.5.39",
20
"warframe-public-export-plus": "^0.5.40",
21
21
"warframe-riven-info": "^0.1.2",
22
22
"winston": "^3.17.0",
23
23
"winston-daily-rotate-file": "^5.0.0"
@@ -0,0 +1,66 @@
1
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
2
import { getJSONfromString } from "@/src/helpers/stringHelpers";
3
import { Guild } from "@/src/models/guildModel";
4
import { config } from "@/src/services/configService";
5
import { getInventory } from "@/src/services/inventoryService";
6
import { getAccountIdForRequest } from "@/src/services/loginService";
7
import { RequestHandler } from "express";
8
import { Types } from "mongoose";
9
10
export const contributeGuildClassController: RequestHandler = async (req, res) => {
11
const accountId = await getAccountIdForRequest(req);
12
const payload = getJSONfromString<IContributeGuildClassRequest>(String(req.body));
13
const guild = (await Guild.findOne({ _id: payload.GuildId }))!;
14
15
// First contributor initiates ceremony and locks the pending class.
16
if (!guild.CeremonyContributors) {
17
guild.CeremonyContributors = [];
18
guild.CeremonyClass = guildXpToClass(guild.XP);
19
guild.CeremonyEndo = 0;
20
for (let i = guild.Class; i != guild.CeremonyClass; ++i) {
21
guild.CeremonyEndo += (i + 1) * 1000;
22
}
23
}
24
25
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
26
27
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
28
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
29
guild.Class = guild.CeremonyClass!;
30
guild.CeremonyClass = undefined;
31
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
32
}
33
34
await guild.save();
35
36
// Either way, endo is given to the contributor.
37
const inventory = await getInventory(accountId, "FusionPoints");
38
inventory.FusionPoints += guild.CeremonyEndo!;
39
await inventory.save();
40
41
res.json({
42
NumContributors: guild.CeremonyContributors.length,
43
FusionPointReward: guild.CeremonyEndo,
44
Class: guild.Class,
45
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
46
});
47
};
48
49
interface IContributeGuildClassRequest {
50
GuildId: string;
51
RequiredContributors: number;
52
}
53
54
const guildXpToClass = (xp: number): number => {
55
const cummXp = [
56
0, 11000, 34000, 69000, 114000, 168000, 231000, 302000, 381000, 68000, 563000, 665000, 774000, 891000
57
];
58
let highest = 0;
59
for (let i = 0; i != cummXp.length; ++i) {
60
if (xp < cummXp[i]) {
61
break;
62
}
63
highest = i;
64
}
65
return highest;
66
};
@@ -1,13 +1,18 @@
1
1
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
2
2
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
3
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
3
import {
4
getDojoClient,
5
getGuildForRequestEx,
6
processDojoBuildMaterialsGathered,
7
scaleRequiredCount
8
} from "@/src/services/guildService";
4
9
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
5
10
import { getAccountIdForRequest } from "@/src/services/loginService";
6
11
import { IDojoContributable } from "@/src/types/guildTypes";
7
12
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
8
13
import { IInventoryChanges } from "@/src/types/purchaseTypes";
9
14
import { RequestHandler } from "express";
10
import { ExportDojoRecipes, IDojoRecipe } from "warframe-public-export-plus";
15
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
11
16
12
17
interface IContributeToDojoComponentRequest {
13
18
ComponentId: string;
@@ -57,7 +62,7 @@ const processContribution = (
57
62
request: IContributeToDojoComponentRequest,
58
63
inventory: TInventoryDatabaseDocument,
59
64
inventoryChanges: IInventoryChanges,
60
meta: IDojoRecipe,
65
meta: IDojoBuild,
61
66
component: IDojoContributable
62
67
): void => {
63
68
component.RegularCredits ??= 0;
@@ -134,6 +139,7 @@ const processContribution = (
134
139
}
135
140
if (fullyFunded) {
136
141
component.CompletionTime = new Date(Date.now() + meta.time * 1000);
142
processDojoBuildMaterialsGathered(guild, meta);
137
143
}
138
144
}
139
145
};
@@ -3,7 +3,7 @@ import { getInventory, updateCurrency } from "@/src/services/inventoryService";
3
3
import { getAccountIdForRequest } from "@/src/services/loginService";
4
4
import { IDojoContributable } from "@/src/types/guildTypes";
5
5
import { RequestHandler } from "express";
6
import { ExportDojoRecipes, IDojoRecipe } from "warframe-public-export-plus";
6
import { ExportDojoRecipes, IDojoBuild } from "warframe-public-export-plus";
7
7
8
8
interface IDojoComponentRushRequest {
9
9
DecoType?: string;
@@ -40,7 +40,7 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
40
40
});
41
41
};
42
42
43
const processContribution = (component: IDojoContributable, meta: IDojoRecipe, platinumDonated: number): void => {
43
const processContribution = (component: IDojoContributable, meta: IDojoBuild, platinumDonated: number): void => {
44
44
const fullPlatinumCost = scaleRequiredCount(meta.skipTimePrice);
45
45
const fullDurationSeconds = meta.time;
46
46
const secondsPerPlatinum = fullDurationSeconds / fullPlatinumCost;
@@ -2,8 +2,9 @@ import { RequestHandler } from "express";
2
2
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
3
3
import { Guild } from "@/src/models/guildModel";
4
4
import { getAccountIdForRequest } from "@/src/services/loginService";
5
import { toOid } from "@/src/helpers/inventoryHelpers";
5
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
6
6
import { getGuildVault } from "@/src/services/guildService";
7
import { logger } from "@/src/utils/logger";
7
8
8
9
const getGuildController: RequestHandler = async (req, res) => {
9
10
const accountId = await getAccountIdForRequest(req);
@@ -15,6 +16,13 @@ const getGuildController: RequestHandler = async (req, res) => {
15
16
if (inventory.GuildId) {
16
17
const guild = await Guild.findOne({ _id: inventory.GuildId });
17
18
if (guild) {
19
if (guild.CeremonyResetDate && Date.now() >= guild.CeremonyResetDate.getTime()) {
20
logger.debug(`ascension ceremony is over`);
21
guild.CeremonyEndo = undefined;
22
guild.CeremonyContributors = undefined;
23
guild.CeremonyResetDate = undefined;
24
await guild.save();
25
}
18
26
res.json({
19
27
_id: toOid(guild._id),
20
28
Name: guild.Name,
@@ -64,7 +72,12 @@ const getGuildController: RequestHandler = async (req, res) => {
64
72
}
65
73
],
66
74
Tier: 1,
67
Vault: getGuildVault(guild)
75
Vault: getGuildVault(guild),
76
Class: guild.Class,
77
XP: guild.XP,
78
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)),
79
NumContributors: guild.CeremonyContributors?.length ?? 0,
80
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
68
81
});
69
82
return;
70
83
}
@@ -7,6 +7,7 @@ import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
7
7
import { IInventoryChanges } from "@/src/types/purchaseTypes";
8
8
import { config } from "@/src/services/configService";
9
9
import { ITechProjectDatabase } from "@/src/types/guildTypes";
10
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
10
11
11
12
export const guildTechController: RequestHandler = async (req, res) => {
12
13
const accountId = await getAccountIdForRequest(req);
@@ -35,7 +36,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
35
36
}) - 1
36
37
];
37
38
if (config.noDojoResearchCosts) {
38
processFundedProject(techProject, recipe);
39
processFundedProject(guild, techProject, recipe);
39
40
}
40
41
}
41
42
await guild.save();
@@ -93,7 +94,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
93
94
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
94
95
// This research is now fully funded.
95
96
const recipe = ExportDojoRecipes.research[data.RecipeType!];
96
processFundedProject(techProject, recipe);
97
processFundedProject(guild, techProject, recipe);
97
98
}
98
99
99
100
await guild.save();
@@ -131,9 +132,16 @@ export const guildTechController: RequestHandler = async (req, res) => {
131
132
}
132
133
};
133
134
134
const processFundedProject = (techProject: ITechProjectDatabase, recipe: IDojoResearch): void => {
135
const processFundedProject = (
136
guild: TGuildDatabaseDocument,
137
techProject: ITechProjectDatabase,
138
recipe: IDojoResearch
139
): void => {
135
140
techProject.State = 1;
136
141
techProject.CompletionDate = new Date(new Date().getTime() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
142
if (recipe.guildXpValue) {
143
guild.XP += recipe.guildXpValue;
144
}
137
145
};
138
146
139
147
type TGuildTechRequest = {
@@ -1,6 +1,6 @@
1
1
import { RequestHandler } from "express";
2
2
import { IDojoComponentClient } from "@/src/types/guildTypes";
3
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
3
import { getDojoClient, getGuildForRequest, processDojoBuildMaterialsGathered } from "@/src/services/guildService";
4
4
import { Types } from "mongoose";
5
5
import { ExportDojoRecipes } from "warframe-public-export-plus";
6
6
import { config } from "@/src/services/configService";
@@ -35,6 +35,9 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
35
35
];
36
36
if (config.noDojoRoomBuildStage) {
37
37
component.CompletionTime = new Date(Date.now());
38
if (room) {
39
processDojoBuildMaterialsGathered(guild, room);
40
}
38
41
}
39
42
await guild.save();
40
43
res.json(await getDojoClient(guild, 0));
@@ -70,7 +70,14 @@ const guildSchema = new Schema<IGuildDatabase>(
70
70
VaultMiscItems: { type: [typeCountSchema], default: undefined },
71
71
VaultShipDecorations: { type: [typeCountSchema], default: undefined },
72
72
VaultFusionTreasures: { type: [fusionTreasuresSchema], default: undefined },
73
TechProjects: { type: [techProjectSchema], default: undefined }
73
TechProjects: { type: [techProjectSchema], default: undefined },
74
Class: { type: Number, default: 0 },
75
XP: { type: Number, default: 0 },
76
ClaimedXP: { type: [String], default: undefined },
77
CeremonyClass: Number,
78
CeremonyContributors: { type: [Types.ObjectId], default: undefined },
79
CeremonyResetDate: Date,
80
CeremonyEndo: Number
74
81
},
75
82
{ id: false }
76
83
);
@@ -13,6 +13,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl
13
13
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
14
14
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
15
15
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
16
import { contributeGuildClassController } from "@/src/controllers/api/contributeGuildClassController";
16
17
import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController";
17
18
import { contributeToVaultController } from "@/src/controllers/api/contributeToVaultController";
18
19
import { createGuildController } from "@/src/controllers/api/createGuildController";
@@ -153,6 +154,7 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
153
154
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
154
155
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
155
156
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
157
apiRouter.post("/contributeGuildClass.php", contributeGuildClassController);
156
158
apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
157
159
apiRouter.post("/contributeToVault.php", contributeToVaultController);
158
160
apiRouter.post("/createGuild.php", createGuildController);
@@ -60,6 +60,7 @@ interface IConfig {
60
60
fastDojoRoomDestruction?: boolean;
61
61
noDojoResearchCosts?: boolean;
62
62
noDojoResearchTime?: boolean;
63
fastClanAscension?: boolean;
63
64
spoofMasteryRank?: number;
64
65
}
65
66