Feature: Allow PMCs that kill the player to message them a positive/negative message (!81)

Store bots spawned in raid inside cache (`MatchBotDetailsCacheService`)
Clear cache after raid

Co-authored-by: Dev <dev@noreply.dev.sp-tarkov.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/81
This commit is contained in:
chomp 2023-03-22 10:25:34 +00:00
parent 8749e3a608
commit d595e26ee6
7 changed files with 164 additions and 13 deletions

View File

@ -11,14 +11,14 @@
"appendBroToMessageEndChancePercent": 35 "appendBroToMessageEndChancePercent": 35
}, },
"killer": { "killer": {
"responseChancePercent": 20, "responseChancePercent": 16,
"responseTypeWeights": { "responseTypeWeights": {
"positive": 5, "positive": 5,
"negative": 2, "negative": 2,
"plead": 2 "plead": 2
}, },
"stripCapitalisationChancePercent": 20, "stripCapitalisationChancePercent": 20,
"allCapsChancePercent": 20, "allCapsChancePercent": 15,
"appendBroToMessageEndChancePercent": 35 "appendBroToMessageEndChancePercent": 15
} }
} }

View File

@ -270,7 +270,7 @@
"pmcresponse-victim_negative_8": "I am malding so hard right now", "pmcresponse-victim_negative_8": "I am malding so hard right now",
"pmcresponse-victim_negative_9": "Good job sweatlord", "pmcresponse-victim_negative_9": "Good job sweatlord",
"pmcresponse-victim_negative_10": "I was AFK!!", "pmcresponse-victim_negative_10": "I was AFK!!",
"pmcresponse-victim_negative_11": "Reported you for cheating :)", "pmcresponse-victim_negative_11": "Reported you for cheating",
"pmcresponse-victim_negative_12": "You only got me because of lag", "pmcresponse-victim_negative_12": "You only got me because of lag",
"pmcresponse-victim_negative_13": "I need to go play SPT instead to get away from the hackers like you", "pmcresponse-victim_negative_13": "I need to go play SPT instead to get away from the hackers like you",
"pmcresponse-victim_negative_14": "If I knew the map better I'd have won", "pmcresponse-victim_negative_14": "If I knew the map better I'd have won",
@ -284,7 +284,7 @@
"pmcresponse-victim_negative_22": "Wow hiding in the corner like a rat, amazing", "pmcresponse-victim_negative_22": "Wow hiding in the corner like a rat, amazing",
"pmcresponse-victim_negative_23": "I hope you stub your toe on a piece of furniture", "pmcresponse-victim_negative_23": "I hope you stub your toe on a piece of furniture",
"pmcresponse-victim_negative_24": "Wow why did you kill me, i'm telling my mom", "pmcresponse-victim_negative_24": "Wow why did you kill me, i'm telling my mom",
"pmcresponse-victim_negative_25": "Reported (:", "pmcresponse-victim_negative_25": "Reported",
"pmcresponse-victim_negative_26": "My mom thinks I should have won that fight", "pmcresponse-victim_negative_26": "My mom thinks I should have won that fight",
"pmcresponse-victim_negative_27": "Wow, killing a noob like me, you must feel so proud", "pmcresponse-victim_negative_27": "Wow, killing a noob like me, you must feel so proud",
"pmcresponse-victim_negative_28": "I bet you play SPT because you cheat on live", "pmcresponse-victim_negative_28": "I bet you play SPT because you cheat on live",
@ -341,6 +341,53 @@
"pmcresponse-suffix_16": "amigo", "pmcresponse-suffix_16": "amigo",
"pmcresponse-suffix_17": "bud", "pmcresponse-suffix_17": "bud",
"pmcresponse-suffix_18": "guy", "pmcresponse-suffix_18": "guy",
"pmcresponse-suffix_18": "m8",
"pmcresponse-suffix_19": ":)",
"pmcresponse-suffix_20": "(:",
"pmcresponse-suffix_21": ":))))))",
"pmcresponse-killer_positive_1": "Good fight",
"pmcresponse-killer_positive_2": "You fought well",
"pmcresponse-killer_positive_3": "I will stash your gear",
"pmcresponse-killer_positive_4": "You nearly got me, great fight",
"pmcresponse-killer_positive_5": "Well played, nearly had me",
"pmcresponse-killer_positive_6": "You almost had me",
"pmcresponse-killer_positive_7": "If I didnt have the drop on you I would be dead",
"pmcresponse-killer_positive_8": "Good fite",
"pmcresponse-killer_positive_9": "Well fought",
"pmcresponse-killer_positive_10": "Whatever you were shooting destroyed my armor, good fight",
"pmcresponse-killer_positive_11": "Nothing personal, gotta get these Jaeger quests complete",
"pmcresponse-killer_positive_12": "You had me very worried for a bit during that fight",
"pmcresponse-killer_positive_13": "Impressive skills",
"pmcresponse-killer_positive_14": "Respect, you gave me a good fight",
"pmcresponse-killer_positive_15": "Clean fight, respect",
"pmcresponse-killer_positive_16": "That was a real cat and mouse fight, awesome",
"pmcresponse-killer_negative_1": "ty 4 the free loot",
"pmcresponse-killer_negative_2": "Thanks for the new kit",
"pmcresponse-killer_negative_3": "No wonder you died, your gun is trash",
"pmcresponse-killer_negative_4": "Why are you wearing that armor lmao",
"pmcresponse-killer_negative_5": "lmaoooo",
"pmcresponse-killer_negative_6": "Dont worry your gear will be on the flea soon",
"pmcresponse-killer_negative_7": "No wonder you play SPT with your aim",
"pmcresponse-killer_negative_8": "It is what it is",
"pmcresponse-killer_negative_9": "Thanks for looting for me",
"pmcresponse-killer_negative_10": "At least put up a fight next time",
"pmcresponse-killer_negative_11": "I think you need some more practice",
"pmcresponse-killer_negative_12": "Try to put up a challenge next time",
"pmcresponse-killer_negative_13": "Rip little timmy",
"pmcresponse-killer_negative_14": "Another dirty little rat taken care of",
"pmcresponse-killer_negative_15": "That was embarassing to watch",
"pmcresponse-killer_negative_15": "I expected at least a little resistance, oh well",
"pmcresponse-killer_negative_16": "I hope you didnt insure that gear as you wont be getting it back",
"pmcresponse-killer_negative_17": "I have a youtube series on how to get better at tarkov if you are interested",
"pmcresponse-killer_negative_18": "Another dogtag for my collection",
"pmcresponse-killer_plead_1": "I was trying to extract a quest item and you were in my path",
"pmcresponse-killer_plead_2": "I was looting barrel caches and you were in the way, sorry",
"pmcresponse-killer_plead_3": "I need PMC kills, I am sure you understand",
"pmcresponse-killer_plead_4": "See you next time",
"pmcresponse-killer_plead_5": "You didnt even have a salewa on you, I am never getting this quest completed",
"pmcresponse-killer_plead_6": "I spent ages looking for your body and someone already looted it",
"pmcresponse-killer_plead_7": "I finally find your body and all you have is garbage",
"pmcresponse-killer_plead_8": "I swear you killed me before",
"launcher-profile_standard": "Same as live, basic stash size (10x28), 500,000 roubles", "launcher-profile_standard": "Same as live, basic stash size (10x28), 500,000 roubles",
"launcher-profile_leftbehind": "Same as Standard plus; larger stash size (10x38), extra equipment/items, 500 dollars", "launcher-profile_leftbehind": "Same as Standard plus; larger stash size (10x38), extra equipment/items, 500 dollars",
"launcher-profile_preparetoescape": "Same as Left Behind plus; larger stash size (10x48), extra equipment/items, higher starting reputation with traders, 250 euros", "launcher-profile_preparetoescape": "Same as Left Behind plus; larger stash size (10x48), extra equipment/items, higher starting reputation with traders, 250 euros",

