feat: create genetic imprint
All checks were successful
Build / build (pull_request) Successful in 1m23s

This commit is contained in:
Sainan 2025-06-28 02:15:41 +02:00
parent 3d21813a79
commit e6ed373bdf
8 changed files with 94 additions and 35 deletions

View File

@ -11,17 +11,17 @@
"node": true "node": true
}, },
"rules": { "rules": {
"@typescript-eslint/explicit-function-return-type": "warn", "@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/restrict-template-expressions": "warn", "@typescript-eslint/restrict-template-expressions": "error",
"@typescript-eslint/restrict-plus-operands": "warn", "@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/no-unsafe-member-access": "warn", "@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }], "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "warn", "@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "warn", "@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-explicit-any": "error",
"no-loss-of-precision": "warn", "no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "warn", "@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-base-to-string": "off",
"no-case-declarations": "error", "no-case-declarations": "error",
"prettier/prettier": "error", "prettier/prettier": "error",

View File

@ -13,7 +13,8 @@ import {
addItem, addItem,
addRecipes, addRecipes,
occupySlot, occupySlot,
combineInventoryChanges combineInventoryChanges,
addKubrowPetPrint
} 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";
@ -119,6 +120,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} }
} }
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis; pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") { } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,

View File

@ -45,9 +45,9 @@ 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] != "/") {
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") { if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i])); const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
if (index != -1) { if (index != -1) {
inventory.KubrowPetEggs!.splice(index, 1); inventory.KubrowPetEggs.splice(index, 1);
} }
} else { } else {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory; const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
@ -72,6 +72,10 @@ export const startRecipeController: RequestHandler = async (req, res) => {
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") { if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType); inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId)); pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]);
const pet = inventory.KubrowPets.id(pr.KubrowPet)!;
pet.Details!.PrintsRemaining -= 1;
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") { } else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const spectreLoadout: ISpectreLoadout = { const spectreLoadout: ISpectreLoadout = {
ItemType: recipe.resultType, ItemType: recipe.resultType,

View File

@ -99,7 +99,9 @@ import {
ILotusCustomization, ILotusCustomization,
IEndlessXpReward, IEndlessXpReward,
IPersonalGoalProgressDatabase, IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient IPersonalGoalProgressClient,
IKubrowPetPrintClient,
IKubrowPetPrintDatabase
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -1008,6 +1010,27 @@ const traitsSchema = new Schema<ITraits>(
{ _id: false } { _id: false }
); );
const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
ItemType: String,
Name: String,
IsMale: Boolean,
Size: Number,
DominantTraits: traitsSchema,
RecessiveTraits: traitsSchema
});
kubrowPetPrintSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient;
client.ItemId = toOid(db._id);
delete obj._id;
delete obj.__v;
}
});
const detailsSchema = new Schema<IKubrowPetDetailsDatabase>( const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
{ {
Name: String, Name: String,
@ -1511,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
KubrowPetEggs: [kubrowPetEggSchema], KubrowPetEggs: [kubrowPetEggSchema],
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets //Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
//KubrowPetPrints: [Schema.Types.Mixed], KubrowPetPrints: [kubrowPetPrintSchema],
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc //Item for EquippedGear example:Scaner,LoadoutTechSummon etc
Consumables: [typeCountSchema], Consumables: [typeCountSchema],
@ -1850,6 +1873,7 @@ export type InventoryDocumentProps = {
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>; CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>; PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>; CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
KubrowPetPrints: Types.DocumentArray<IKubrowPetPrintDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> }; } & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// eslint-disable-next-line @typescript-eslint/no-empty-object-type // eslint-disable-next-line @typescript-eslint/no-empty-object-type

View File

@ -29,7 +29,8 @@ import {
ICalendarProgress, ICalendarProgress,
INemesisWeaponTargetFingerprint, INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint, INemesisPetTargetFingerprint,
IDialogueDatabase IDialogueDatabase,
IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate"; import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -424,7 +425,6 @@ export const addItem = async (
ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg", ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg",
_id: new Types.ObjectId() _id: new Types.ObjectId()
}; };
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push(egg); inventory.KubrowPetEggs.push(egg);
changes.push({ changes.push({
ItemType: egg.ItemType, ItemType: egg.ItemType,
@ -784,7 +784,11 @@ 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"
) { ) {
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") { if (
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" &&
typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
) {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase); return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
} }
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) { } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
@ -1048,8 +1052,13 @@ export const addKubrowPet = (
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades); const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) { if (!details) {
let traits: ITraits; const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
].includes(kubrowPetName);
let traits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") { if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
traits = { traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire", BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
@ -1064,12 +1073,7 @@ export const addKubrowPet = (
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire" Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
}; };
} else { } else {
const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
].includes(kubrowPetName);
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails; const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
traits = { traits = {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type, BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type, SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
@ -1088,7 +1092,7 @@ export const addKubrowPet = (
Name: "", Name: "",
IsPuppy: !premiumPurchase, IsPuppy: !premiumPurchase,
HasCollar: true, HasCollar: true,
PrintsRemaining: 3, PrintsRemaining: isCatbrow ? 3 : 2,
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating, 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. 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),
@ -1112,6 +1116,26 @@ export const addKubrowPet = (
return inventoryChanges; return inventoryChanges;
}; };
export const addKubrowPetPrint = (
inventory: TInventoryDatabaseDocument,
pet: IEquipmentDatabase,
inventoryChanges: IInventoryChanges
): void => {
inventoryChanges.KubrowPetPrints ??= [];
inventoryChanges.KubrowPetPrints.push(
inventory.KubrowPetPrints[
inventory.KubrowPetPrints.push({
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint",
Name: pet.Details!.Name,
IsMale: pet.Details!.IsMale,
Size: pet.Details!.Size,
DominantTraits: pet.Details!.DominantTraits,
RecessiveTraits: pet.Details!.RecessiveTraits
}) - 1
].toJSON<IKubrowPetPrintClient>()
);
};
export const updateSlots = ( export const updateSlots = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
slotName: SlotNames, slotName: SlotNames,

View File

@ -525,7 +525,6 @@ export const addMissionInventoryUpdates = async (
} }
case "KubrowPetEggs": { case "KubrowPetEggs": {
for (const egg of value) { for (const egg of value) {
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push({ inventory.KubrowPetEggs.push({
ItemType: egg.ItemType, ItemType: egg.ItemType,
_id: new Types.ObjectId() _id: new Types.ObjectId()

View File

@ -40,6 +40,7 @@ export interface IInventoryDatabase
| "InfestedFoundry" | "InfestedFoundry"
| "DialogueHistory" | "DialogueHistory"
| "KubrowPetEggs" | "KubrowPetEggs"
| "KubrowPetPrints"
| "PendingCoupon" | "PendingCoupon"
| "Drones" | "Drones"
| "RecentVendorPurchases" | "RecentVendorPurchases"
@ -79,7 +80,8 @@ export interface IInventoryDatabase
KahlLoadOuts: IOperatorConfigDatabase[]; KahlLoadOuts: IOperatorConfigDatabase[];
InfestedFoundry?: IInfestedFoundryDatabase; InfestedFoundry?: IInfestedFoundryDatabase;
DialogueHistory?: IDialogueHistoryDatabase; DialogueHistory?: IDialogueHistoryDatabase;
KubrowPetEggs?: IKubrowPetEggDatabase[]; KubrowPetEggs: IKubrowPetEggDatabase[];
KubrowPetPrints: IKubrowPetPrintDatabase[];
PendingCoupon?: IPendingCouponDatabase; PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[]; Drones: IDroneDatabase[];
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[]; RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
@ -306,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
FocusUpgrades: IFocusUpgrade[]; FocusUpgrades: IFocusUpgrade[];
HasContributedToDojo?: boolean; HasContributedToDojo?: boolean;
HWIDProtectEnabled?: boolean; HWIDProtectEnabled?: boolean;
//KubrowPetPrints: IKubrowPetPrint[]; KubrowPetPrints: IKubrowPetPrintClient[];
AlignmentReplay?: IAlignment; AlignmentReplay?: IAlignment;
PersonalGoalProgress?: IPersonalGoalProgressClient[]; PersonalGoalProgress?: IPersonalGoalProgressClient[];
ThemeStyle: string; ThemeStyle: string;
@ -722,8 +724,8 @@ export interface IKubrowPetEggDatabase {
_id: Types.ObjectId; _id: Types.ObjectId;
} }
export interface IKubrowPetPrint { export interface IKubrowPetPrintClient {
ItemType: KubrowPetPrintItemType; ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint";
Name: string; Name: string;
IsMale: boolean; IsMale: boolean;
Size: number; // seems to be 0.7 to 1.0 Size: number; // seems to be 0.7 to 1.0
@ -733,6 +735,10 @@ export interface IKubrowPetPrint {
InheritedModularParts?: any[]; InheritedModularParts?: any[];
} }
export interface IKubrowPetPrintDatabase extends Omit<IKubrowPetPrintClient, "ItemId" | "InheritedModularParts"> {
_id: Types.ObjectId;
}
export interface ITraits { export interface ITraits {
BaseColor: string; BaseColor: string;
SecondaryColor: string; SecondaryColor: string;
@ -746,15 +752,11 @@ export interface ITraits {
Tail?: string; Tail?: string;
} }
export enum KubrowPetPrintItemType {
LotusTypesGameKubrowPetImprintedTraitPrint = "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
}
export interface IKubrowPetDetailsDatabase { export interface IKubrowPetDetailsDatabase {
Name?: string; Name?: string;
IsPuppy?: boolean; IsPuppy?: boolean;
HasCollar: boolean; HasCollar: boolean;
PrintsRemaining?: number; PrintsRemaining: number;
Status: Status; Status: Status;
HatchDate?: Date; HatchDate?: Date;
DominantTraits: ITraits; DominantTraits: ITraits;

View File

@ -7,7 +7,8 @@ import {
ITypeCount, ITypeCount,
IRecentVendorPurchaseClient, IRecentVendorPurchaseClient,
TEquipmentKey, TEquipmentKey,
ICrewMemberClient ICrewMemberClient,
IKubrowPetPrintClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
export enum PurchaseSource { export enum PurchaseSource {
@ -78,6 +79,7 @@ export type IInventoryChanges = {
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0 NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0 RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[]; CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
} & Record< } & Record<
Exclude< Exclude<
string, string,