Added profile and trader commands to commando (!309)

Added new spt commands for commando to alter profile levels and skill, as well as trader rep and money spent.

Co-authored-by: clodan <clodan@clodan.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/309
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 2024-04-27 16:56:28 +00:00 committed by chomp
parent d7a8e1558a
commit a1e48ca245
8 changed files with 348 additions and 14 deletions

View File

@ -90,7 +90,9 @@ import { BotHelper } from "@spt-aki/helpers/BotHelper";
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
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 { GiveSptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand";
import { ProfileSptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ProfileCommand/ProfileSptCommand";
import { TraderSptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/TraderCommand/TraderSptCommand";
import { CommandoDialogueChatBot } from "@spt-aki/helpers/Dialogue/CommandoDialogueChatBot";
import { SptDialogueChatBot } from "@spt-aki/helpers/Dialogue/SptDialogueChatBot";
import { DialogueHelper } from "@spt-aki/helpers/DialogueHelper";
@ -375,6 +377,8 @@ export class Container
// SptCommando Commands
depContainer.registerType("SptCommand", "GiveSptCommand");
depContainer.registerType("SptCommand", "TraderSptCommand");
depContainer.registerType("SptCommand", "ProfileSptCommand");
}
private static registerUtils(depContainer: DependencyContainer): void
@ -598,6 +602,12 @@ export class Container
});
// SptCommands
depContainer.register<GiveSptCommand>("GiveSptCommand", GiveSptCommand, { lifecycle: Lifecycle.Singleton });
depContainer.register<TraderSptCommand>("TraderSptCommand", TraderSptCommand, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<ProfileSptCommand>("ProfileSptCommand", ProfileSptCommand, {
lifecycle: Lifecycle.Singleton,
});
}
private static registerLoaders(depContainer: DependencyContainer): void

View File

@ -57,12 +57,34 @@ export abstract class AbstractDialogueChatBot implements IDialogueChatBot
if (splitCommand[0].toLowerCase() === "help")
{
const helpMessage = this.chatCommands.map((c) =>
`Available commands:\n\n${c.getCommandPrefix()}:\n\n${
Array.from(c.getCommands()).map((command) => c.getCommandHelp(command)).join("\n")
}`
).join("\n");
this.mailSendService.sendUserMessageToPlayer(sessionId, this.getChatBot(), helpMessage);
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
"The available commands will be listed below:",
);
// due to BSG being dumb with messages we need a mandatory timeout between messages so they get out on the right order
setTimeout(() =>
{
for (const chatCommand of this.chatCommands)
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
`Commands available for "${chatCommand.getCommandPrefix()}" prefix:`,
);
setTimeout(() =>
{
for (const subCommand of chatCommand.getCommands())
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
this.getChatBot(),
`Subcommand ${subCommand}:\n${chatCommand.getCommandHelp(subCommand)}`,
);
}
}, 1000);
}
}, 1000);
return request.dialogId;
}

View File

