fix: consume a slot when item is crafted instead of bought via plat #1029

Merged
Sainan merged 4 commits from consume-slots into main 2025-02-26 15:41:08 -08:00
6 changed files with 103 additions and 88 deletions

View File

@ -91,7 +91,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
} }
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,
...(await addItem(inventory, recipe.resultType, recipe.num)).InventoryChanges ...(await addItem(inventory, recipe.resultType, recipe.num, false)).InventoryChanges
}; };
await inventory.save(); await inventory.save();
res.json({ InventoryChanges }); res.json({ InventoryChanges });

View File

@ -54,10 +54,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
} }
} }
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
const inventoryChanges: IInventoryChanges = { const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
...updateCurrency(inventory, contributions.RegularCredits, false), inventoryChanges.MiscItems = miscItemChanges;
MiscItems: miscItemChanges
};
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) { if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
// This research is now fully funded. // This research is now fully funded.

View File

@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
const requests = req.body as IAddItemRequest[]; const requests = req.body as IAddItemRequest[];
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
for (const request of requests) { for (const request of requests) {
await addItem(inventory, request.ItemType, request.ItemCount); await addItem(inventory, request.ItemType, request.ItemCount, true);
} }
await inventory.save(); await inventory.save();
res.end(); res.end();

View File

@ -5,7 +5,7 @@ import {
} from "@/src/models/inventoryModels/inventoryModel"; } from "@/src/models/inventoryModels/inventoryModel";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { HydratedDocument, Types } from "mongoose"; import { HydratedDocument, Types } from "mongoose";
import { SlotNames, IInventoryChanges, IBinChanges, ICurrencyChanges } from "@/src/types/purchaseTypes"; import { SlotNames, IInventoryChanges, IBinChanges, slotNames } from "@/src/types/purchaseTypes";
import { import {
IChallengeProgress, IChallengeProgress,
IConsumable, IConsumable,
@ -126,13 +126,17 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
for (const item of right) { for (const item of right) {
left.push(item); left.push(item);
} }
} else if (typeof delta[key] == "object") { } else if (slotNames.indexOf(key as SlotNames) != -1) {
console.assert(key.substring(-3) == "Bin"); const left = InventoryChanges[key as SlotNames]!;
console.assert(key != "InfestedFoundry"); const right = delta[key as SlotNames]!;
const left = InventoryChanges[key] as IBinChanges; if (right.count) {
const right = delta[key] as IBinChanges; left.count ??= 0;
left.count += right.count; left.count += right.count;
left.platinum += right.platinum; }
if (right.platinum) {
left.platinum ??= 0;
left.platinum += right.platinum;
}
left.Slots += right.Slots; left.Slots += right.Slots;
if (right.Extra) { if (right.Extra) {
left.Extra ??= 0; left.Extra ??= 0;
@ -159,10 +163,32 @@ export const getInventory = async (
return inventory; return inventory;
}; };
const occupySlot = (
inventory: TInventoryDatabaseDocument,
bin: InventorySlot,
premiumPurchase: boolean
): IInventoryChanges => {
const slotChanges = {
Slots: 0,
Extra: 0
};
if (premiumPurchase) {
slotChanges.Extra += 1;
} else {
// { count: 1, platinum: 0, Slots: -1 }
slotChanges.Slots -= 1;
}
updateSlots(inventory, bin, slotChanges.Slots, slotChanges.Extra);
const inventoryChanges: IInventoryChanges = {};
inventoryChanges[bin] = slotChanges satisfies IBinChanges;
return inventoryChanges;
};
export const addItem = async ( export const addItem = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
typeName: string, typeName: string,
quantity: number = 1 quantity: number = 1,
premiumPurchase: boolean = false
): Promise<{ InventoryChanges: IInventoryChanges }> => { ): Promise<{ InventoryChanges: IInventoryChanges }> => {
// Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments. // Bundles are technically StoreItems but a) they don't have a normal counterpart, and b) they are used in non-StoreItem contexts, e.g. email attachments.
if (typeName in ExportBundles) { if (typeName in ExportBundles) {
@ -302,14 +328,13 @@ export const addItem = async (
const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName); const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName);
if (weapon.additionalItems) { if (weapon.additionalItems) {
for (const item of weapon.additionalItems) { for (const item of weapon.additionalItems) {
combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1)); combineInventoryChanges(inventoryChanges, (await addItem(inventory, item, 1)).InventoryChanges);
} }
} }
updateSlots(inventory, InventorySlot.WEAPONS, 0, 1);
return { return {
InventoryChanges: { InventoryChanges: {
...inventoryChanges, ...inventoryChanges,
WeaponBin: { count: 1, platinum: 0, Slots: -1 } ...occupySlot(inventory, InventorySlot.WEAPONS, premiumPurchase)
} }
}; };
} else { } else {
@ -378,44 +403,26 @@ export const addItem = async (
case "Powersuits": case "Powersuits":
switch (typeName.substr(1).split("/")[2]) { switch (typeName.substr(1).split("/")[2]) {
default: { default: {
const inventoryChanges = addPowerSuit(inventory, typeName);
updateSlots(inventory, InventorySlot.SUITS, 0, 1);
return { return {
InventoryChanges: { InventoryChanges: {
...inventoryChanges, ...addPowerSuit(inventory, typeName),
SuitBin: { ...occupySlot(inventory, InventorySlot.SUITS, premiumPurchase)
count: 1,
platinum: 0,
Slots: -1
}
} }
}; };
} }
case "Archwing": { case "Archwing": {
const inventoryChanges = addSpaceSuit(inventory, typeName);
updateSlots(inventory, InventorySlot.SPACESUITS, 0, 1);
return { return {
InventoryChanges: { InventoryChanges: {
...inventoryChanges, ...addSpaceSuit(inventory, typeName),
SpaceSuitBin: { ...occupySlot(inventory, InventorySlot.SPACESUITS, premiumPurchase)
count: 1,
platinum: 0,
Slots: -1
}
} }
}; };
} }
case "EntratiMech": { case "EntratiMech": {
const inventoryChanges = addMechSuit(inventory, typeName);
updateSlots(inventory, InventorySlot.MECHSUITS, 0, 1);
return { return {
InventoryChanges: { InventoryChanges: {
...inventoryChanges, ...addMechSuit(inventory, typeName),
MechBin: { ...occupySlot(inventory, InventorySlot.MECHSUITS, premiumPurchase)
count: 1,
platinum: 0,
Slots: -1
}
} }
}; };
} }
@ -446,12 +453,10 @@ export const addItem = async (
case "Types": case "Types":
switch (typeName.substr(1).split("/")[2]) { switch (typeName.substr(1).split("/")[2]) {
case "Sentinels": { case "Sentinels": {
const inventoryChanges = addSentinel(inventory, typeName);
updateSlots(inventory, InventorySlot.SENTINELS, 0, 1);
return { return {
InventoryChanges: { InventoryChanges: {
...inventoryChanges, ...addSentinel(inventory, typeName),
SentinelBin: { count: 1, platinum: 0, Slots: -1 } ...occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase)
} }
}; };
} }
@ -531,9 +536,9 @@ export const addItems = async (
let inventoryDelta; let inventoryDelta;
for (const item of items) { for (const item of items) {
if (typeof item === "string") { if (typeof item === "string") {
inventoryDelta = await addItem(inventory, item); inventoryDelta = await addItem(inventory, item, 1, true);
} else { } else {
inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount); inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount, true);
} }
combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges); combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
} }
@ -682,8 +687,8 @@ export const updateCurrency = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
price: number, price: number,
usePremium: boolean usePremium: boolean
): ICurrencyChanges => { ): IInventoryChanges => {
const currencyChanges: ICurrencyChanges = {}; const currencyChanges: IInventoryChanges = {};
if (price != 0 && isCurrencyTracked(usePremium)) { if (price != 0 && isCurrencyTracked(usePremium)) {
if (usePremium) { if (usePremium) {
if (inventory.PremiumCreditsFree > 0) { if (inventory.PremiumCreditsFree > 0) {

View File

@ -164,7 +164,7 @@ export const handlePurchase = async (
addMiscItems(inventory, [invItem]); addMiscItems(inventory, [invItem]);
purchaseResponse.InventoryChanges.MiscItems ??= []; purchaseResponse.InventoryChanges.MiscItems ??= [];
(purchaseResponse.InventoryChanges.MiscItems as IMiscItem[]).push(invItem); purchaseResponse.InventoryChanges.MiscItems.push(invItem);
} else if (!config.infiniteRegalAya) { } else if (!config.infiniteRegalAya) {
inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity; inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity;
} }
@ -191,11 +191,11 @@ const handleItemPrices = (
addMiscItems(inventory, [invItem]); addMiscItems(inventory, [invItem]);
inventoryChanges.MiscItems ??= []; inventoryChanges.MiscItems ??= [];
const change = (inventoryChanges.MiscItems as IMiscItem[]).find(x => x.ItemType == item.ItemType); const change = inventoryChanges.MiscItems.find(x => x.ItemType == item.ItemType);
if (change) { if (change) {
change.ItemCount += invItem.ItemCount; change.ItemCount += invItem.ItemCount;
} else { } else {
(inventoryChanges.MiscItems as IMiscItem[]).push(invItem); inventoryChanges.MiscItems.push(invItem);
} }
} }
}; };
@ -251,7 +251,7 @@ export const handleStoreItemAcquisition = async (
} }
switch (storeCategory) { switch (storeCategory) {
default: { default: {
purchaseResponse = await addItem(inventory, internalName, quantity); purchaseResponse = await addItem(inventory, internalName, quantity, true);
break; break;
} }
case "Types": case "Types":
@ -300,16 +300,14 @@ const handleSlotPurchase = (
logger.debug(`added ${slotsPurchased} slot ${slotName}`); logger.debug(`added ${slotsPurchased} slot ${slotName}`);
return { const inventoryChanges: IInventoryChanges = {};
InventoryChanges: { inventoryChanges[slotName] = {
[slotName]: { count: 0,
count: 0, platinum: 1,
platinum: 1, Slots: slotsPurchased,
Slots: slotsPurchased, Extra: slotsPurchased
Extra: slotsPurchased
}
}
}; };
return { InventoryChanges: inventoryChanges };
}; };
const handleBoosterPackPurchase = async ( const handleBoosterPackPurchase = async (

View File

@ -1,5 +1,5 @@
import { IEquipmentClient } from "./inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "./inventoryTypes/commonInventoryTypes";
import { IDroneClient, IInfestedFoundryClient, TEquipmentKey } from "./inventoryTypes/inventoryTypes"; import { IDroneClient, IInfestedFoundryClient, IMiscItem, TEquipmentKey } from "./inventoryTypes/inventoryTypes";
export interface IPurchaseRequest { export interface IPurchaseRequest {
PurchaseParams: IPurchaseParams; PurchaseParams: IPurchaseParams;
@ -22,20 +22,31 @@ export interface IPurchaseParams {
IsWeekly?: boolean; // for Source 7 IsWeekly?: boolean; // for Source 7
} }
export interface ICurrencyChanges {
RegularCredits?: number;
PremiumCredits?: number;
PremiumCreditsFree?: number;
}
export type IInventoryChanges = { export type IInventoryChanges = {
[_ in SlotNames]?: IBinChanges; [_ in SlotNames]?: IBinChanges;
} & { } & {
[_ in TEquipmentKey]?: IEquipmentClient[]; [_ in TEquipmentKey]?: IEquipmentClient[];
} & ICurrencyChanges & { } & {
InfestedFoundry?: IInfestedFoundryClient; RegularCredits?: number;
Drones?: IDroneClient[]; PremiumCredits?: number;
} & Record<string, IBinChanges | number | object[] | IInfestedFoundryClient>; PremiumCreditsFree?: number;
InfestedFoundry?: IInfestedFoundryClient;
Drones?: IDroneClient[];
MiscItems?: IMiscItem[];
} & Record<
Exclude<
string,
| SlotNames
| TEquipmentKey
| "RegularCredits"
| "PremiumCredits"
| "PremiumCreditsFree"
| "InfestedFoundry"
| "Drones"
| "MiscItems"
>,
number | object[]
>;
export interface IAffiliationMods { export interface IAffiliationMods {
Tag: string; Tag: string;
@ -51,8 +62,8 @@ export interface IPurchaseResponse {
} }
export type IBinChanges = { export type IBinChanges = {
count: number; count?: number;
platinum: number; platinum?: number;
Slots: number; Slots: number;
Extra?: number; Extra?: number;
}; };
@ -69,18 +80,21 @@ export type SlotPurchaseName =
| "TwoCrewShipSalvageSlotItem" | "TwoCrewShipSalvageSlotItem"
| "CrewMemberSlotItem"; | "CrewMemberSlotItem";
export type SlotNames = export const slotNames = [
| "SuitBin" "SuitBin",
| "WeaponBin" "WeaponBin",
| "MechBin" "MechBin",
| "PveBonusLoadoutBin" "PveBonusLoadoutBin",
| "SentinelBin" "SentinelBin",
| "SpaceSuitBin" "SpaceSuitBin",
| "SpaceWeaponBin" "SpaceWeaponBin",
| "OperatorAmpBin" "OperatorAmpBin",
| "RandomModBin" "RandomModBin",
| "CrewShipSalvageBin" "CrewShipSalvageBin",
| "CrewMemberBin"; "CrewMemberBin"
] as const;
export type SlotNames = (typeof slotNames)[number];
export type SlotPurchase = { export type SlotPurchase = {
[P in SlotPurchaseName]: { name: SlotNames; slotsPerPurchase: number }; [P in SlotPurchaseName]: { name: SlotNames; slotsPerPurchase: number };