View File

@ -21,6 +21,7 @@ import { ConfigServer } from "../servers/ConfigServer";
import { DatabaseServer } from "../servers/DatabaseServer"; import { DatabaseServer } from "../servers/DatabaseServer";
import { BotGenerationCacheService } from "../services/BotGenerationCacheService"; import { BotGenerationCacheService } from "../services/BotGenerationCacheService";
import { LocalisationService } from "../services/LocalisationService"; import { LocalisationService } from "../services/LocalisationService";
import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService";
import { JsonUtil } from "../utils/JsonUtil"; import { JsonUtil } from "../utils/JsonUtil";
@injectable() @injectable()
@ -36,6 +37,7 @@ export class BotController
@inject("BotHelper") protected botHelper: BotHelper, @inject("BotHelper") protected botHelper: BotHelper,
@inject("BotDifficultyHelper") protected botDifficultyHelper: BotDifficultyHelper, @inject("BotDifficultyHelper") protected botDifficultyHelper: BotDifficultyHelper,
@inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService, @inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService,
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
@ -173,7 +175,15 @@ export class BotController
} }
} }
// Get bot from cache, add to return array // Get bot from cache, add to return array
botsToReturn.push(this.botGenerationCacheService.getBot(cacheKey)); const botToReturn = this.botGenerationCacheService.getBot(cacheKey);
if (info.conditions.length === 1)
{
// Cache bot when we're returning 1 bot, this indicated the bot is being requested to be spawned
this.matchBotDetailsCacheService.cacheBot(botToReturn);
}
botsToReturn.push(botToReturn);
} }
return botsToReturn; return botsToReturn;

