From d595e26ee6902c67eda1a011d1ffa1d84d6cb652 Mon Sep 17 00:00:00 2001 From: chomp Date: Wed, 22 Mar 2023 10:25:34 +0000 Subject: [PATCH] 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 Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/81 --- project/assets/configs/pmcchatresponse.json | 6 +-- project/assets/locales/en.json | 51 ++++++++++++++++++- project/src/controllers/BotController.ts | 12 ++++- project/src/controllers/InraidController.ts | 6 ++- project/src/di/Container.ts | 2 + .../services/MatchBotDetailsCacheService.ts | 51 +++++++++++++++++++ .../src/services/PmcChatResponseService.ts | 49 +++++++++++++++--- 7 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 project/src/services/MatchBotDetailsCacheService.ts diff --git a/project/assets/configs/pmcchatresponse.json b/project/assets/configs/pmcchatresponse.json index 049b4990..05cd5053 100644 --- a/project/assets/configs/pmcchatresponse.json +++ b/project/assets/configs/pmcchatresponse.json @@ -11,14 +11,14 @@ "appendBroToMessageEndChancePercent": 35 }, "killer": { - "responseChancePercent": 20, + "responseChancePercent": 16, "responseTypeWeights": { "positive": 5, "negative": 2, "plead": 2 }, "stripCapitalisationChancePercent": 20, - "allCapsChancePercent": 20, - "appendBroToMessageEndChancePercent": 35 + "allCapsChancePercent": 15, + "appendBroToMessageEndChancePercent": 15 } } diff --git a/project/assets/locales/en.json b/project/assets/locales/en.json index ba11fdfb..9a0b2063 100644 --- a/project/assets/locales/en.json +++ b/project/assets/locales/en.json @@ -270,7 +270,7 @@ "pmcresponse-victim_negative_8": "I am malding so hard right now", "pmcresponse-victim_negative_9": "Good job sweatlord", "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_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", @@ -284,7 +284,7 @@ "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_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_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", @@ -341,6 +341,53 @@ "pmcresponse-suffix_16": "amigo", "pmcresponse-suffix_17": "bud", "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_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", diff --git a/project/src/controllers/BotController.ts b/project/src/controllers/BotController.ts index a1dfa5d1..0f5b315c 100644 --- a/project/src/controllers/BotController.ts +++ b/project/src/controllers/BotController.ts @@ -21,6 +21,7 @@ import { ConfigServer } from "../servers/ConfigServer"; import { DatabaseServer } from "../servers/DatabaseServer"; import { BotGenerationCacheService } from "../services/BotGenerationCacheService"; import { LocalisationService } from "../services/LocalisationService"; +import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService"; import { JsonUtil } from "../utils/JsonUtil"; @injectable() @@ -36,6 +37,7 @@ export class BotController @inject("BotHelper") protected botHelper: BotHelper, @inject("BotDifficultyHelper") protected botDifficultyHelper: BotDifficultyHelper, @inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService, + @inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ConfigServer") protected configServer: ConfigServer, @@ -173,7 +175,15 @@ export class BotController } } // 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; diff --git a/project/src/controllers/InraidController.ts b/project/src/controllers/InraidController.ts index 39e6572b..63c8081f 100644 --- a/project/src/controllers/InraidController.ts +++ b/project/src/controllers/InraidController.ts @@ -29,6 +29,7 @@ import { DatabaseServer } from "../servers/DatabaseServer"; import { SaveServer } from "../servers/SaveServer"; import { InsuranceService } from "../services/InsuranceService"; import { LocaleService } from "../services/LocaleService"; +import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService"; import { PmcChatResponseService } from "../services/PmcChatResponseService"; import { JsonUtil } from "../utils/JsonUtil"; import { TimeUtil } from "../utils/TimeUtil"; @@ -50,6 +51,7 @@ export class InraidController @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("LocaleService") protected localeService: LocaleService, @inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService, + @inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService, @inject("QuestHelper") protected questHelper: QuestHelper, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper, @@ -145,7 +147,9 @@ export class InraidController 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); } diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index 7d79524b..aa9a2929 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -200,6 +200,7 @@ import { ItemBaseClassService } from "../services/ItemBaseClassService"; import { ItemFilterService } from "../services/ItemFilterService"; import { LocaleService } from "../services/LocaleService"; import { LocalisationService } from "../services/LocalisationService"; +import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService"; import { MatchLocationService } from "../services/MatchLocationService"; import { CustomItemService } from "../services/mod/CustomItemService"; import { DynamicRouterModService } from "../services/mod/dynamicRouter/DynamicRouterModService"; @@ -604,6 +605,7 @@ export class Container depContainer.register("BotEquipmentModPoolService", BotEquipmentModPoolService, { lifecycle: Lifecycle.Singleton }); depContainer.register("BotWeaponModLimitService", BotWeaponModLimitService, { lifecycle: Lifecycle.Singleton }); depContainer.register("SeasonalEventService", SeasonalEventService, { lifecycle: Lifecycle.Singleton }); + depContainer.register("MatchBotDetailsCacheService", MatchBotDetailsCacheService, { lifecycle: Lifecycle.Singleton }); depContainer.register("TraderPurchasePersisterService", TraderPurchasePersisterService); depContainer.register("PmcChatResponseService", PmcChatResponseService); } diff --git a/project/src/services/MatchBotDetailsCacheService.ts b/project/src/services/MatchBotDetailsCacheService.ts new file mode 100644 index 00000000..db05a241 --- /dev/null +++ b/project/src/services/MatchBotDetailsCacheService.ts @@ -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 = {}; + + 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; + } + +} \ No newline at end of file diff --git a/project/src/services/PmcChatResponseService.ts b/project/src/services/PmcChatResponseService.ts index e1356ba0..c34191c2 100644 --- a/project/src/services/PmcChatResponseService.ts +++ b/project/src/services/PmcChatResponseService.ts @@ -3,15 +3,17 @@ import { inject, injectable } from "tsyringe"; import { NotificationSendHelper } from "../helpers/NotificationSendHelper"; import { WeightedRandomHelper } from "../helpers/WeightedRandomHelper"; 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 { ConfigTypes } from "../models/enums/ConfigTypes"; import { MemberCategory } from "../models/enums/MemberCategory"; import { MessageType } from "../models/enums/MessageType"; import { IPmcChatResponse } from "../models/spt/config/IPmChatResponse"; +import { ILogger } from "../models/spt/utils/ILogger"; import { ConfigServer } from "../servers/ConfigServer"; import { RandomUtil } from "../utils/RandomUtil"; import { LocalisationService } from "./LocalisationService"; +import { MatchBotDetailsCacheService } from "./MatchBotDetailsCacheService"; @injectable() export class PmcChatResponseService @@ -19,8 +21,10 @@ export class PmcChatResponseService protected pmcResponsesConfig: IPmcChatResponse; constructor( + @inject("WinstonLogger") protected logger: ILogger, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper, + @inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("ConfigServer") protected configServer: ConfigServer @@ -56,16 +60,43 @@ export class PmcChatResponseService * @param pmcData Players profile */ // 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 = { - _id: "", - info: undefined + if (!this.randomUtil.getChance100(this.pmcResponsesConfig.killer.responseChancePercent)) + { + 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); + 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 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 let responseText = this.localisationService.getText(this.randomUtil.getArrayValue(possibleResponseLocaleKeys));