forked from OpenWF/SpaceNinjaServer
		
	feat: kubrow & kavat incubation (#2131)
Closes #377 Reviewed-on: OpenWF/SpaceNinjaServer#2131 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									65387ccdea
								
							
						
					
					
						commit
						9def5c265e
					
				
							
								
								
									
										27
									
								
								src/controllers/api/adoptPetController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/controllers/api/adoptPetController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const adoptPetController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "KubrowPets");
 | 
			
		||||
    const data = getJSONfromString<IAdoptPetRequest>(String(req.body));
 | 
			
		||||
    const details = inventory.KubrowPets.id(data.petId)!.Details!;
 | 
			
		||||
    details.Name = data.name;
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        petId: data.petId,
 | 
			
		||||
        newName: data.name
 | 
			
		||||
    } satisfies IAdoptPetResponse);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAdoptPetRequest {
 | 
			
		||||
    petId: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IAdoptPetResponse {
 | 
			
		||||
    petId: string;
 | 
			
		||||
    newName: string;
 | 
			
		||||
}
 | 
			
		||||
@ -17,7 +17,7 @@ import {
 | 
			
		||||
} from "@/src/services/inventoryService";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { InventorySlot, IPendingRecipeDatabase, Status } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { IRecipe } from "warframe-public-export-plus";
 | 
			
		||||
@ -105,7 +105,21 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                ...updateCurrency(inventory, cost, true)
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
			
		||||
 | 
			
		||||
        if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
 | 
			
		||||
            const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
 | 
			
		||||
            if (pet.Details!.HatchDate!.getTime() > Date.now()) {
 | 
			
		||||
                pet.Details!.HatchDate = new Date();
 | 
			
		||||
            }
 | 
			
		||||
            let canSetActive = true;
 | 
			
		||||
            for (const pet of inventory.KubrowPets) {
 | 
			
		||||
                if (pet.Details!.Status == Status.StatusAvailable) {
 | 
			
		||||
                    canSetActive = false;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusIncubating;
 | 
			
		||||
        } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
 | 
			
		||||
            InventoryChanges = {
 | 
			
		||||
                ...InventoryChanges,
 | 
			
		||||
                ...(await addItem(
 | 
			
		||||
@ -118,7 +132,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
 | 
			
		||||
                ))
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        if (config.claimingBlueprintRefundsIngredients) {
 | 
			
		||||
        if (
 | 
			
		||||
            config.claimingBlueprintRefundsIngredients &&
 | 
			
		||||
            recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
 | 
			
		||||
        ) {
 | 
			
		||||
            await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
 | 
			
		||||
        }
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
 | 
			
		||||
@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { getRecipe } from "@/src/services/itemDataService";
 | 
			
		||||
import { addItem, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
			
		||||
import { addItem, addKubrowPet, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
			
		||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { ExportWeapons } from "warframe-public-export-plus";
 | 
			
		||||
import { getRandomElement } from "@/src/services/rngService";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
 | 
			
		||||
interface IStartRecipeRequest {
 | 
			
		||||
    RecipeName: string;
 | 
			
		||||
@ -42,6 +44,12 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i != recipe.ingredients.length; ++i) {
 | 
			
		||||
        if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
 | 
			
		||||
            if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
 | 
			
		||||
                const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
 | 
			
		||||
                if (index != -1) {
 | 
			
		||||
                    inventory.KubrowPetEggs!.splice(index, 1);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
 | 
			
		||||
                if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
 | 
			
		||||
                    throw new Error(`unexpected equipment ingredient type: ${category}`);
 | 
			
		||||
@ -54,12 +62,17 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
                pr[category].push(inventory[category][equipmentIndex]);
 | 
			
		||||
                inventory[category].splice(equipmentIndex, 1);
 | 
			
		||||
                freeUpSlot(inventory, InventorySlot.WEAPONS);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
			
		||||
    let inventoryChanges: IInventoryChanges | undefined;
 | 
			
		||||
    if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
 | 
			
		||||
        inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
 | 
			
		||||
        pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
 | 
			
		||||
    } else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
 | 
			
		||||
        const spectreLoadout: ISpectreLoadout = {
 | 
			
		||||
            ItemType: recipe.resultType,
 | 
			
		||||
            Suits: "",
 | 
			
		||||
@ -116,5 +129,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.json({ RecipeId: toOid(pr._id) });
 | 
			
		||||
    res.json({ RecipeId: toOid(pr._id), InventoryChanges: inventoryChanges });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1097,7 +1097,8 @@ const pendingRecipeSchema = new Schema<IPendingRecipeDatabase>(
 | 
			
		||||
        LongGuns: { type: [EquipmentSchema], default: undefined },
 | 
			
		||||
        Pistols: { type: [EquipmentSchema], default: undefined },
 | 
			
		||||
        Melee: { type: [EquipmentSchema], default: undefined },
 | 
			
		||||
        SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined }
 | 
			
		||||
        SuitToUnbrand: { type: Schema.Types.ObjectId, default: undefined },
 | 
			
		||||
        KubrowPet: { type: Schema.Types.ObjectId, default: undefined }
 | 
			
		||||
    },
 | 
			
		||||
    { id: false }
 | 
			
		||||
);
 | 
			
		||||
@ -1115,6 +1116,7 @@ pendingRecipeSchema.set("toJSON", {
 | 
			
		||||
        delete returnedObject.Pistols;
 | 
			
		||||
        delete returnedObject.Melees;
 | 
			
		||||
        delete returnedObject.SuitToUnbrand;
 | 
			
		||||
        delete returnedObject.KubrowPet;
 | 
			
		||||
        (returnedObject as IPendingRecipeClient).CompletionDate = {
 | 
			
		||||
            $date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { addIgnoredUserController } from "@/src/controllers/api/addIgnoredUserCo
 | 
			
		||||
import { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
 | 
			
		||||
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
 | 
			
		||||
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
 | 
			
		||||
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
 | 
			
		||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
 | 
			
		||||
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
 | 
			
		||||
import { artifactsController } from "@/src/controllers/api/artifactsController";
 | 
			
		||||
@ -226,6 +227,7 @@ apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
 | 
			
		||||
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
 | 
			
		||||
apiRouter.post("/addToAlliance.php", addToAllianceController);
 | 
			
		||||
apiRouter.post("/addToGuild.php", addToGuildController);
 | 
			
		||||
apiRouter.post("/adoptPet.php", adoptPetController);
 | 
			
		||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
			
		||||
apiRouter.post("/archonFusion.php", archonFusionController);
 | 
			
		||||
apiRouter.post("/artifacts.php", artifactsController);
 | 
			
		||||
 | 
			
		||||
@ -86,6 +86,7 @@ import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
			
		||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
 | 
			
		||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
 | 
			
		||||
import { TAccountDocument } from "./loginService";
 | 
			
		||||
import { unixTimesInMs } from "../constants/timeConstants";
 | 
			
		||||
 | 
			
		||||
export const createInventory = async (
 | 
			
		||||
    accountOwnerId: Types.ObjectId,
 | 
			
		||||
@ -780,7 +781,9 @@ export const addItem = async (
 | 
			
		||||
                        typeName.substr(1).split("/")[3] == "CatbrowPet" ||
 | 
			
		||||
                        typeName.substr(1).split("/")[3] == "KubrowPet"
 | 
			
		||||
                    ) {
 | 
			
		||||
                        if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
 | 
			
		||||
                            return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
 | 
			
		||||
                        if (!seed) {
 | 
			
		||||
                            throw new Error(`Expected crew member to have a seed`);
 | 
			
		||||
@ -1025,12 +1028,13 @@ export const addSpaceSuit = (
 | 
			
		||||
export const addKubrowPet = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    kubrowPetName: string,
 | 
			
		||||
    details: IKubrowPetDetailsDatabase | undefined,
 | 
			
		||||
    premiumPurchase: boolean,
 | 
			
		||||
    details?: IKubrowPetDetailsDatabase,
 | 
			
		||||
    premiumPurchase: boolean = false,
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): IInventoryChanges => {
 | 
			
		||||
    combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
 | 
			
		||||
 | 
			
		||||
    // TODO: When incubating, this should only be given when claiming the recipe.
 | 
			
		||||
    const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
 | 
			
		||||
    const exalted = kubrowPet?.exalted ?? [];
 | 
			
		||||
    for (const specialItem of exalted) {
 | 
			
		||||
@ -1079,11 +1083,11 @@ export const addKubrowPet = (
 | 
			
		||||
 | 
			
		||||
        details = {
 | 
			
		||||
            Name: "",
 | 
			
		||||
            IsPuppy: false,
 | 
			
		||||
            IsPuppy: !premiumPurchase,
 | 
			
		||||
            HasCollar: true,
 | 
			
		||||
            PrintsRemaining: 2,
 | 
			
		||||
            Status: Status.StatusStasis,
 | 
			
		||||
            HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
 | 
			
		||||
            PrintsRemaining: 3,
 | 
			
		||||
            Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
 | 
			
		||||
            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.
 | 
			
		||||
            IsMale: !!getRandomInt(0, 1),
 | 
			
		||||
            Size: getRandomInt(70, 100) / 100,
 | 
			
		||||
            DominantTraits: traits,
 | 
			
		||||
 | 
			
		||||
@ -765,7 +765,8 @@ export interface IKubrowPetDetailsClient extends Omit<IKubrowPetDetailsDatabase,
 | 
			
		||||
 | 
			
		||||
export enum Status {
 | 
			
		||||
    StatusAvailable = "STATUS_AVAILABLE",
 | 
			
		||||
    StatusStasis = "STATUS_STASIS"
 | 
			
		||||
    StatusStasis = "STATUS_STASIS",
 | 
			
		||||
    StatusIncubating = "STATUS_INCUBATING"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ILastSortieRewardClient {
 | 
			
		||||
@ -929,10 +930,14 @@ export interface IPendingRecipeDatabase {
 | 
			
		||||
    Pistols?: IEquipmentDatabase[];
 | 
			
		||||
    Melee?: IEquipmentDatabase[];
 | 
			
		||||
    SuitToUnbrand?: Types.ObjectId;
 | 
			
		||||
    KubrowPet?: Types.ObjectId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IPendingRecipeClient
 | 
			
		||||
    extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand"> {
 | 
			
		||||
    extends Omit<
 | 
			
		||||
        IPendingRecipeDatabase,
 | 
			
		||||
        "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand" | "KubrowPet"
 | 
			
		||||
    > {
 | 
			
		||||
    CompletionDate: IMongoDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user