Fixed give command to use dice coefficient making it more precise. Fixed give command giving invalid items to players. (!323)
Co-authored-by: clodan <clodan@clodan.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/323 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
79a5d32cb2
commit
84d5462955
@ -33,7 +33,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"atomically": "~1.7",
|
"atomically": "~1.7",
|
||||||
"buffer-crc32": "^1.0.0",
|
"buffer-crc32": "^1.0.0",
|
||||||
"closest-match": "~1.3",
|
|
||||||
"date-fns": "~2.30",
|
"date-fns": "~2.30",
|
||||||
"date-fns-tz": "~2.0",
|
"date-fns-tz": "~2.0",
|
||||||
"i18n": "~0.15",
|
"i18n": "~0.15",
|
||||||
@ -44,6 +43,7 @@
|
|||||||
"reflect-metadata": "~0.2",
|
"reflect-metadata": "~0.2",
|
||||||
"semver": "~7.6",
|
"semver": "~7.6",
|
||||||
"source-map-support": "~0.5",
|
"source-map-support": "~0.5",
|
||||||
|
"string-similarity-js": "~2.1",
|
||||||
"tsyringe": "~4.8",
|
"tsyringe": "~4.8",
|
||||||
"typescript": "~5.4",
|
"typescript": "~5.4",
|
||||||
"winston": "~3.12",
|
"winston": "~3.12",
|
||||||
|
@ -3,6 +3,7 @@ import { ISptCommand } from "@spt-aki/helpers/Dialogue/Commando/SptCommands/ISpt
|
|||||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||||
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
||||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||||
|
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||||
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 { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
||||||
@ -13,7 +14,7 @@ import { LocaleService } from "@spt-aki/services/LocaleService";
|
|||||||
import { MailSendService } from "@spt-aki/services/MailSendService";
|
import { MailSendService } from "@spt-aki/services/MailSendService";
|
||||||
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
||||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||||
import { closestMatch, distance } from "closest-match";
|
import { stringSimilarity } from "string-similarity-js";
|
||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -28,7 +29,7 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
* spt give 5 <== this is the reply when the algo isn't sure about an item
|
* spt give 5 <== this is the reply when the algo isn't sure about an item
|
||||||
*/
|
*/
|
||||||
private static commandRegex = /^spt give (((([a-z]{2,5}) )?"(.+)"|\w+) )?([0-9]+)$/;
|
private static commandRegex = /^spt give (((([a-z]{2,5}) )?"(.+)"|\w+) )?([0-9]+)$/;
|
||||||
private static maxAllowedDistance = 1.5;
|
private static acceptableConfidence = 0.9;
|
||||||
|
|
||||||
protected savedCommand: Map<string, SavedCommand> = new Map<string, SavedCommand>();
|
protected savedCommand: Map<string, SavedCommand> = new Map<string, SavedCommand>();
|
||||||
|
|
||||||
@ -125,7 +126,9 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
|
|
||||||
if (isItemName)
|
if (isItemName)
|
||||||
{
|
{
|
||||||
locale = result[4] ? result[4] : this.localeService.getDesiredGameLocale();
|
try
|
||||||
|
{
|
||||||
|
locale = result[4] ? result[4] : (this.localeService.getDesiredGameLocale() ?? "en");
|
||||||
if (!this.localeService.getServerSupportedLocales().includes(locale))
|
if (!this.localeService.getServerSupportedLocales().includes(locale))
|
||||||
{
|
{
|
||||||
this.mailSendService.sendUserMessageToPlayer(
|
this.mailSendService.sendUserMessageToPlayer(
|
||||||
@ -135,34 +138,39 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
);
|
);
|
||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const localizedGlobal = this.databaseServer.getTables().locales.global[locale];
|
catch (e)
|
||||||
|
|
||||||
const closestItemsMatchedByName = closestMatch(
|
|
||||||
item.toLowerCase(),
|
|
||||||
this.itemHelper.getItems().filter((i) => i._type !== "Node").filter((i) =>
|
|
||||||
!this.itemFilterService.isItemBlacklisted(i._id)
|
|
||||||
).map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase()).filter((i) => i !== undefined),
|
|
||||||
true,
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
if (closestItemsMatchedByName === undefined || closestItemsMatchedByName.length === 0)
|
|
||||||
{
|
{
|
||||||
this.mailSendService.sendUserMessageToPlayer(
|
this.mailSendService.sendUserMessageToPlayer(
|
||||||
sessionId,
|
sessionId,
|
||||||
commandHandler,
|
commandHandler,
|
||||||
"That item could not be found. Please refine your request and try again.",
|
`An error occurred while trying to use localized text. Locale will be defaulted to 'en'.`,
|
||||||
);
|
);
|
||||||
return request.dialogId;
|
this.logger.error(e);
|
||||||
|
locale = "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closestItemsMatchedByName.length > 1)
|
const localizedGlobal = this.databaseServer.getTables().locales.global[locale];
|
||||||
|
|
||||||
|
const closestItemsMatchedByName = this.itemHelper.getItems()
|
||||||
|
.filter((i) => this.isItemAllowed(i))
|
||||||
|
.map((i) => localizedGlobal[`${i?._id} Name`]?.toLowerCase())
|
||||||
|
.filter((i) => i !== undefined)
|
||||||
|
.map(i => ({match: stringSimilarity(item.toLocaleLowerCase(), i.toLocaleLowerCase()), itemName: i}))
|
||||||
|
.sort((a1, a2) => a2.match - a1.match);
|
||||||
|
|
||||||
|
if (closestItemsMatchedByName[0].match >= GiveSptCommand.acceptableConfidence)
|
||||||
|
{
|
||||||
|
item = closestItemsMatchedByName[0].itemName;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
let i = 1;
|
let i = 1;
|
||||||
const slicedItems = closestItemsMatchedByName.slice(0, 10);
|
const slicedItems = closestItemsMatchedByName.slice(0, 10);
|
||||||
// max 10 item names and map them
|
// max 10 item names and map them
|
||||||
const itemList = slicedItems.map((itemName) => `${i++}. ${itemName}`).join("\n");
|
const itemList = slicedItems.map((match) => `${i++}. ${match.itemName} (conf: ${(match.match * 100).toFixed(2)})`)
|
||||||
this.savedCommand.set(sessionId, new SavedCommand(quantity, slicedItems, locale));
|
.join("\n");
|
||||||
|
this.savedCommand.set(sessionId, new SavedCommand(quantity, slicedItems.map(i => i.itemName), locale));
|
||||||
this.mailSendService.sendUserMessageToPlayer(
|
this.mailSendService.sendUserMessageToPlayer(
|
||||||
sessionId,
|
sessionId,
|
||||||
commandHandler,
|
commandHandler,
|
||||||
@ -170,30 +178,15 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
);
|
);
|
||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dist = distance(item, closestItemsMatchedByName[0]);
|
|
||||||
if (dist > GiveSptCommand.maxAllowedDistance)
|
|
||||||
{
|
|
||||||
this.mailSendService.sendUserMessageToPlayer(
|
|
||||||
sessionId,
|
|
||||||
commandHandler,
|
|
||||||
`Found a possible match for "${item}" but uncertain. Match: "${
|
|
||||||
closestItemsMatchedByName[0]
|
|
||||||
}". Please refine your request and try again.`,
|
|
||||||
);
|
|
||||||
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
|
// 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 tplId.
|
// item is just the tplId.
|
||||||
const tplId = isItemName
|
const tplId = isItemName
|
||||||
? this.itemHelper.getItems().filter((i) => !this.itemFilterService.isItemBlacklisted(i._id)).find((i) =>
|
? this.itemHelper.getItems()
|
||||||
this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item
|
.filter((i) => this.isItemAllowed(i))
|
||||||
)._id
|
.find((i) => this.databaseServer.getTables().locales.global[locale][`${i?._id} Name`]?.toLowerCase() === item)._id
|
||||||
: item;
|
: item;
|
||||||
|
|
||||||
const checkedItem = this.itemHelper.getItem(tplId);
|
const checkedItem = this.itemHelper.getItem(tplId);
|
||||||
@ -285,4 +278,21 @@ export class GiveSptCommand implements ISptCommand
|
|||||||
this.mailSendService.sendSystemMessageToPlayer(sessionId, "SPT GIVE", itemsToSend);
|
this.mailSendService.sendSystemMessageToPlayer(sessionId, "SPT GIVE", itemsToSend);
|
||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "simple" function that checks if an item is supposed to be given to a player or not
|
||||||
|
* @param templateItem the template item to check
|
||||||
|
* @returns true if its obtainable, false if its not
|
||||||
|
*/
|
||||||
|
protected isItemAllowed(templateItem: ITemplateItem): boolean
|
||||||
|
{
|
||||||
|
return templateItem._type !== "Node" &&
|
||||||
|
!this.itemHelper.isQuestItem(templateItem._id) &&
|
||||||
|
!this.itemFilterService.isItemBlacklisted(templateItem._id) &&
|
||||||
|
(templateItem._props?.Prefab?.path ?? "") !== "" &&
|
||||||
|
!this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.HIDEOUT_AREA_CONTAINER) &&
|
||||||
|
!this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.LOOT_CONTAINER) &&
|
||||||
|
!this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.RANDOM_LOOT_CONTAINER) &&
|
||||||
|
!this.itemHelper.isOfBaseclass(templateItem._id, BaseClasses.MOB_CONTAINER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,4 +111,5 @@ export enum BaseClasses
|
|||||||
BARREL = "555ef6e44bdc2de9068b457e",
|
BARREL = "555ef6e44bdc2de9068b457e",
|
||||||
CHARGING_HANDLE = "55818a6f4bdc2db9688b456b",
|
CHARGING_HANDLE = "55818a6f4bdc2db9688b456b",
|
||||||
COMB_MUZZLE_DEVICE = "550aa4dd4bdc2dc9348b4569 ",
|
COMB_MUZZLE_DEVICE = "550aa4dd4bdc2dc9348b4569 ",
|
||||||
|
HIDEOUT_AREA_CONTAINER = "63da6da4784a55176c018dba"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user