using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace NHSE.Core
{
///
/// Logic for retrieving details based off input strings.
///
public static class ItemParser
{
///
/// Invert the recipe dictionary so we can look up recipe IDs from an input item ID.
///
public static readonly IReadOnlyDictionary InvertedRecipeDictionary =
RecipeList.Recipes.ToDictionary(z => z.Value, z => z.Key);
// Users can put spaces between item codes, or newlines. Recognize both!
private static readonly string[] SplittersHex = {" ", "\n", "\r\n"};
private static readonly string[] SplittersName = {",", "\n", "\r\n"};
///
/// Gets a list of items from the requested hex string(s).
///
///
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of item IDs.
///
/// 8 byte hex item values (u64 format)
/// Options for packaging items
public static IReadOnlyCollection- GetItemsFromUserInput(string requestHex, IConfigItem cfg)
{
try
{
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetItemsHexCode(split, cfg);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetItemsLanguage(split, cfg, GameLanguage.DefaultLanguage);
}
}
///
/// Gets a list of DIY item cards from the requested list of DIY IDs.
///
///
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of DIY IDs.
///
/// 8 byte hex item values (u64 format)
public static IReadOnlyCollection
- GetDIYsFromUserInput(string requestHex)
{
try
{
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsHexCode(split);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
#pragma warning restore CA1031 // Do not catch general exception types
{
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsLanguage(split);
}
}
///
/// Gets a list of items from the requested list of DIY hex code strings.
///
///
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
///
/// List of recipe IDs as u16 hex
public static IReadOnlyCollection
- GetDIYItemsHexCode(IReadOnlyList split)
{
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
bool parse = ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var value);
if (!parse)
throw new Exception($"Item value out of expected range ({text}).");
if (!RecipeList.Recipes.TryGetValue((ushort)value, out _))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = (ushort)value };
}
return result;
}
///
/// Gets a list of DIY item cards from the requested list of item name strings.
///
///
/// If a item name parse fails or a recipe ID does not exist, exceptions will be thrown.
///
/// List of item names
/// Language code to parse with. If the first entry in is a language code, it will be used instead of .
public static IReadOnlyCollection
- GetDIYItemsLanguage(IReadOnlyList split, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = GetItem(text, lang);
if (!InvertedRecipeDictionary.TryGetValue(item.ItemId, out var diy))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = diy };
}
return result;
}
///
/// Gets a list of items from the requested list of item name strings.
///
///
/// If a item name parse fails or the item ID does not exist as a known item, exceptions will be thrown.
///
/// List of item names
/// Item packaging options
/// Language code to parse with. If the first entry in is a language code, it will be used instead of .
public static IReadOnlyCollection
- GetItemsLanguage(IReadOnlyList split, IConfigItem config, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = CreateItem(text, i, config, lang);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
///
/// Gets a list of items from the requested list of item hex code strings.
///
///
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
///
/// List of recipe IDs as u16 hex
/// Item packaging options
public static IReadOnlyCollection
- GetItemsHexCode(IReadOnlyList split, IConfigItem config)
{
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var convert = GetBytesFromString(text);
var item = CreateItem(convert, i, config);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
private static byte[] GetBytesFromString(string text)
{
if (!ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE.ToBytes();
return BitConverter.GetBytes(value);
}
private static Item CreateItem(string name, int requestIndex, IConfigItem config, string lang = "en")
{
var item = GetItem(name, lang);
if (item.IsNone)
throw new Exception($"Failed to convert item (index {requestIndex}: {name}) for Language {lang}.");
if (!ItemInfo.IsSaneItemForDrop(item))
throw new Exception($"Unsupported item: (index {requestIndex}: {name}).");
if (config.WrapAllItems && item.ShouldWrapItem())
item.SetWrapping(ItemWrapping.WrappingPaper, config.WrappingPaper, true);
return item;
}
private static Item CreateItem(byte[] convert, int requestIndex, IConfigItem config)
{
Item item;
try
{
item = convert.ToClass
- ();
}
catch (Exception ex)
{
throw new Exception($"Failed to convert item (index {requestIndex}: {ex.Message}).");
}
if (!ItemInfo.IsSaneItemForDrop(item) || convert.Length != Item.SIZE)
throw new Exception($"Unsupported item: (index {requestIndex}).");
if (config.WrapAllItems && item.ShouldWrapItem())
item.SetWrapping(ItemWrapping.WrappingPaper, config.WrappingPaper, true);
return item;
}
private static readonly CompareInfo Comparer = CultureInfo.InvariantCulture.CompareInfo;
private const CompareOptions opt = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth;
///
/// Gets the first item name-value that contains the (case insensitive).
///
/// Requested Item
/// Game strings language to fetch with
/// Returns if no match found.
public static Item GetItem(string itemName, string lang = "en")
{
var strings = GameInfo.GetStrings(lang).ItemDataSource;
return GetItem(itemName, strings);
}
///
/// Gets the first item name-value that contains the (case insensitive).
///
/// Requested Item
/// Game strings
/// Returns if no match found.
public static Item GetItem(string itemName, IEnumerable strings)
{
if (TryGetItem(itemName, strings, out var id))
return new Item(id);
return Item.NO_ITEM;
}
///
/// Gets the first item name-value that contains the (case insensitive).
///
/// Requested Item
/// List of item name-values
/// Item ID, if found. Otherwise, 0
/// True if found, false if none.
public static bool TryGetItem(string itemName, IEnumerable strings, out ushort value)
{
foreach (var item in strings)
{
var result = Comparer.Compare(item.Text, 0, itemName, 0, opt);
if (result != 0)
continue;
value = (ushort)item.Value;
return true;
}
value = Item.NONE;
return false;
}
///
/// Gets an enumerable list of item key-value pairs that contain (case insensitive) the requested .
///
/// Item name
/// Item names (and their Item ID values)
public static IEnumerable GetItemsMatching(string itemName, IReadOnlyList strings)
{
foreach (var item in strings)
{
var result = Comparer.IndexOf(item.Text, itemName, opt);
if (result < 0)
continue;
yield return item;
}
}
///
/// Gets an enumerable list of item key-value pairs that contain (case insensitive) the requested .
///
///
/// Orders the items based on the closest match ().
///
/// Item name
/// Item names (and their Item ID values)
public static IEnumerable GetItemsMatchingOrdered(string itemName, IReadOnlyList strings)
{
var matches = GetItemsMatching(itemName, strings);
return GetItemsClosestOrdered(itemName, matches);
}
///
/// Gets an enumerable list of item key-value pairs ordered by the closest for the requested .
///
/// Item name
/// Item names (and their Item ID values)
public static IEnumerable GetItemsClosestOrdered(string itemName, IEnumerable strings)
{
return strings.OrderBy(z => LevenshteinDistance.Compute(z.Text, itemName));
}
///
/// Gets the Item Name and raw 8-byte value as a string.
///
/// Item value
public static string GetItemText(Item item)
{
var value = BitConverter.ToUInt64(item.ToBytesClass(), 0);
var name = GameInfo.Strings.GetItemName(item.ItemId);
return $"{name}: {value:X16}";
}
///
/// Gets the u16 item ID from the input hex code.
///
/// Hex code for the item (preferably 4 digits)
public static ushort GetID(string text)
{
if (!ulong.TryParse(text.Trim(), NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE;
return (ushort)value;
}
}
}