feat: kubrow & kavat incubation (#2131)
Closes #377 Reviewed-on: #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";
|
} from "@/src/services/inventoryService";
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
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 { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
import { IRecipe } from "warframe-public-export-plus";
|
import { IRecipe } from "warframe-public-export-plus";
|
||||||
@ -105,7 +105,21 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
|||||||
...updateCurrency(inventory, cost, true)
|
...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 = {
|
||||||
...InventoryChanges,
|
...InventoryChanges,
|
||||||
...(await addItem(
|
...(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 refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
||||||
}
|
}
|
||||||
await inventory.save();
|
await inventory.save();
|
||||||
|
@ -3,12 +3,14 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
|||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { getRecipe } from "@/src/services/itemDataService";
|
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 { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { InventorySlot, ISpectreLoadout } from "@/src/types/inventoryTypes/inventoryTypes";
|
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 { ExportWeapons } from "warframe-public-export-plus";
|
||||||
|
import { getRandomElement } from "@/src/services/rngService";
|
||||||
|
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||||
|
|
||||||
interface IStartRecipeRequest {
|
interface IStartRecipeRequest {
|
||||||
RecipeName: string;
|
RecipeName: string;
|
||||||
@ -42,24 +44,35 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
||||||
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
||||||
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
||||||
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
||||||
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
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}`);
|
||||||
|
}
|
||||||
|
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
||||||
|
if (equipmentIndex == -1) {
|
||||||
|
throw new Error(`could not find equipment item to use for recipe`);
|
||||||
|
}
|
||||||
|
pr[category] ??= [];
|
||||||
|
pr[category].push(inventory[category][equipmentIndex]);
|
||||||
|
inventory[category].splice(equipmentIndex, 1);
|
||||||
|
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
||||||
}
|
}
|
||||||
const equipmentIndex = inventory[category].findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
|
|
||||||
if (equipmentIndex == -1) {
|
|
||||||
throw new Error(`could not find equipment item to use for recipe`);
|
|
||||||
}
|
|
||||||
pr[category] ??= [];
|
|
||||||
pr[category].push(inventory[category][equipmentIndex]);
|
|
||||||
inventory[category].splice(equipmentIndex, 1);
|
|
||||||
freeUpSlot(inventory, InventorySlot.WEAPONS);
|
|
||||||
} else {
|
} else {
|
||||||
await addItem(inventory, recipe.ingredients[i].ItemType, recipe.ingredients[i].ItemCount * -1);
|
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 = {
|
const spectreLoadout: ISpectreLoadout = {
|
||||||
ItemType: recipe.resultType,
|
ItemType: recipe.resultType,
|
||||||
Suits: "",
|
Suits: "",
|
||||||
@ -116,5 +129,5 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
|||||||
|
|
||||||
await inventory.save();
|
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 },
|
LongGuns: { type: [EquipmentSchema], default: undefined },
|
||||||
Pistols: { type: [EquipmentSchema], default: undefined },
|
Pistols: { type: [EquipmentSchema], default: undefined },
|
||||||
Melee: { 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 }
|
{ id: false }
|
||||||
);
|
);
|
||||||
@ -1115,6 +1116,7 @@ pendingRecipeSchema.set("toJSON", {
|
|||||||
delete returnedObject.Pistols;
|
delete returnedObject.Pistols;
|
||||||
delete returnedObject.Melees;
|
delete returnedObject.Melees;
|
||||||
delete returnedObject.SuitToUnbrand;
|
delete returnedObject.SuitToUnbrand;
|
||||||
|
delete returnedObject.KubrowPet;
|
||||||
(returnedObject as IPendingRecipeClient).CompletionDate = {
|
(returnedObject as IPendingRecipeClient).CompletionDate = {
|
||||||
$date: { $numberLong: (returnedObject as IPendingRecipeDatabase).CompletionDate.getTime().toString() }
|
$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 { addPendingFriendController } from "@/src/controllers/api/addPendingFriendController";
|
||||||
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
|
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
|
||||||
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
|
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
|
||||||
|
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
|
||||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
|
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
|
||||||
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
|
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
|
||||||
import { artifactsController } from "@/src/controllers/api/artifactsController";
|
import { artifactsController } from "@/src/controllers/api/artifactsController";
|
||||||
@ -226,6 +227,7 @@ apiRouter.post("/addIgnoredUser.php", addIgnoredUserController);
|
|||||||
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
|
apiRouter.post("/addPendingFriend.php", addPendingFriendController);
|
||||||
apiRouter.post("/addToAlliance.php", addToAllianceController);
|
apiRouter.post("/addToAlliance.php", addToAllianceController);
|
||||||
apiRouter.post("/addToGuild.php", addToGuildController);
|
apiRouter.post("/addToGuild.php", addToGuildController);
|
||||||
|
apiRouter.post("/adoptPet.php", adoptPetController);
|
||||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
|
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
|
||||||
apiRouter.post("/archonFusion.php", archonFusionController);
|
apiRouter.post("/archonFusion.php", archonFusionController);
|
||||||
apiRouter.post("/artifacts.php", artifactsController);
|
apiRouter.post("/artifacts.php", artifactsController);
|
||||||
|
@ -86,6 +86,7 @@ import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
|||||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
|
||||||
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
|
||||||
import { TAccountDocument } from "./loginService";
|
import { TAccountDocument } from "./loginService";
|
||||||
|
import { unixTimesInMs } from "../constants/timeConstants";
|
||||||
|
|
||||||
export const createInventory = async (
|
export const createInventory = async (
|
||||||
accountOwnerId: Types.ObjectId,
|
accountOwnerId: Types.ObjectId,
|
||||||
@ -780,7 +781,9 @@ export const addItem = async (
|
|||||||
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
|
||||||
typeName.substr(1).split("/")[3] == "KubrowPet"
|
typeName.substr(1).split("/")[3] == "KubrowPet"
|
||||||
) {
|
) {
|
||||||
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
|
||||||
|
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
|
||||||
|
}
|
||||||
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
|
||||||
if (!seed) {
|
if (!seed) {
|
||||||
throw new Error(`Expected crew member to have a seed`);
|
throw new Error(`Expected crew member to have a seed`);
|
||||||
@ -1025,12 +1028,13 @@ export const addSpaceSuit = (
|
|||||||
export const addKubrowPet = (
|
export const addKubrowPet = (
|
||||||
inventory: TInventoryDatabaseDocument,
|
inventory: TInventoryDatabaseDocument,
|
||||||
kubrowPetName: string,
|
kubrowPetName: string,
|
||||||
details: IKubrowPetDetailsDatabase | undefined,
|
details?: IKubrowPetDetailsDatabase,
|
||||||
premiumPurchase: boolean,
|
premiumPurchase: boolean = false,
|
||||||
inventoryChanges: IInventoryChanges = {}
|
inventoryChanges: IInventoryChanges = {}
|
||||||
): IInventoryChanges => {
|
): IInventoryChanges => {
|
||||||
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
|
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 kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
|
||||||
const exalted = kubrowPet?.exalted ?? [];
|
const exalted = kubrowPet?.exalted ?? [];
|
||||||
for (const specialItem of exalted) {
|
for (const specialItem of exalted) {
|
||||||
@ -1079,11 +1083,11 @@ export const addKubrowPet = (
|
|||||||
|
|
||||||
details = {
|
details = {
|
||||||
Name: "",
|
Name: "",
|
||||||
IsPuppy: false,
|
IsPuppy: !premiumPurchase,
|
||||||
HasCollar: true,
|
HasCollar: true,
|
||||||
PrintsRemaining: 2,
|
PrintsRemaining: 3,
|
||||||
Status: Status.StatusStasis,
|
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
|
||||||
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
|
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),
|
IsMale: !!getRandomInt(0, 1),
|
||||||
Size: getRandomInt(70, 100) / 100,
|
Size: getRandomInt(70, 100) / 100,
|
||||||
DominantTraits: traits,
|
DominantTraits: traits,
|
||||||
|
@ -765,7 +765,8 @@ export interface IKubrowPetDetailsClient extends Omit<IKubrowPetDetailsDatabase,
|
|||||||
|
|
||||||
export enum Status {
|
export enum Status {
|
||||||
StatusAvailable = "STATUS_AVAILABLE",
|
StatusAvailable = "STATUS_AVAILABLE",
|
||||||
StatusStasis = "STATUS_STASIS"
|
StatusStasis = "STATUS_STASIS",
|
||||||
|
StatusIncubating = "STATUS_INCUBATING"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILastSortieRewardClient {
|
export interface ILastSortieRewardClient {
|
||||||
@ -929,10 +930,14 @@ export interface IPendingRecipeDatabase {
|
|||||||
Pistols?: IEquipmentDatabase[];
|
Pistols?: IEquipmentDatabase[];
|
||||||
Melee?: IEquipmentDatabase[];
|
Melee?: IEquipmentDatabase[];
|
||||||
SuitToUnbrand?: Types.ObjectId;
|
SuitToUnbrand?: Types.ObjectId;
|
||||||
|
KubrowPet?: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPendingRecipeClient
|
export interface IPendingRecipeClient
|
||||||
extends Omit<IPendingRecipeDatabase, "CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand"> {
|
extends Omit<
|
||||||
|
IPendingRecipeDatabase,
|
||||||
|
"CompletionDate" | "LongGuns" | "Pistols" | "Melee" | "SuitToUnbrand" | "KubrowPet"
|
||||||
|
> {
|
||||||
CompletionDate: IMongoDate;
|
CompletionDate: IMongoDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user