0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-12 22:10:44 -05:00

Feature: Add system that allows PMCs to send text messages to player post-raic. Can be positive or negative (!40)

Co-authored-by: Dev <dev@noreply.dev.sp-tarkov.com>
Reviewed-on: SPT-AKI/Server#40
This commit is contained in:
chomp 2023-03-08 13:26:32 +00:00
parent e4bc6c8d68
commit 1137912d51
8 changed files with 290 additions and 18 deletions

View File

@ -0,0 +1,24 @@
{
"victim": {
"responseChancePercent": 25,
"responseTypeWeights": {
"positive": 8,
"negative": 2,
"plead": 2,
},
"stripCapitalisationChancePercent": 25,
"allCapsChancePercent": 25,
"appendBroToMessageEndChancePercent": 35
},
"killer": {
"responseChancePercent": 20,
"responseTypeWeights": {
"positive": 5,
"negative": 2,
"plead": 2,
},
"stripCapitalisationChancePercent": 25,
"allCapsChancePercent": 25,
"appendBroToMessageEndChancePercent": 35
}
}

View File

@ -147,8 +147,8 @@
"ragfair-unable_to_find_item_in_inventory": "Unable to find item with id: {{id}} in inventory",
"ragfair-unable_to_find_requested_items_in_inventory": "Unable to find any requested items in the inventory",
"ragfair-unable_to_pay_commission_fee": "Unable to pay commission fee",
"ragfair-offer_no_longer_exists": "Offer no longer exists",
"ragfair-unable_to_purchase_0_count_item": "Unable to purchase item: %s with a count of 0",
"ragfair-offer_no_longer_exists": "Offer no longer exists",
"ragfair-unable_to_purchase_0_count_item": "Unable to purchase item: %s with a count of 0",
"ragfair-unable_to_place_offer_with_no_requirements": "Unable to place offer with no requirements",
"repeatable-accepted_repeatable_quest_not_found_in_active_quests": "Accepted a repeatable quest: %s which could not be found in the activeQuests array. Please report this bug",
"repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive": "Generate Completion Quest: No items remain. Either Whitelist is too small or Blacklist too restrictive",
@ -215,5 +215,30 @@
"websocket-player_connected": "[WS] Player: %s has connected",
"websocket-received_message": "[WS] Received message from user %s ",
"websocket-socket_lost_deleting_handle": "[WS] Socket lost, deleting handle",
"websocket-started": "Started websocket at %s"
}
"websocket-started": "Started websocket at %s",
"pmcresponse-victim_positive_1": "Nice shot",
"pmcresponse-victim_positive_2": "Great shot",
"pmcresponse-victim_positive_3": "Good kill man",
"pmcresponse-victim_positive_4": "Deserved kill, good one",
"pmcresponse-victim_positive_5": "Lucky kill",
"pmcresponse-victim_positive_6": "Good fight",
"pmcresponse-victim_positive_7": "That was fair, nice kill",
"pmcresponse-victim_positive_8": "You're a good shot, that's for sure",
"pmcresponse-victim_negative_1": "Nice aimbot",
"pmcresponse-victim_negative_2": "Cheap shot",
"pmcresponse-victim_negative_3": "Wow esp much",
"pmcresponse-victim_negative_4": "Cheap kill",
"pmcresponse-victim_negative_5": "Nice cheese strats",
"pmcresponse-victim_negative_6": "How much did your hacks cost",
"pmcresponse-victim_negative_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_plead_1": "I was questing",
"pmcresponse-victim_plead_2": "I just wanted to do a quest, why'd you kill me :(",
"pmcresponse-victim_plead_3": "Hope ur happy i cant even afford a new kit",
"pmcresponse-victim_plead_4": "Bro i'm new to the game why you kill me",
"pmcresponse-victim_plead_5": "I am never gonna get this stupid quest done",
"pmcresponse-victim_plead_6": "Did you at least stash my gear?!"
}

View File