View File

@ -29,6 +29,7 @@ import { DatabaseServer } from "../servers/DatabaseServer";
import { SaveServer } from "../servers/SaveServer"; import { SaveServer } from "../servers/SaveServer";
import { InsuranceService } from "../services/InsuranceService"; import { InsuranceService } from "../services/InsuranceService";
import { LocaleService } from "../services/LocaleService"; import { LocaleService } from "../services/LocaleService";
import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService";
import { PmcChatResponseService } from "../services/PmcChatResponseService"; import { PmcChatResponseService } from "../services/PmcChatResponseService";
import { JsonUtil } from "../utils/JsonUtil"; import { JsonUtil } from "../utils/JsonUtil";
import { TimeUtil } from "../utils/TimeUtil"; import { TimeUtil } from "../utils/TimeUtil";
@ -50,6 +51,7 @@ export class InraidController
@inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("LocaleService") protected localeService: LocaleService, @inject("LocaleService") protected localeService: LocaleService,
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService, @inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
@inject("QuestHelper") protected questHelper: QuestHelper, @inject("QuestHelper") protected questHelper: QuestHelper,
@inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper,
@ -145,7 +147,9 @@ export class InraidController
if (isDead) if (isDead)
{ {
//TODO - find way to get killer name //this.pmcChatResponseService.sendKillerResponse(sessionID, pmcData); this.pmcChatResponseService.sendKillerResponse(sessionID, pmcData, offraidData.profile.Stats.Aggressor);
this.matchBotDetailsCacheService.clearCache();
pmcData = this.performPostRaidActionsWhenDead(offraidData, pmcData, insuranceEnabled, preRaidGear, sessionID); pmcData = this.performPostRaidActionsWhenDead(offraidData, pmcData, insuranceEnabled, preRaidGear, sessionID);
} }

View File

