Added configs and simplification to ChatBot (!180)

Co-authored-by: clodan <clodan@clodan.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/180
Co-authored-by: Alex <clodan@noreply.dev.sp-tarkov.com>
Co-committed-by: Alex <clodan@noreply.dev.sp-tarkov.com>
This commit is contained in:
Alex 2023-12-25 08:38:42 +00:00 committed by chomp
parent 26a6553eaa
commit 0ade8f4b9c
10 changed files with 136 additions and 93 deletions

View File

@ -12,6 +12,13 @@
}, },
"features": { "features": {
"autoInstallModDependencies": false, "autoInstallModDependencies": false,
"compressProfile": false "compressProfile": false,
"chatbotFeatures": {
"sptFriendEnabled": true,
"commandoEnabled": true,
"commandoFeatures": {
"giveCommandEnabled": false
}
}
} }
} }

View File

@ -8,8 +8,11 @@ import { IGetMailDialogViewRequestData } from "@spt-aki/models/eft/dialog/IGetMa
import { IGetMailDialogViewResponseData } from "@spt-aki/models/eft/dialog/IGetMailDialogViewResponseData"; import { IGetMailDialogViewResponseData } from "@spt-aki/models/eft/dialog/IGetMailDialogViewResponseData";
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
import { Dialogue, DialogueInfo, IAkiProfile, IUserDialogInfo, Message } from "@spt-aki/models/eft/profile/IAkiProfile"; import { Dialogue, DialogueInfo, IAkiProfile, IUserDialogInfo, Message } from "@spt-aki/models/eft/profile/IAkiProfile";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { MessageType } from "@spt-aki/models/enums/MessageType"; 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 { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { SaveServer } from "@spt-aki/servers/SaveServer"; import { SaveServer } from "@spt-aki/servers/SaveServer";
import { MailSendService } from "@spt-aki/services/MailSendService"; import { MailSendService } from "@spt-aki/services/MailSendService";
import { TimeUtil } from "@spt-aki/utils/TimeUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@ -17,30 +20,41 @@ import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@injectable() @injectable()
export class DialogueController export class DialogueController
{ {
protected registeredDialogueChatBots: Map<string, IDialogueChatBot> = new Map<string, IDialogueChatBot>();
constructor( constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("SaveServer") protected saveServer: SaveServer, @inject("SaveServer") protected saveServer: SaveServer,
@inject("TimeUtil") protected timeUtil: TimeUtil, @inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper, @inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
@inject("MailSendService") protected mailSendService: MailSendService, @inject("MailSendService") protected mailSendService: MailSendService,
@inject("ConfigServer") protected configServer: ConfigServer,
@injectAll("DialogueChatBot") protected dialogueChatBots: IDialogueChatBot[], @injectAll("DialogueChatBot") protected dialogueChatBots: IDialogueChatBot[],
) )
{ {
for (const dialogueChatBot of dialogueChatBots) const coreConfigs = this.configServer.getConfig<ICoreConfig>(ConfigTypes.CORE);
// if give command is disabled or commando commands are disabled
if (!coreConfigs.features?.chatbotFeatures?.commandoEnabled)
{ {
if (this.registeredDialogueChatBots.has(dialogueChatBot.getChatBot()._id)) const sptCommando = this.dialogueChatBots.find((c) =>
{ c.getChatBot()._id.toLocaleLowerCase() === "sptcommando"
this.logger.error(
`Could not register ${dialogueChatBot.getChatBot()._id} as it is already in use. Skipping.`,
); );
continue; this.dialogueChatBots.splice(this.dialogueChatBots.indexOf(sptCommando), 1);
} }
this.registeredDialogueChatBots.set(dialogueChatBot.getChatBot()._id, dialogueChatBot); if (!coreConfigs.features?.chatbotFeatures?.sptFriendEnabled)
{
const sptFriend = this.dialogueChatBots.find((c) => c.getChatBot()._id.toLocaleLowerCase() === "sptFriend");
this.dialogueChatBots.splice(this.dialogueChatBots.indexOf(sptFriend), 1);
} }
} }
public registerChatBot(chatBot: IDialogueChatBot): void
{
if (this.dialogueChatBots.some((cb) => cb.getChatBot()._id === chatBot.getChatBot()._id))
{
throw new Error(`The chat bot ${chatBot.getChatBot()._id} being registered already exists!`);
}
this.dialogueChatBots.push(chatBot);
}
/** Handle onUpdate spt event */ /** Handle onUpdate spt event */
public update(): void public update(): void
{ {
@ -59,11 +73,7 @@ export class DialogueController
public getFriendList(sessionID: string): IGetFriendListDataResponse public getFriendList(sessionID: string): IGetFriendListDataResponse
{ {
// Force a fake friend called SPT into friend list // Force a fake friend called SPT into friend list
return { return { Friends: this.dialogueChatBots.map((v) => v.getChatBot()), Ignore: [], InIgnoreList: [] };
Friends: Array.from(this.registeredDialogueChatBots.values()).map((v) => v.getChatBot()),
Ignore: [],
InIgnoreList: [],
};
} }
/** /**
@ -196,11 +206,10 @@ export class DialogueController
if (request.type === MessageType.USER_MESSAGE) if (request.type === MessageType.USER_MESSAGE)
{ {
profile.dialogues[request.dialogId].Users = []; profile.dialogues[request.dialogId].Users = [];
if (this.registeredDialogueChatBots.has(request.dialogId)) const chatBot = this.dialogueChatBots.find((cb) => cb.getChatBot()._id === request.dialogId);
if (chatBot)
{ {
profile.dialogues[request.dialogId].Users.push( profile.dialogues[request.dialogId].Users.push(chatBot.getChatBot());
this.registeredDialogueChatBots.get(request.dialogId).getChatBot(),
);
} }
} }
} }
@ -364,12 +373,10 @@ export class DialogueController
{ {
this.mailSendService.sendPlayerMessageToNpc(sessionId, request.dialogId, request.text); this.mailSendService.sendPlayerMessageToNpc(sessionId, request.dialogId, request.text);
if (this.registeredDialogueChatBots.has(request.dialogId)) return this.dialogueChatBots.find((cb) => cb.getChatBot()._id === request.dialogId)?.handleMessage(
{ sessionId,
return this.registeredDialogueChatBots.get(request.dialogId).handleMessage(sessionId, request); request,
} ) ?? request.dialogId;
return request.dialogId;
} }
/** /**

View File

@ -86,6 +86,10 @@ import { BotGeneratorHelper } from "@spt-aki/helpers/BotGeneratorHelper";
import { BotHelper } from "@spt-aki/helpers/BotHelper"; import { BotHelper } from "@spt-aki/helpers/BotHelper";
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper"; import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
import { ContainerHelper } from "@spt-aki/helpers/ContainerHelper"; import { ContainerHelper } from "@spt-aki/helpers/ContainerHelper";
import { SptCommandoCommands } from "@spt-aki/helpers/Dialogue/Commando/SptCommandoCommands";
import { GiveSptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveSptCommand";
import { CommandoDialogueChatBot } from "@spt-aki/helpers/Dialogue/CommandoDialogueChatBot";
import { SptDialogueChatBot } from "@spt-aki/helpers/Dialogue/SptDialogueChatBot";
import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper"; import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper";
import { DurabilityLimitsHelper } from "@spt-aki/helpers/DurabilityLimitsHelper"; import { DurabilityLimitsHelper } from "@spt-aki/helpers/DurabilityLimitsHelper";
import { GameEventHelper } from "@spt-aki/helpers/GameEventHelper"; import { GameEventHelper } from "@spt-aki/helpers/GameEventHelper";
@ -246,10 +250,6 @@ import { VFS } from "@spt-aki/utils/VFS";
import { Watermark, WatermarkLocale } from "@spt-aki/utils/Watermark"; import { Watermark, WatermarkLocale } from "@spt-aki/utils/Watermark";
import { WinstonMainLogger } from "@spt-aki/utils/logging/WinstonMainLogger"; import { WinstonMainLogger } from "@spt-aki/utils/logging/WinstonMainLogger";
import { WinstonRequestLogger } from "@spt-aki/utils/logging/WinstonRequestLogger"; 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 * Handle the registration of classes to be used by the Dependency Injection code
@ -583,12 +583,15 @@ export class Container
// ChatBots // ChatBots
depContainer.register<SptDialogueChatBot>("SptDialogueChatBot", SptDialogueChatBot); depContainer.register<SptDialogueChatBot>("SptDialogueChatBot", SptDialogueChatBot);
depContainer.register<CommandoDialogueChatBot>("CommandoDialogueChatBot", CommandoDialogueChatBot); depContainer.register<CommandoDialogueChatBot>("CommandoDialogueChatBot", CommandoDialogueChatBot, {
lifecycle: Lifecycle.Singleton,
});
// SptCommando // SptCommando
depContainer.register<SptCommandoCommands>("SptCommandoCommands", SptCommandoCommands); depContainer.register<SptCommandoCommands>("SptCommandoCommands", SptCommandoCommands, {
lifecycle: Lifecycle.Singleton,
});
// SptCommands // SptCommands
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand); depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand);
} }
private static registerLoaders(depContainer: DependencyContainer): void private static registerLoaders(depContainer: DependencyContainer): void
@ -753,7 +756,9 @@ export class Container
depContainer.register<CustomizationController>("CustomizationController", { depContainer.register<CustomizationController>("CustomizationController", {
useClass: CustomizationController, useClass: CustomizationController,
}); });
depContainer.register<DialogueController>("DialogueController", { useClass: DialogueController }); depContainer.register<DialogueController>("DialogueController", { useClass: DialogueController }, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<GameController>("GameController", { useClass: GameController }); depContainer.register<GameController>("GameController", { useClass: GameController });
depContainer.register<HandbookController>("HandbookController", { useClass: HandbookController }); depContainer.register<HandbookController>("HandbookController", { useClass: HandbookController });
depContainer.register<HealthController>("HealthController", { useClass: HealthController }); depContainer.register<HealthController>("HealthController", { useClass: HealthController });

View File

@ -1,7 +0,0 @@
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;
}

View File

@ -1,9 +1,10 @@
import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
export interface ICommandoCommand export interface ICommandoCommand
{ {
getCommandPrefix(): string; getCommandPrefix(): string;
getCommandHelp(command: string): string; getCommandHelp(command: string): string;
getCommands(): Set<string>; getCommands(): Set<string>;
getCommandAction(command: string): ICommandoAction; handle(command: string, commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string;
} }

View File

@ -1,22 +1,44 @@
import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction";
import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand"; import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand";
import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand"; import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand";
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile"; import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
import { injectAll, injectable } from "tsyringe"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { ICoreConfig } from "@spt-aki/models/spt/config/ICoreConfig";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { inject, injectAll, injectable } from "tsyringe";
@injectable() @injectable()
export class SptCommandoCommands implements ICommandoCommand export class SptCommandoCommands implements ICommandoCommand
{ {
constructor( constructor(
@injectAll("SptCommand") protected sptCommands: ISptCommand[] @inject("ConfigServer") protected configServer: ConfigServer,
@injectAll("SptCommand") protected sptCommands: ISptCommand[],
) )
{ {
const coreConfigs = this.configServer.getConfig<ICoreConfig>(ConfigTypes.CORE);
// if give command is disabled or commando commands are disabled
if (
!(coreConfigs.features?.chatbotFeatures?.commandoFeatures?.giveCommandEnabled
&& coreConfigs.features?.chatbotFeatures?.commandoEnabled)
)
{
const giveCommand = this.sptCommands.find((c) => c.getCommand().toLocaleLowerCase() === "give");
this.sptCommands.splice(this.sptCommands.indexOf(giveCommand), 1);
}
}
public registerSptCommandoCommand(command: ISptCommand): void
{
if (this.sptCommands.some((c) => c.getCommand() === command.getCommand()))
{
throw new Error(`The command ${command.getCommand()} being registered for SPT Commands already exists!`);
}
this.sptCommands.push(command);
} }
public getCommandHelp(command: string): string public getCommandHelp(command: string): string
{ {
return this.sptCommands.find(c => c.getCommand() === command)?.getCommandHelp(); return this.sptCommands.find((c) => c.getCommand() === command)?.getCommandHelp();
} }
public getCommandPrefix(): string public getCommandPrefix(): string
@ -26,13 +48,20 @@ export class SptCommandoCommands implements ICommandoCommand
public getCommands(): Set<string> public getCommands(): Set<string>
{ {
return new Set(this.sptCommands.map(c => c.getCommand())); return new Set(this.sptCommands.map((c) => c.getCommand()));
} }
public getCommandAction(command: string): ICommandoAction public handle(
command: string,
commandHandler: IUserDialogInfo,
sessionId: string,
request: ISendMessageRequest,
): string
{ {
return this.sptCommands.find(c => c.getCommand() === command); return this.sptCommands.find((c) => c.getCommand() === command).performAction(
commandHandler,
sessionId,
request,
);
} }
} }

View File

@ -35,7 +35,7 @@ export class GiveSptCommand implements ISptCommand
return "Usage: spt give tplId quantity"; return "Usage: spt give tplId quantity";
} }
public handle(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string public performAction(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string
{ {
const giveCommand = request.text.split(" "); const giveCommand = request.text.split(" ");
if (giveCommand[1] !== "give") if (giveCommand[1] !== "give")

View File

@ -1,7 +1,9 @@
import { ICommandoAction } from "@spt-aki/helpers/Dialogue/Commando/ICommandoAction"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
export interface ISptCommand extends ICommandoAction export interface ISptCommand
{ {
getCommand(): string; getCommand(): string;
getCommandHelp(): string; getCommandHelp(): string;
performAction(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string;
} }

View File

@ -1,6 +1,5 @@
import { inject, injectAll, injectable } from "tsyringe"; 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 { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand";
import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot"; import { IDialogueChatBot } from "@spt-aki/helpers/Dialogue/IDialogueChatBot";
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest"; import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
@ -12,42 +11,30 @@ import { MailSendService } from "@spt-aki/services/MailSendService";
@injectable() @injectable()
export class CommandoDialogueChatBot implements IDialogueChatBot 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<string, Map<string, ICommandoAction>> = new Map<string, Map<string, ICommandoAction>>();
public constructor( public constructor(
@inject("WinstonLogger") protected logger: ILogger, @inject("WinstonLogger") protected logger: ILogger,
@inject("MailSendService") protected mailSendService: MailSendService, @inject("MailSendService") protected mailSendService: MailSendService,
@injectAll("CommandoCommand") protected commandoCommands: ICommandoCommand[] @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<string, ICommandoAction>(); public registerCommandoCommand(commandoCommand: ICommandoCommand): void
this.registeredCommands.set(commandoCommand.getCommandPrefix(), commandMap);
for (const command of commandoCommand.getCommands())
{ {
commandMap.set(command, commandoCommand.getCommandAction(command)) if (this.commandoCommands.some((cc) => cc.getCommandPrefix() === commandoCommand.getCommandPrefix()))
} {
throw new Error(
`The commando command ${commandoCommand.getCommandPrefix()} being registered already exists!`,
);
} }
this.commandoCommands.push(commandoCommand);
} }
public getChatBot(): IUserDialogInfo public getChatBot(): IUserDialogInfo
{ {
return { return {
_id: "sptCommando", _id: "sptCommando",
info: { info: { Level: 1, MemberCategory: MemberCategory.DEVELOPER, Nickname: "Commando", Side: "Usec" },
Level: 1,
MemberCategory: MemberCategory.DEVELOPER,
Nickname: "Commando",
Side: "Usec",
},
}; };
} }
@ -61,28 +48,27 @@ export class CommandoDialogueChatBot implements IDialogueChatBot
const splitCommand = request.text.split(" "); const splitCommand = request.text.split(" ");
if (this.registeredCommands.has(splitCommand[0]) && this.registeredCommands.get(splitCommand[0]).has(splitCommand[1])) const commandos = this.commandoCommands.filter((c) => c.getCommandPrefix() === splitCommand[0]);
return this.registeredCommands.get(splitCommand[0]).get(splitCommand[1]).handle(this.getChatBot(), sessionId, request); if (commandos[0]?.getCommands().has(splitCommand[1]))
{
return commandos[0].handle(splitCommand[1], this.getChatBot(), sessionId, request);
}
if (splitCommand[0].toLowerCase() === "help") if (splitCommand[0].toLowerCase() === "help")
{ {
const helpMessage = this.commandoCommands.filter(c => this.registeredCommands.has(c.getCommandPrefix())) const helpMessage = this.commandoCommands.map((c) =>
.filter(c => Array.from(c.getCommands()).some(com => this.registeredCommands.get(c.getCommandPrefix()).has(com))) `Help for ${c.getCommandPrefix()}:\n${
.map(c => `Help for ${c.getCommandPrefix()}:\n${Array.from(c.getCommands()).map(command => c.getCommandHelp(command)).join("\n")}`) Array.from(c.getCommands()).map((command) => c.getCommandHelp(command)).join("\n")
.join("\n"); }`
this.mailSendService.sendUserMessageToPlayer( ).join("\n");
sessionId, this.mailSendService.sendUserMessageToPlayer(sessionId, this.getChatBot(), helpMessage);
this.getChatBot(),
helpMessage
);
return request.dialogId; return request.dialogId;
} }
this.mailSendService.sendUserMessageToPlayer( this.mailSendService.sendUserMessageToPlayer(
sessionId, sessionId,
this.getChatBot(), this.getChatBot(),
`Im sorry soldier, I dont recognize the command you are trying to use! Type "help" to see available commands.` `Im sorry soldier, I dont recognize the command you are trying to use! Type "help" to see available commands.`,
); );
} }
} }

View File

@ -32,4 +32,17 @@ export interface IServerFeatures
/* Controls whether or not the server attempts to download mod dependencies not included in the server's executable */ /* Controls whether or not the server attempts to download mod dependencies not included in the server's executable */
autoInstallModDependencies: boolean; autoInstallModDependencies: boolean;
compressProfile: boolean; compressProfile: boolean;
chatbotFeatures: IChatbotFeatures;
}
export interface IChatbotFeatures
{
sptFriendEnabled: boolean;
commandoEnabled: boolean;
commandoFeatures: ICommandoFeatures;
}
export interface ICommandoFeatures
{
giveCommandEnabled: boolean;
} }