返回提交历史
Added
src/controllers/api/adoptPetController.ts
+27
-0
Modified
src/controllers/api/claimCompletedRecipeController.ts
+20
-3
Modified
src/controllers/api/startRecipeController.ts
+28
-15
Modified
src/models/inventoryModels/inventoryModel.ts
+3
-1
Modified
src/routes/api.ts
+2
-0
Modified
src/services/inventoryService.ts
+11
-7
Modified
src/types/inventoryTypes/inventoryTypes.ts
+7
-2
XFEstudio/XFESpaceNinjaServer
feat: kubrow & kavat incubation (#2131)
Closes #377 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2131 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
9def5c26
代码差异
7 个文件
+98
-28
@@ -0,0 +1,27 @@
1
import { getJSONfromString } from "@/src/helpers/stringHelpers";
2
import { getInventory } from "@/src/services/inventoryService";
3
import { getAccountIdForRequest } from "@/src/services/loginService";
4
import { RequestHandler } from "express";
5
6
export const adoptPetController: RequestHandler = async (req, res) => {
7
const accountId = await getAccountIdForRequest(req);
8
const inventory = await getInventory(accountId, "KubrowPets");
9
const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
10
const details = inventory.KubrowPets.id(data.petId)!.Details!;
11
details.Name = data.name;
12
await inventory.save();
13
res.json({
14
petId: data.petId,
15
newName: data.name
16
} satisfies IAdoptPetResponse);
17
};
18
19
interface IAdoptPetRequest {
20
petId: string;
21
name: string;
22
}
23
24
interface IAdoptPetResponse {
25
petId: string;
26
newName: string;
27
}
@@ -17,7 +17,7 @@ import {
17
17
} from "@/src/services/inventoryService";
18
18
import { IInventoryChanges } from "@/src/types/purchaseTypes";
19
19
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
20
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
20
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
21
21
import { toOid2 } from "@/src/helpers/inventoryHelpers";
22
22
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
23
23
import { IRecipe } from "warframe-public-export-plus";
@@ -105,7 +105,21 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
105
105
...updateCurrency(inventory, cost, true)
106
106
};
107
107
}
108
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
108
109
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
110
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
111
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
112
pet.Details!.HatchDate = new Date();
113
}
114
let canSetActive = true;
115
for (const pet of inventory.KubrowPets) {
116
if (pet.Details!.Status == Status.StatusAvailable) {
117
canSetActive = false;
118
break;
119
}
120
}
121
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusIncubating;
122
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
109
123
InventoryChanges = {
110
124
...InventoryChanges,
111
125
...(await addItem(
@@ -118,7 +132,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
118
132
))
119
133
};
120
134
}
121
if (config.claimingBlueprintRefundsIngredients) {
135
if (
136
config.claimingBlueprintRefundsIngredients &&
137
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
138
) {
122
139
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
123
140
}
124
141
await inventory.save();
@@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
3
3
import { logger } from "@/src/utils/logger";
4
4
import { RequestHandler } from "express";
5
5
import { getRecipe } from "@/src/services/itemDataService";
6
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
6
import { addItem, addKubrowPet, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
7
7
import { unixTimesInMs } from "@/src/constants/timeConstants";
8
8
import { Types } from "mongoose";
9
9
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
10
import { toOid } from "@/src/helpers/inventoryHelpers";
10
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
11
11
import { ExportWeapons } from "warframe-public-export-plus";
12
import { getRandomElement } from "@/src/services/rngService";
13
import { IInventoryChanges } from "@/src/types/purchaseTypes";
12
14
13
15
interface IStartRecipeRequest {
14
16
RecipeName: string;
@@ -42,24 +44,35 @@ export const startRecipeController: RequestHandler = async (req, res) => {
42
44
43
45
for (let i = 0; i != recipe.ingredients.length; ++i) {
44
46
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
45
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
46
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
47
throw new Error(`unexpected equipment ingredient type: ${category}`);
48
}
49
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
50
if (equipmentIndex == -1) {
51
throw new Error(`could not find equipment item to use for recipe`);
47
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
48
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
49
if (index != -1) {
50
inventory.KubrowPetEggs!.splice(index, 1);
51
}
52
} else {
53
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
54
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
55
throw new Error(`unexpected equipment ingredient type: ${category}`);
56
}
57
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
58
if (equipmentIndex == -1) {
59
throw new Error(`could not find equipment item to use for recipe`);
60
}
61
pr[category] ??= [];
62
pr[category].push(inventory[category][equipmentIndex]);
63
inventory[category].splice(equipmentIndex, 1);
64
freeUpSlot(inventory, InventorySlot.WEAPONS);
52
65
}
53
pr[category] ??= [];
54
pr[category].push(inventory[category][equipmentIndex]);
55
inventory[category].splice(equipmentIndex, 1);
56
freeUpSlot(inventory, InventorySlot.WEAPONS);
57
66
} else {
58
67
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
59
68
}
60
69
}
61
70
62
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
71
let inventoryChanges: IInventoryChanges | undefined;
72
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
73
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
74
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
75
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
63
76
const spectreLoadout: ISpectreLoadout = {
64
77
ItemType: recipe.resultType,
65
78
Suits: "",
@@ -116,5 +129,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
116
129
117
130
await inventory.save();
118
131
119
res.json({ RecipeId: toOid(pr._id) });
132
res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
120
133
};
@@ -1097,7 +1097,8 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
1097
1097
LongGuns: { type: [EquipmentSchema], default: undefined },
1098
1098
Pistols: { type: [EquipmentSchema], default: undefined },
1099
1099
Melee: { type: [EquipmentSchema], default: undefined },
1100
SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined }
1100
SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined },
1101
KubrowPet: { type: Schema.Types.ObjectId, default: undefined }
1101
1102
},
1102
1103
{ id: false }
1103
1104
);
@@ -1115,6 +1116,7 @@ pendingRecipeSchema.set("toJSON", {
1115
1116
delete returnedObject.Pistols;
1116
1117
delete returnedObject.Melees;
1117
1118
delete returnedObject.SuitToUnbrand;
1119
delete returnedObject.KubrowPet;
1118
1120
(returnedObject as IPendingRecipeClient).CompletionDate = {
1119
1121
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
1120
1122
};
@@ -9,6 +9,7 @@ import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserCo
9
9
import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
10
10
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
11
11
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
12
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
12
13
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
13
14
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
14
15
import { artifactsController } from "@/src/controllers/api/artifactsController";
@@ -226,6 +227,7 @@ apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
226
227
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
227
228
apiRouter.post("/addToAlliance.php", addToAllianceController);
228
229
apiRouter.post("/addToGuild.php", addToGuildController);
230
apiRouter.post("/adoptPet.php", adoptPetController);
229
231
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
230
232
apiRouter.post("/archonFusion.php", archonFusionController);
231
233
apiRouter.post("/artifacts.php", artifactsController);
@@ -86,6 +86,7 @@ import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
86
86
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
87
87
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
88
88
import { TAccountDocument } from "./loginService";
89
import { unixTimesInMs } from "../constants/timeConstants";
89
90
90
91
export const createInventory = async (
91
92
accountOwnerId: Types.ObjectId,
@@ -780,7 +781,9 @@ export const addItem = async (
780
781
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
781
782
typeName.substr(1).split("/")[3] == "KubrowPet"
782
783
) {
783
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
784
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
785
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
786
}
784
787
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
785
788
if (!seed) {
786
789
throw new Error(`Expected crew member to have a seed`);
@@ -1025,12 +1028,13 @@ export const addSpaceSuit = (
1025
1028
export const addKubrowPet = (
1026
1029
inventory: TInventoryDatabaseDocument,
1027
1030
kubrowPetName: string,
1028
details: IKubrowPetDetailsDatabase | undefined,
1029
premiumPurchase: boolean,
1031
details?: IKubrowPetDetailsDatabase,
1032
premiumPurchase: boolean = false,
1030
1033
inventoryChanges: IInventoryChanges = {}
1031
1034
): IInventoryChanges => {
1032
1035
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
1033
1036
1037
// TODO: When incubating, this should only be given when claiming the recipe.
1034
1038
const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
1035
1039
const exalted = kubrowPet?.exalted ?? [];
1036
1040
for (const specialItem of exalted) {
@@ -1079,11 +1083,11 @@ export const addKubrowPet = (
1079
1083
1080
1084
details = {
1081
1085
Name: "",
1082
IsPuppy: false,
1086
IsPuppy: !premiumPurchase,
1083
1087
HasCollar: true,
1084
PrintsRemaining: 2,
1085
Status: Status.StatusStasis,
1086
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
1088
PrintsRemaining: 3,
1089
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
1090
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
1087
1091
IsMale: !!getRandomInt(0, 1),
1088
1092
Size: getRandomInt(70, 100) / 100,
1089
1093
DominantTraits: traits,
@@ -765,7 +765,8 @@ export interface IKubrowPetDetailsClient extends Omit<IKubrowPetDetailsDatabase,
765
765
766
766
export enum Status {
767
767
StatusAvailable = "STATUS_AVAILABLE",
768
StatusStasis = "STATUS_STASIS"
768
StatusStasis = "STATUS_STASIS",
769
StatusIncubating = "STATUS_INCUBATING"
769
770
}
770
771
771
772
export interface ILastSortieRewardClient {
@@ -929,10 +930,14 @@ export interface IPendingRecipeDatabase {
929
930
Pistols?: IEquipmentDatabase[];
930
931
Melee?: IEquipmentDatabase[];
931
932
SuitToUnbrand?: Types.ObjectId;
933
KubrowPet?: Types.ObjectId;
932
934
}
933
935
934
936
export interface IPendingRecipeClient
935
extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand"> {
937
extends Omit<
938
IPendingRecipeDatabase,
939
"CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand" | "KubrowPet"
940
> {
936
941
CompletionDate: IMongoDate;
937
942
}
938
943