Reworked how insurance picks attachments to delete before return
Now has a chance to not pick any to remove (default 10%) Now only removes attachments that are above a rouble price (default 2000) Stores attachments in a dictionary weighted by rouble price Picks random amount of attachments to remove and then picks from pool by price, removing items from pool as they're picked
This commit is contained in:
parent
9fb1d9728e
commit
77a5b0a4b4
@ -17,5 +17,7 @@
|
||||
"patron_in_weapon"
|
||||
],
|
||||
"returnTimeOverrideSeconds": 0,
|
||||
"runIntervalSeconds": 600
|
||||
"runIntervalSeconds": 600,
|
||||
"minAttachmentRoublePriceToBeTaken": 2000,
|
||||
"chanceNoAttachmentsTakenPercent": 10
|
||||
}
|
@ -4,6 +4,7 @@ import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper";
|
||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
|
||||
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
|
||||
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { IGetInsuranceCostRequestData } from "@spt-aki/models/eft/insurance/IGetInsuranceCostRequestData";
|
||||
@ -25,8 +26,9 @@ import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||
import { PaymentService } from "@spt-aki/services/PaymentService";
|
||||
import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
|
||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { MathUtil } from "@spt-aki/utils/MathUtil";
|
||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
||||
|
||||
@injectable()
|
||||
@ -39,6 +41,7 @@ export class InsuranceController
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||
@inject("MathUtil") protected mathUtil: MathUtil,
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||
@ -47,6 +50,7 @@ export class InsuranceController
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
||||
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
|
||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||
@inject("PaymentService") protected paymentService: PaymentService,
|
||||
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
||||
@ -448,83 +452,77 @@ export class InsuranceController
|
||||
*/
|
||||
protected processAttachmentByParent(attachments: Item[], traderId: string, toDelete: Set<string>): void
|
||||
{
|
||||
const sortedAttachments = this.sortAttachmentsByPrice(attachments);
|
||||
this.logAttachmentsDetails(sortedAttachments);
|
||||
// Create dict of item ids + their flea/handbook price (highest is chosen)
|
||||
const weightedAttachmentByPrice = this.weightAttachmentsByPrice(attachments);
|
||||
|
||||
const successfulRolls = this.countSuccessfulRolls(sortedAttachments, traderId);
|
||||
this.logger.debug(`Number of attachments to be deleted: ${successfulRolls}`);
|
||||
// Get how many attachments we want to pull off parent
|
||||
const countOfAttachmentsToRemove = this.getAttachmentCountToRemove(weightedAttachmentByPrice, traderId);
|
||||
|
||||
this.attachmentDeletionByValue(sortedAttachments, successfulRolls, toDelete);
|
||||
// Create prob array and add all attachments with rouble price as the weight
|
||||
const attachmentsProbabilityArray = new ProbabilityObjectArray<string, number>(this.mathUtil, this.jsonUtil);
|
||||
for (const attachmentTpl of Object.keys(weightedAttachmentByPrice))
|
||||
{
|
||||
attachmentsProbabilityArray.push(
|
||||
new ProbabilityObject(attachmentTpl, weightedAttachmentByPrice[attachmentTpl]),
|
||||
);
|
||||
}
|
||||
|
||||
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked
|
||||
const attachmentIdsToRemove = attachmentsProbabilityArray.draw(countOfAttachmentsToRemove, false);
|
||||
for (const attachmentId of attachmentIdsToRemove)
|
||||
{
|
||||
toDelete.add(attachmentId);
|
||||
}
|
||||
|
||||
this.logger.debug(`Number of attachments to be deleted: ${attachmentIdsToRemove.length}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the attachment items by their dynamic price in descending order.
|
||||
*
|
||||
* @param attachments The array of attachments items.
|
||||
* @returns An array of items enriched with their max price and common locale-name.
|
||||
*/
|
||||
protected sortAttachmentsByPrice(attachments: Item[]): EnrichedItem[]
|
||||
protected weightAttachmentsByPrice(attachments: Item[]): Record<string, number>
|
||||
{
|
||||
return attachments.map((item) => ({
|
||||
...item,
|
||||
name: this.itemHelper.getItemName(item._tpl),
|
||||
dynamicPrice: this.ragfairPriceService.getDynamicItemPrice(item._tpl, this.roubleTpl, item, null, false),
|
||||
})).sort((a, b) => b.dynamicPrice - a.dynamicPrice);
|
||||
}
|
||||
const result: Record<string, number> = {};
|
||||
|
||||
/**
|
||||
* Logs the details of each attachment item.
|
||||
*
|
||||
* @param attachments The array of attachment items.
|
||||
*/
|
||||
protected logAttachmentsDetails(attachments: EnrichedItem[]): void
|
||||
{
|
||||
let index = 1;
|
||||
// Get a dictionary of item tpls + their rouble price
|
||||
for (const attachment of attachments)
|
||||
{
|
||||
this.logger.debug(`Attachment ${index}: "${attachment.name}" - Price: ${attachment.dynamicPrice}`);
|
||||
index++;
|
||||
const price = this.ragfairPriceService.getDynamicItemPrice(attachment._tpl, this.roubleTpl);
|
||||
result[attachment._id] = Math.round(price);
|
||||
}
|
||||
|
||||
this.weightedRandomHelper.reduceWeightValues(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of successful rolls for the attachment items.
|
||||
*
|
||||
* @param attachments The array of attachment items.
|
||||
* @param traderId The ID of the trader that has insured these attachments.
|
||||
* @returns The number of successful rolls.
|
||||
* Get count of items to remove from weapon (take into account trader + price of attachment)
|
||||
* @param weightedAttachmentByPrice Dict of item Tpls and thier rouble price
|
||||
* @param traderId Trader attachment insured against
|
||||
* @returns Attachment count to remove
|
||||
*/
|
||||
protected countSuccessfulRolls(attachments: Item[], traderId: string): number
|
||||
protected getAttachmentCountToRemove(weightedAttachmentByPrice: Record<string, number>, traderId: string): number
|
||||
{
|
||||
const rolls = Array.from({ length: attachments.length }, () => this.rollForDelete(traderId));
|
||||
return rolls.filter(Boolean).length;
|
||||
}
|
||||
let removeCount = 0;
|
||||
|
||||
/**
|
||||
* Marks the most valuable attachments for deletion based on the number of successful rolls made.
|
||||
*
|
||||
* @param attachments The array of attachment items.
|
||||
* @param successfulRolls The number of successful rolls.
|
||||
* @param toDelete The array that accumulates the IDs of the items to be deleted.
|
||||
*/
|
||||
protected attachmentDeletionByValue(
|
||||
attachments: EnrichedItem[],
|
||||
successfulRolls: number,
|
||||
toDelete: Set<string>,
|
||||
): void
|
||||
{
|
||||
const valuableToDelete = attachments.slice(0, successfulRolls).map(({ _id }) => _id);
|
||||
|
||||
for (const attachmentsId of valuableToDelete)
|
||||
if (this.randomUtil.getChance100(this.insuranceConfig.chanceNoAttachmentsTakenPercent))
|
||||
{
|
||||
const valuableChild = attachments.find(({ _id }) => _id === attachmentsId);
|
||||
if (valuableChild)
|
||||
return removeCount;
|
||||
}
|
||||
|
||||
for (const attachmentId of Object.keys(weightedAttachmentByPrice))
|
||||
{
|
||||
// Below min price to be taken, skip
|
||||
if (weightedAttachmentByPrice[attachmentId] < this.insuranceConfig.minAttachmentRoublePriceToBeTaken)
|
||||
{
|
||||
const { name, dynamicPrice } = valuableChild;
|
||||
this.logger.debug(`Marked attachment "${name}" for removal - Dyanmic Price: ${dynamicPrice}`);
|
||||
toDelete.add(attachmentsId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.rollForDelete(traderId))
|
||||
{
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return removeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -588,7 +586,7 @@ export class InsuranceController
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a insured item should be removed from the player's inventory based on a random roll and
|
||||
* Determines whether an insured item should be removed from the player's inventory based on a random roll and
|
||||
* trader-specific return chance.
|
||||
*
|
||||
* @param traderId The ID of the trader who insured the item.
|
||||
|
@ -15,4 +15,8 @@ export interface IInsuranceConfig extends IBaseConfig
|
||||
returnTimeOverrideSeconds: number;
|
||||
/** How often server should process insurance in seconds */
|
||||
runIntervalSeconds: number;
|
||||
// Lowest rouble price for an attachment to be allowed to be taken
|
||||
minAttachmentRoublePriceToBeTaken: number;
|
||||
// Chance out of 100% no attachments from a parent are taken
|
||||
chanceNoAttachmentsTakenPercent: number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user