2023-03-03 16:23:46 +01:00
|
|
|
import { inject, injectable } from "tsyringe";
|
|
|
|
import { ITraderBase } from "../models/eft/common/tables/ITrader";
|
|
|
|
|
|
|
|
import { DialogueHelper } from "../helpers/DialogueHelper";
|
|
|
|
import { HandbookHelper } from "../helpers/HandbookHelper";
|
|
|
|
import { SecureContainerHelper } from "../helpers/SecureContainerHelper";
|
|
|
|
import { TraderHelper } from "../helpers/TraderHelper";
|
|
|
|
import { IPmcData } from "../models/eft/common/IPmcData";
|
|
|
|
import { InsuredItem } from "../models/eft/common/tables/IBotBase";
|
|
|
|
import { Item } from "../models/eft/common/tables/IItem";
|
|
|
|
import { ISaveProgressRequestData } from "../models/eft/inRaid/ISaveProgressRequestData";
|
|
|
|
import { ConfigTypes } from "../models/enums/ConfigTypes";
|
|
|
|
import { MessageType } from "../models/enums/MessageType";
|
|
|
|
import { IInsuranceConfig } from "../models/spt/config/IInsuranceConfig";
|
|
|
|
import { ILogger } from "../models/spt/utils/ILogger";
|
|
|
|
import { ConfigServer } from "../servers/ConfigServer";
|
|
|
|
import { DatabaseServer } from "../servers/DatabaseServer";
|
|
|
|
import { SaveServer } from "../servers/SaveServer";
|
|
|
|
import { RandomUtil } from "../utils/RandomUtil";
|
|
|
|
import { TimeUtil } from "../utils/TimeUtil";
|
|
|
|
import { LocalisationService } from "./LocalisationService";
|
|
|
|
|
2023-05-27 23:31:24 +02:00
|
|
|
export interface IInsuranceData
|
|
|
|
{
|
|
|
|
locationLost?: string
|
|
|
|
timestampLost?: number
|
|
|
|
items: Item[]
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
@injectable()
|
|
|
|
export class InsuranceService
|
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
protected insured: Record<string, Record<string, IInsuranceData>> = {};
|
2023-03-03 16:23:46 +01:00
|
|
|
protected insuranceConfig: IInsuranceConfig;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
|
|
@inject("SecureContainerHelper") protected secureContainerHelper: SecureContainerHelper,
|
|
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
|
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
|
|
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
|
|
|
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
|
|
|
@inject("HandbookHelper") protected handbookHelper: HandbookHelper,
|
|
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer
|
|
|
|
)
|
|
|
|
{
|
|
|
|
this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public insuranceExists(sessionId: string): boolean
|
|
|
|
{
|
|
|
|
return this.insured[sessionId] !== undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
public insuranceTraderArrayExists(sessionId: string, traderId: string): boolean
|
|
|
|
{
|
|
|
|
return this.insured[sessionId][traderId] !== undefined;
|
|
|
|
}
|
|
|
|
|
2023-05-27 23:31:24 +02:00
|
|
|
public getInsurance(sessionId: string): Record<string, IInsuranceData>
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
|
|
|
return this.insured[sessionId];
|
|
|
|
}
|
|
|
|
|
|
|
|
public getInsuranceItems(sessionId: string, traderId: string): Item[]
|
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
return this.insured[sessionId][traderId].items;
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public resetInsurance(sessionId: string): void
|
|
|
|
{
|
|
|
|
this.insured[sessionId] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
public resetInsuranceTraderArray(sessionId: string, traderId: string): void
|
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
this.insured[sessionId][traderId] = {items: []};
|
|
|
|
}
|
|
|
|
|
|
|
|
public addInsuranceItemToArray(sessionId: string, traderId: string, itemToAdd: Item): void
|
|
|
|
{
|
|
|
|
this.insured[sessionId][traderId].items.push(itemToAdd);
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
2023-05-27 23:31:24 +02:00
|
|
|
public addLocationLostToInsuranceItem(sessionId: string, traderId: string, locationName: string): void
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
this.insured[sessionId][traderId].locationLost = locationName;
|
|
|
|
}
|
|
|
|
|
|
|
|
public addTimestampLostToInsuranceItem(sessionId: string, traderId: string, timestampLost: number): void
|
|
|
|
{
|
|
|
|
this.insured[sessionId][traderId].timestampLost = timestampLost;
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the rouble price for an item by templateId
|
|
|
|
* @param itemTpl item tpl to get handbook price for
|
|
|
|
* @returns handbook price in roubles, Return 0 if not found
|
|
|
|
*/
|
|
|
|
public getItemPrice(itemTpl: string): number
|
|
|
|
{
|
|
|
|
const handbookPrice = this.handbookHelper.getTemplatePrice(itemTpl);
|
|
|
|
if (handbookPrice > 1)
|
|
|
|
{
|
|
|
|
return handbookPrice;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends stored insured items as message to player
|
|
|
|
* @param pmcData profile to modify
|
|
|
|
* @param sessionID SessionId of current player
|
|
|
|
* @param mapId Id of the map player died/exited that caused the insurance to be issued on
|
|
|
|
*/
|
|
|
|
public sendInsuredItems(pmcData: IPmcData, sessionID: string, mapId: string): void
|
|
|
|
{
|
|
|
|
for (const traderId in this.getInsurance(sessionID))
|
|
|
|
{
|
|
|
|
const trader = this.traderHelper.getTrader(traderId, sessionID);
|
|
|
|
const insuranceReturnTimestamp = this.getInsuranceReturnTimestamp(pmcData, trader);
|
2023-05-27 23:31:24 +02:00
|
|
|
const insurance = this.getInsurance(sessionID)[traderId];
|
2023-03-03 16:23:46 +01:00
|
|
|
const dialogueTemplates = this.databaseServer.getTables().traders[traderId].dialogue;
|
2023-05-27 23:31:24 +02:00
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
let messageContent = this.dialogueHelper.createMessageContext(this.randomUtil.getArrayValue(dialogueTemplates.insuranceStart), MessageType.NPC_TRADER, trader.insurance.max_storage_time);
|
|
|
|
|
|
|
|
this.dialogueHelper.addDialogueMessage(traderId, messageContent, sessionID);
|
|
|
|
|
|
|
|
messageContent = {
|
|
|
|
templateId: this.randomUtil.getArrayValue(dialogueTemplates.insuranceFound),
|
|
|
|
type: MessageType.INSURANCE_RETURN,
|
|
|
|
text: "", // live insurance returns have an empty string for the text property
|
|
|
|
maxStorageTime: trader.insurance.max_storage_time * TimeUtil.oneHourAsSeconds,
|
|
|
|
profileChangeEvents: [],
|
|
|
|
systemData: {
|
|
|
|
date: this.timeUtil.getDateMailFormat(),
|
|
|
|
time: this.timeUtil.getTimeMailFormat(),
|
|
|
|
location: mapId
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Remove 'hideout' slotid property on all insurance items
|
2023-04-24 15:03:41 +02:00
|
|
|
this.removeLocationProperty(sessionID, traderId);
|
2023-03-03 16:23:46 +01:00
|
|
|
|
|
|
|
this.saveServer.getProfile(sessionID).insurance.push({
|
|
|
|
scheduledTime: insuranceReturnTimestamp,
|
|
|
|
traderId: traderId,
|
|
|
|
messageContent: messageContent,
|
2023-05-27 23:31:24 +02:00
|
|
|
items: insurance.items,
|
|
|
|
locationlost: insurance.locationLost,
|
|
|
|
timeStampLost: insurance.timestampLost
|
2023-03-03 16:23:46 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this.resetInsurance(sessionID);
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:03:41 +02:00
|
|
|
protected removeLocationProperty(sessionId: string, traderId: string): void
|
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
const insuredItems = this.getInsurance(sessionId)[traderId].items;
|
|
|
|
for (const insuredItem of this.getInsurance(sessionId)[traderId].items)
|
2023-04-24 15:03:41 +02:00
|
|
|
{
|
|
|
|
const isParentHere = insuredItems.find(isParent => isParent._id === insuredItem.parentId);
|
|
|
|
if (!isParentHere)
|
|
|
|
{
|
|
|
|
insuredItem.slotId = "hideout";
|
|
|
|
delete insuredItem.location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
/**
|
|
|
|
* Get a timestamp of what insurance items should be sent to player based on the type of trader used to insure
|
|
|
|
* @param pmcData Player profile
|
|
|
|
* @param trader Trader used to insure items
|
|
|
|
* @returns Timestamp to return items to player in seconds
|
|
|
|
*/
|
|
|
|
protected getInsuranceReturnTimestamp(pmcData: IPmcData, trader: ITraderBase): number
|
|
|
|
{
|
|
|
|
// If override inconfig is non-zero, use that instead of trader values
|
|
|
|
if (this.insuranceConfig.returnTimeOverrideSeconds > 0)
|
|
|
|
{
|
|
|
|
this.logger.debug(`Insurance override used: returning in ${this.insuranceConfig.returnTimeOverrideSeconds} seconds`);
|
|
|
|
return this.timeUtil.getTimestamp() + this.insuranceConfig.returnTimeOverrideSeconds;
|
|
|
|
}
|
|
|
|
|
|
|
|
const insuranceReturnTimeBonus = pmcData.Bonuses.find(b => b.type === "InsuranceReturnTime");
|
|
|
|
const insuranceReturnTimeBonusPercent = 1.0 - (insuranceReturnTimeBonus ? Math.abs(insuranceReturnTimeBonus.value) : 0) / 100;
|
|
|
|
|
|
|
|
const traderMinReturnAsSeconds = trader.insurance.min_return_hour * TimeUtil.oneHourAsSeconds;
|
|
|
|
const traderMaxReturnAsSeconds = trader.insurance.max_return_hour * TimeUtil.oneHourAsSeconds;
|
|
|
|
const randomisedReturnTimeSeconds = this.randomUtil.getInt(traderMinReturnAsSeconds, traderMaxReturnAsSeconds);
|
|
|
|
|
|
|
|
return this.timeUtil.getTimestamp() + (randomisedReturnTimeSeconds * insuranceReturnTimeBonusPercent);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Store lost gear post-raid inside profile
|
|
|
|
* @param pmcData player profile to store gear in
|
|
|
|
* @param offraidData post-raid request object
|
|
|
|
* @param preRaidGear gear player wore prior to raid
|
|
|
|
* @param sessionID Session id
|
2023-03-03 18:53:28 +01:00
|
|
|
* @param playerDied did the player die in raid
|
2023-05-27 23:31:24 +02:00
|
|
|
* @param locationName Name of map gear was lost on
|
2023-03-03 16:23:46 +01:00
|
|
|
*/
|
2023-05-27 23:31:24 +02:00
|
|
|
public storeLostGear(pmcData: IPmcData, offraidData: ISaveProgressRequestData, preRaidGear: Item[], sessionID: string, playerDied: boolean, locationName: string): void
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-04-24 15:03:41 +02:00
|
|
|
const preRaidGearHash = this.createItemHashTable(preRaidGear);
|
|
|
|
const offRaidGearHash = this.createItemHashTable(offraidData.profile.Inventory.items);
|
2023-03-03 16:23:46 +01:00
|
|
|
|
2023-04-24 15:03:41 +02:00
|
|
|
const equipmentToSendToPlayer = [];
|
2023-03-03 16:23:46 +01:00
|
|
|
for (const insuredItem of pmcData.InsuredItems)
|
|
|
|
{
|
2023-03-03 18:53:28 +01:00
|
|
|
// Check insured item was on player during raid
|
2023-03-03 16:23:46 +01:00
|
|
|
if (preRaidGearHash[insuredItem.itemId])
|
|
|
|
{
|
2023-03-03 18:53:28 +01:00
|
|
|
// This item exists in preRaidGear, meaning we brought it into the raid
|
|
|
|
// Check if item missing OR player died with item on
|
|
|
|
if (!offRaidGearHash[insuredItem.itemId] || playerDied)
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-04-24 15:03:41 +02:00
|
|
|
equipmentToSendToPlayer.push({
|
2023-03-03 16:23:46 +01:00
|
|
|
"pmcData": pmcData,
|
|
|
|
"insuredItem": insuredItem,
|
|
|
|
"item": preRaidGearHash[insuredItem.itemId],
|
|
|
|
"sessionID": sessionID
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 18:53:28 +01:00
|
|
|
// Process all insured items lost in-raid
|
2023-04-24 15:03:41 +02:00
|
|
|
for (const gear of equipmentToSendToPlayer)
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
this.addGearToSend(gear, locationName);
|
2023-03-03 16:23:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:03:41 +02:00
|
|
|
/**
|
|
|
|
* Create a hash table for an array of items, keyed by items _id
|
|
|
|
* @param items Items to hash
|
|
|
|
* @returns Hashtable
|
|
|
|
*/
|
|
|
|
protected createItemHashTable(items: Item[]): Record<string, Item>
|
|
|
|
{
|
|
|
|
const hashTable: Record<string, Item> = {};
|
|
|
|
for (const item of items)
|
|
|
|
{
|
|
|
|
hashTable[item._id] = item;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hashTable;
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
/**
|
|
|
|
* Add gear item to InsuredItems array in player profile
|
2023-05-27 23:31:24 +02:00
|
|
|
* @param gear Details on what item to store (player profile/item to store in profile/item db details/session id)
|
|
|
|
* @param locationName Name of map gear was lost on
|
2023-03-03 16:23:46 +01:00
|
|
|
*/
|
2023-05-27 23:31:24 +02:00
|
|
|
protected addGearToSend(gear: { pmcData: any; insuredItem: InsuredItem, item: Item, sessionID: string; }, locationName: string): void
|
2023-03-03 16:23:46 +01:00
|
|
|
{
|
2023-05-27 23:31:24 +02:00
|
|
|
const pmcData = gear.pmcData;
|
|
|
|
const insuredItem = gear.insuredItem;
|
|
|
|
const actualItem = gear.item;
|
|
|
|
const sessionID = gear.sessionID;
|
|
|
|
|
2023-03-03 16:23:46 +01:00
|
|
|
// Skip items defined in config
|
|
|
|
if (this.insuranceConfig.blacklistedEquipment.includes(actualItem.slotId))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pocketSlots = [
|
|
|
|
"pocket1",
|
|
|
|
"pocket2",
|
|
|
|
"pocket3",
|
|
|
|
"pocket4"
|
|
|
|
];
|
|
|
|
|
|
|
|
// Check and correct the validity of the slotId
|
|
|
|
if (!("slotId" in actualItem) || pocketSlots.includes(actualItem.slotId))
|
|
|
|
{
|
|
|
|
actualItem.slotId = "hideout";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark root-level items for later
|
|
|
|
if (actualItem.parentId === pmcData.Inventory.equipment)
|
|
|
|
{
|
|
|
|
actualItem.slotId = "hideout";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the location attribute of the item in the container.
|
|
|
|
if (actualItem.slotId === "hideout" && "location" in actualItem)
|
|
|
|
{
|
|
|
|
delete actualItem.location;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove found in raid status
|
|
|
|
if ("upd" in actualItem && "SpawnedInSession" in actualItem.upd)
|
|
|
|
{
|
|
|
|
actualItem.upd.SpawnedInSession = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark to add to insurance
|
|
|
|
if (!this.insuranceExists(sessionID))
|
|
|
|
{
|
|
|
|
this.resetInsurance(sessionID);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.insuranceTraderArrayExists(sessionID, insuredItem.tid))
|
|
|
|
{
|
|
|
|
this.resetInsuranceTraderArray(sessionID, insuredItem.tid);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.addInsuranceItemToArray(sessionID, insuredItem.tid, actualItem);
|
2023-05-27 23:31:24 +02:00
|
|
|
|
|
|
|
this.addLocationLostToInsuranceItem(sessionID, insuredItem.tid, locationName);
|
|
|
|
this.addTimestampLostToInsuranceItem(sessionID, insuredItem.tid, this.timeUtil.getTimestamp());
|
2023-03-03 16:23:46 +01:00
|
|
|
|
|
|
|
// Remove processed item from array
|
|
|
|
pmcData.InsuredItems = pmcData.InsuredItems.filter((item) =>
|
|
|
|
{
|
|
|
|
return item.itemId !== insuredItem.itemId;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public getPremium(pmcData: IPmcData, inventoryItem: Item, traderId: string): number
|
|
|
|
{
|
|
|
|
let insuranceMultiplier = this.insuranceConfig.insuranceMultiplier[traderId];
|
|
|
|
if (!insuranceMultiplier)
|
|
|
|
{
|
|
|
|
insuranceMultiplier = 0.3;
|
|
|
|
this.logger.warning(this.localisationService.getText("insurance-missing_insurance_price_multiplier", traderId));
|
|
|
|
}
|
|
|
|
|
|
|
|
let premium = this.getItemPrice(inventoryItem._tpl) * insuranceMultiplier;
|
|
|
|
const coef = this.traderHelper.getLoyaltyLevel(traderId, pmcData).insurance_price_coef;
|
|
|
|
|
|
|
|
if (coef > 0)
|
|
|
|
{
|
|
|
|
premium *= (1 - this.traderHelper.getLoyaltyLevel(traderId, pmcData).insurance_price_coef / 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Math.round(premium);
|
|
|
|
}
|
|
|
|
}
|