XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

3 个文件 +187 -88
Modified src/controllers/api/guildTechController.ts +144 -81
@@ -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[];
Modified src/models/inventoryModels/inventoryModel.ts +33 -3
@@ -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
Modified src/types/inventoryTypes/inventoryTypes.ts +10 -4
@@ -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 {