返回提交历史
Modified
config.json.example
+1
-0
Added
src/controllers/api/abortDojoComponentController.ts
+19
-0
Added
src/controllers/api/contributeToDojoComponentController.ts
+82
-0
Modified
src/controllers/api/guildTechController.ts
+1
-6
Modified
src/controllers/api/setDojoComponentMessageController.ts
+1
-1
Modified
src/controllers/api/startDojoRecipeController.ts
+15
-9
Modified
src/models/guildModel.ts
+2
-0
Modified
src/routes/api.ts
+4
-0
Modified
src/services/configService.ts
+1
-0
Modified
src/services/guildService.ts
+32
-17
Modified
static/webui/index.html
+4
-0
Modified
static/webui/translations/en.js
+1
-0
Modified
static/webui/translations/ru.js
+1
-0
XFEstudio/XFESpaceNinjaServer
feat: dojo component "collecting materials" stage (#1071)
Closes #1051 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1071 Co-authored-by: Sainan <sainan@calamity.inc> Co-committed-by: Sainan <sainan@calamity.inc>
67a275a0
代码差异
13 个文件
+164
-33
@@ -32,6 +32,7 @@
32
32
"unlockArcanesEverywhere": true,
33
33
"noDailyStandingLimits": true,
34
34
"instantResourceExtractorDrones": false,
35
"noDojoRoomBuildStage": true,
35
36
"noDojoResearchCosts": true,
36
37
"noDojoResearchTime": true,
37
38
"spoofMasteryRank": -1
@@ -0,0 +1,19 @@
1
import { getDojoClient, getGuildForRequestEx } from "@/src/services/guildService";
2
import { getInventory } from "@/src/services/inventoryService";
3
import { getAccountIdForRequest } from "@/src/services/loginService";
4
import { RequestHandler } from "express";
5
6
export const abortDojoComponentController: RequestHandler = async (req, res) => {
7
const accountId = await getAccountIdForRequest(req);
8
const inventory = await getInventory(accountId);
9
const guild = await getGuildForRequestEx(req, inventory);
10
const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
11
// TODO: Move already-contributed credits & items to the clan vault
12
guild.DojoComponents.pull({ _id: request.ComponentId });
13
await guild.save();
14
res.json(getDojoClient(guild, 0));
15
};
16
17
export interface IAbortDojoComponentRequest {
18
ComponentId: string;
19
}
@@ -0,0 +1,82 @@
1
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
2
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
3
import { getAccountIdForRequest } from "@/src/services/loginService";
4
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
5
import { IInventoryChanges } from "@/src/types/purchaseTypes";
6
import { RequestHandler } from "express";
7
import { ExportDojoRecipes } from "warframe-public-export-plus";
8
9
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
10
const accountId = await getAccountIdForRequest(req);
11
const inventory = await getInventory(accountId);
12
const guild = await getGuildForRequestEx(req, inventory);
13
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
14
const component = guild.DojoComponents.id(request.ComponentId)!;
15
const componentMeta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
16
17
component.RegularCredits ??= 0;
18
if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(componentMeta.price)) {
19
request.RegularCredits = scaleRequiredCount(componentMeta.price) - component.RegularCredits;
20
}
21
component.RegularCredits += request.RegularCredits;
22
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, request.RegularCredits, false);
23
24
component.MiscItems ??= [];
25
const miscItemChanges: IMiscItem[] = [];
26
for (const ingredientContribution of request.IngredientContributions) {
27
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
28
if (componentMiscItem) {
29
const ingredientMeta = componentMeta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
30
if (
31
componentMiscItem.ItemCount + ingredientContribution.ItemCount >
32
scaleRequiredCount(ingredientMeta.ItemCount)
33
) {
34
ingredientContribution.ItemCount =
35
scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
36
}
37
componentMiscItem.ItemCount += ingredientContribution.ItemCount;
38
} else {
39
component.MiscItems.push(ingredientContribution);
40
}
41
miscItemChanges.push({
42
ItemType: ingredientContribution.ItemType,
43
ItemCount: ingredientContribution.ItemCount * -1
44
});
45
}
46
addMiscItems(inventory, miscItemChanges);
47
inventoryChanges.MiscItems = miscItemChanges;
48
49
if (component.RegularCredits >= scaleRequiredCount(componentMeta.price)) {
50
let fullyFunded = true;
51
for (const ingredient of componentMeta.ingredients) {
52
const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
53
if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
54
fullyFunded = false;
55
break;
56
}
57
}
58
if (fullyFunded) {
59
component.RegularCredits = undefined;
60
component.MiscItems = undefined;
61
component.CompletionTime = new Date(Date.now() + componentMeta.time * 1000);
62
}
63
}
64
65
await guild.save();
66
await inventory.save();
67
res.json({
68
...getDojoClient(guild, 0, component._id),
69
InventoryChanges: inventoryChanges
70
});
71
};
72
73
export interface IContributeToDojoComponentRequest {
74
ComponentId: string;
75
IngredientContributions: {
76
ItemType: string;
77
ItemCount: number;
78
}[];
79
RegularCredits: number;
80
VaultIngredientContributions: [];
81
VaultCredits: number;
82
}
@@ -1,5 +1,5 @@
1
1
import { RequestHandler } from "express";
2
import { getGuildForRequestEx } from "@/src/services/guildService";
2
import { getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
3
3
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
4
4
import { getAccountIdForRequest } from "@/src/services/loginService";
5
5
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
@@ -130,8 +130,3 @@ interface IGuildTechContributeFields {
130
130
VaultCredits: number;
131
131
VaultMiscItems: IMiscItem[];
132
132
}
133
134
const scaleRequiredCount = (count: number): number => {
135
// The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
136
return Math.max(1, Math.trunc(count / 100));
137
};
@@ -12,7 +12,7 @@ export const setDojoComponentMessageController: RequestHandler = async (req, res
12
12
component.Message = payload.Message;
13
13
}
14
14
await guild.save();
15
res.json(getDojoClient(guild, 1));
15
res.json(getDojoClient(guild, 0, component._id));
16
16
};
17
17
18
18
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
@@ -3,6 +3,7 @@ import { IDojoComponentClient } from "@/src/types/guildTypes";
3
3
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
4
4
import { Types } from "mongoose";
5
5
import { ExportDojoRecipes } from "warframe-public-export-plus";
6
import { config } from "@/src/services/configService";
6
7
7
8
interface IStartDojoRecipeRequest {
8
9
PlacedComponent: IDojoComponentClient;
@@ -20,15 +21,20 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
20
21
guild.DojoEnergy += room.energy;
21
22
}
22
23
23
guild.DojoComponents.push({
24
_id: new Types.ObjectId(),
25
pf: request.PlacedComponent.pf,
26
ppf: request.PlacedComponent.ppf,
27
pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
28
op: request.PlacedComponent.op,
29
pp: request.PlacedComponent.pp,
30
CompletionTime: new Date(Date.now()) // TOOD: Omit this field & handle the "Collecting Materials" state.
31
});
24
const component =
25
guild.DojoComponents[
26
guild.DojoComponents.push({
27
_id: new Types.ObjectId(),
28
pf: request.PlacedComponent.pf,
29
ppf: request.PlacedComponent.ppf,
30
pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
31
op: request.PlacedComponent.op,
32
pp: request.PlacedComponent.pp
33
}) - 1
34
];
35
if (config.noDojoRoomBuildStage) {
36
component.CompletionTime = new Date(Date.now());
37
}
32
38
await guild.save();
33
39
res.json(getDojoClient(guild, 0));
34
40
};
@@ -16,6 +16,8 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
16
16
pp: String,
17
17
Name: String,
18
18
Message: String,
19
RegularCredits: Number,
20
MiscItems: { type: [typeCountSchema], default: undefined },
19
21
CompletionTime: Date
20
22
});
21
23
@@ -1,5 +1,6 @@
1
1
import express from "express";
2
2
import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandonLibraryDailyTaskController";
3
import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController";
3
4
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
4
5
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
5
6
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
@@ -11,6 +12,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl
11
12
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
12
13
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
13
14
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
15
import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController";
14
16
import { createGuildController } from "@/src/controllers/api/createGuildController";
15
17
import { creditsController } from "@/src/controllers/api/creditsController";
16
18
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
@@ -135,6 +137,7 @@ apiRouter.get("/surveys.php", surveysController);
135
137
apiRouter.get("/updateSession.php", updateSessionGetController);
136
138
137
139
// post
140
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
138
141
apiRouter.post("/activateRandomMod.php", activateRandomModController);
139
142
apiRouter.post("/addFriendImage.php", addFriendImageController);
140
143
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
@@ -144,6 +147,7 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
144
147
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
145
148
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
146
149
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
150
apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
147
151
apiRouter.post("/createGuild.php", createGuildController);
148
152
apiRouter.post("/drones.php", dronesController);
149
153
apiRouter.post("/endlessXp.php", endlessXpController);
@@ -58,6 +58,7 @@ interface IConfig {
58
58
unlockArcanesEverywhere?: boolean;
59
59
noDailyStandingLimits?: boolean;
60
60
instantResourceExtractorDrones?: boolean;
61
noDojoRoomBuildStage?: boolean;
61
62
noDojoResearchCosts?: boolean;
62
63
noDojoResearchTime?: boolean;
63
64
spoofMasteryRank?: number;
@@ -5,6 +5,7 @@ import { Guild, TGuildDatabaseDocument } from "@/src/models/guildModel";
5
5
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
6
6
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
7
7
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
8
import { Types } from "mongoose";
8
9
9
10
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
10
11
const accountId = await getAccountIdForRequest(req);
@@ -27,7 +28,11 @@ export const getGuildForRequestEx = async (
27
28
return guild;
28
29
};
29
30
30
export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): IDojoClient => {
31
export const getDojoClient = (
32
guild: TGuildDatabaseDocument,
33
status: number,
34
componentId: Types.ObjectId | undefined = undefined
35
): IDojoClient => {
31
36
const dojo: IDojoClient = {
32
37
_id: { $oid: guild._id.toString() },
33
38
Name: guild.Name,
@@ -41,23 +46,33 @@ export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): ID
41
46
DojoComponents: []
42
47
};
43
48
guild.DojoComponents.forEach(dojoComponent => {
44
const clientComponent: IDojoComponentClient = {
45
id: toOid(dojoComponent._id),
46
pf: dojoComponent.pf,
47
ppf: dojoComponent.ppf,
48
Name: dojoComponent.Name,
49
Message: dojoComponent.Message,
50
DecoCapacity: 600
51
};
52
if (dojoComponent.pi) {
53
clientComponent.pi = toOid(dojoComponent.pi);
54
clientComponent.op = dojoComponent.op!;
55
clientComponent.pp = dojoComponent.pp!;
49
if (!componentId || componentId == dojoComponent._id) {
50
const clientComponent: IDojoComponentClient = {
51
id: toOid(dojoComponent._id),
52
pf: dojoComponent.pf,
53
ppf: dojoComponent.ppf,
54
Name: dojoComponent.Name,
55
Message: dojoComponent.Message,
56
DecoCapacity: 600
57
};
58
if (dojoComponent.pi) {
59
clientComponent.pi = toOid(dojoComponent.pi);
60
clientComponent.op = dojoComponent.op!;
61
clientComponent.pp = dojoComponent.pp!;
62
}
63
if (dojoComponent.CompletionTime) {
64
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
65
} else {
66
clientComponent.RegularCredits = dojoComponent.RegularCredits;
67
clientComponent.MiscItems = dojoComponent.MiscItems;
68
}
69
dojo.DojoComponents.push(clientComponent);
56
70
}
57
if (dojoComponent.CompletionTime) {
58
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
59
}
60
dojo.DojoComponents.push(clientComponent);
61
71
});
62
72
return dojo;
63
73
};
74
75
export const scaleRequiredCount = (count: number): number => {
76
// The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
77
return Math.max(1, Math.trunc(count / 100));
78
};
@@ -521,6 +521,10 @@
521
521
<input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
522
522
<label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label>
523
523
</div>
524
<div class="form-check">
525
<input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
526
<label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>
527
</div>
524
528
<div class="form-check">
525
529
<input class="form-check-input" type="checkbox" id="noDojoResearchCosts" />
526
530
<label class="form-check-label" for="noDojoResearchCosts" data-loc="cheats_noDojoResearchCosts"></label>
@@ -112,6 +112,7 @@ dict = {
112
112
cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`,
113
113
cheats_noDailyStandingLimits: `No Daily Standing Limits`,
114
114
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
115
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
115
116
cheats_noDojoResearchCosts: `No Dojo Research Costs`,
116
117
cheats_noDojoResearchTime: `No Dojo Research Time`,
117
118
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,