@ -1,5 +1,5 @@
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveCommand/SavedCommand";
import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISptCommand";
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/SavedCommand";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
@ -107,6 +107,15 @@ export class GiveSptCommand implements ISptCommand
isItemName = result[5] !== undefined;
item = result[5] ? result[5] : result[2];
quantity = +result[6];
if (quantity <= 0)
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
`Invalid quantity! Must be 1 or higher. Use \"help\" for more information.`,
);
return request.dialogId;
}
if (isItemName)
{

View File

@ -0,0 +1,163 @@
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveCommand/SavedCommand";
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 { 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 { SkillTypes } from "@spt-aki/models/enums/SkillTypes";
import { IProfileChangeEvent, ProfileChangeEventType } from "@spt-aki/models/spt/dialog/ISendMessageDetails";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocaleService } from "@spt-aki/services/LocaleService";
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 ProfileSptCommand implements ISptCommand
{
/**
* Regex to account for all these cases:
* spt profile level 20
* spt profile skill metabolism 10
*/
private static commandRegex =
/^spt profile (?<command>level|skill)((?<=.*skill) (?<skill>[\w]+)){0,1} (?<quantity>(?!0+)[0-9]+)$/;
protected savedCommand: SavedCommand;
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,
@inject("LocaleService") protected localeService: LocaleService,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
)
{
}
public getCommand(): string
{
return "profile";
}
public getCommandHelp(): string
{
return "spt profile\n========\nSets the profile level or skill to the desired level through the message system.\n\n\tspt profile level [desired level]\n\t\tEx: spt profile level 20\n\n\tspt profile skill [skill name] [quantity]\n\t\tEx: spt profile skill metabolism 51";
}
public performAction(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string
{
if (!ProfileSptCommand.commandRegex.test(request.text))
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of trader command. Use \"help\" for more information.",
);
return request.dialogId;
}
const result = ProfileSptCommand.commandRegex.exec(request.text);
const command: string = result.groups.command;
const skill: string = result.groups.skill;
const quantity: number = +result.groups.quantity;
let event: IProfileChangeEvent;
switch (command)
{
case "level":
if (quantity < 1 || quantity > this.profileHelper.getMaxLevel())
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of profile command, the level was outside bounds: 1 to 70. Use \"help\" for more information.",
);
return request.dialogId;
}
event = this.handleLevelCommand(quantity);
break;
case "skill":
{
const enumSkill = Object.values(SkillTypes).find((t) =>
t.toLocaleLowerCase() === skill.toLocaleLowerCase()
);
if (enumSkill === undefined)
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of profile command, the skill was not found. Use \"help\" for more information.",
);
return request.dialogId;
}
if (quantity < 0 || quantity > 51)
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of profile command, the skill level was outside bounds: 1 to 51. Use \"help\" for more information.",
);
return request.dialogId;
}
event = this.handleSkillCommand(enumSkill, quantity);
break;
}
default:
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
`If you are reading this, this is bad. Please report this to SPT staff with a screenshot. Command ${command}.`,
);
return request.dialogId;
}
this.mailSendService.sendSystemMessageToPlayer(
sessionId,
"A single ruble is being attached, required by BSG logic.",
[{
_id: this.hashUtil.generate(),
_tpl: "5449016a4bdc2d6f028b456f",
upd: { StackObjectsCount: 1 },
parentId: this.hashUtil.generate(),
slotId: "main",
}],
undefined,
[event],
);
return request.dialogId;
}
protected handleSkillCommand(skill: string, level: number): IProfileChangeEvent
{
const event: IProfileChangeEvent = {
_id: this.hashUtil.generate(),
Type: ProfileChangeEventType.SKILL_POINTS,
value: level * 100,
entity: skill,
};
return event;
}
protected handleLevelCommand(level: number): IProfileChangeEvent
{
const exp = this.profileHelper.getExperience(level);
const event: IProfileChangeEvent = {
_id: this.hashUtil.generate(),
Type: ProfileChangeEventType.PROFILE_LEVEL,
value: exp,
entity: null,
};
return event;
}
}

View File

