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

代码差异

7 个文件 +98 -28
Added src/controllers/api/adoptPetController.ts +27 -0
@@ -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 }
Modified src/controllers/api/claimCompletedRecipeController.ts +20 -3
@@ -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();
Modified src/controllers/api/startRecipeController.ts +28 -15
@@ -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 };
Modified src/models/inventoryModels/inventoryModel.ts +3 -1
@@ -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 };
Modified src/routes/api.ts +2 -0
@@ -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);
Modified src/services/inventoryService.ts +11 -7
@@ -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,
Modified src/types/inventoryTypes/inventoryTypes.ts +7 -2
@@ -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