返回提交历史
Modified
src/models/inventoryModels/inventoryModel.ts
+56
-2
Modified
src/services/inventoryService.ts
+83
-1
Modified
src/services/purchaseService.ts
+18
-5
Modified
src/types/inventoryTypes/inventoryTypes.ts
+23
-17
Modified
src/types/purchaseTypes.ts
+3
-1
XFEstudio/SpaceNinjaServer
feat: acquisition of CrewMembers (#1705)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1705 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
196182f9
代码差异
5 个文件
+183
-26
@@ -88,7 +88,11 @@ import {
88
88
IPersonalTechProjectDatabase,
89
89
IPersonalTechProjectClient,
90
90
ILastSortieRewardDatabase,
91
ILastSortieRewardClient
91
ILastSortieRewardClient,
92
ICrewMemberSkill,
93
ICrewMemberSkillEfficiency,
94
ICrewMemberDatabase,
95
ICrewMemberClient
92
96
} from "../../types/inventoryTypes/inventoryTypes";
93
97
import { IOid } from "../../types/commonTypes";
94
98
import {
@@ -294,6 +298,55 @@ upgradeSchema.set("toJSON", {
294
298
}
295
299
});
296
300
301
const crewMemberSkillSchema = new Schema<ICrewMemberSkill>(
302
{
303
Assigned: Number
304
},
305
{ _id: false }
306
);
307
308
const crewMemberSkillEfficiencySchema = new Schema<ICrewMemberSkillEfficiency>(
309
{
310
PILOTING: crewMemberSkillSchema,
311
GUNNERY: crewMemberSkillSchema,
312
ENGINEERING: crewMemberSkillSchema,
313
COMBAT: crewMemberSkillSchema,
314
SURVIVABILITY: crewMemberSkillSchema
315
},
316
{ _id: false }
317
);
318
319
const crewMemberSchema = new Schema<ICrewMemberDatabase>(
320
{
321
ItemType: { type: String, required: true },
322
NemesisFingerprint: { type: BigInt, default: 0n },
323
Seed: { type: BigInt, default: 0n },
324
AssignedRole: Number,
325
SkillEfficiency: crewMemberSkillEfficiencySchema,
326
WeaponConfigIdx: Number,
327
WeaponId: { type: Schema.Types.ObjectId, default: "000000000000000000000000" },
328
XP: { type: Number, default: 0 },
329
PowersuitType: { type: String, required: true },
330
Configs: [ItemConfigSchema],
331
SecondInCommand: { type: Boolean, default: false }
332
},
333
{ id: false }
334
);
335
336
crewMemberSchema.set("toJSON", {
337
virtuals: true,
338
transform(_doc, obj) {
339
const db = obj as ICrewMemberDatabase;
340
const client = obj as ICrewMemberClient;
341
342
client.WeaponId = toOid(db.WeaponId);
343
client.ItemId = toOid(db._id);
344
345
delete obj._id;
346
delete obj.__v;
347
}
348
});
349
297
350
const slotsBinSchema = new Schema<ISlots>(
298
351
{
299
352
Slots: Number,
@@ -1363,7 +1416,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1363
1416
CrewShipSalvagedWeaponSkins: [upgradeSchema],
1364
1417
1365
1418
//RailJack Crew
1366
CrewMembers: [Schema.Types.Mixed],
1419
CrewMembers: [crewMemberSchema],
1367
1420
1368
1421
//Complete Mission\Quests
1369
1422
Missions: [missionSchema],
@@ -1645,6 +1698,7 @@ export type InventoryDocumentProps = {
1645
1698
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
1646
1699
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
1647
1700
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
1701
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
1648
1702
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
1649
1703
1650
1704
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -22,7 +22,8 @@ import {
22
22
IDroneClient,
23
23
IUpgradeClient,
24
24
TPartialStartingGear,
25
ILoreFragmentScan
25
ILoreFragmentScan,
26
ICrewMemberClient
26
27
} from "@/src/types/inventoryTypes/inventoryTypes";
27
28
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
28
29
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@@ -713,6 +714,15 @@ export const addItem = async (
713
714
return {
714
715
MiscItems: miscItemChanges
715
716
};
717
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
718
if (!seed) {
719
throw new Error(`Expected crew member to have a seed`);
720
}
721
seed |= 0x33b81en << 32n;
722
return {
723
...addCrewMember(inventory, typeName, seed),
724
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
725
};
716
726
} else if (typeName == "/Lotus/Types/Game/CrewShip/RailJack/DefaultHarness") {
717
727
return addCrewShipHarness(inventory, typeName);
718
728
}
@@ -1212,6 +1222,78 @@ const addDrone = (
1212
1222
return inventoryChanges;
1213
1223
};
1214
1224
1225
/*const getCrewMemberSkills = (seed: bigint, skillPointsToAssign: number): Record<string, number> => {
1226
const rng = new SRng(seed);
1227
1228
const skills = ["PILOTING", "GUNNERY", "ENGINEERING", "COMBAT", "SURVIVABILITY"];
1229
for (let i = 1; i != 5; ++i) {
1230
const swapIndex = rng.randomInt(0, i);
1231
if (swapIndex != i) {
1232
const tmp = skills[i];
1233
skills[i] = skills[swapIndex];
1234
skills[swapIndex] = tmp;
1235
}
1236
}
1237
1238
rng.randomFloat(); // unused afaict
1239
1240
const skillAssignments = [0, 0, 0, 0, 0];
1241
for (let skill = 0; skillPointsToAssign; skill = (skill + 1) % 5) {
1242
const maxIncrease = Math.min(5 - skillAssignments[skill], skillPointsToAssign);
1243
const increase = rng.randomInt(0, maxIncrease);
1244
skillAssignments[skill] += increase;
1245
skillPointsToAssign -= increase;
1246
}
1247
1248
skillAssignments.sort((a, b) => b - a);
1249
1250
const combined: Record<string, number> = {};
1251
for (let i = 0; i != 5; ++i) {
1252
combined[skills[i]] = skillAssignments[i];
1253
}
1254
return combined;
1255
};*/
1256
1257
const addCrewMember = (
1258
inventory: TInventoryDatabaseDocument,
1259
itemType: string,
1260
seed: bigint,
1261
inventoryChanges: IInventoryChanges = {}
1262
): IInventoryChanges => {
1263
// SkillEfficiency is additional to the base stats, so we don't need to compute this
1264
//const skillPointsToAssign = itemType.endsWith("Strong") ? 12 : itemType.indexOf("Medium") != -1 ? 10 : 8;
1265
//const skills = getCrewMemberSkills(seed, skillPointsToAssign);
1266
1267
// Arbiters = male
1268
// CephalonSuda = female
1269
// NewLoka = female
1270
// Perrin = male
1271
// RedVeil = male
1272
// SteelMeridian = female
1273
const powersuitType =
1274
itemType.indexOf("Arbiters") != -1 || itemType.indexOf("Perrin") != -1 || itemType.indexOf("RedVeil") != -1
1275
? "/Lotus/Powersuits/NpcPowersuits/CrewMemberMaleSuit"
1276
: "/Lotus/Powersuits/NpcPowersuits/CrewMemberFemaleSuit";
1277
1278
const index =
1279
inventory.CrewMembers.push({
1280
ItemType: itemType,
1281
NemesisFingerprint: 0n,
1282
Seed: seed,
1283
SkillEfficiency: {
1284
PILOTING: { Assigned: 0 },
1285
GUNNERY: { Assigned: 0 },
1286
ENGINEERING: { Assigned: 0 },
1287
COMBAT: { Assigned: 0 },
1288
SURVIVABILITY: { Assigned: 0 }
1289
},
1290
PowersuitType: powersuitType
1291
}) - 1;
1292
inventoryChanges.CrewMembers ??= [];
1293
inventoryChanges.CrewMembers.push(inventory.CrewMembers[index].toJSON<ICrewMemberClient>());
1294
return inventoryChanges;
1295
};
1296
1215
1297
export const addEmailItem = async (
1216
1298
inventory: TInventoryDatabaseDocument,
1217
1299
typeName: string,
@@ -141,7 +141,8 @@ export const handlePurchase = async (
141
141
inventory,
142
142
purchaseRequest.PurchaseParams.Quantity,
143
143
undefined,
144
undefined,
144
false,
145
purchaseRequest.PurchaseParams.UsePremium,
145
146
seed
146
147
);
147
148
combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
@@ -331,6 +332,7 @@ export const handleStoreItemAcquisition = async (
331
332
quantity: number = 1,
332
333
durability: TRarity = "COMMON",
333
334
ignorePurchaseQuantity: boolean = false,
335
premiumPurchase: boolean = true,
334
336
seed?: bigint
335
337
): Promise<IPurchaseResponse> => {
336
338
let purchaseResponse = {
@@ -352,11 +354,20 @@ export const handleStoreItemAcquisition = async (
352
354
}
353
355
switch (storeCategory) {
354
356
default: {
355
purchaseResponse = { InventoryChanges: await addItem(inventory, internalName, quantity, true, seed) };
357
purchaseResponse = {
358
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
359
};
356
360
break;
357
361
}
358
362
case "Types":
359
purchaseResponse = await handleTypesPurchase(internalName, inventory, quantity, ignorePurchaseQuantity);
363
purchaseResponse = await handleTypesPurchase(
364
internalName,
365
inventory,
366
quantity,
367
ignorePurchaseQuantity,
368
premiumPurchase,
369
seed
370
);
360
371
break;
361
372
case "Boosters":
362
373
purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
@@ -478,13 +489,15 @@ const handleTypesPurchase = async (
478
489
typesName: string,
479
490
inventory: TInventoryDatabaseDocument,
480
491
quantity: number,
481
ignorePurchaseQuantity: boolean
492
ignorePurchaseQuantity: boolean,
493
premiumPurchase: boolean = true,
494
seed?: bigint
482
495
): Promise<IPurchaseResponse> => {
483
496
const typeCategory = getStoreItemTypesCategory(typesName);
484
497
logger.debug(`type category ${typeCategory}`);
485
498
switch (typeCategory) {
486
499
default:
487
return { InventoryChanges: await addItem(inventory, typesName, quantity) };
500
return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
488
501
case "BoosterPacks":
489
502
return handleBoosterPackPurchase(typesName, inventory, quantity);
490
503
case "SlotItems":
@@ -49,6 +49,7 @@ export interface IInventoryDatabase
49
49
| "PersonalTechProjects"
50
50
| "LastSortieReward"
51
51
| "LastLiteSortieReward"
52
| "CrewMembers"
52
53
| TEquipmentKey
53
54
>,
54
55
InventoryDatabaseEquipment {
@@ -83,6 +84,7 @@ export interface IInventoryDatabase
83
84
PersonalTechProjects: IPersonalTechProjectDatabase[];
84
85
LastSortieReward?: ILastSortieRewardDatabase[];
85
86
LastLiteSortieReward?: ILastSortieRewardDatabase[];
87
CrewMembers: ICrewMemberDatabase[];
86
88
}
87
89
88
90
export interface IQuestKeyDatabase {
@@ -324,7 +326,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
324
326
InfestedFoundry?: IInfestedFoundryClient;
325
327
BlessingCooldown?: IMongoDate;
326
328
CrewShipRawSalvage: ITypeCount[];
327
CrewMembers: ICrewMember[];
329
CrewMembers: ICrewMemberClient[];
328
330
LotusCustomization: ILotusCustomization;
329
331
UseAdultOperatorLoadout?: boolean;
330
332
NemesisAbandonedRewards: string[];
@@ -461,13 +463,24 @@ export interface ICompletedJob {
461
463
StageCompletions: number[];
462
464
}
463
465
464
export interface ICrewMember {
466
export interface ICrewMemberSkill {
467
Assigned: number;
468
}
469
470
export interface ICrewMemberSkillEfficiency {
471
PILOTING: ICrewMemberSkill;
472
GUNNERY: ICrewMemberSkill;
473
ENGINEERING: ICrewMemberSkill;
474
COMBAT: ICrewMemberSkill;
475
SURVIVABILITY: ICrewMemberSkill;
476
}
477
478
export interface ICrewMemberClient {
465
479
ItemType: string;
466
NemesisFingerprint: number;
467
Seed: number;
468
HireDate: IMongoDate;
469
AssignedRole: number;
470
SkillEfficiency: ISkillEfficiency;
480
NemesisFingerprint: bigint;
481
Seed: bigint;
482
AssignedRole?: number;
483
SkillEfficiency: ICrewMemberSkillEfficiency;
471
484
WeaponConfigIdx: number;
472
485
WeaponId: IOid;
473
486
XP: number;
@@ -477,16 +490,9 @@ export interface ICrewMember {
477
490
ItemId: IOid;
478
491
}
479
492
480
export interface ISkillEfficiency {
481
PILOTING: ICombat;
482
GUNNERY: ICombat;
483
ENGINEERING: ICombat;
484
COMBAT: ICombat;
485
SURVIVABILITY: ICombat;
486
}
487
488
export interface ICombat {
489
Assigned: number;
493
export interface ICrewMemberDatabase extends Omit<ICrewMemberClient, "WeaponId" | "ItemId"> {
494
WeaponId: Types.ObjectId;
495
_id: Types.ObjectId;
490
496
}
491
497
492
498
export enum InventorySlot {
@@ -6,7 +6,8 @@ import {
6
6
INemesisClient,
7
7
ITypeCount,
8
8
IRecentVendorPurchaseClient,
9
TEquipmentKey
9
TEquipmentKey,
10
ICrewMemberClient
10
11
} from "./inventoryTypes/inventoryTypes";
11
12
12
13
export interface IPurchaseRequest {
@@ -47,6 +48,7 @@ export type IInventoryChanges = {
47
48
Nemesis?: Partial<INemesisClient>;
48
49
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
49
50
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
51
CrewMembers?: ICrewMemberClient[];
50
52
} & Record<
51
53
Exclude<
52
54
string,