@ -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 { PmcChatResponseService } from "../services/PmcChatResponseService";
import { JsonUtil } from "../utils/JsonUtil";
import { TimeUtil } from "../utils/TimeUtil";
@ -48,6 +49,7 @@ export class InraidController
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("LocaleService") protected localeService: LocaleService,
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
@inject("QuestHelper") protected questHelper: QuestHelper,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@ -137,28 +139,22 @@ export class InraidController
{
if (locationName.toLowerCase() === "laboratory")
{
const localeDb = this.localeService.getLocaleDb();
const failedText = localeDb["5a8fd75188a45036844e0b0c"];
const senderDetails: IUserDialogInfo = {
_id: Traders.PRAPOR,
info: {
Nickname: "Prapor",
Level: 1,
Side: "Bear",
MemberCategory: MemberCategory.TRADER
}
};
this.notificationSendHelper.sendMessageToPlayer(sessionID, senderDetails, failedText, MessageType.NPC_TRADER);
this.sendLostInsuranceMessage(sessionID);
}
}
if (isDead)
{
//TODO - find way to get killer name //this.pmcChatResponseService.sendKillerResponse(sessionID, pmcData);
pmcData = this.performPostRaidActionsWhenDead(offraidData, pmcData, insuranceEnabled, preRaidGear, sessionID);
}
const victims = offraidData.profile.Stats.Victims.filter(x => x.Role === "sptBear" || x.Role === "sptUsec");
if (victims?.length > 0)
{
this.pmcChatResponseService.sendVictimResponse(sessionID, victims);
}
if (insuranceEnabled)
{
this.insuranceService.sendInsuredItems(pmcData, sessionID, map.Id);
@ -260,6 +256,24 @@ export class InraidController
this.handlePostRaidPlayerScavProcess(scavData, sessionID, offraidData, pmcData, isDead);
}
protected sendLostInsuranceMessage(sessionID: string): void
{
const localeDb = this.localeService.getLocaleDb();
const failedText = localeDb["5a8fd75188a45036844e0b0c"];
const senderDetails: IUserDialogInfo = {
_id: Traders.PRAPOR,
info: {
Nickname: "Prapor",
Level: 1,
Side: "Bear",
MemberCategory: MemberCategory.TRADER
}
};
this.notificationSendHelper.sendMessageToPlayer(sessionID, senderDetails, failedText, MessageType.NPC_TRADER);
}
/**
* Is the player dead after a raid - dead is anything other than "survived" / "runner"
* @param statusOnExit exit value from offraidData object

View File

@ -1,4 +1,5 @@
import { DependencyContainer, Lifecycle } from "tsyringe";
import { BotCallbacks } from "../callbacks/BotCallbacks";
import { BundleCallbacks } from "../callbacks/BundleCallbacks";
import { CustomizationCallbacks } from "../callbacks/CustomizationCallbacks";
@ -212,6 +213,7 @@ import { NotificationService } from "../services/NotificationService";
import { OpenZoneService } from "../services/OpenZoneService";
import { PaymentService } from "../services/PaymentService";
import { PlayerService } from "../services/PlayerService";
import { PmcChatResponseService } from "../services/PmcChatResponseService";
import { ProfileFixerService } from "../services/ProfileFixerService";
import { ProfileSnapshotService } from "../services/ProfileSnapshotService";
import { RagfairCategoriesService } from "../services/RagfairCategoriesService";
@ -603,6 +605,7 @@ export class Container
depContainer.register<BotWeaponModLimitService>("BotWeaponModLimitService", BotWeaponModLimitService, { lifecycle: Lifecycle.Singleton });
depContainer.register<SeasonalEventService>("SeasonalEventService", SeasonalEventService, { lifecycle: Lifecycle.Singleton });
depContainer.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService);
depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService);
}
private static registerServers(depContainer: DependencyContainer): void

View File

@ -14,6 +14,7 @@ export enum ConfigTypes
LOCATION = "aki-location",
MATCH = "aki-match",
PLAYERSCAV = "aki-playerscav",
PMC_CHAT_RESPONSE = "aki-pmcchatresponse",
QUEST = "aki-quest",
RAGFAIR = "aki-ragfair",
REPAIR = "aki-repair",

View File

@ -0,0 +1,17 @@
import { IBaseConfig } from "./IBaseConfig";
export interface IPmcChatResponse extends IBaseConfig
{
kind: "aki-pmcchatresponse"
victim: IResponseSettings
killer: IResponseSettings
}
export interface IResponseSettings
{
responseChancePercent: number
responseTypeWeights: Record<string, number>
stripCapitalisationChancePercent: number
allCapsChancePercent: number;
appendBroToMessageEndChancePercent: number;
}

View File

@ -42,4 +42,13 @@ export class LocalisationService
{
return this.i18n.__(key.toLowerCase(), args);
}
/**
* Get all locale keys
* @returns string array of keys
*/
public getKeys(): string[]
{
return Object.keys(this.i18n.getCatalog("en"));
}
}

View File

