2024-01-25 14:49:45 +01:00
|
|
|
import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
|
2023-06-14 02:26:19 +02:00
|
|
|
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
|
2024-06-15 02:50:43 +02:00
|
|
|
import { addItem, addBooster, updateCurrency, updateSlots } from "@/src/services/inventoryService";
|
2024-06-20 16:35:24 +02:00
|
|
|
import { IPurchaseRequest, SlotPurchase, IInventoryChanges, IBinChanges } from "@/src/types/purchaseTypes";
|
2024-01-06 16:26:58 +01:00
|
|
|
import { logger } from "@/src/utils/logger";
|
2024-06-17 16:38:26 +02:00
|
|
|
import { ExportBundles, TRarity } from "warframe-public-export-plus";
|
2023-06-14 02:26:19 +02:00
|
|
|
|
|
|
|
export const getStoreItemCategory = (storeItem: string) => {
|
|
|
|
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
|
|
|
|
const storeItemElements = storeItemString.split("/");
|
|
|
|
return storeItemElements[1];
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getStoreItemTypesCategory = (typesItem: string) => {
|
|
|
|
const typesString = getSubstringFromKeyword(typesItem, "Types");
|
|
|
|
const typeElements = typesString.split("/");
|
|
|
|
if (typesItem.includes("StoreItems")) {
|
|
|
|
return typeElements[2];
|
|
|
|
}
|
|
|
|
return typeElements[1];
|
|
|
|
};
|
|
|
|
|
|
|
|
export const handlePurchase = async (purchaseRequest: IPurchaseRequest, accountId: string) => {
|
2024-01-06 16:26:58 +01:00
|
|
|
logger.debug("purchase request", purchaseRequest);
|
2023-06-14 02:26:19 +02:00
|
|
|
|
2024-06-15 22:12:57 +02:00
|
|
|
const purchaseResponse = await handleStoreItemAcquisition(
|
|
|
|
purchaseRequest.PurchaseParams.StoreItem,
|
|
|
|
accountId,
|
2024-06-17 16:38:26 +02:00
|
|
|
purchaseRequest.PurchaseParams.Quantity,
|
|
|
|
"COMMON"
|
2024-06-15 22:12:57 +02:00
|
|
|
);
|
2023-06-14 02:26:19 +02:00
|
|
|
|
2024-06-15 02:50:43 +02:00
|
|
|
if (!purchaseResponse) throw new Error("purchase response was undefined");
|
2023-06-14 02:26:19 +02:00
|
|
|
|
2023-12-28 16:24:52 +01:00
|
|
|
const currencyChanges = await updateCurrency(
|
|
|
|
purchaseRequest.PurchaseParams.ExpectedPrice,
|
|
|
|
purchaseRequest.PurchaseParams.UsePremium,
|
|
|
|
accountId
|
|
|
|
);
|
2023-06-14 02:26:19 +02:00
|
|
|
|
2024-06-15 02:50:43 +02:00
|
|
|
purchaseResponse.InventoryChanges = {
|
2023-12-28 16:24:52 +01:00
|
|
|
...currencyChanges,
|
2024-06-15 02:50:43 +02:00
|
|
|
...purchaseResponse.InventoryChanges
|
2023-12-28 16:24:52 +01:00
|
|
|
};
|
|
|
|
|
2024-06-15 02:50:43 +02:00
|
|
|
return purchaseResponse;
|
2023-12-28 16:24:52 +01:00
|
|
|
};
|
|
|
|
|
2024-06-20 16:35:24 +02:00
|
|
|
const addInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
|
|
|
|
for (const key in delta) {
|
|
|
|
if (!(key in InventoryChanges)) {
|
|
|
|
InventoryChanges[key] = delta[key];
|
|
|
|
} else if (Array.isArray(delta[key])) {
|
|
|
|
const left = InventoryChanges[key] as object[];
|
|
|
|
const right = delta[key] as object[];
|
|
|
|
for (const item of right) {
|
|
|
|
left.push(item);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.assert(key.substring(-3) == "Bin");
|
|
|
|
const left = InventoryChanges[key] as IBinChanges;
|
|
|
|
const right = delta[key] as IBinChanges;
|
|
|
|
left.count += right.count;
|
|
|
|
left.platinum += right.platinum;
|
|
|
|
left.Slots += right.Slots;
|
|
|
|
if (right.Extra) {
|
|
|
|
left.Extra ??= 0;
|
|
|
|
left.Extra += right.Extra;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-06-15 22:12:57 +02:00
|
|
|
const handleStoreItemAcquisition = async (
|
|
|
|
storeItemName: string,
|
|
|
|
accountId: string,
|
2024-06-17 16:38:26 +02:00
|
|
|
quantity: number,
|
|
|
|
durability: TRarity
|
2024-06-20 16:35:24 +02:00
|
|
|
): Promise<{ InventoryChanges: IInventoryChanges }> => {
|
2024-06-15 22:12:57 +02:00
|
|
|
let purchaseResponse = {
|
|
|
|
InventoryChanges: {}
|
|
|
|
};
|
|
|
|
logger.debug(`handling acquision of ${storeItemName}`);
|
|
|
|
if (storeItemName in ExportBundles) {
|
|
|
|
const bundle = ExportBundles[storeItemName];
|
|
|
|
logger.debug("acquiring bundle", bundle);
|
|
|
|
for (const component of bundle.components) {
|
2024-06-20 16:35:24 +02:00
|
|
|
addInventoryChanges(
|
|
|
|
purchaseResponse.InventoryChanges,
|
|
|
|
(
|
|
|
|
await handleStoreItemAcquisition(
|
|
|
|
component.typeName,
|
|
|
|
accountId,
|
|
|
|
component.purchaseQuantity,
|
|
|
|
component.durability
|
|
|
|
)
|
|
|
|
).InventoryChanges
|
|
|
|
);
|
2024-06-15 22:12:57 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const storeCategory = getStoreItemCategory(storeItemName);
|
|
|
|
const internalName = storeItemName.replace("/StoreItems", "");
|
|
|
|
logger.debug(`store category ${storeCategory}`);
|
|
|
|
switch (storeCategory) {
|
|
|
|
default:
|
|
|
|
purchaseResponse = await addItem(accountId, internalName);
|
|
|
|
break;
|
|
|
|
case "Types":
|
|
|
|
purchaseResponse = await handleTypesPurchase(internalName, accountId, quantity);
|
|
|
|
break;
|
|
|
|
case "Boosters":
|
2024-06-17 16:38:26 +02:00
|
|
|
purchaseResponse = await handleBoostersPurchase(internalName, accountId, durability);
|
2024-06-15 22:12:57 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return purchaseResponse;
|
|
|
|
};
|
|
|
|
|
2023-12-28 16:24:52 +01:00
|
|
|
export const slotPurchaseNameToSlotName: SlotPurchase = {
|
|
|
|
SuitSlotItem: { name: "SuitBin", slotsPerPurchase: 1 },
|
|
|
|
TwoSentinelSlotItem: { name: "SentinelBin", slotsPerPurchase: 2 },
|
|
|
|
TwoWeaponSlotItem: { name: "WeaponBin", slotsPerPurchase: 2 },
|
|
|
|
SpaceSuitSlotItem: { name: "SpaceSuitBin", slotsPerPurchase: 1 },
|
|
|
|
TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", slotsPerPurchase: 2 },
|
|
|
|
MechSlotItem: { name: "MechBin", slotsPerPurchase: 1 },
|
|
|
|
TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", slotsPerPurchase: 2 },
|
|
|
|
RandomModSlotItem: { name: "RandomModBin", slotsPerPurchase: 3 },
|
|
|
|
TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", slotsPerPurchase: 2 },
|
|
|
|
CrewMemberSlotItem: { name: "CrewMemberBin", slotsPerPurchase: 1 }
|
|
|
|
};
|
|
|
|
|
|
|
|
// // extra = everything above the base +2 slots (depending on slot type)
|
|
|
|
// // new slot above base = extra + 1 and slots +1
|
|
|
|
// // new frame = slots -1
|
|
|
|
// // number of frames = extra - slots + 2
|
2024-06-20 16:35:24 +02:00
|
|
|
const handleSlotPurchase = async (
|
|
|
|
slotPurchaseNameFull: string,
|
|
|
|
accountId: string
|
|
|
|
): Promise<{ InventoryChanges: IInventoryChanges }> => {
|
2024-01-06 16:26:58 +01:00
|
|
|
logger.debug(`slot name ${slotPurchaseNameFull}`);
|
2023-12-28 16:24:52 +01:00
|
|
|
const slotPurchaseName = parseSlotPurchaseName(
|
|
|
|
slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1)
|
|
|
|
);
|
2024-01-06 16:26:58 +01:00
|
|
|
logger.debug(`slot purchase name ${slotPurchaseName}`);
|
2023-12-28 16:24:52 +01:00
|
|
|
|
2024-01-06 16:26:58 +01:00
|
|
|
const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;
|
|
|
|
const slotsPerPurchase = slotPurchaseNameToSlotName[slotPurchaseName].slotsPerPurchase;
|
2023-12-28 16:24:52 +01:00
|
|
|
|
2024-01-06 16:26:58 +01:00
|
|
|
await updateSlots(accountId, slotName, slotsPerPurchase, slotsPerPurchase);
|
|
|
|
|
|
|
|
logger.debug(`added ${slotsPerPurchase} slot ${slotName}`);
|
2023-12-28 16:24:52 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
InventoryChanges: {
|
2024-01-06 16:26:58 +01:00
|
|
|
[slotName]: {
|
2023-12-28 16:24:52 +01:00
|
|
|
count: 0,
|
|
|
|
platinum: 1,
|
2024-01-06 16:26:58 +01:00
|
|
|
Slots: slotsPerPurchase,
|
|
|
|
Extra: slotsPerPurchase
|
2023-12-28 16:24:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2023-06-14 02:26:19 +02:00
|
|
|
};
|
|
|
|
|
2023-12-28 16:24:52 +01:00
|
|
|
//TODO: change to getInventory, apply changes then save at the end
|
2024-06-20 16:35:24 +02:00
|
|
|
const handleTypesPurchase = async (
|
|
|
|
typesName: string,
|
|
|
|
accountId: string,
|
|
|
|
quantity: number
|
|
|
|
): Promise<{ InventoryChanges: IInventoryChanges }> => {
|
2023-06-14 02:26:19 +02:00
|
|
|
const typeCategory = getStoreItemTypesCategory(typesName);
|
2024-01-06 16:26:58 +01:00
|
|
|
logger.debug(`type category ${typeCategory}`);
|
2023-06-14 02:26:19 +02:00
|
|
|
switch (typeCategory) {
|
2024-06-15 02:50:43 +02:00
|
|
|
default:
|
|
|
|
return await addItem(accountId, typesName, quantity);
|
2023-12-28 16:24:52 +01:00
|
|
|
case "SlotItems":
|
|
|
|
return await handleSlotPurchase(typesName, accountId);
|
2023-06-14 02:26:19 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-08-31 14:29:09 +04:00
|
|
|
const boosterCollection = [
|
|
|
|
"/Lotus/Types/Boosters/ResourceAmountBooster",
|
|
|
|
"/Lotus/Types/Boosters/AffinityBooster",
|
|
|
|
"/Lotus/Types/Boosters/ResourceDropChanceBooster",
|
|
|
|
"/Lotus/Types/Boosters/CreditBooster"
|
|
|
|
];
|
|
|
|
|
2024-06-17 16:38:26 +02:00
|
|
|
const boosterDuration: Record<TRarity, number> = {
|
|
|
|
COMMON: 3 * 86400,
|
|
|
|
UNCOMMON: 7 * 86400,
|
|
|
|
RARE: 30 * 86400,
|
|
|
|
LEGENDARY: 90 * 86400
|
|
|
|
};
|
|
|
|
|
2024-06-20 16:35:24 +02:00
|
|
|
const handleBoostersPurchase = async (
|
|
|
|
boosterStoreName: string,
|
|
|
|
accountId: string,
|
|
|
|
durability: TRarity
|
|
|
|
): Promise<{ InventoryChanges: IInventoryChanges }> => {
|
2024-06-17 16:38:26 +02:00
|
|
|
const ItemType = boosterStoreName.replace("StoreItem", "");
|
|
|
|
if (!boosterCollection.find(x => x == ItemType)) {
|
|
|
|
logger.error(`unknown booster type: ${ItemType}`);
|
2024-06-15 22:12:57 +02:00
|
|
|
return { InventoryChanges: {} };
|
|
|
|
}
|
2023-08-31 14:29:09 +04:00
|
|
|
|
2024-06-17 16:38:26 +02:00
|
|
|
const ExpiryDate = boosterDuration[durability];
|
2023-08-31 14:29:09 +04:00
|
|
|
|
|
|
|
await addBooster(ItemType, ExpiryDate, accountId);
|
|
|
|
|
|
|
|
return {
|
|
|
|
InventoryChanges: {
|
|
|
|
Boosters: [{ ItemType, ExpiryDate }]
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|