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: 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
Sainan <sainan@calamity.inc>
提交于

代码差异

13 个文件 +164 -33
Modified config.json.example +1 -0
@@ -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
Added src/controllers/api/abortDojoComponentController.ts +19 -0
@@ -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 }
Added src/controllers/api/contributeToDojoComponentController.ts +82 -0
@@ -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 }
Modified src/controllers/api/guildTechController.ts +1 -6
@@ -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 };
Modified src/controllers/api/setDojoComponentMessageController.ts +1 -1
@@ -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 };
Modified src/controllers/api/startDojoRecipeController.ts +15 -9
@@ -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 };
Modified src/models/guildModel.ts +2 -0
@@ -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
Modified src/routes/api.ts +4 -0
@@ -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);
Modified src/services/configService.ts +1 -0
@@ -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;
Modified src/services/guildService.ts +32 -17
@@ -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 };
Modified static/webui/index.html +4 -0
@@ -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>
Modified static/webui/translations/en.js +1 -0
@@ -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)`,
Modified static/webui/translations/ru.js +1 -0