@ -0,0 +1,114 @@
import { SavedCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/GiveCommand/SavedCommand";
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 { ISendMessageRequest } from "@spt-aki/models/eft/dialog/ISendMessageRequest";
import { IUserDialogInfo } from "@spt-aki/models/eft/profile/IAkiProfile";
import { IProfileChangeEvent, ProfileChangeEventType } from "@spt-aki/models/spt/dialog/ISendMessageDetails";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocaleService } from "@spt-aki/services/LocaleService";
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 TraderSptCommand implements ISptCommand
{
/**
* Regex to account for all these cases:
* spt trader prapor rep 100
* spt trader mechanic spend 1000000
*/
private static commandRegex = /^spt trader (?<trader>[\w]+) (?<command>rep|spend) (?<quantity>(?!0+)[0-9]+)$/;
protected savedCommand: SavedCommand;
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,
@inject("LocaleService") protected localeService: LocaleService,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
)
{
}
public getCommand(): string
{
return "trader";
}
public getCommandHelp(): string
{
return "spt trader\n========\nSets the reputation or money spent to the input quantity through the message system.\n\n\tspt trader [trader] rep [quantity]\n\t\tEx: spt trader prapor rep 2\n\n\tspt trader [trader] spend [quantity]\n\t\tEx: spt trader therapist spend 1000000";
}
public performAction(commandHandler: IUserDialogInfo, sessionId: string, request: ISendMessageRequest): string
{
if (!TraderSptCommand.commandRegex.test(request.text))
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of trader command. Use \"help\" for more information.",
);
return request.dialogId;
}
const result = TraderSptCommand.commandRegex.exec(request.text);
const trader: string = result.groups.trader;
const command: string = result.groups.command;
const quantity: number = +result.groups.quantity;
const dbTrader = Object.values(this.databaseServer.getTables().traders).find((t) =>
t.base.nickname.toLocaleLowerCase() === trader.toLocaleLowerCase()
);
if (dbTrader === undefined)
{
this.mailSendService.sendUserMessageToPlayer(
sessionId,
commandHandler,
"Invalid use of trader command, the trader was not found. Use \"help\" for more information.",
);
return request.dialogId;
}
let profileChangeEventType: ProfileChangeEventType;
switch (command)
{
case "rep":
profileChangeEventType = ProfileChangeEventType.TRADER_STANDING;
break;
case "spend":
profileChangeEventType = ProfileChangeEventType.TRADER_SALES_SUM;
break;
}
const event: IProfileChangeEvent = {
_id: this.hashUtil.generate(),
Type: profileChangeEventType,
value: quantity,
entity: dbTrader.base._id,
};
this.mailSendService.sendSystemMessageToPlayer(
sessionId,
"A single ruble is being attached, required by BSG logic.",
[{
_id: this.hashUtil.generate(),
_tpl: "5449016a4bdc2d6f028b456f",
upd: { StackObjectsCount: 1 },
parentId: this.hashUtil.generate(),
slotId: "main",
}],
undefined,
[event],
);
return request.dialogId;
}
}

View File

@ -34,7 +34,17 @@ export interface ISendMessageDetails
export interface IProfileChangeEvent
{
_id: string;
Type: "TraderSalesSum" | "TraderStanding" | "ProfileLevel" | "SkillPoints" | "ExamineAllItems" | "UnlockTrader";
Type: ProfileChangeEventType;
value: number;
entity?: string;
}
export enum ProfileChangeEventType
{
TRADER_SALES_SUM = "TraderSalesSum",
TRADER_STANDING = "TraderStanding",
PROFILE_LEVEL = "ProfileLevel",
SKILL_POINTS = "SkillPoints",
EXAMINE_ALL_ITEMS = "ExamineAllItems",
UNLOCK_TRADER = "UnlockTrader",
}

View File

@ -10,7 +10,7 @@ import { Dialogue, IUserDialogInfo, Message, MessageItems } from "@spt-aki/model
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
import { MessageType } from "@spt-aki/models/enums/MessageType";
import { Traders } from "@spt-aki/models/enums/Traders";
import { ISendMessageDetails } from "@spt-aki/models/spt/dialog/ISendMessageDetails";
import { IProfileChangeEvent, ISendMessageDetails } from "@spt-aki/models/spt/dialog/ISendMessageDetails";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { SaveServer } from "@spt-aki/servers/SaveServer";
@ -169,7 +169,8 @@ export class MailSendService
sessionId: string,
message: string,
items: Item[] = [],
maxStorageTimeSeconds = null,
maxStorageTimeSeconds?: number,
profileChangeEvents?: IProfileChangeEvent[],
): void
{
const details: ISendMessageDetails = {
@ -185,6 +186,11 @@ export class MailSendService
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800; // 48 hours if no value supplied
}
if ((profileChangeEvents?.length ?? 0) > 0)
{
details.profileChangeEvents = profileChangeEvents;
}
this.sendMessageToPlayer(details);
}
@ -199,8 +205,8 @@ export class MailSendService
sessionId: string,
messageLocaleId: string,
items: Item[] = [],
profileChangeEvents = [],
maxStorageTimeSeconds = null,
profileChangeEvents?: IProfileChangeEvent[],
maxStorageTimeSeconds?: number,
): void
{
const details: ISendMessageDetails = {
@ -216,7 +222,7 @@ export class MailSendService
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800; // 48 hours if no value supplied
}
if (profileChangeEvents.length > 0)
if ((profileChangeEvents?.length ?? 0) > 0)
{
details.profileChangeEvents = profileChangeEvents;
}