返回提交历史
Added
src/controllers/api/gardeningController.ts
+84
-0
Modified
src/controllers/api/getShipController.ts
+10
-3
Modified
src/models/personalRoomsModel.ts
+42
-7
Modified
src/routes/api.ts
+2
-0
Modified
src/services/inventoryService.ts
+25
-0
Modified
src/services/personalRoomsService.ts
+68
-2
Modified
src/types/missionTypes.ts
+2
-0
Modified
src/types/personalRoomsTypes.ts
+4
-4
Modified
src/types/shipTypes.ts
+25
-11
XFEstudio/XFESpaceNinjaServer
feat: gardening (#1849)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1849 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
a90d3a51
代码差异
9 个文件
+262
-27
@@ -0,0 +1,84 @@
1
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
2
import { getJSONfromString } from "@/src/helpers/stringHelpers";
3
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
4
import { toStoreItem } from "@/src/services/itemDataService";
5
import { getAccountIdForRequest } from "@/src/services/loginService";
6
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
7
import { IMongoDate } from "@/src/types/commonTypes";
8
import { IMissionReward } from "@/src/types/missionTypes";
9
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
10
import { IInventoryChanges } from "@/src/types/purchaseTypes";
11
import { IGardeningClient } from "@/src/types/shipTypes";
12
import { RequestHandler } from "express";
13
import { dict_en, ExportResources } from "warframe-public-export-plus";
14
15
export const gardeningController: RequestHandler = async (req, res) => {
16
const data = getJSONfromString<IGardeningRequest>(String(req.body));
17
if (data.Mode != "HarvestAll") {
18
throw new Error(`unexpected gardening mode: ${data.Mode}`);
19
}
20
21
const accountId = await getAccountIdForRequest(req);
22
const [inventory, personalRooms] = await Promise.all([
23
getInventory(accountId, "MiscItems"),
24
getPersonalRooms(accountId, "Apartment")
25
]);
26
27
// Harvest plants
28
const inventoryChanges: IInventoryChanges = {};
29
const rewards: Record<string, IMissionReward[][]> = {};
30
for (const planter of personalRooms.Apartment.Gardening.Planters) {
31
rewards[planter.Name] = [];
32
for (const plant of planter.Plants) {
33
const itemType =
34
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
35
plant.PlantType.substring(plant.PlantType.length - 1);
36
const itemCount = Math.random() < 0.775 ? 2 : 4;
37
38
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
39
40
rewards[planter.Name].push([
41
{
42
StoreItem: toStoreItem(itemType),
43
TypeName: itemType,
44
ItemCount: itemCount,
45
DailyCooldown: false,
46
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
47
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
48
ProductCategory: "MiscItems"
49
}
50
]);
51
}
52
}
53
54
// Refresh garden
55
personalRooms.Apartment.Gardening = createGarden();
56
57
await Promise.all([inventory.save(), personalRooms.save()]);
58
59
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
60
const plant = planter.Plants[planter.Plants.length - 1];
61
res.json({
62
GardenTagName: planter.Name,
63
PlantType: plant.PlantType,
64
PlotIndex: plant.PlotIndex,
65
EndTime: toMongoDate(plant.EndTime),
66
InventoryChanges: inventoryChanges,
67
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
68
Rewards: rewards
69
} satisfies IGardeningResponse);
70
};
71
72
interface IGardeningRequest {
73
Mode: string;
74
}
75
76
interface IGardeningResponse {
77
GardenTagName: string;
78
PlantType: string;
79
PlotIndex: number;
80
EndTime: IMongoDate;
81
InventoryChanges: IInventoryChanges;
82
Gardening: IGardeningClient;
83
Rewards: Record<string, IMissionReward[][]>;
84
}
@@ -2,17 +2,24 @@ import { RequestHandler } from "express";
2
2
import { config } from "@/src/services/configService";
3
3
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
4
4
import { getAccountIdForRequest } from "@/src/services/loginService";
5
import { getPersonalRooms } from "@/src/services/personalRoomsService";
5
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
6
6
import { getShip } from "@/src/services/shipService";
7
7
import { toOid } from "@/src/helpers/inventoryHelpers";
8
8
import { IGetShipResponse } from "@/src/types/shipTypes";
9
import { IPersonalRooms } from "@/src/types/personalRoomsTypes";
9
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
10
10
import { getLoadout } from "@/src/services/loadoutService";
11
11
12
12
export const getShipController: RequestHandler = async (req, res) => {
13
13
const accountId = await getAccountIdForRequest(req);
14
14
const personalRoomsDb = await getPersonalRooms(accountId);
15
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
15
16
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
17
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
18
personalRoomsDb.Apartment.Gardening = createGarden();
19
await personalRoomsDb.save();
20
}
21
22
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
16
23
const loadout = await getLoadout(accountId);
17
24
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
18
25
@@ -1,14 +1,17 @@
1
import { toOid } from "@/src/helpers/inventoryHelpers";
1
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
2
2
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
3
3
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
4
4
import {
5
5
IFavouriteLoadoutDatabase,
6
IGardening,
6
IGardeningDatabase,
7
7
IPlacedDecosDatabase,
8
8
IPictureFrameInfo,
9
9
IRoom,
10
10
ITailorShopDatabase,
11
IApartmentDatabase
11
IApartmentDatabase,
12
IPlanterDatabase,
13
IPlantDatabase,
14
IPlantClient
12
15
} from "@/src/types/shipTypes";
13
16
import { Schema, model } from "mongoose";
14
17
@@ -77,15 +80,45 @@ favouriteLoadoutSchema.set("toJSON", {
77
80
}
78
81
});
79
82
80
const gardeningSchema = new Schema<IGardening>({
81
Planters: [Schema.Types.Mixed] //TODO: add when implementing gardening
83
const plantSchema = new Schema<IPlantDatabase>(
84
{
85
PlantType: String,
86
EndTime: Date,
87
PlotIndex: Number
88
},
89
{ _id: false }
90
);
91
92
plantSchema.set("toJSON", {
93
virtuals: true,
94
transform(_doc, obj) {
95
const client = obj as IPlantClient;
96
const db = obj as IPlantDatabase;
97
98
client.EndTime = toMongoDate(db.EndTime);
99
}
82
100
});
83
101
102
const planterSchema = new Schema<IPlanterDatabase>(
103
{
104
Name: { type: String, required: true },
105
Plants: { type: [plantSchema], default: [] }
106
},
107
{ _id: false }
108
);
109
110
const gardeningSchema = new Schema<IGardeningDatabase>(
111
{
112
Planters: { type: [planterSchema], default: [] }
113
},
114
{ _id: false }
115
);
116
84
117
const apartmentSchema = new Schema<IApartmentDatabase>(
85
118
{
86
119
Rooms: [roomSchema],
87
120
FavouriteLoadouts: [favouriteLoadoutSchema],
88
Gardening: gardeningSchema // TODO: ensure this is correct
121
Gardening: gardeningSchema
89
122
},
90
123
{ _id: false }
91
124
);
@@ -98,7 +131,9 @@ const apartmentDefault: IApartmentDatabase = {
98
131
{ Name: "DuviriHallway", MaxCapacity: 1600 }
99
132
],
100
133
FavouriteLoadouts: [],
101
Gardening: {}
134
Gardening: {
135
Planters: []
136
}
102
137
};
103
138
104
139
const orbiterSchema = new Schema<IOrbiter>(
@@ -48,6 +48,7 @@ import { findSessionsController } from "@/src/controllers/api/findSessionsContro
48
48
import { fishmongerController } from "@/src/controllers/api/fishmongerController";
49
49
import { focusController } from "@/src/controllers/api/focusController";
50
50
import { fusionTreasuresController } from "@/src/controllers/api/fusionTreasuresController";
51
import { gardeningController } from "@/src/controllers/api/gardeningController";
51
52
import { genericUpdateController } from "@/src/controllers/api/genericUpdateController";
52
53
import { getAllianceController } from "@/src/controllers/api/getAllianceController";
53
54
import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController";
@@ -240,6 +241,7 @@ apiRouter.post("/findSessions.php", findSessionsController);
240
241
apiRouter.post("/fishmonger.php", fishmongerController);
241
242
apiRouter.post("/focus.php", focusController);
242
243
apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
244
apiRouter.post("/gardening.php", gardeningController);
243
245
apiRouter.post("/genericUpdate.php", genericUpdateController);
244
246
apiRouter.post("/getAlliance.php", getAllianceController);
245
247
apiRouter.post("/getFriends.php", getFriendsController);
@@ -213,6 +213,15 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
213
213
for (const key in delta) {
214
214
if (!(key in InventoryChanges)) {
215
215
InventoryChanges[key] = delta[key];
216
} else if (key == "MiscItems") {
217
for (const deltaItem of delta[key]!) {
218
const existing = InventoryChanges[key]!.find(x => x.ItemType == deltaItem.ItemType);
219
if (existing) {
220
existing.ItemCount += deltaItem.ItemCount;
221
} else {
222
InventoryChanges[key]!.push(deltaItem);
223
}
224
}
216
225
} else if (Array.isArray(delta[key])) {
217
226
const left = InventoryChanges[key] as object[];
218
227
const right: object[] = delta[key];
@@ -1468,6 +1477,22 @@ export const addGearExpByCategory = (
1468
1477
});
1469
1478
};
1470
1479
1480
export const addMiscItem = (
1481
inventory: TInventoryDatabaseDocument,
1482
type: string,
1483
count: number,
1484
inventoryChanges: IInventoryChanges
1485
): void => {
1486
const miscItemChanges: IMiscItem[] = [
1487
{
1488
ItemType: type,
1489
ItemCount: count
1490
}
1491
];
1492
addMiscItems(inventory, miscItemChanges);
1493
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
1494
};
1495
1471
1496
export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray: IMiscItem[]): void => {
1472
1497
const { MiscItems } = inventory;
1473
1498
@@ -1,9 +1,14 @@
1
1
import { PersonalRooms } from "@/src/models/personalRoomsModel";
2
2
import { addItem, getInventory } from "@/src/services/inventoryService";
3
3
import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
4
import { IGardeningDatabase } from "../types/shipTypes";
5
import { getRandomElement } from "./rngService";
4
6
5
export const getPersonalRooms = async (accountId: string): Promise<TPersonalRoomsDatabaseDocument> => {
6
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId });
7
export const getPersonalRooms = async (
8
accountId: string,
9
projection?: string
10
): Promise<TPersonalRoomsDatabaseDocument> => {
11
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId }, projection);
7
12
8
13
if (!personalRooms) {
9
14
throw new Error(`personal rooms not found for account ${accountId}`);
@@ -25,3 +30,64 @@ export const updateShipFeature = async (accountId: string, shipFeature: string):
25
30
await addItem(inventory, shipFeature, -1);
26
31
await inventory.save();
27
32
};
33
34
export const createGarden = (): IGardeningDatabase => {
35
const plantTypes = [
36
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantA",
37
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantB",
38
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantC",
39
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantD",
40
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantE",
41
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantF"
42
];
43
const endTime = new Date((Math.trunc(Date.now() / 1000) + 79200) * 1000); // Plants will take 22 hours to grow
44
return {
45
Planters: [
46
{
47
Name: "Garden0",
48
Plants: [
49
{
50
PlantType: getRandomElement(plantTypes),
51
EndTime: endTime,
52
PlotIndex: 0
53
},
54
{
55
PlantType: getRandomElement(plantTypes),
56
EndTime: endTime,
57
PlotIndex: 1
58
}
59
]
60
},
61
{
62
Name: "Garden1",
63
Plants: [
64
{
65
PlantType: getRandomElement(plantTypes),
66
EndTime: endTime,
67
PlotIndex: 0
68
},
69
{
70
PlantType: getRandomElement(plantTypes),
71
EndTime: endTime,
72
PlotIndex: 1
73
}
74
]
75
},
76
{
77
Name: "Garden2",
78
Plants: [
79
{
80
PlantType: getRandomElement(plantTypes),
81
EndTime: endTime,
82
PlotIndex: 0
83
},
84
{
85
PlantType: getRandomElement(plantTypes),
86
EndTime: endTime,
87
PlotIndex: 1
88
}
89
]
90
}
91
]
92
};
93
};
@@ -8,6 +8,8 @@ export interface IMissionReward {
8
8
TypeName?: string;
9
9
UpgradeLevel?: number;
10
10
ItemCount: number;
11
DailyCooldown?: boolean;
12
Rarity?: number;
11
13
TweetText?: string;
12
14
ProductCategory?: string;
13
15
FromEnemyCache?: boolean;
@@ -1,12 +1,12 @@
1
1
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
2
2
import {
3
IApartment,
4
3
IRoom,
5
4
IPlacedDecosDatabase,
6
5
ITailorShop,
7
6
ITailorShopDatabase,
8
7
TBootLocation,
9
IApartmentDatabase
8
IApartmentDatabase,
9
IApartmentClient
10
10
} from "@/src/types/shipTypes";
11
11
import { Document, Model, Types } from "mongoose";
12
12
@@ -21,10 +21,10 @@ export interface IOrbiter {
21
21
BootLocation?: TBootLocation;
22
22
}
23
23
24
export interface IPersonalRooms {
24
export interface IPersonalRoomsClient {
25
25
ShipInteriorColors: IColor;
26
26
Ship: IOrbiter;
27
Apartment: IApartment;
27
Apartment: IApartmentClient;
28
28
TailorShop: ITailorShop;
29
29
}
30
30
@@ -1,12 +1,12 @@
1
1
import { Types } from "mongoose";
2
import { IOid } from "@/src/types/commonTypes";
2
import { IMongoDate, IOid } from "@/src/types/commonTypes";
3
3
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
4
4
import { ILoadoutClient } from "./saveLoadoutTypes";
5
5
6
6
export interface IGetShipResponse {
7
7
ShipOwnerId: string;
8
8
Ship: IShip;
9
Apartment: IApartment;
9
Apartment: IApartmentClient;
10
10
TailorShop: ITailorShop;
11
11
LoadOutInventory: { LoadOutPresets: ILoadoutClient };
12
12
}
@@ -51,28 +51,42 @@ export interface IRoom {
51
51
PlacedDecos?: IPlacedDecosDatabase[];
52
52
}
53
53
54
export interface IPlants {
54
export interface IPlantClient {
55
55
PlantType: string;
56
EndTime: IOid;
56
EndTime: IMongoDate;
57
57
PlotIndex: number;
58
58
}
59
export interface IPlanters {
59
60
export interface IPlantDatabase extends Omit<IPlantClient, "EndTime"> {
61
EndTime: Date;
62
}
63
64
export interface IPlanterClient {
65
Name: string;
66
Plants: IPlantClient[];
67
}
68
69
export interface IPlanterDatabase {
60
70
Name: string;
61
Plants: IPlants[];
71
Plants: IPlantDatabase[];
72
}
73
74
export interface IGardeningClient {
75
Planters: IPlanterClient[];
62
76
}
63
77
64
export interface IGardening {
65
Planters?: IPlanters[];
78
export interface IGardeningDatabase {
79
Planters: IPlanterDatabase[];
66
80
}
67
81
68
export interface IApartment {
69
Gardening: IGardening;
82
export interface IApartmentClient {
83
Gardening: IGardeningClient;
70
84
Rooms: IRoom[];
71
85
FavouriteLoadouts: IFavouriteLoadout[];
72
86
}
73
87
74
88
export interface IApartmentDatabase {
75
Gardening: IGardening;
89
Gardening: IGardeningDatabase;
76
90
Rooms: IRoom[];
77
91
FavouriteLoadouts: IFavouriteLoadoutDatabase[];
78
92
}