返回提交历史
Modified
src/controllers/api/guildTechController.ts
+144
-81
Modified
src/models/inventoryModels/inventoryModel.ts
+33
-3
Modified
src/types/inventoryTypes/inventoryTypes.ts
+10
-4
XFEstudio/XFESpaceNinjaServer
feat: personal research (#1602)
This should be good enough for the railjack quest at least Closes #1599 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1602 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
a75e0c59
代码差异
3 个文件
+187
-88
@@ -32,11 +32,11 @@ import { logger } from "@/src/utils/logger";
32
32
export const guildTechController: RequestHandler = async (req, res) => {
33
33
const accountId = await getAccountIdForRequest(req);
34
34
const inventory = await getInventory(accountId);
35
const guild = await getGuildForRequestEx(req, inventory);
36
35
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
37
36
if (data.Action == "Sync") {
38
37
let needSave = false;
39
38
const techProjects: ITechProjectClient[] = [];
39
const guild = await getGuildForRequestEx(req, inventory);
40
40
if (guild.TechProjects) {
41
41
for (const project of guild.TechProjects) {
42
42
const techProject: ITechProjectClient = {
@@ -59,110 +59,170 @@ export const guildTechController: RequestHandler = async (req, res) => {
59
59
}
60
60
res.json({ TechProjects: techProjects });
61
61
} else if (data.Action == "Start") {
62
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
63
res.status(400).send("-1").end();
64
return;
65
}
66
const recipe = ExportDojoRecipes.research[data.RecipeType];
67
guild.TechProjects ??= [];
68
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
62
if (data.Mode == "Guild") {
63
const guild = await getGuildForRequestEx(req, inventory);
64
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
65
res.status(400).send("-1").end();
66
return;
67
}
68
const recipe = ExportDojoRecipes.research[data.RecipeType];
69
guild.TechProjects ??= [];
70
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
71
const techProject =
72
guild.TechProjects[
73
guild.TechProjects.push({
74
ItemType: data.RecipeType,
75
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
76
ReqItems: recipe.ingredients.map(x => ({
77
ItemType: x.ItemType,
78
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
79
})),
80
State: 0
81
}) - 1
82
];
83
setGuildTechLogState(guild, techProject.ItemType, 5);
84
if (config.noDojoResearchCosts) {
85
processFundedGuildTechProject(guild, techProject, recipe);
86
} else {
87
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
88
guild.ActiveDojoColorResearch = data.RecipeType;
89
}
90
}
91
}
92
await guild.save();
93
res.end();
94
} else {
95
const recipe = ExportDojoRecipes.research[data.RecipeType];
69
96
const techProject =
70
guild.TechProjects[
71
guild.TechProjects.push({
97
inventory.PersonalTechProjects[
98
inventory.PersonalTechProjects.push({
99
State: 0,
100
ReqCredits: recipe.price,
72
101
ItemType: data.RecipeType,
73
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
74
ReqItems: recipe.ingredients.map(x => ({
75
ItemType: x.ItemType,
76
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
77
})),
78
State: 0
102
ReqItems: recipe.ingredients
79
103
}) - 1
80
104
];
81
setGuildTechLogState(guild, techProject.ItemType, 5);
82
if (config.noDojoResearchCosts) {
83
processFundedGuildTechProject(guild, techProject, recipe);
84
} else {
85
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
86
guild.ActiveDojoColorResearch = data.RecipeType;
87
}
88
}
105
await inventory.save();
106
res.json({
107
isPersonal: true,
108
action: "Start",
109
personalTech: techProject.toJSON()
110
});
89
111
}
90
await guild.save();
91
res.end();
92
112
} else if (data.Action == "Contribute") {
93
if (!hasAccessToDojo(inventory)) {
94
res.status(400).send("-1").end();
95
return;
96
}
113
if ((req.query.guildId as string) == "000000000000000000000000") {
114
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
115
116
techProject.ReqCredits -= data.RegularCredits;
117
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
118
119
const miscItemChanges = [];
120
for (const miscItem of data.MiscItems) {
121
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
122
if (reqItem) {
123
if (miscItem.ItemCount > reqItem.ItemCount) {
124
miscItem.ItemCount = reqItem.ItemCount;
125
}
126
reqItem.ItemCount -= miscItem.ItemCount;
127
miscItemChanges.push({
128
ItemType: miscItem.ItemType,
129
ItemCount: miscItem.ItemCount * -1
130
});
131
}
132
}
133
addMiscItems(inventory, miscItemChanges);
134
inventoryChanges.MiscItems = miscItemChanges;
97
135
98
const guildMember = (await GuildMember.findOne(
99
{ accountId, guildId: guild._id },
100
"RegularCreditsContributed MiscItemsContributed"
101
))!;
136
techProject.HasContributions = true;
102
137
103
const contributions = data;
104
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
138
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
139
techProject.State = 1;
140
const recipe = ExportDojoRecipes.research[techProject.ItemType];
141
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
142
}
105
143
106
if (contributions.VaultCredits) {
107
if (contributions.VaultCredits > techProject.ReqCredits) {
108
contributions.VaultCredits = techProject.ReqCredits;
144
await inventory.save();
145
res.json({
146
InventoryChanges: inventoryChanges,
147
PersonalResearch: { $oid: data.ResearchId },
148
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
149
});
150
} else {
151
if (!hasAccessToDojo(inventory)) {
152
res.status(400).send("-1").end();
153
return;
109
154
}
110
techProject.ReqCredits -= contributions.VaultCredits;
111
guild.VaultRegularCredits! -= contributions.VaultCredits;
112
}
113
155
114
if (contributions.RegularCredits > techProject.ReqCredits) {
115
contributions.RegularCredits = techProject.ReqCredits;
116
}
117
techProject.ReqCredits -= contributions.RegularCredits;
156
const guild = await getGuildForRequestEx(req, inventory);
157
const guildMember = (await GuildMember.findOne(
158
{ accountId, guildId: guild._id },
159
"RegularCreditsContributed MiscItemsContributed"
160
))!;
161
162
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
163
164
if (data.VaultCredits) {
165
if (data.VaultCredits > techProject.ReqCredits) {
166
data.VaultCredits = techProject.ReqCredits;
167
}
168
techProject.ReqCredits -= data.VaultCredits;
169
guild.VaultRegularCredits! -= data.VaultCredits;
170
}
171
172
if (data.RegularCredits > techProject.ReqCredits) {
173
data.RegularCredits = techProject.ReqCredits;
174
}
175
techProject.ReqCredits -= data.RegularCredits;
176
177
guildMember.RegularCreditsContributed ??= 0;
178
guildMember.RegularCreditsContributed += data.RegularCredits;
118
179
119
guildMember.RegularCreditsContributed ??= 0;
120
guildMember.RegularCreditsContributed += contributions.RegularCredits;
180
if (data.VaultMiscItems.length) {
181
for (const miscItem of data.VaultMiscItems) {
182
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
183
if (reqItem) {
184
if (miscItem.ItemCount > reqItem.ItemCount) {
185
miscItem.ItemCount = reqItem.ItemCount;
186
}
187
reqItem.ItemCount -= miscItem.ItemCount;
121
188
122
if (contributions.VaultMiscItems.length) {
123
for (const miscItem of contributions.VaultMiscItems) {
189
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
190
vaultMiscItem.ItemCount -= miscItem.ItemCount;
191
}
192
}
193
}
194
195
const miscItemChanges = [];
196
for (const miscItem of data.MiscItems) {
124
197
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
125
198
if (reqItem) {
126
199
if (miscItem.ItemCount > reqItem.ItemCount) {
127
200
miscItem.ItemCount = reqItem.ItemCount;
128
201
}
129
202
reqItem.ItemCount -= miscItem.ItemCount;
203
miscItemChanges.push({
204
ItemType: miscItem.ItemType,
205
ItemCount: miscItem.ItemCount * -1
206
});
130
207
131
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
132
vaultMiscItem.ItemCount -= miscItem.ItemCount;
208
addGuildMemberMiscItemContribution(guildMember, miscItem);
133
209
}
134
210
}
135
}
211
addMiscItems(inventory, miscItemChanges);
212
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
213
inventoryChanges.MiscItems = miscItemChanges;
136
214
137
const miscItemChanges = [];
138
for (const miscItem of contributions.MiscItems) {
139
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
140
if (reqItem) {
141
if (miscItem.ItemCount > reqItem.ItemCount) {
142
miscItem.ItemCount = reqItem.ItemCount;
143
}
144
reqItem.ItemCount -= miscItem.ItemCount;
145
miscItemChanges.push({
146
ItemType: miscItem.ItemType,
147
ItemCount: miscItem.ItemCount * -1
148
});
215
// Check if research is fully funded now.
216
await processGuildTechProjectContributionsUpdate(guild, techProject);
149
217
150
addGuildMemberMiscItemContribution(guildMember, miscItem);
151
}
218
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
219
res.json({
220
InventoryChanges: inventoryChanges,
221
Vault: getGuildVault(guild)
222
});
152
223
}
153
addMiscItems(inventory, miscItemChanges);
154
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
155
inventoryChanges.MiscItems = miscItemChanges;
156
157
// Check if research is fully funded now.
158
await processGuildTechProjectContributionsUpdate(guild, techProject);
159
160
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
161
res.json({
162
InventoryChanges: inventoryChanges,
163
Vault: getGuildVault(guild)
164
});
165
224
} else if (data.Action.split(",")[0] == "Buy") {
225
const guild = await getGuildForRequestEx(req, inventory);
166
226
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
167
227
res.status(400).send("-1").end();
168
228
return;
@@ -190,6 +250,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
190
250
}
191
251
});
192
252
} else if (data.Action == "Fabricate") {
253
const guild = await getGuildForRequestEx(req, inventory);
193
254
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
194
255
res.status(400).send("-1").end();
195
256
return;
@@ -206,6 +267,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
206
267
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
207
268
res.json({ inventoryChanges: inventoryChanges });
208
269
} else if (data.Action == "Pause") {
270
const guild = await getGuildForRequestEx(req, inventory);
209
271
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
210
272
res.status(400).send("-1").end();
211
273
return;
@@ -217,6 +279,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
217
279
await removePigmentsFromGuildMembers(guild._id);
218
280
res.end();
219
281
} else if (data.Action == "Unpause") {
282
const guild = await getGuildForRequestEx(req, inventory);
220
283
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
221
284
res.status(400).send("-1").end();
222
285
return;
@@ -239,7 +302,7 @@ type TGuildTechRequest =
239
302
240
303
interface IGuildTechBasicRequest {
241
304
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
242
Mode: "Guild";
305
Mode: "Guild" | "Personal";
243
306
RecipeType: string;
244
307
}
245
308
@@ -251,7 +314,7 @@ interface IGuildTechBuyRequest {
251
314
252
315
interface IGuildTechContributeRequest {
253
316
Action: "Contribute";
254
ResearchId: "";
317
ResearchId: string;
255
318
RecipeType: string;
256
319
RegularCredits: number;
257
320
MiscItems: IMiscItem[];
@@ -84,7 +84,9 @@ import {
84
84
IInfNode,
85
85
IDiscoveredMarker,
86
86
IWeeklyMission,
87
ILockedWeaponGroupDatabase
87
ILockedWeaponGroupDatabase,
88
IPersonalTechProjectDatabase,
89
IPersonalTechProjectClient
88
90
} from "../../types/inventoryTypes/inventoryTypes";
89
91
import { IOid } from "../../types/commonTypes";
90
92
import {
@@ -498,7 +500,34 @@ const seasonChallengeHistorySchema = new Schema<ISeasonChallenge>(
498
500
{ _id: false }
499
501
);
500
502
501
//TODO: check whether this is complete
503
const personalTechProjectSchema = new Schema<IPersonalTechProjectDatabase>({
504
State: Number,
505
ReqCredits: Number,
506
ItemType: String,
507
ReqItems: { type: [typeCountSchema], default: undefined },
508
HasContributions: Boolean,
509
CompletionDate: Date
510
});
511
512
personalTechProjectSchema.virtual("ItemId").get(function () {
513
return { $oid: this._id.toString() };
514
});
515
516
personalTechProjectSchema.set("toJSON", {
517
virtuals: true,
518
transform(_doc, ret, _options) {
519
delete ret._id;
520
delete ret.__v;
521
522
const db = ret as IPersonalTechProjectDatabase;
523
const client = ret as IPersonalTechProjectClient;
524
525
if (db.CompletionDate) {
526
client.CompletionDate = toMongoDate(db.CompletionDate);
527
}
528
}
529
});
530
502
531
const playerSkillsSchema = new Schema<IPlayerSkills>(
503
532
{
504
533
LPP_SPACE: { type: Number, default: 0 },
@@ -1442,7 +1471,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1442
1471
1443
1472
//Railjack craft
1444
1473
//https://warframe.fandom.com/wiki/Rising_Tide
1445
PersonalTechProjects: [Schema.Types.Mixed],
1474
PersonalTechProjects: { type: [personalTechProjectSchema], default: [] },
1446
1475
1447
1476
//Modulars lvl and exp(Railjack|Duviri)
1448
1477
//https://warframe.fandom.com/wiki/Intrinsics
@@ -1585,6 +1614,7 @@ export type InventoryDocumentProps = {
1585
1614
Drones: Types.DocumentArray<IDroneDatabase>;
1586
1615
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
1587
1616
CrewShipSalvagedWeaponsSkins: Types.DocumentArray<IUpgradeDatabase>;
1617
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
1588
1618
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
1589
1619
1590
1620
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -46,6 +46,7 @@ export interface IInventoryDatabase
46
46
| "EntratiVaultCountResetDate"
47
47
| "BrandedSuits"
48
48
| "LockedWeaponGroup"
49
| "PersonalTechProjects"
49
50
| TEquipmentKey
50
51
>,
51
52
InventoryDatabaseEquipment {
@@ -77,6 +78,7 @@ export interface IInventoryDatabase
77
78
EntratiVaultCountResetDate?: Date;
78
79
BrandedSuits?: Types.ObjectId[];
79
80
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
81
PersonalTechProjects: IPersonalTechProjectDatabase[];
80
82
}
81
83
82
84
export interface IQuestKeyDatabase {
@@ -306,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
306
308
NemesisHistory: INemesisBaseClient[];
307
309
LastNemesisAllySpawnTime?: IMongoDate;
308
310
Settings?: ISettings;
309
PersonalTechProjects: IPersonalTechProject[];
311
PersonalTechProjects: IPersonalTechProjectClient[];
310
312
PlayerSkills: IPlayerSkills;
311
313
CrewShipAmmo: ITypeCount[];
312
314
CrewShipWeaponSkins: IUpgradeClient[];
@@ -941,16 +943,20 @@ export interface IPersonalGoalProgress {
941
943
ReceivedClanReward1?: boolean;
942
944
}
943
945
944
export interface IPersonalTechProject {
946
export interface IPersonalTechProjectDatabase {
945
947
State: number;
946
948
ReqCredits: number;
947
949
ItemType: string;
948
950
ReqItems: ITypeCount[];
951
HasContributions?: boolean;
952
CompletionDate?: Date;
953
}
954
955
export interface IPersonalTechProjectClient extends Omit<IPersonalTechProjectDatabase, "CompletionDate"> {
949
956
CompletionDate?: IMongoDate;
950
ItemId: IOid;
951
957
ProductCategory?: string;
952
958
CategoryItemId?: IOid;
953
HasContributions?: boolean;
959
ItemId: IOid;
954
960
}
955
961
956
962
export interface IPlayerSkills {