@ -200,6 +200,7 @@ import { ItemBaseClassService } from "../services/ItemBaseClassService";
import { ItemFilterService } from "../services/ItemFilterService"; import { ItemFilterService } from "../services/ItemFilterService";
import { LocaleService } from "../services/LocaleService"; import { LocaleService } from "../services/LocaleService";
import { LocalisationService } from "../services/LocalisationService"; import { LocalisationService } from "../services/LocalisationService";
import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService";
import { MatchLocationService } from "../services/MatchLocationService"; import { MatchLocationService } from "../services/MatchLocationService";
import { CustomItemService } from "../services/mod/CustomItemService"; import { CustomItemService } from "../services/mod/CustomItemService";
import { DynamicRouterModService } from "../services/mod/dynamicRouter/DynamicRouterModService"; import { DynamicRouterModService } from "../services/mod/dynamicRouter/DynamicRouterModService";
@ -604,6 +605,7 @@ export class Container
depContainer.register<BotEquipmentModPoolService>("BotEquipmentModPoolService", BotEquipmentModPoolService, { lifecycle: Lifecycle.Singleton }); depContainer.register<BotEquipmentModPoolService>("BotEquipmentModPoolService", BotEquipmentModPoolService, { lifecycle: Lifecycle.Singleton });
depContainer.register<BotWeaponModLimitService>("BotWeaponModLimitService", BotWeaponModLimitService, { lifecycle: Lifecycle.Singleton }); depContainer.register<BotWeaponModLimitService>("BotWeaponModLimitService", BotWeaponModLimitService, { lifecycle: Lifecycle.Singleton });
depContainer.register<SeasonalEventService>("SeasonalEventService", SeasonalEventService, { lifecycle: Lifecycle.Singleton }); depContainer.register<SeasonalEventService>("SeasonalEventService", SeasonalEventService, { lifecycle: Lifecycle.Singleton });
depContainer.register<MatchBotDetailsCacheService>("MatchBotDetailsCacheService", MatchBotDetailsCacheService, { lifecycle: Lifecycle.Singleton });
depContainer.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService); depContainer.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService);
depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService); depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService);
} }

View File

@ -0,0 +1,51 @@
import { inject, injectable } from "tsyringe";
import { IBotBase } from "../models/eft/common/tables/IBotBase";
import { ILogger } from "../models/spt/utils/ILogger";
/** Cache bots in a dictionary, keyed by the bots name, keying by name isnt idea as its not unique but this is used by the post-raid system which doesnt have any bot ids, only name */
@injectable()
export class MatchBotDetailsCacheService
{
protected botDetailsCache: Record<string, IBotBase> = {};
constructor(
@inject("WinstonLogger") protected logger: ILogger
)
{}
/**
* Store a bot in the cache, keyed by its name
* @param botToCache Bot details to cache
*/
public cacheBot(botToCache: IBotBase): void
{
this.botDetailsCache[botToCache.Info.Nickname.trim()] = botToCache;
}
/**
* Clean the cache of all bot details
*/
public clearCache(): void
{
this.botDetailsCache = {};
}
/**
* Find a bot in the cache by its name
* @param botName Name of bot to find
* @returns Bot details
*/
public getBotByName(botName: string): IBotBase
{
const botInCache = this.botDetailsCache[botName];
if (!botInCache)
{
this.logger.warning(`Bot not found in cache with name ${botName}`);
}
return botInCache;
}
}

View File

