diff --git a/project/src/controllers/DialogueController.ts b/project/src/controllers/DialogueController.ts index 90f31704..dd81d382 100644 --- a/project/src/controllers/DialogueController.ts +++ b/project/src/controllers/DialogueController.ts @@ -26,7 +26,7 @@ export class DialogueController const profiles = this.saveServer.getProfiles(); for (const sessionID in profiles) { - this.removeExpiredItems(sessionID); + this.removeExpiredItemsFromMessages(sessionID); } } @@ -220,20 +220,40 @@ export class DialogueController } /** - * Delete expired items. triggers when updating traders. - * @param sessionID Session id + * Delete expired items from all messages in player profile. triggers when updating traders. + * @param sessionId Session id */ - protected removeExpiredItems(sessionID: string): void + protected removeExpiredItemsFromMessages(sessionId: string): void { - for (const dialogueId in this.saveServer.getProfile(sessionID).dialogues) + for (const dialogueId in this.saveServer.getProfile(sessionId).dialogues) { - for (const message of this.saveServer.getProfile(sessionID).dialogues[dialogueId].messages) + this.removeExpiredItemsFromMessage(sessionId, dialogueId); + } + } + + /** + * Removes expired items from a message in player profile + * @param sessionId Session id + * @param dialogueId Dialog id + */ + protected removeExpiredItemsFromMessage(sessionId: string, dialogueId: string): void + { + for (const message of this.saveServer.getProfile(sessionId).dialogues[dialogueId].messages) + { + if (this.messageHasExpired(message)) { - if ((this.timeUtil.getTimestamp()) > (message.dt + message.maxStorageTime)) - { - message.items = {}; - } + message.items = {}; } } } + + /** + * Has a dialog message expired + * @param message Message to check expiry of + * @returns true or false + */ + protected messageHasExpired(message: Message): boolean + { + return (this.timeUtil.getTimestamp()) > (message.dt + message.maxStorageTime); + } } \ No newline at end of file diff --git a/project/src/controllers/RagfairController.ts b/project/src/controllers/RagfairController.ts index e67bd6ed..bb89da3b 100644 --- a/project/src/controllers/RagfairController.ts +++ b/project/src/controllers/RagfairController.ts @@ -327,67 +327,38 @@ export class RagfairController } } - public addPlayerOffer(pmcData: IPmcData, info: IAddOfferRequestData, sessionID: string): IItemEventRouterResponse + /** + * List item(s) on flea for sale + * @param pmcData Player profile + * @param offerRequest Flea list creatio offer + * @param sessionID Session id + * @returns IItemEventRouterResponse + */ + public addPlayerOffer(pmcData: IPmcData, offerRequest: IAddOfferRequestData, sessionID: string): IItemEventRouterResponse { let output = this.eventOutputHolder.getOutput(sessionID); - let requirementsPriceInRub = 0; - const invItems: Item[] = []; - if (!info?.items || info.items.length === 0) + const validationMessage = ""; + if (!this.isValidPlayerOfferRequest(offerRequest, validationMessage)) { - this.logger.error(this.localisationService.getText("ragfair-invalid_player_offer_request")); - - return this.httpResponse.appendErrorToOutput(output); + return this.httpResponse.appendErrorToOutput(output, validationMessage); } - if (!info.requirements) + // Get an array of items from player inventory to list on flea + const getItemsFromInventoryErrorMessage = ""; + const itemsInInventoryToList = this.getItemsToListOnFleaFromInventory(pmcData, offerRequest.items, getItemsFromInventoryErrorMessage); + if (!itemsInInventoryToList) { - return this.httpResponse.appendErrorToOutput(output, this.localisationService.getText("ragfair-unable_to_place_offer_with_no_requirements")); - } - - for (const item of info.requirements) - { - const requestedItemTpl = item._tpl; - - if (this.paymentHelper.isMoneyTpl(requestedItemTpl)) - { - requirementsPriceInRub += this.handbookHelper.inRUB(item.count, requestedItemTpl); - } - else - { - requirementsPriceInRub += this.ragfairPriceService.getDynamicPriceForItem(requestedItemTpl) * item.count; - } - } - - // Count how many items are being sold and multiply the requested amount accordingly - for (const itemId of info.items) - { - let item = pmcData.Inventory.items.find(i => i._id === itemId); - - if (item === undefined) - { - this.logger.error(this.localisationService.getText("ragfair-unable_to_find_item_in_inventory", {id: itemId})); - - return this.httpResponse.appendErrorToOutput(output); - } - - item = this.itemHelper.fixItemStackCount(item); - invItems.push(...this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId)); - } - - if (!invItems?.length) - { - this.logger.error(this.localisationService.getText("ragfair-unable_to_find_requested_items_in_inventory")); - - return this.httpResponse.appendErrorToOutput(output); + this.httpResponse.appendErrorToOutput(output, getItemsFromInventoryErrorMessage); } // Preparations are done, create the offer - const offer = this.createPlayerOffer(this.saveServer.getProfile(sessionID), info.requirements, this.ragfairHelper.mergeStackable(invItems), info.sellInOnePiece, requirementsPriceInRub); + const requirementsPriceInRub = this.calculateRequirementsPriceInRub(offerRequest.requirements); + const offer = this.createPlayerOffer(this.saveServer.getProfile(sessionID), offerRequest.requirements, this.ragfairHelper.mergeStackable(itemsInInventoryToList), offerRequest.sellInOnePiece, requirementsPriceInRub); const rootItem = offer.items[0]; const qualityMultiplier = this.itemHelper.getItemQualityModifier(rootItem); const averageOfferPrice = this.ragfairPriceService.getFleaPriceForItem(rootItem._tpl) * rootItem.upd.StackObjectsCount * qualityMultiplier; - const itemStackCount = (!info.sellInOnePiece) ? offer.items[0].upd.StackObjectsCount : 1; + const itemStackCount = (!offerRequest.sellInOnePiece) ? offer.items[0].upd.StackObjectsCount : 1; const singleOfferValue = averageOfferPrice / itemStackCount; let sellChance = this.ragfairConfig.sell.chance.base * qualityMultiplier; @@ -397,7 +368,7 @@ export class RagfairController // Subtract flea market fee from stash if (this.ragfairConfig.sell.fees) { - const tax = this.ragfairTaxHelper.calculateTax(rootItem, pmcData, requirementsPriceInRub, itemStackCount, info.sellInOnePiece); + const tax = this.ragfairTaxHelper.calculateTax(rootItem, pmcData, requirementsPriceInRub, itemStackCount, offerRequest.sellInOnePiece); const request: IProcessBuyTradeRequestData = { tid: "ragfair", @@ -428,7 +399,7 @@ export class RagfairController output.profileChanges[sessionID].ragFairOffers.push(offer); // Remove items from inventory after creating offer - for (const itemToRemove of info.items) + for (const itemToRemove of offerRequest.items) { this.inventoryHelper.removeItem(pmcData, itemToRemove, sessionID, output); } @@ -436,6 +407,95 @@ export class RagfairController return output; } + /** + * Is the item to be listed on the flea valid + * @param offerRequest Client offer request + * @param errorMessage message to show to player when offer is invalid + * @returns Is offer valid + */ + protected isValidPlayerOfferRequest(offerRequest: IAddOfferRequestData, errorMessage: string): boolean + { + if (!offerRequest?.items || offerRequest.items.length === 0) + { + errorMessage = this.localisationService.getText("ragfair-invalid_player_offer_request"); + this.logger.error(errorMessage); + + return false; + } + + if (!offerRequest.requirements) + { + errorMessage = this.localisationService.getText("ragfair-unable_to_place_offer_with_no_requirements"); + this.logger.error(errorMessage); + + return false; + } + + return true; + } + + /** + * Get the handbook price in roubles for the items being listed + * @param requirements + * @returns Rouble price + */ + protected calculateRequirementsPriceInRub(requirements: Requirement[]): number + { + let requirementsPriceInRub = 0; + for (const item of requirements) + { + const requestedItemTpl = item._tpl; + + if (this.paymentHelper.isMoneyTpl(requestedItemTpl)) + { + requirementsPriceInRub += this.handbookHelper.inRUB(item.count, requestedItemTpl); + } + else + { + requirementsPriceInRub += this.ragfairPriceService.getDynamicPriceForItem(requestedItemTpl) * item.count; + } + } + + return requirementsPriceInRub; + } + + /** + * Using item ids from flea offer request, find corrispnding items from player inventory and return as array + * @param pmcData Player profile + * @param itemIdsFromFleaOfferRequest Ids from request + * @param errorMessage if item is not found, add error message to this parameter + * @returns Array of items from player inventory + */ + protected getItemsToListOnFleaFromInventory(pmcData: IPmcData, itemIdsFromFleaOfferRequest: string[], errorMessage: string): Item[] + { + const itemsToReturn = []; + // Count how many items are being sold and multiply the requested amount accordingly + for (const itemId of itemIdsFromFleaOfferRequest) + { + let item = pmcData.Inventory.items.find(i => i._id === itemId); + if (!item) + { + errorMessage = this.localisationService.getText("ragfair-unable_to_find_item_in_inventory", {id: itemId}); + this.logger.error(errorMessage); + + return null; + } + + item = this.itemHelper.fixItemStackCount(item); + itemsToReturn.push(...this.itemHelper.findAndReturnChildrenAsItems(pmcData.Inventory.items, itemId)); + } + + if (!itemsToReturn?.length) + { + errorMessage = this.localisationService.getText("ragfair-unable_to_find_requested_items_in_inventory"); + this.logger.error(errorMessage); + + return null; + } + + return itemsToReturn; + } + public createPlayerOffer(profile: IAkiProfile, requirements: Requirement[], items: Item[], sellInOnePiece: boolean, amountToSend: number): IRagfairOffer { const loyalLevel = 1; diff --git a/project/src/generators/LootGenerator.ts b/project/src/generators/LootGenerator.ts index 7092eef9..8fe8f4a5 100644 --- a/project/src/generators/LootGenerator.ts +++ b/project/src/generators/LootGenerator.ts @@ -13,6 +13,11 @@ import { LocalisationService } from "../services/LocalisationService"; import { HashUtil } from "../utils/HashUtil"; import { RandomUtil } from "../utils/RandomUtil"; +type ItemLimit = { + current: number, + max: number +}; + @injectable() export class LootGenerator { @@ -71,19 +76,18 @@ export class LootGenerator } /** - * Construct item limit record to hold max and current item count + * Construct item limit record to hold max and current item count for each item type * @param limits limits as defined in config * @returns record, key: item tplId, value: current/max item count allowed */ - protected initItemLimitCounter(limits: Record): Record + protected initItemLimitCounter(limits: Record): Record { - const itemTypeCounts: Record = {}; - - for (const x in limits) + const itemTypeCounts: Record = {}; + for (const itemTypeId in limits) { - itemTypeCounts[x] = { + itemTypeCounts[itemTypeId] = { current: 0, - max: limits[x] + max: limits[itemTypeId] }; } diff --git a/project/src/helpers/RagfairServerHelper.ts b/project/src/helpers/RagfairServerHelper.ts index efa24f70..8dc50ca4 100644 --- a/project/src/helpers/RagfairServerHelper.ts +++ b/project/src/helpers/RagfairServerHelper.ts @@ -207,7 +207,7 @@ export class RagfairServerHelper } // generated offer - // recurse if name is longer than max characters allowed (15 characters) + // recurivse if name is longer than max characters allowed (15 characters) const type = (this.randomUtil.getInt(0, 1) === 0) ? "usec" : "bear"; const name = this.randomUtil.getStringArrayValue(this.databaseServer.getTables().bots.types[type].firstName); return (name.length > 15) ? this.getNickname(userID) : name; diff --git a/project/src/helpers/TraderHelper.ts b/project/src/helpers/TraderHelper.ts index d8b5a0f3..8131422f 100644 --- a/project/src/helpers/TraderHelper.ts +++ b/project/src/helpers/TraderHelper.ts @@ -126,17 +126,26 @@ export class TraderHelper const traderInfo = pmcData.TradersInfo[traderId]; // Add standing to trader - traderInfo.standing += standingToAdd; - - // dont allow standing to fall below 0 - if (traderInfo.standing < 0) - { - traderInfo.standing = 0; - } + traderInfo.standing = this.addStandingValuesTogether(traderInfo.standing, standingToAdd); this.lvlUp(traderId, sessionId); } + /** + * Add standing to current standing and clamp value if it goes too low + * @param currentStanding current trader standing + * @param standingToAdd stansding to add to trader standing + * @returns current standing + added standing (clamped if needed) + */ + protected addStandingValuesTogether(currentStanding: number, standingToAdd: number): number + { + const newStanding = currentStanding + standingToAdd; + + return newStanding < 0 + ? 0 + : newStanding; + } + /** * Calculate traders level based on exp amount and increments level if over threshold * @param traderID trader to process