@ -0,0 +1,179 @@
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 { 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 { ConfigServer } from "../servers/ConfigServer";
import { RandomUtil } from "../utils/RandomUtil";
import { LocalisationService } from "./LocalisationService";
@injectable()
export class PmcChatResponseService
{
protected pmcResponsesConfig: IPmcChatResponse;
constructor(
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("ConfigServer") protected configServer: ConfigServer
)
{
this.pmcResponsesConfig = this.configServer.getConfig(ConfigTypes.PMC_CHAT_RESPONSE);
}
/**
* Chooses a random victim from those provided and sends a message to the player, can be positive or negative
* @param sessionId Session id
* @param pmcVictims Array of bots killed by player
*/
public sendVictimResponse(sessionId: string, pmcVictims: Victim[]): void
{
const victim = this.chooseRandomVictim(pmcVictims);
const message = this.chooseMessage(true);
this.notificationSendHelper.sendMessageToPlayer(sessionId, victim, message, MessageType.USER_MESSAGE);
}
/**
* Not fully implemented yet, needs method of acquiring killers details after raid
* @param sessionId Session id
* @param pmcData Players profile
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public sendKillerResponse(sessionId: string, pmcData: IPmcData): void
{
const killer: IUserDialogInfo = {
_id: "",
info: undefined
};
const message = this.chooseMessage(false);
this.notificationSendHelper.sendMessageToPlayer(sessionId, killer, message, MessageType.USER_MESSAGE);
}
/**
* Choose a localised message to send the player (different if sender was killed or killed player)
* @param isVictim
* @returns
*/
protected chooseMessage(isVictim: boolean): string
{
// Positive/negative etc
const responseType = this.chooseResponseType(isVictim);
// Get all locale keys
const possibleResponseLocaleKeys = this.getResponseLocaleKeys(responseType, isVictim);
// Choose random response from above list and request it from localisation service
let responseText = this.localisationService.getText(this.randomUtil.getArrayValue(possibleResponseLocaleKeys));
if (this.appendBroToMessageEnd(isVictim))
{
responseText += " bro";
}
if (this.stripCapitalistion(isVictim))
{
responseText = responseText.toLowerCase();
}
if (this.allCaps(isVictim))
{
responseText = responseText.toUpperCase();
}
return responseText;
}
/**
* Should capitalisation be stripped from the message response before sending
* @param isVictim Was responder a victim of player
* @returns true = should be stripped
*/
protected stripCapitalistion(isVictim: boolean): boolean
{
const chance = isVictim
? this.pmcResponsesConfig.victim.stripCapitalisationChancePercent
: this.pmcResponsesConfig.killer.stripCapitalisationChancePercent;
return this.randomUtil.getChance100(chance);
}
/**
* Should capitalisation be stripped from the message response before sending
* @param isVictim Was responder a victim of player
* @returns true = should be stripped
*/
protected allCaps(isVictim: boolean): boolean
{
const chance = isVictim
? this.pmcResponsesConfig.victim.allCapsChancePercent
: this.pmcResponsesConfig.killer.allCapsChancePercent;
return this.randomUtil.getChance100(chance);
}
/**
* Should the word 'bro' be appended to the message being sent to player
* @param isVictim Was responder a victim of player
* @returns true = should be stripped
*/
appendBroToMessageEnd(isVictim: boolean): boolean
{
const chance = isVictim
? this.pmcResponsesConfig.victim.appendBroToMessageEndChancePercent
: this.pmcResponsesConfig.killer.appendBroToMessageEndChancePercent;
return this.randomUtil.getChance100(chance);
}
/**
* Choose a type of response based on the weightings in pmc response config
* @param isVictim Was responder killed by player
* @returns Response type (positive/negative)
*/
protected chooseResponseType(isVictim = true): string
{
const responseWeights = isVictim
? this.pmcResponsesConfig.victim.responseTypeWeights
: this.pmcResponsesConfig.killer.responseTypeWeights;
return this.weightedRandomHelper.getWeightedInventoryItem(responseWeights);
}
/**
* Get locale keys related to the type of response to send (victim/killer)
* @param keyType Positive/negative
* @param isVictim Was responder killed by player
* @returns
*/
protected getResponseLocaleKeys(keyType: string, isVictim = true): string[]
{
const keyBase = isVictim ? "pmcresponse-victim_" : "pmcresponse-killer_";
const keys = this.localisationService.getKeys();
return keys.filter(x => x.startsWith(`${keyBase}${keyType}`));
}
/**
* Randomly draw a victim of the the array and return thier details
* @param pmcVictims Possible victims to choose from
* @returns IUserDialogInfo
*/
protected chooseRandomVictim(pmcVictims: Victim[]): IUserDialogInfo
{
const randomVictim = this.randomUtil.getArrayValue(pmcVictims);
return {_id: randomVictim.Name, info:{Nickname: randomVictim.Name, Level: randomVictim.Level, Side: randomVictim.Side, MemberCategory: MemberCategory.UNIQUE_ID}};
}
}