From 26a6553eaafef609719a7c186f10bf00bcb72d80 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 24 Dec 2023 19:54:27 +0000 Subject: [PATCH] Add customizable chat bots and chat commands (!179) * Use ICommandoCommand interface to register a new command for Commando! Our new and shiny chat bot that takes care of all your commanding needs * Use IDialogueChatBot to register you new chatty friend bot! * If you are feeling lazy, you can also use the ISptCommand and register a command that will use "spt" prefix * spt give command has been added! Feeling like cheating today? hehe use "spt give tplId quantity" and get a new shiny item on your inbox! Co-authored-by: clodan Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/179 Co-authored-by: Alex Co-committed-by: Alex --- project/.eslintrc.json | 1 + project/src/callbacks/DialogueCallbacks.ts | 4 +- project/src/controllers/DialogueController.ts | 174 ++++-------------- project/src/di/Container.ts | 23 +++ .../Dialogue/Commando/ICommandoAction.ts | 7 + .../Dialogue/Commando/ICommandoCommand.ts | 9 + .../Dialogue/Commando/SptCommandoCommands.ts | 38 ++++ .../Commando/SptCommands/GiveSptCommand.ts | 128 +++++++++++++ .../Commando/SptCommands/ISptCommand.ts | 7 + .../Dialogue/CommandoDialogueChatBot.ts | 88 +++++++++ .../src/helpers/Dialogue/IDialogueChatBot.ts | 8 + .../helpers/Dialogue/SptDialogueChatBot.ts | 151 +++++++++++++++ project/src/utils/HttpResponseUtil.ts | 14 +- 13 files changed, 501 insertions(+), 151 deletions(-) create mode 100644 project/src/helpers/Dialogue/Commando/ICommandoAction.ts create mode 100644 project/src/helpers/Dialogue/Commando/ICommandoCommand.ts create mode 100644 project/src/helpers/Dialogue/Commando/SptCommandoCommands.ts create mode 100644 project/src/helpers/Dialogue/Commando/SptCommands/GiveSptCommand.ts create mode 100644 project/src/helpers/Dialogue/Commando/SptCommands/ISptCommand.ts create mode 100644 project/src/helpers/Dialogue/CommandoDialogueChatBot.ts create mode 100644 project/src/helpers/Dialogue/IDialogueChatBot.ts create mode 100644 project/src/helpers/Dialogue/SptDialogueChatBot.ts diff --git a/project/.eslintrc.json b/project/.eslintrc.json index 54b23e15..72de65ff 100644 --- a/project/.eslintrc.json +++ b/project/.eslintrc.json @@ -11,6 +11,7 @@ "plugin:@typescript-eslint/eslint-recommended" ], "rules": { + "brace-style": ["error", "allman"], "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-explicit-any": "off", // We use a bunch of these. diff --git a/project/src/callbacks/DialogueCallbacks.ts b/project/src/callbacks/DialogueCallbacks.ts index 804e7344..aef83abf 100644 --- a/project/src/callbacks/DialogueCallbacks.ts +++ b/project/src/callbacks/DialogueCallbacks.ts @@ -90,7 +90,7 @@ export class DialogueCallbacks implements OnUpdate sessionID: string, ): IGetBodyResponseData { - return this.httpResponse.getBody(this.dialogueController.generateDialogueList(sessionID)); + return this.httpResponse.getBody(this.dialogueController.generateDialogueList(sessionID), 0, null, false); } /** Handle client/mail/dialog/view */ @@ -100,7 +100,7 @@ export class DialogueCallbacks implements OnUpdate sessionID: string, ): IGetBodyResponseData { - return this.httpResponse.getBody(this.dialogueController.generateDialogueView(info, sessionID)); + return this.httpResponse.getBody(this.dialogueController.generateDialogueView(info, sessionID), 0, null, false); } /** Handle client/mail/dialog/info */ diff --git a/project/src/controllers/DialogueController.ts b/project/src/controllers/DialogueController.ts index 095bf7c9..1407d3d6 100644 --- a/project/src/controllers/DialogueController.ts +++ b/project/src/controllers/DialogueController.ts @@ -1,46 +1,44 @@ -import { inject, injectable } from "tsyringe"; +import { inject, injectAll, injectable } from "tsyringe"; +import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot"; import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper"; -import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { IGetAllAttachmentsResponse } from "@spt-aki/models/eft/dialog/IGetAllAttachmentsResponse"; import { IGetFriendListDataResponse } from "@spt-aki/models/eft/dialog/IGetFriendListDataResponse"; import { IGetMailDialogViewRequestData } from "@spt-aki/models/eft/dialog/IGetMailDialogViewRequestData"; import { IGetMailDialogViewResponseData } from "@spt-aki/models/eft/dialog/IGetMailDialogViewResponseData"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; import { Dialogue, DialogueInfo, IAkiProfile, IUserDialogInfo, Message } from "@spt-aki/models/eft/profile/IAkiProfile"; -import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; -import { GiftSentResult } from "@spt-aki/models/enums/GiftSentResult"; -import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; import { MessageType } from "@spt-aki/models/enums/MessageType"; -import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; -import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; -import { GiftService } from "@spt-aki/services/GiftService"; import { MailSendService } from "@spt-aki/services/MailSendService"; -import { HashUtil } from "@spt-aki/utils/HashUtil"; -import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @injectable() export class DialogueController { - protected coreConfig: ICoreConfig; + protected registeredDialogueChatBots: Map = new Map(); constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("SaveServer") protected saveServer: SaveServer, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("DialogueHelper") protected dialogueHelper: DialogueHelper, - @inject("ProfileHelper") protected profileHelper: ProfileHelper, - @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("MailSendService") protected mailSendService: MailSendService, - @inject("GiftService") protected giftService: GiftService, - @inject("HashUtil") protected hashUtil: HashUtil, - @inject("ConfigServer") protected configServer: ConfigServer, + @injectAll("DialogueChatBot") protected dialogueChatBots: IDialogueChatBot[], ) { - this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE); + for (const dialogueChatBot of dialogueChatBots) + { + if (this.registeredDialogueChatBots.has(dialogueChatBot.getChatBot()._id)) + { + this.logger.error( + `Could not register ${dialogueChatBot.getChatBot()._id} as it is already in use. Skipping.`, + ); + continue; + } + this.registeredDialogueChatBots.set(dialogueChatBot.getChatBot()._id, dialogueChatBot); + } } /** Handle onUpdate spt event */ @@ -61,7 +59,11 @@ export class DialogueController public getFriendList(sessionID: string): IGetFriendListDataResponse { // Force a fake friend called SPT into friend list - return { Friends: [this.getSptFriendData()], Ignore: [], InIgnoreList: [] }; + return { + Friends: Array.from(this.registeredDialogueChatBots.values()).map((v) => v.getChatBot()), + Ignore: [], + InIgnoreList: [], + }; } /** @@ -118,7 +120,8 @@ export class DialogueController // User to user messages are special in that they need the player to exist in them, add if they don't if ( - messageType === MessageType.USER_MESSAGE && !dialog.Users?.find((x) => x._id === profile.characters.pmc._id) + messageType === MessageType.USER_MESSAGE + && !dialog.Users?.find((x) => x._id === profile.characters.pmc.sessionId) ) { if (!dialog.Users) @@ -127,7 +130,7 @@ export class DialogueController } dialog.Users.push({ - _id: profile.characters.pmc._id, + _id: profile.characters.pmc.sessionId, info: { Level: profile.characters.pmc.Info.Level, Nickname: profile.characters.pmc.Info.Nickname, @@ -193,7 +196,12 @@ export class DialogueController if (request.type === MessageType.USER_MESSAGE) { profile.dialogues[request.dialogId].Users = []; - profile.dialogues[request.dialogId].Users.push(this.getSptFriendData(request.dialogId)); + if (this.registeredDialogueChatBots.has(request.dialogId)) + { + profile.dialogues[request.dialogId].Users.push( + this.registeredDialogueChatBots.get(request.dialogId).getChatBot(), + ); + } } } @@ -356,134 +364,14 @@ export class DialogueController { this.mailSendService.sendPlayerMessageToNpc(sessionId, request.dialogId, request.text); - // Handle when player types a keyword to sptFriend user - if (request.dialogId.includes("sptFriend")) + if (this.registeredDialogueChatBots.has(request.dialogId)) { - this.handleChatWithSPTFriend(sessionId, request); + return this.registeredDialogueChatBots.get(request.dialogId).handleMessage(sessionId, request); } return request.dialogId; } - /** - * Send responses back to player when they communicate with SPT friend on friends list - * @param sessionId Session Id - * @param request send message request - */ - protected handleChatWithSPTFriend(sessionId: string, request: ISendMessageRequest): void - { - const sender = this.profileHelper.getPmcProfile(sessionId); - - const sptFriendUser = this.getSptFriendData(); - - const giftSent = this.giftService.sendGiftToPlayer(sessionId, request.text); - - if (giftSent === GiftSentResult.SUCCESS) - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue([ - "Hey! you got the right code!", - "A secret code, how exciting!", - "You found a gift code!", - ]), - ); - - return; - } - - if (giftSent === GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED) - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue(["Looks like you already used that code", "You already have that!!"]), - ); - - return; - } - - if (request.text.toLowerCase().includes("love you")) - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue([ - "That's quite forward but i love you too in a purely chatbot-human way", - "I love you too buddy :3!", - "uwu", - `love you too ${sender?.Info?.Nickname}`, - ]), - ); - } - - if (request.text.toLowerCase() === "spt") - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue(["Its me!!", "spt? i've heard of that project"]), - ); - } - - if (["hello", "hi", "sup", "yo", "hey"].includes(request.text.toLowerCase())) - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue([ - "Howdy", - "Hi", - "Greetings", - "Hello", - "bonjor", - "Yo", - "Sup", - "Heyyyyy", - "Hey there", - `Hello ${sender?.Info?.Nickname}`, - ]), - ); - } - - if (request.text.toLowerCase() === "nikita") - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue([ - "I know that guy!", - "Cool guy, he made EFT!", - "Legend", - "Remember when he said webel-webel-webel-webel, classic nikita moment", - ]), - ); - } - - if (request.text.toLowerCase() === "are you a bot") - { - this.mailSendService.sendUserMessageToPlayer( - sessionId, - sptFriendUser, - this.randomUtil.getArrayValue(["beep boop", "**sad boop**", "probably", "sometimes", "yeah lol"]), - ); - } - } - - protected getSptFriendData(friendId = "sptFriend"): IUserDialogInfo - { - return { - _id: friendId, - info: { - Level: 1, - MemberCategory: MemberCategory.DEVELOPER, - Nickname: this.coreConfig.sptFriendNickname, - Side: "Usec", - }, - }; - } - /** * Get messages from a specific dialog that have items not expired * @param sessionId Session id diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index b5ea9d6a..59585894 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -246,6 +246,10 @@ import { VFS } from "@spt-aki/utils/VFS"; import { Watermark, WatermarkLocale } from "@spt-aki/utils/Watermark"; import { WinstonMainLogger } from "@spt-aki/utils/logging/WinstonMainLogger"; import { WinstonRequestLogger } from "@spt-aki/utils/logging/WinstonRequestLogger"; +import {SptDialogueChatBot} from "@spt-aki/helpers/Dialogue/SptDialogueChatBot"; +import {CommandoDialogueChatBot} from "@spt-aki/helpers/Dialogue/CommandoDialogueChatBot"; +import {GiveSptCommand} from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveSptCommand"; +import {SptCommandoCommands} from "@spt-aki/helpers/Dialogue/Commando/SptCommandoCommands"; /** * Handle the registration of classes to be used by the Dependency Injection code @@ -357,6 +361,16 @@ export class Container depContainer.registerType("SaveLoadRouter", "InraidSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "InsuranceSaveLoadRouter"); depContainer.registerType("SaveLoadRouter", "ProfileSaveLoadRouter"); + + // Chat Bots + depContainer.registerType("DialogueChatBot", "SptDialogueChatBot"); + depContainer.registerType("DialogueChatBot", "CommandoDialogueChatBot"); + + // Commando Commands + depContainer.registerType("CommandoCommand", "SptCommandoCommands"); + + // SptCommando Commands + depContainer.registerType("SptCommand", "GiveSptCommand"); } private static registerUtils(depContainer: DependencyContainer): void @@ -566,6 +580,15 @@ export class Container }); depContainer.register("BotDifficultyHelper", { useClass: BotDifficultyHelper }); depContainer.register("RepeatableQuestHelper", { useClass: RepeatableQuestHelper }); + + // ChatBots + depContainer.register("SptDialogueChatBot", SptDialogueChatBot); + depContainer.register("CommandoDialogueChatBot", CommandoDialogueChatBot); + // SptCommando + depContainer.register("SptCommandoCommands", SptCommandoCommands); + // SptCommands + depContainer.register("GiveSptCommand", GiveSptCommand); + } private static registerLoaders(depContainer: DependencyContainer): void diff --git a/project/src/helpers/Dialogue/Commando/ICommandoAction.ts b/project/src/helpers/Dialogue/Commando/ICommandoAction.ts new file mode 100644 index 00000000..62cfaf01 --- /dev/null +++ b/project/src/helpers/Dialogue/Commando/ICommandoAction.ts @@ -0,0 +1,7 @@ +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; + +export interface ICommandoAction +{ + handle(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string; +} diff --git a/project/src/helpers/Dialogue/Commando/ICommandoCommand.ts b/project/src/helpers/Dialogue/Commando/ICommandoCommand.ts new file mode 100644 index 00000000..39338bb0 --- /dev/null +++ b/project/src/helpers/Dialogue/Commando/ICommandoCommand.ts @@ -0,0 +1,9 @@ +import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; + +export interface ICommandoCommand +{ + getCommandPrefix(): string; + getCommandHelp(command: string): string; + getCommands(): Set; + getCommandAction(command: string): ICommandoAction; +} diff --git a/project/src/helpers/Dialogue/Commando/SptCommandoCommands.ts b/project/src/helpers/Dialogue/Commando/SptCommandoCommands.ts new file mode 100644 index 00000000..a087880d --- /dev/null +++ b/project/src/helpers/Dialogue/Commando/SptCommandoCommands.ts @@ -0,0 +1,38 @@ +import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; +import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand"; +import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand"; +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { injectAll, injectable } from "tsyringe"; + +@injectable() +export class SptCommandoCommands implements ICommandoCommand +{ + constructor( + @injectAll("SptCommand") protected sptCommands: ISptCommand[] + ) + { + } + + public getCommandHelp(command: string): string + { + return this.sptCommands.find(c => c.getCommand() === command)?.getCommandHelp(); + } + + public getCommandPrefix(): string + { + return "spt"; + } + + public getCommands(): Set + { + return new Set(this.sptCommands.map(c => c.getCommand())); + } + + public getCommandAction(command: string): ICommandoAction + { + return this.sptCommands.find(c => c.getCommand() === command); + } + + +} diff --git a/project/src/helpers/Dialogue/Commando/SptCommands/GiveSptCommand.ts b/project/src/helpers/Dialogue/Commando/SptCommands/GiveSptCommand.ts new file mode 100644 index 00000000..e19f732e --- /dev/null +++ b/project/src/helpers/Dialogue/Commando/SptCommands/GiveSptCommand.ts @@ -0,0 +1,128 @@ +import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand"; +import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; +import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; +import { Item } from "@spt-aki/models/eft/common/tables/IItem"; +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; +import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; +import { MailSendService } from "@spt-aki/services/MailSendService"; +import { HashUtil } from "@spt-aki/utils/HashUtil"; +import { JsonUtil } from "@spt-aki/utils/JsonUtil"; +import { inject, injectable } from "tsyringe"; + +@injectable() +export class GiveSptCommand implements ISptCommand +{ + public constructor( + @inject("WinstonLogger") protected logger: ILogger, + @inject("ItemHelper") protected itemHelper: ItemHelper, + @inject("HashUtil") protected hashUtil: HashUtil, + @inject("JsonUtil") protected jsonUtil: JsonUtil, + @inject("PresetHelper") protected presetHelper: PresetHelper, + @inject("MailSendService") protected mailSendService: MailSendService, + ) + { + } + + public getCommand(): string + { + return "give"; + } + + public getCommandHelp(): string + { + return "Usage: spt give tplId quantity"; + } + + public handle(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string + { + const giveCommand = request.text.split(" "); + if (giveCommand[1] !== "give") + { + this.logger.error("Invalid action received for give command!"); + return request.dialogId; + } + + if (!giveCommand[2]) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + "Invalid use of give command! Template ID is missing. Use \"Help\" for more info", + ); + return request.dialogId; + } + const tplId = giveCommand[2]; + + if (!giveCommand[3]) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + "Invalid use of give command! Quantity is missing. Use \"Help\" for more info", + ); + return request.dialogId; + } + const quantity = giveCommand[3]; + + if (Number.isNaN(+quantity)) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + "Invalid use of give command! Quantity is not a valid integer. Use \"Help\" for more info", + ); + return request.dialogId; + } + + const checkedItem = this.itemHelper.getItem(tplId); + if (!checkedItem[0]) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + "Invalid template ID requested for give command. The item doesnt exists on the DB.", + ); + return request.dialogId; + } + + const itemsToSend: Item[] = []; + if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.WEAPON)) + { + const preset = this.presetHelper.getDefaultPreset(checkedItem[1]._id); + if (!preset) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + commandHandler, + "Invalid weapon template ID requested. There are no default presets for this weapon.", + ); + return request.dialogId; + } + itemsToSend.push(...this.jsonUtil.clone(preset._items)); + } + else if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.AMMO_BOX)) + { + for (let i = 0; i < +quantity; i++) + { + const ammoBoxArray: Item[] = []; + ammoBoxArray.push({ _id: this.hashUtil.generate(), _tpl: checkedItem[1]._id }); + this.itemHelper.addCartridgesToAmmoBox(ammoBoxArray, checkedItem[1]); + itemsToSend.push(...ammoBoxArray); + } + } + else + { + const item: Item = { + _id: this.hashUtil.generate(), + _tpl: checkedItem[1]._id, + upd: { StackObjectsCount: +quantity }, + }; + itemsToSend.push(...this.itemHelper.splitStack(item)); + } + + this.mailSendService.sendSystemMessageToPlayer(sessionId, "Give command!", itemsToSend); + return request.dialogId; + } +} diff --git a/project/src/helpers/Dialogue/Commando/SptCommands/ISptCommand.ts b/project/src/helpers/Dialogue/Commando/SptCommands/ISptCommand.ts new file mode 100644 index 00000000..b187b62e --- /dev/null +++ b/project/src/helpers/Dialogue/Commando/SptCommands/ISptCommand.ts @@ -0,0 +1,7 @@ +import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; + +export interface ISptCommand extends ICommandoAction +{ + getCommand(): string; + getCommandHelp(): string; +} diff --git a/project/src/helpers/Dialogue/CommandoDialogueChatBot.ts b/project/src/helpers/Dialogue/CommandoDialogueChatBot.ts new file mode 100644 index 00000000..8bbaff1d --- /dev/null +++ b/project/src/helpers/Dialogue/CommandoDialogueChatBot.ts @@ -0,0 +1,88 @@ +import { inject, injectAll, injectable } from "tsyringe"; + +import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; +import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand"; +import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot"; +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; +import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; +import { MailSendService } from "@spt-aki/services/MailSendService"; + +@injectable() +export class CommandoDialogueChatBot implements IDialogueChatBot +{ + + // A map that contains the command prefix. That contains a map that contains the prefix commands with their respective actions. + protected registeredCommands: Map> = new Map>(); + public constructor( + @inject("WinstonLogger") protected logger: ILogger, + @inject("MailSendService") protected mailSendService: MailSendService, + @injectAll("CommandoCommand") protected commandoCommands: ICommandoCommand[] + ) + { + for (const commandoCommand of commandoCommands) + { + if (this.registeredCommands.has(commandoCommand.getCommandPrefix()) || commandoCommand.getCommandPrefix().toLowerCase() === "help") + { + this.logger.error(`Could not registered command prefix ${commandoCommand.getCommandPrefix()} as it already has been registered. Skipping.`); + continue; + } + + const commandMap = new Map(); + this.registeredCommands.set(commandoCommand.getCommandPrefix(), commandMap); + for (const command of commandoCommand.getCommands()) + { + commandMap.set(command, commandoCommand.getCommandAction(command)) + } + } + } + + public getChatBot(): IUserDialogInfo + { + return { + _id: "sptCommando", + info: { + Level: 1, + MemberCategory: MemberCategory.DEVELOPER, + Nickname: "Commando", + Side: "Usec", + }, + }; + } + + public handleMessage(sessionId: string, request: ISendMessageRequest): string + { + if ((request.text ?? "").length === 0) + { + this.logger.error("Commando command came in as empty text! Invalid data!"); + return request.dialogId; + } + + const splitCommand = request.text.split(" "); + + if (this.registeredCommands.has(splitCommand[0]) && this.registeredCommands.get(splitCommand[0]).has(splitCommand[1])) + return this.registeredCommands.get(splitCommand[0]).get(splitCommand[1]).handle(this.getChatBot(), sessionId, request); + + if (splitCommand[0].toLowerCase() === "help") + { + const helpMessage = this.commandoCommands.filter(c => this.registeredCommands.has(c.getCommandPrefix())) + .filter(c => Array.from(c.getCommands()).some(com => this.registeredCommands.get(c.getCommandPrefix()).has(com))) + .map(c => `Help for ${c.getCommandPrefix()}:\n${Array.from(c.getCommands()).map(command => c.getCommandHelp(command)).join("\n")}`) + .join("\n"); + this.mailSendService.sendUserMessageToPlayer( + sessionId, + this.getChatBot(), + helpMessage + ); + return request.dialogId; + } + + this.mailSendService.sendUserMessageToPlayer( + sessionId, + this.getChatBot(), + `Im sorry soldier, I dont recognize the command you are trying to use! Type "help" to see available commands.` + ); + } + +} diff --git a/project/src/helpers/Dialogue/IDialogueChatBot.ts b/project/src/helpers/Dialogue/IDialogueChatBot.ts new file mode 100644 index 00000000..a39ffa8a --- /dev/null +++ b/project/src/helpers/Dialogue/IDialogueChatBot.ts @@ -0,0 +1,8 @@ +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; + +export interface IDialogueChatBot +{ + getChatBot(): IUserDialogInfo; + handleMessage(sessionId: string, request: ISendMessageRequest): string; +} diff --git a/project/src/helpers/Dialogue/SptDialogueChatBot.ts b/project/src/helpers/Dialogue/SptDialogueChatBot.ts new file mode 100644 index 00000000..2e56b338 --- /dev/null +++ b/project/src/helpers/Dialogue/SptDialogueChatBot.ts @@ -0,0 +1,151 @@ +import { inject, injectable } from "tsyringe"; + +import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot"; +import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; +import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; +import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; +import { GiftSentResult } from "@spt-aki/models/enums/GiftSentResult"; +import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; +import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig"; +import { ConfigServer } from "@spt-aki/servers/ConfigServer"; +import { GiftService } from "@spt-aki/services/GiftService"; +import { MailSendService } from "@spt-aki/services/MailSendService"; +import { RandomUtil } from "@spt-aki/utils/RandomUtil"; + +@injectable() +export class SptDialogueChatBot implements IDialogueChatBot +{ + protected coreConfig: ICoreConfig; + public constructor( + @inject("ProfileHelper") protected profileHelper: ProfileHelper, + @inject("RandomUtil") protected randomUtil: RandomUtil, + @inject("MailSendService") protected mailSendService: MailSendService, + @inject("GiftService") protected giftService: GiftService, + @inject("ConfigServer") protected configServer: ConfigServer + ) + { + this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE); + } + + public getChatBot(): IUserDialogInfo + { + return { + _id: "sptFriend", + info: { + Level: 1, + MemberCategory: MemberCategory.DEVELOPER, + Nickname: this.coreConfig.sptFriendNickname, + Side: "Usec", + }, + }; + } + + /** + * Send responses back to player when they communicate with SPT friend on friends list + * @param sessionId Session Id + * @param request send message request + */ + public handleMessage(sessionId: string, request: ISendMessageRequest): string + { + const sender = this.profileHelper.getPmcProfile(sessionId); + + const sptFriendUser = this.getChatBot(); + + const giftSent = this.giftService.sendGiftToPlayer(sessionId, request.text); + + if (giftSent === GiftSentResult.SUCCESS) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue([ + "Hey! you got the right code!", + "A secret code, how exciting!", + "You found a gift code!", + ]) + ); + + return; + } + + if (giftSent === GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue(["Looks like you already used that code", "You already have that!!"]) + ); + + return; + } + + if (request.text.toLowerCase().includes("love you")) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue([ + "That's quite forward but i love you too in a purely chatbot-human way", + "I love you too buddy :3!", + "uwu", + `love you too ${sender?.Info?.Nickname}`, + ]) + ); + } + + if (request.text.toLowerCase() === "spt") + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue(["Its me!!", "spt? i've heard of that project"]) + ); + } + + if (["hello", "hi", "sup", "yo", "hey"].includes(request.text.toLowerCase())) + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue([ + "Howdy", + "Hi", + "Greetings", + "Hello", + "bonjor", + "Yo", + "Sup", + "Heyyyyy", + "Hey there", + `Hello ${sender?.Info?.Nickname}`, + ]) + ); + } + + if (request.text.toLowerCase() === "nikita") + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue([ + "I know that guy!", + "Cool guy, he made EFT!", + "Legend", + "Remember when he said webel-webel-webel-webel, classic nikita moment", + ]) + ); + } + + if (request.text.toLowerCase() === "are you a bot") + { + this.mailSendService.sendUserMessageToPlayer( + sessionId, + sptFriendUser, + this.randomUtil.getArrayValue(["beep boop", "**sad boop**", "probably", "sometimes", "yeah lol"]) + ); + } + + return request.dialogId; + } +} diff --git a/project/src/utils/HttpResponseUtil.ts b/project/src/utils/HttpResponseUtil.ts index e543c2ae..307d8949 100644 --- a/project/src/utils/HttpResponseUtil.ts +++ b/project/src/utils/HttpResponseUtil.ts @@ -36,14 +36,16 @@ export class HttpResponseUtil /** * Game client needs server responses in a particular format - * @param data - * @param err - * @param errmsg - * @returns + * @param data + * @param err + * @param errmsg + * @returns */ - public getBody(data: T, err = 0, errmsg = null): IGetBodyResponseData + public getBody(data: T, err = 0, errmsg = null, sanitize = true): IGetBodyResponseData { - return this.clearString(this.getUnclearedBody(data, err, errmsg)); + return sanitize + ? this.clearString(this.getUnclearedBody(data, err, errmsg)) + : (this.getUnclearedBody(data, err, errmsg) as any); } public getUnclearedBody(data: any, err = 0, errmsg = null): string