@ -3,15 +3,17 @@ import { inject, injectable } from "tsyringe";
import { NotificationSendHelper } from "../helpers/NotificationSendHelper"; import { NotificationSendHelper } from "../helpers/NotificationSendHelper";
import { WeightedRandomHelper } from "../helpers/WeightedRandomHelper"; import { WeightedRandomHelper } from "../helpers/WeightedRandomHelper";
import { IPmcData } from "../models/eft/common/IPmcData"; import { IPmcData } from "../models/eft/common/IPmcData";
import { Victim } from "../models/eft/common/tables/IBotBase"; import { Aggressor, Victim } from "../models/eft/common/tables/IBotBase";
import { IUserDialogInfo } from "../models/eft/profile/IAkiProfile"; import { IUserDialogInfo } from "../models/eft/profile/IAkiProfile";
import { ConfigTypes } from "../models/enums/ConfigTypes"; import { ConfigTypes } from "../models/enums/ConfigTypes";
import { MemberCategory } from "../models/enums/MemberCategory"; import { MemberCategory } from "../models/enums/MemberCategory";
import { MessageType } from "../models/enums/MessageType"; import { MessageType } from "../models/enums/MessageType";
import { IPmcChatResponse } from "../models/spt/config/IPmChatResponse"; import { IPmcChatResponse } from "../models/spt/config/IPmChatResponse";
import { ILogger } from "../models/spt/utils/ILogger";
import { ConfigServer } from "../servers/ConfigServer"; import { ConfigServer } from "../servers/ConfigServer";
import { RandomUtil } from "../utils/RandomUtil"; import { RandomUtil } from "../utils/RandomUtil";
import { LocalisationService } from "./LocalisationService"; import { LocalisationService } from "./LocalisationService";
import { MatchBotDetailsCacheService } from "./MatchBotDetailsCacheService";
@injectable() @injectable()
export class PmcChatResponseService export class PmcChatResponseService
@ -19,8 +21,10 @@ export class PmcChatResponseService
protected pmcResponsesConfig: IPmcChatResponse; protected pmcResponsesConfig: IPmcChatResponse;
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("RandomUtil") protected randomUtil: RandomUtil, @inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper, @inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper,
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("ConfigServer") protected configServer: ConfigServer @inject("ConfigServer") protected configServer: ConfigServer
@ -56,16 +60,43 @@ export class PmcChatResponseService
* @param pmcData Players profile * @param pmcData Players profile
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
public sendKillerResponse(sessionId: string, pmcData: IPmcData): void public sendKillerResponse(sessionId: string, pmcData: IPmcData, killer: Aggressor): void
{ {
const killer: IUserDialogInfo = { if (!this.randomUtil.getChance100(this.pmcResponsesConfig.killer.responseChancePercent))
_id: "", {
info: undefined return;
}
// find bot by name in cache
const killerDetailsInCache = this.matchBotDetailsCacheService.getBotByName(killer.Name.trim());
if (!killerDetailsInCache)
{
return;
}
// If kill was not a PMC, skip
if (!["sptUsec", "sptBear"].includes(killerDetailsInCache.Info.Settings.Role))
{
return;
}
const killerDetails: IUserDialogInfo = {
_id: killerDetailsInCache._id,
info: {
Nickname: killerDetailsInCache.Info.Nickname,
Side: killerDetailsInCache.Info.Side,
Level: killerDetailsInCache.Info.Level,
MemberCategory: killerDetailsInCache.Info.MemberCategory
}
}; };
const message = this.chooseMessage(false); const message = this.chooseMessage(false);
if (!message)
{
return;
}
this.notificationSendHelper.sendMessageToPlayer(sessionId, killer, message, MessageType.USER_MESSAGE); this.notificationSendHelper.sendMessageToPlayer(sessionId, killerDetails, message, MessageType.USER_MESSAGE);
} }
/** /**
@ -80,6 +111,12 @@ export class PmcChatResponseService
// Get all locale keys // Get all locale keys
const possibleResponseLocaleKeys = this.getResponseLocaleKeys(responseType, isVictim); const possibleResponseLocaleKeys = this.getResponseLocaleKeys(responseType, isVictim);
if (possibleResponseLocaleKeys.length === 0)
{
this.logger.warning(`No pmc response keys found for type: ${responseType}`);
return undefined;
}
// Choose random response from above list and request it from localisation service // Choose random response from above list and request it from localisation service
let responseText = this.localisationService.getText(this.randomUtil.getArrayValue(possibleResponseLocaleKeys)); let responseText = this.localisationService.getText(this.randomUtil.getArrayValue(possibleResponseLocaleKeys));