Expanded give command logic (!182)
* Added give by name * Refactored Commando so its abstracted, that way modders can use it too! :) Co-authored-by: clodan <clodan@clodan.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/182 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:
parent
2e993c2687
commit
9846adc68b
@ -27,6 +27,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"atomically": "1.7.0",
|
||||
"closest-match": "1.3.3",
|
||||
"i18n": "0.15.1",
|
||||
"json-fixer": "1.6.15",
|
||||
"json5": "2.2.3",
|
||||
|
@ -591,7 +591,7 @@ export class Container
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
});
|
||||
// SptCommands
|
||||
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand);
|
||||
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand, { lifecycle: Lifecycle.Singleton });
|
||||
}
|
||||
|
||||
private static registerLoaders(depContainer: DependencyContainer): void
|
||||
|
75
project/src/helpers/Dialogue/AbstractDialogueChatBot.ts
Normal file
75
project/src/helpers/Dialogue/AbstractDialogueChatBot.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { IChatCommand, ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
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 { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||
|
||||
export abstract class AbstractDialogueChatBot implements IDialogueChatBot
|
||||
{
|
||||
public constructor(
|
||||
protected logger: ILogger,
|
||||
protected mailSendService: MailSendService,
|
||||
// We are keeping the alias for a few versions so modders can update in case they were using them
|
||||
protected chatCommands: IChatCommand[] | ICommandoCommand[],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use registerChatCommand instead
|
||||
*/
|
||||
public registerCommandoCommand(chatCommand: IChatCommand | ICommandoCommand): void
|
||||
{
|
||||
this.registerChatCommand(chatCommand);
|
||||
}
|
||||
|
||||
public registerChatCommand(chatCommand: IChatCommand | ICommandoCommand): void
|
||||
{
|
||||
if (this.chatCommands.some((cc) => cc.getCommandPrefix() === chatCommand.getCommandPrefix()))
|
||||
{
|
||||
throw new Error(
|
||||
`The commando command ${chatCommand.getCommandPrefix()} being registered already exists!`,
|
||||
);
|
||||
}
|
||||
this.chatCommands.push(chatCommand);
|
||||
}
|
||||
|
||||
public abstract getChatBot(): IUserDialogInfo;
|
||||
|
||||
protected abstract getUnrecognizedCommandMessage(): string;
|
||||
|
||||
public handleMessage(sessionId: string, request: ISendMessageRequest): string
|
||||
{
|
||||
if ((request.text ?? "").length === 0)
|
||||
{
|
||||
this.logger.error("Command came in as empty text! Invalid data!");
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const splitCommand = request.text.split(" ");
|
||||
|
||||
const commandos = this.chatCommands.filter((c) => c.getCommandPrefix() === splitCommand[0]);
|
||||
if (commandos[0]?.getCommands().has(splitCommand[1]))
|
||||
{
|
||||
return commandos[0].handle(splitCommand[1], this.getChatBot(), sessionId, request);
|
||||
}
|
||||
|
||||
if (splitCommand[0].toLowerCase() === "help")
|
||||
{
|
||||
const helpMessage = this.chatCommands.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(),
|
||||
this.getUnrecognizedCommandMessage(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
|
||||
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
|
||||
|
||||
export interface ICommandoCommand
|
||||
/**
|
||||
* @deprecated Use IChatCommand instead
|
||||
*/
|
||||
export type ICommandoCommand = IChatCommand;
|
||||
export interface IChatCommand
|
||||
{
|
||||
getCommandPrefix(): string;
|
||||
getCommandHelp(command: string): string;
|
@ -1,4 +1,4 @@
|
||||
import { ICommandoCommand } from "@spt-aki/helpers/Dialogue/Commando/ICommandoCommand";
|
||||
import { IChatCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
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";
|
||||
@ -8,7 +8,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
@injectable()
|
||||
export class SptCommandoCommands implements ICommandoCommand
|
||||
export class SptCommandoCommands implements IChatCommand
|
||||
{
|
||||
constructor(
|
||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||
|
@ -10,10 +10,27 @@ 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";
|
||||
import { LocaleService } from "@spt-aki/services/LocaleService";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { closestMatch, distance } from "closest-match";
|
||||
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/SavedCommand";
|
||||
|
||||
@injectable()
|
||||
export class GiveSptCommand implements ISptCommand
|
||||
{
|
||||
/**
|
||||
* Regex to account for all these cases:
|
||||
* spt give "item name" 5
|
||||
* spt give templateId 5
|
||||
* spt give en "item name in english" 5
|
||||
* spt give es "nombre en español" 5
|
||||
* spt give 5 <== this is the reply when the algo isnt sure about an item
|
||||
*/
|
||||
private static commandRegex = /^spt give (((([a-z]{2,5}) )?"(.+)"|\w+) )?([0-9]+)$/;
|
||||
private static maxAllowedDistance = 1.5;
|
||||
|
||||
protected savedCommand: SavedCommand;
|
||||
|
||||
public constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||
@ -21,6 +38,8 @@ export class GiveSptCommand implements ISptCommand
|
||||
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
||||
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@inject("LocaleService") protected localeService: LocaleService,
|
||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||
)
|
||||
{
|
||||
}
|
||||
@ -32,49 +51,129 @@ export class GiveSptCommand implements ISptCommand
|
||||
|
||||
public getCommandHelp(): string
|
||||
{
|
||||
return "Usage: spt give tplId quantity";
|
||||
return "Usage:\n\t- spt give tplId quantity\n\t- spt give locale \"item name\" quantity\n\t- spt give \"item name\" quantity\nIf using name, must be as seen in the wiki.";
|
||||
}
|
||||
|
||||
public performAction(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])
|
||||
if (!GiveSptCommand.commandRegex.test(request.text))
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Template ID is missing. Use \"Help\" for more info",
|
||||
"Invalid use of give command! Use \"Help\" for more info",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
const tplId = giveCommand[2];
|
||||
|
||||
if (!giveCommand[3])
|
||||
const result = GiveSptCommand.commandRegex.exec(request.text);
|
||||
|
||||
let item: string;
|
||||
let quantity: number;
|
||||
let isItemName: boolean;
|
||||
let locale: string;
|
||||
// this is a reply to a give request previously made pending a reply
|
||||
if (result[1] === undefined)
|
||||
{
|
||||
if (this.savedCommand === undefined)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Quantity is missing. Use \"Help\" for more info",
|
||||
"Invalid use of give command! Use \"Help\" for more info",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
const quantity = giveCommand[3];
|
||||
|
||||
if (Number.isNaN(+quantity))
|
||||
if (+result[6] > this.savedCommand.potentialItemNames.length)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid use of give command! Quantity is not a valid integer. Use \"Help\" for more info",
|
||||
"Invalid item selected, outside of bounds! Use \"Help\" for more info",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
item = this.savedCommand.potentialItemNames[+result[6] - 1];
|
||||
quantity = this.savedCommand.quantity;
|
||||
locale = this.savedCommand.locale;
|
||||
isItemName = true;
|
||||
this.savedCommand = undefined;
|
||||
}
|
||||
else
|
||||
{
|
||||
// a new give request was entered, we need to ignore the old saved command
|
||||
this.savedCommand = undefined;
|
||||
isItemName = result[5] !== undefined;
|
||||
item = result[5] ? result[5] : result[2];
|
||||
quantity = +result[6];
|
||||
|
||||
if (isItemName)
|
||||
{
|
||||
locale = result[4] ? result[4] : this.localeService.getDesiredGameLocale();
|
||||
if (!this.localeService.getServerSupportedLocales().includes(locale))
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`Invalid use of give command! Unknown locale "${locale}". Use "Help" for more info`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
const localizedGlobal = this.databaseServer.getTables().locales.global[locale];
|
||||
|
||||
const closestItemsMatchedByName = closestMatch(item.toLowerCase(), this.itemHelper.getItems()
|
||||
.filter(i => i._type !== "Node")
|
||||
.map(i => localizedGlobal[`${i?._id} Name`]?.toLowerCase())
|
||||
.filter(i => i !== undefined), true) as string[];
|
||||
|
||||
if (closestItemsMatchedByName === undefined || closestItemsMatchedByName.length === 0)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"We couldnt find any items that are similar to what you entered.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
if (closestItemsMatchedByName.length > 1)
|
||||
{
|
||||
let i = 1;
|
||||
const slicedItems = closestItemsMatchedByName.slice(0, 10);
|
||||
// max 10 item names and map them
|
||||
const itemList = slicedItems.map(iname => `\t${i++}. ${iname}`).join("\n");
|
||||
this.savedCommand = new SavedCommand(quantity, slicedItems, locale);
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`We couldnt find the exact name match you were looking for. The closest matches are:\n${itemList}\nType in "spt give number" to indicate which one you want.`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
else
|
||||
{
|
||||
const dist = distance(item, closestItemsMatchedByName[0]);
|
||||
if (dist > GiveSptCommand.maxAllowedDistance)
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
`There was only one match for your item search of "${item}" but its outside the acceptable bounds: ${closestItemsMatchedByName[0]}`,
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
// only one available so we get that entry and use it
|
||||
item = closestItemsMatchedByName[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if item is an item name, we need to search using that item name and the locale which one we want
|
||||
// otherwise item is just the tpl id
|
||||
const tplId = isItemName ? this.itemHelper.getItems()
|
||||
.find(i => this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item)
|
||||
._id
|
||||
: item;
|
||||
|
||||
const checkedItem = this.itemHelper.getItem(tplId);
|
||||
if (!checkedItem[0])
|
||||
@ -82,7 +181,7 @@ export class GiveSptCommand implements ISptCommand
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"Invalid template ID requested for give command. The item doesn't exist in the DB.",
|
||||
"Invalid template ID requested for give command. The item doesnt exists on the DB.",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
@ -100,14 +199,7 @@ export class GiveSptCommand implements ISptCommand
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
|
||||
for (let i = 0; i < +quantity; i++)
|
||||
{
|
||||
// Make sure IDs are unique before adding to array - prevent collisions
|
||||
const presetToSend = this.itemHelper.replaceIDs(null, this.jsonUtil.clone(preset._items));
|
||||
itemsToSend.push(... presetToSend);
|
||||
}
|
||||
|
||||
itemsToSend.push(...this.jsonUtil.clone(preset._items));
|
||||
}
|
||||
else if (this.itemHelper.isOfBaseclass(checkedItem[1]._id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
@ -124,13 +216,22 @@ export class GiveSptCommand implements ISptCommand
|
||||
const item: Item = {
|
||||
_id: this.hashUtil.generate(),
|
||||
_tpl: checkedItem[1]._id,
|
||||
upd: {
|
||||
StackObjectsCount: +quantity,
|
||||
SpawnedInSession: true
|
||||
},
|
||||
upd: { StackObjectsCount: +quantity },
|
||||
};
|
||||
try
|
||||
{
|
||||
itemsToSend.push(...this.itemHelper.splitStack(item));
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.mailSendService.sendUserMessageToPlayer(
|
||||
sessionId,
|
||||
commandHandler,
|
||||
"The amount of items you requested to be given caused an error, try using a smaller amount!",
|
||||
);
|
||||
return request.dialogId;
|
||||
}
|
||||
}
|
||||
|
||||
this.mailSendService.sendSystemMessageToPlayer(sessionId, "Give command!", itemsToSend);
|
||||
return request.dialogId;
|
||||
|
@ -0,0 +1,6 @@
|
||||
export class SavedCommand
|
||||
{
|
||||
public constructor(public quantity: number, public potentialItemNames: string[], public locale: string)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,33 +1,22 @@
|
||||
import { inject, injectAll, injectable } from "tsyringe";
|
||||
|
||||
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 { IChatCommand } from "@spt-aki/helpers/Dialogue/Commando/IChatCommand";
|
||||
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";
|
||||
import { AbstractDialogueChatBot } from "@spt-aki/helpers/Dialogue/AbstractDialogueChatBot";
|
||||
|
||||
@injectable()
|
||||
export class CommandoDialogueChatBot implements IDialogueChatBot
|
||||
export class CommandoDialogueChatBot extends AbstractDialogueChatBot
|
||||
{
|
||||
public constructor(
|
||||
@inject("WinstonLogger") protected logger: ILogger,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@injectAll("CommandoCommand") protected commandoCommands: ICommandoCommand[],
|
||||
@inject("WinstonLogger") logger: ILogger,
|
||||
@inject("MailSendService") mailSendService: MailSendService,
|
||||
@injectAll("CommandoCommand") chatCommands: IChatCommand[],
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public registerCommandoCommand(commandoCommand: ICommandoCommand): void
|
||||
{
|
||||
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);
|
||||
super(logger, mailSendService, chatCommands);
|
||||
}
|
||||
|
||||
public getChatBot(): IUserDialogInfo
|
||||
@ -38,37 +27,8 @@ export class CommandoDialogueChatBot implements IDialogueChatBot
|
||||
};
|
||||
}
|
||||
|
||||
public handleMessage(sessionId: string, request: ISendMessageRequest): string
|
||||
protected getUnrecognizedCommandMessage(): 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(" ");
|
||||
|
||||
const commandos = this.commandoCommands.filter((c) => c.getCommandPrefix() === splitCommand[0]);
|
||||
if (commandos[0]?.getCommands().has(splitCommand[1]))
|
||||
{
|
||||
return commandos[0].handle(splitCommand[1], this.getChatBot(), sessionId, request);
|
||||
}
|
||||
|
||||
if (splitCommand[0].toLowerCase() === "help")
|
||||
{
|
||||
const helpMessage = this.commandoCommands.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.`,
|
||||
);
|
||||
return `Im sorry soldier, I dont recognize the command you are trying to use! Type "help" to see available commands.`;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user