diff --git a/NewWorldCompanion.Constants/EmguConstants.cs b/NewWorldCompanion.Constants/EmguConstants.cs
index aefe3ae..3a09279 100644
--- a/NewWorldCompanion.Constants/EmguConstants.cs
+++ b/NewWorldCompanion.Constants/EmguConstants.cs
@@ -7,8 +7,8 @@ public class EmguConstants
public const int Default1600900AreaUpper = 7000;
public const int Default19201080AreaLower = 6000;
public const int Default19201080AreaUpper = 8000;
- public const int Default25601440AreaLower = 10000;
- public const int Default25601440AreaUpper = 15000;
+ public const int Default25601440AreaLower = 13000;
+ public const int Default25601440AreaUpper = 15500;
public const int Default38402160AreaLower = 22600;
public const int Default38402160AreaUpper = 26600;
diff --git a/NewWorldCompanion.Entities/ItemDefinition.cs b/NewWorldCompanion.Entities/ItemDefinition.cs
index b691e85..0065ac0 100644
--- a/NewWorldCompanion.Entities/ItemDefinition.cs
+++ b/NewWorldCompanion.Entities/ItemDefinition.cs
@@ -12,5 +12,7 @@ public class ItemDefinition
public string Name { get; set; } = string.Empty;
/// Used to define if an item is tradable
public bool BindOnPickup { get; set; } = false;
+ public bool BindOnEquip { get; set; } = false;
+
}
}
diff --git a/NewWorldCompanion.Entities/SettingsNWC.cs b/NewWorldCompanion.Entities/SettingsNWC.cs
index 99f0af5..203b455 100644
--- a/NewWorldCompanion.Entities/SettingsNWC.cs
+++ b/NewWorldCompanion.Entities/SettingsNWC.cs
@@ -14,6 +14,7 @@ public class SettingsNWC
// Overlay
public bool TooltipEnabled { get; set; } = true;
public bool ExtendedTooltipEnabled { get; set; } = false;
+ public bool NamedItemsTooltipEnabled { get; set; } = false;
public int PriceServerId { get; set; } = 1;
// Shape detection
@@ -28,5 +29,19 @@ public class SettingsNWC
public int EmguThresholdMaxR { get; set; } = 200;
public int EmguThresholdMaxG { get; set; } = 235;
public int EmguThresholdMaxB { get; set; } = 255;
+
+ // Named items
+ public bool NamedItemsFilterTier2 { get; set; } = true;
+ public bool NamedItemsFilterTier3 { get; set; } = true;
+ public bool NamedItemsFilterTier4 { get; set; } = true;
+ public bool NamedItemsFilterTier5 { get; set; } = true;
+ public bool NamedItemsFilterItemClassArmor { get; set; } = true;
+ public bool NamedItemsFilterItemClassJewelry { get; set; } = true;
+ public bool NamedItemsFilterItemClassWeapon { get; set; } = true;
+ public bool NamedItemsFilterStorageCollected { get; set; } = true;
+ public bool NamedItemsFilterStorageMissing { get; set; } = true;
+ public bool NamedItemsFilterStorageDuplicates { get; set; } = true;
+ public bool NamedItemsFilterBindOnEquip { get; set; } = true;
+ public bool NamedItemsFilterBindOnPickup { get; set; } = true;
}
}
diff --git a/NewWorldCompanion.Events/ScreenCaptureEvent.cs b/NewWorldCompanion.Events/ScreenCaptureEvent.cs
index bd41e53..0110e11 100644
--- a/NewWorldCompanion.Events/ScreenCaptureEvent.cs
+++ b/NewWorldCompanion.Events/ScreenCaptureEvent.cs
@@ -9,4 +9,9 @@ public class ScreenCaptureReadyEvent : PubSubEvent
public class MouseCoordinatesUpdatedEvent : PubSubEvent
{
}
+
+ public class MouseDeltaUpdatedEvent : PubSubEvent
+ {
+
+ }
}
diff --git a/NewWorldCompanion.Events/StorageManagerEvents.cs b/NewWorldCompanion.Events/StorageManagerEvents.cs
new file mode 100644
index 0000000..4e860d4
--- /dev/null
+++ b/NewWorldCompanion.Events/StorageManagerEvents.cs
@@ -0,0 +1,8 @@
+using Prism.Events;
+
+namespace NewWorldCompanion.Events
+{
+ public class StorageManagerUpdated : PubSubEvent
+ {
+ }
+}
\ No newline at end of file
diff --git a/NewWorldCompanion.Interfaces/INewWorldDataStore.cs b/NewWorldCompanion.Interfaces/INewWorldDataStore.cs
index fb61c83..6d0db46 100644
--- a/NewWorldCompanion.Interfaces/INewWorldDataStore.cs
+++ b/NewWorldCompanion.Interfaces/INewWorldDataStore.cs
@@ -14,14 +14,18 @@ public interface INewWorldDataStore
string LoadStatusCraftingRecipes { get; }
string LoadStatusHouseItems { get; }
string LoadStatusLocalisation { get; }
+ string LoadStatusLocalisationNamed { get; }
List GetCraftingRecipes();
bool IsBindOnPickup(string itemName);
+ bool IsNamedItem(string itemName);
string GetItemId(string itemName);
ItemDefinition? GetItem(string itemId);
string GetLevenshteinItemName(string itemName);
List GetOverlayResources();
+ List GetNamedItems();
string GetItemLocalisation(string itemMasterName);
+ string GetNamedItemLocalisation(string itemMasterName);
List GetRelatedRecipes(string itemId);
CraftingRecipeJson GetCraftingRecipeDetails(string itemId);
void UpdateStoreData();
diff --git a/NewWorldCompanion.Interfaces/IStorageManager.cs b/NewWorldCompanion.Interfaces/IStorageManager.cs
index 8d55461..07825e2 100644
--- a/NewWorldCompanion.Interfaces/IStorageManager.cs
+++ b/NewWorldCompanion.Interfaces/IStorageManager.cs
@@ -15,6 +15,7 @@ ObservableCollection- Items
get;
}
+ string GetItemStorageInfo(string itemName);
void SaveStorage();
}
}
\ No newline at end of file
diff --git a/NewWorldCompanion.Services/NewWorldDataStore.cs b/NewWorldCompanion.Services/NewWorldDataStore.cs
index ba7dc89..cc43930 100644
--- a/NewWorldCompanion.Services/NewWorldDataStore.cs
+++ b/NewWorldCompanion.Services/NewWorldDataStore.cs
@@ -1,10 +1,12 @@
-using NewWorldCompanion.Constants;
+using Microsoft.Extensions.Logging;
+using NewWorldCompanion.Constants;
using NewWorldCompanion.Entities;
using NewWorldCompanion.Events;
using NewWorldCompanion.Helpers;
using NewWorldCompanion.Interfaces;
using Prism.Events;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -21,11 +23,14 @@ namespace NewWorldCompanion.Services
public class NewWorldDataStore : INewWorldDataStore
{
private readonly IEventAggregator _eventAggregator;
+ private readonly ILogger _logger;
private List _masterItemDefinitionsJson = new List();
+ private List _masterItemDefinitionsJson_Named = new List();
private List _craftingRecipesJson = new List();
private List _houseItemsJson = new List();
- private Dictionary _itemDefinitionsLocalisation = new Dictionary();
+ private ConcurrentDictionary _itemDefinitionsLocalisation = new ConcurrentDictionary(50, 15000);
+ private ConcurrentDictionary _namedItemDefinitionsLocalisation = new ConcurrentDictionary(50, 15000);
private bool _available = false;
@@ -33,17 +38,21 @@ public class NewWorldDataStore : INewWorldDataStore
private string _loadStatusCraftingRecipes = string.Empty;
private string _loadStatusHouseItems = string.Empty;
private string _loadStatusLocalisation = string.Empty;
+ private string _loadStatusLocalisationNamed = string.Empty;
// Start of Constructor region
#region Constructor
- public NewWorldDataStore(IEventAggregator eventAggregator)
+ public NewWorldDataStore(IEventAggregator eventAggregator, ILogger logger)
{
// Init IEventAggregator
_eventAggregator = eventAggregator;
+ // Init logger
+ _logger = logger;
+
// Init store data
Task.Run(() => UpdateStoreData());
}
@@ -59,6 +68,7 @@ public NewWorldDataStore(IEventAggregator eventAggregator)
public string LoadStatusCraftingRecipes { get => _loadStatusCraftingRecipes; set => _loadStatusCraftingRecipes = value; }
public string LoadStatusHouseItems { get => _loadStatusHouseItems; set => _loadStatusHouseItems = value; }
public string LoadStatusLocalisation { get => _loadStatusLocalisation; set => _loadStatusLocalisation = value; }
+ public string LoadStatusLocalisationNamed { get => _loadStatusLocalisationNamed; set => _loadStatusLocalisationNamed = value; }
#endregion
@@ -73,8 +83,10 @@ public void UpdateStoreData()
_loadStatusItemDefinitions = $"ItemDefinitions: 0. Loading common items";
_eventAggregator.GetEvent().Publish();
- // MasterItemDefinitions Common
_masterItemDefinitionsJson.Clear();
+ _masterItemDefinitionsJson_Named.Clear();
+
+ // MasterItemDefinitions Common
var masterItemDefinitionsJson = new List();
resourcePath = @".\Data\MasterItemDefinitions_Common.json";
using (FileStream? stream = File.OpenRead(resourcePath))
@@ -164,6 +176,7 @@ public void UpdateStoreData()
masterItemDefinitionsJson = JsonSerializer.Deserialize
>(stream, options) ?? new List();
_masterItemDefinitionsJson.AddRange(masterItemDefinitionsJson);
+ _masterItemDefinitionsJson_Named.AddRange(masterItemDefinitionsJson);
}
}
@@ -232,6 +245,7 @@ public void UpdateStoreData()
_loadStatusHouseItems = $"HouseItems: {_houseItemsJson.Count}";
_loadStatusLocalisation = $"Localisation: 0. Loading localisations";
+ _loadStatusLocalisationNamed = $"Localisation (named): 0. Loading localisations";
_eventAggregator.GetEvent().Publish();
// ItemDefinitionsLocalisation - Itemdefinitions
@@ -246,7 +260,8 @@ public void UpdateStoreData()
where loc.Name.LocalName == "string"
select loc;
- foreach (var loc in query)
+ Parallel.ForEach(query, new ParallelOptions { MaxDegreeOfParallelism = 50 },
+ loc =>
{
string key = loc.Attribute("key")?.Value ?? string.Empty;
string value = loc.Value;
@@ -255,22 +270,31 @@ public void UpdateStoreData()
// MasterItemDefinitions_Common.json
// MasterItemDefinitions_Crafting.json
// MasterItemDefinitions_Loot.json
+ // MasterItemDefinitions_Named.json
// MasterItemDefinitions_Quest.json
if (_masterItemDefinitionsJson.Any(d => d.Name?.Equals("@" + key, StringComparison.OrdinalIgnoreCase) ?? false))
{
_itemDefinitionsLocalisation.TryAdd(key.ToLower(), value);
}
+ if (_masterItemDefinitionsJson_Named.Any(d => (d.Name?.Equals("@" + key, StringComparison.OrdinalIgnoreCase) ?? false) && d.ItemClass.Contains("Named")))
+ {
+ _namedItemDefinitionsLocalisation.TryAdd(key.ToLower(), value);
+ }
_loadStatusLocalisation = $"Localisation data: {_itemDefinitionsLocalisation.Count}";
+ _loadStatusLocalisationNamed = $"Localisation data (named): {_namedItemDefinitionsLocalisation.Count}";
_eventAggregator.GetEvent().Publish();
- }
+ });
}
}
// ItemDefinitionsLocalisation - Itemdefinitions - Cleanup duplicates
- _itemDefinitionsLocalisation.Remove("ArrowBT2_MasterName".ToLower());
- _itemDefinitionsLocalisation.Remove("ArrowBT4_MasterName".ToLower());
- _itemDefinitionsLocalisation.Remove("ArrowBT5_MasterName".ToLower());
+ _itemDefinitionsLocalisation.Remove("ArrowBT2_MasterName".ToLower(), out string? _);
+ _itemDefinitionsLocalisation.Remove("ArrowBT4_MasterName".ToLower(), out string? _);
+ _itemDefinitionsLocalisation.Remove("ArrowBT5_MasterName".ToLower(), out string? _);
+ // ItemDefinitionsLocalisation - Itemdefinitions - Cleanup resource / item conflicts
+ // TODO: Remove workaround when named items are separated.
+ _itemDefinitionsLocalisation.Remove("2hGreatSword_FlintT5_MasterName".ToLower(), out string? _);
// ItemDefinitionsLocalisation - HouseItems
resourcePath = @".\Data\javelindata_housingitems.loc.xml";
@@ -283,7 +307,9 @@ public void UpdateStoreData()
where loc.Name.LocalName == "string"
select loc;
- foreach (var loc in query)
+ //foreach (var loc in query)
+ Parallel.ForEach(query, new ParallelOptions { MaxDegreeOfParallelism = 50 },
+ loc =>
{
string key = loc.Attribute("key")?.Value ?? string.Empty;
string value = loc.Value;
@@ -297,7 +323,7 @@ public void UpdateStoreData()
_loadStatusLocalisation = $"Localisation data: {_itemDefinitionsLocalisation.Count}";
_eventAggregator.GetEvent().Publish();
- }
+ });
}
}
@@ -417,8 +443,10 @@ public List GetNamedItems()
{
ItemClass = masterItem.ItemClass,
ItemID = masterItem.ItemID,
- Localisation = GetItemLocalisation(masterItem.Name),
- Tier = masterItem.Tier
+ Localisation = GetNamedItemLocalisation(masterItem.Name),
+ Tier = masterItem.Tier,
+ BindOnEquip = masterItem.BindOnEquip,
+ BindOnPickup = masterItem.BindOnPickup
});
}
@@ -441,7 +469,37 @@ public bool IsBindOnPickup(string itemName)
return true;
}
- public string GetItemId(string itemName)
+ public bool IsNamedItem(string itemName)
+ {
+ //var localisationId = _itemDefinitionsLocalisation.FirstOrDefault(x => x.Value.Replace("\\n", " ").Equals(itemName, StringComparison.OrdinalIgnoreCase)).Key;
+ //var item = _masterItemDefinitionsJson.FirstOrDefault(i => i.Name.Equals($"@{localisationId}", StringComparison.OrdinalIgnoreCase));
+
+ // TODO: Remove workaround when there is an alternative overlay for named items.
+ var localisationIds = _itemDefinitionsLocalisation.Where(x => x.Value.Replace("\\n", " ").Equals(itemName, StringComparison.OrdinalIgnoreCase));
+ if (localisationIds.Count() == 0)
+ {
+ return false;
+ }
+ else
+ {
+ _logger.LogWarning($"Item: {itemName} is not unique. There are {localisationIds.Count()} entries found");
+ }
+
+ // Check all similar named items. If one of them is not "named" return false.
+ // Workaround need for weapon and resource that both share the name "Flint".
+ bool uniqueNamedItem = true;
+ foreach (var localisationId in localisationIds)
+ {
+ var item = _masterItemDefinitionsJson.FirstOrDefault(i => i.Name.Equals($"@{localisationId.Key}", StringComparison.OrdinalIgnoreCase));
+ if (!item.ItemClass.Contains("Named"))
+ {
+ uniqueNamedItem = false;
+ }
+ }
+ return uniqueNamedItem;
+ }
+
+ public string GetItemId(string itemName)//
{
var localisationId = _itemDefinitionsLocalisation.FirstOrDefault(x => x.Value.Replace("\\n", " ").Equals(itemName, StringComparison.OrdinalIgnoreCase)).Key;
MasterItemDefinitionsJson? item = _masterItemDefinitionsJson.FirstOrDefault(i => i.Name.Equals($"@{localisationId}", StringComparison.OrdinalIgnoreCase));
@@ -491,7 +549,7 @@ public string GetLevenshteinItemName(string itemName)
Debug.WriteLine($"Levenshtein. Item: {itemName}, Match: {currentItem}, Distance: {currentDistance}");
//return currentDistance <= Math.Max(3, itemName.Length) ? currentItem : itemName;
- return currentDistance <= 3 ? currentItem : itemName;
+ return currentDistance <= 5 ? currentItem : itemName;
}
public string GetItemLocalisation(string itemMasterName)
@@ -499,6 +557,11 @@ public string GetItemLocalisation(string itemMasterName)
return _itemDefinitionsLocalisation.GetValueOrDefault(itemMasterName.Trim(new char[] { '@' }).ToLower()) ?? itemMasterName.Trim(new char[] { '@' });
}
+ public string GetNamedItemLocalisation(string itemMasterName)
+ {
+ return _namedItemDefinitionsLocalisation.GetValueOrDefault(itemMasterName.Trim(new char[] { '@' }).ToLower()) ?? itemMasterName.Trim(new char[] { '@' });
+ }
+
public List GetRelatedRecipes(string itemId)
{
// Note: The following recipes are ignored:
diff --git a/NewWorldCompanion.Services/OverlayHandler.cs b/NewWorldCompanion.Services/OverlayHandler.cs
index 815ab9f..f3eee3d 100644
--- a/NewWorldCompanion.Services/OverlayHandler.cs
+++ b/NewWorldCompanion.Services/OverlayHandler.cs
@@ -23,8 +23,7 @@ public class OverlayHandler : IOverlayHandler
private readonly IOcrHandler _ocrHandler;
private readonly IPriceManager _priceManager;
private readonly IScreenProcessHandler _screenProcessHandler;
-
- private DispatcherTimer _timer = new();
+ private readonly IStorageManager _storageManager;
private readonly GraphicsWindow _window;
private readonly Dictionary _brushes;
@@ -36,13 +35,14 @@ public class OverlayHandler : IOverlayHandler
private int _overlayY = 0;
private int _overlayWidth = 0;
private int _overlayHeigth = 0;
+ private double _mouseDelta = 0;
// Start of Constructor region
#region Constructor
public OverlayHandler(IEventAggregator eventAggregator, ISettingsManager settingsManager, ICraftingRecipeManager craftingRecipeManager, INewWorldDataStore newWorldDataStore,
- IOcrHandler ocrHandler, IPriceManager priceManager, IScreenProcessHandler screenProcessHandler)
+ IOcrHandler ocrHandler, IPriceManager priceManager, IScreenProcessHandler screenProcessHandler, IStorageManager storageManager)
{
// Init IEventAggregator
_eventAggregator = eventAggregator;
@@ -51,6 +51,7 @@ public OverlayHandler(IEventAggregator eventAggregator, ISettingsManager setting
_eventAggregator.GetEvent().Subscribe(HandleOverlayShowEvent);
_eventAggregator.GetEvent().Subscribe(HandleRoiImageReadyEvent);
_eventAggregator.GetEvent().Subscribe(HandleNewWorldDataStoreUpdatedEvent);
+ _eventAggregator.GetEvent().Subscribe(HandleMouseDeltaUpdatedEvent);
// Init services
_settingsManager = settingsManager;
@@ -59,6 +60,7 @@ public OverlayHandler(IEventAggregator eventAggregator, ISettingsManager setting
_ocrHandler = ocrHandler;
_priceManager = priceManager;
_screenProcessHandler = screenProcessHandler;
+ _storageManager = storageManager;
_brushes = new Dictionary();
_fonts = new Dictionary();
@@ -119,7 +121,20 @@ private void DrawGraphics(object? sender, DrawGraphicsEventArgs e)
}
else
{
- DrawGraphicsItem(e, itemName);
+ if (!_newWorldDataStore.IsNamedItem(itemName))
+ {
+ DrawGraphicsItem(e, itemName);
+ }
+ else if (_settingsManager.Settings.NamedItemsTooltipEnabled)
+ {
+ DrawGraphicsNamedItem(e, itemName);
+ }
+ else
+ {
+ // Clear
+ var gfx = e.Graphics;
+ gfx.ClearScene();
+ }
}
}
@@ -179,6 +194,24 @@ private void DrawGraphicsItem(DrawGraphicsEventArgs e, string itemName)
}
}
+ private void DrawGraphicsNamedItem(DrawGraphicsEventArgs e, string itemName)
+ {
+ string infoItemName = itemName;
+ string infoStorage = $"Storage: {_storageManager.GetItemStorageInfo(itemName)}";
+
+ _window.X = _overlayX;
+ _window.Y = _overlayY;
+ _window.Width = _overlayWidth;
+ _window.Height = _overlayHeigth;
+
+ var gfx = e.Graphics;
+ gfx.ClearScene(_brushes["background"]);
+ gfx.DrawText(_fonts["consolas"], _brushes["text"], 20, 20, infoItemName);
+ gfx.DrawText(_fonts["consolas"], _brushes["text"], 20, 40, "Named");
+ gfx.DrawText(_fonts["consolas"], _brushes["text"], 20, 60, infoStorage);
+ gfx.DrawRectangle(_brushes["border"], 0, 0, _overlayWidth, _overlayHeigth, 1);
+ }
+
private void DrawGraphicsRecipe(DrawGraphicsEventArgs e, CraftingRecipe craftingRecipe)
{
NwmarketpriceJson nwmarketpriceJson = _priceManager.GetPriceData(craftingRecipe.LocalisationUserFriendly);
@@ -255,7 +288,7 @@ private void HandleOverlayShowEvent()
// - Tradable items
string itemName = _itemName;
var craftingRecipe = _craftingRecipeManager.CraftingRecipes.FirstOrDefault(r => r.LocalisationUserFriendly.StartsWith(itemName, StringComparison.OrdinalIgnoreCase));
- if (craftingRecipe != null || !_newWorldDataStore.IsBindOnPickup(itemName))
+ if (craftingRecipe != null || !_newWorldDataStore.IsBindOnPickup(itemName) || _newWorldDataStore.IsNamedItem(itemName))
{
_window.Show();
}
@@ -266,8 +299,12 @@ private void HandleOverlayHideEvent()
{
if (_window.IsInitialized)
{
- _window.Hide();
- }
+ if ((_settingsManager.Settings.NamedItemsTooltipEnabled && _mouseDelta != 0) ||
+ !_settingsManager.Settings.NamedItemsTooltipEnabled)
+ {
+ _window.Hide();
+ }
+ }
}
private void HandleRoiImageReadyEvent()
@@ -283,6 +320,11 @@ private void HandleNewWorldDataStoreUpdatedEvent()
StartOverlay();
}
+ private void HandleMouseDeltaUpdatedEvent(double delta)
+ {
+ _mouseDelta = delta;
+ }
+
#endregion
// Start of Methods region
diff --git a/NewWorldCompanion.Services/ScreenCaptureHandler.cs b/NewWorldCompanion.Services/ScreenCaptureHandler.cs
index d9c4b85..53f862d 100644
--- a/NewWorldCompanion.Services/ScreenCaptureHandler.cs
+++ b/NewWorldCompanion.Services/ScreenCaptureHandler.cs
@@ -27,6 +27,8 @@ public class ScreenCaptureHandler : IScreenCaptureHandler
private Bitmap? _currentScreenMouseArea = null;
private int _delay = 100;
private int _delayCoordinates = 100;
+ private int _mouseX = 0;
+ private int _mouseY = 0;
private int _offsetX = 0;
private int _offsetY = 0;
private ScreenCapture _screenCapture = new ScreenCapture();
@@ -77,7 +79,7 @@ public ScreenCaptureHandler(IEventAggregator eventAggregator, ILogger _delayCoordinates; set => _delayCoordinates = value; }
public bool IsActive
{
- get => _settingsManager.Settings.TooltipEnabled;
+ get => _settingsManager.Settings.TooltipEnabled || _settingsManager.Settings.NamedItemsTooltipEnabled;
}
public string MouseCoordinates { get; set; } = string.Empty;
public string MouseCoordinatesScaled { get; set; } = string.Empty;
@@ -191,10 +193,19 @@ private void UpdateCoordinates()
var dpiScaling = Math.Round(dpi / (double)96, 2);
MouseCoordinatesScaled = $"X: {(int)(cursorInfo.ptScreenPos.x / dpiScaling)}, Y: {(int)(cursorInfo.ptScreenPos.y / dpiScaling)}";
+ int x1 = _mouseX;
+ int y1 = _mouseY;
+ int x2 = cursorInfo.ptScreenPos.x;
+ int y2 = cursorInfo.ptScreenPos.y;
+ double delta = Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));
+ _mouseX = cursorInfo.ptScreenPos.x;
+ _mouseY = cursorInfo.ptScreenPos.y;
+
_coordinatesTimer.Interval = TimeSpan.FromMilliseconds(DelayCoordinates);
_coordinatesTimer.IsEnabled = true;
_eventAggregator.GetEvent().Publish();
+ _eventAggregator.GetEvent().Publish(delta);
}
public BitmapSource? ImageSourceFromScreenCapture()
diff --git a/NewWorldCompanion.Services/ScreenProcessHandler.cs b/NewWorldCompanion.Services/ScreenProcessHandler.cs
index b224666..c0efacc 100644
--- a/NewWorldCompanion.Services/ScreenProcessHandler.cs
+++ b/NewWorldCompanion.Services/ScreenProcessHandler.cs
@@ -9,6 +9,7 @@
using Prism.Events;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -116,6 +117,160 @@ private void HandleScreenCaptureReadyEvent()
private void ProcessImage(Mat img)
{
+ bool result = ProcessImageNormal(img);
+ if (!result)
+ {
+ result = ProcessImageSwirling(img);
+ }
+ if (!result)
+ {
+ _eventAggregator.GetEvent().Publish();
+ }
+ }
+
+ private bool ProcessImageNormal(Mat img)
+ {
+ bool result = true;
+
+ UMat filter = new UMat();
+ UMat cannyEdges = new UMat();
+ Mat crop = new Mat(img.Size, DepthType.Cv8U, 3);
+
+ //Convert the image to grayscale and filter out the noise
+ CvInvoke.CvtColor(img, filter, ColorConversion.Bgr2Gray);
+ //CvInvoke.GaussianBlur(filter, filter, new System.Drawing.Size(3, 3), 1);
+
+ // Canny and edge detection
+ double cannyThreshold = HysteresisLower;
+ double cannyThresholdLinking = HysteresisUpper;
+ CvInvoke.Canny(filter, cannyEdges, cannyThreshold, cannyThresholdLinking);
+
+ // Find rectangles
+ List rectangleList = new List();
+ VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
+
+ CvInvoke.FindContours(cannyEdges, contours, null, RetrType.List, ChainApproxMethod.ChainApproxSimple);
+ int count = contours.Size;
+ for (int i = 0; i < count; i++)
+ {
+ using (VectorOfPoint contour = contours[i])
+ using (VectorOfPoint approxContour = new VectorOfPoint())
+ {
+ CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * 0.05, true);
+ // Only consider contours with area between upper and lower values
+ // e.g. when using 2560x1440
+ // Equipment icon size: ca. 5000-6000
+ // Schematic icon size: ca. 10000-10800
+ if (CvInvoke.ContourArea(approxContour, false) > AreaLower &&
+ CvInvoke.ContourArea(approxContour, false) < AreaUpper)
+ {
+ // The contour has 4 vertices.
+ if (approxContour.Size >= 2)
+ {
+ // Determine if all the angles in the contour are within [80, 100] degree
+ bool isRectangle = true;
+ System.Drawing.Point[] pts = approxContour.ToArray();
+ LineSegment2D[] edges = Emgu.CV.PointCollection.PolyLine(pts, true);
+
+ int angleCounter = 0;
+ for (int j = 0; j < edges.Length; j++)
+ {
+ double angle = Math.Abs(edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
+
+ if (angle > 80 && angle < 100)
+ {
+ angleCounter++;
+ }
+ }
+ isRectangle = angleCounter > 1;
+
+ if (isRectangle)
+ {
+ var rotatedRec = CvInvoke.MinAreaRect(approxContour);
+ if (!rectangleList.Exists(rec =>
+ ((rec.Center.X - rotatedRec.Center.X > -2 && rec.Center.X - rotatedRec.Center.X < 2) || (rotatedRec.Center.X - rec.Center.X > -2 && rotatedRec.Center.X - rec.Center.X < 2)) &&
+ ((rec.Center.Y - rotatedRec.Center.Y > -2 && rec.Center.Y - rotatedRec.Center.Y < 2) || (rotatedRec.Center.Y - rec.Center.Y > -2 && rotatedRec.Center.Y - rec.Center.Y < 2))))
+ {
+ // We only want squares
+ float ratio = rotatedRec.Size.Width / rotatedRec.Size.Height;
+ if (ratio > 0.8 && ratio < 1.2)
+ {
+ rectangleList.Add(rotatedRec);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Draw rectangles
+ foreach (RotatedRect rectangle in rectangleList)
+ {
+ CvInvoke.Polylines(img, Array.ConvertAll(rectangle.GetVertices(), System.Drawing.Point.Round), true,
+ new Bgr(Color.DarkOrange).MCvScalar, 2);
+ }
+ ProcessedImage = img.ToBitmap();
+ _eventAggregator.GetEvent().Publish();
+
+ // Create roi
+ if (rectangleList.Count == 1)
+ {
+ var roiRectangle = new Rectangle
+ (
+ rectangleList[0].MinAreaRect().X + rectangleList[0].MinAreaRect().Width + 5,
+ rectangleList[0].MinAreaRect().Y,
+ (int)(rectangleList[0].MinAreaRect().Width * 2.8),
+ (int)(rectangleList[0].MinAreaRect().Height * 0.5)
+ );
+
+ // Update overlay position
+ // Note: Using a constant height of 100. So that all text rows fit the overlay for every resolution.
+ OverlayX = _screenCaptureHandler.OffsetX + rectangleList[0].MinAreaRect().X;
+ //OverlayY = _screenCaptureHandler.OffsetY + rectangleList[0].MinAreaRect().Y - (rectangleList[0].MinAreaRect().Height + 12);
+ OverlayY = _screenCaptureHandler.OffsetY + rectangleList[0].MinAreaRect().Y - (100 + 12);
+ OverlayWidth = rectangleList[0].MinAreaRect().Width + (int)(rectangleList[0].MinAreaRect().Width * 2.8);
+ //OverlayHeigth = rectangleList[0].MinAreaRect().Height;
+ OverlayHeigth = 100;
+
+ try
+ {
+ // Validate width
+ roiRectangle.Width = (roiRectangle.X + roiRectangle.Width) <= img.Width ? roiRectangle.Width : roiRectangle.Width - (roiRectangle.X + roiRectangle.Width - img.Width);
+
+ if (roiRectangle.Width > 0 && roiRectangle.Y > 0)
+ {
+ crop = new Mat(img, roiRectangle);
+ RoiImage = crop.ToBitmap();
+ _eventAggregator.GetEvent().Publish();
+ ProcessImageOCR(crop);
+ }
+ else
+ {
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, MethodBase.GetCurrentMethod()?.Name);
+
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
+ }
+ }
+ else
+ {
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
+ }
+ return result;
+ }
+
+ private bool ProcessImageSwirling(Mat img)
+ {
+ bool result = true;
+
UMat filter = new UMat();
UMat cannyEdges = new UMat();
Mat crop = new Mat(img.Size, DepthType.Cv8U, 3);
@@ -149,22 +304,24 @@ private void ProcessImage(Mat img)
CvInvoke.ContourArea(approxContour, false) < AreaUpper)
{
// The contour has 4 vertices.
- if (approxContour.Size == 4)
+ if (approxContour.Size >= 2)
{
// Determine if all the angles in the contour are within [80, 100] degree
bool isRectangle = true;
System.Drawing.Point[] pts = approxContour.ToArray();
LineSegment2D[] edges = Emgu.CV.PointCollection.PolyLine(pts, true);
+ int angleCounter = 0;
for (int j = 0; j < edges.Length; j++)
{
double angle = Math.Abs(edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
- if (angle < 80 || angle > 100)
+
+ if (angle > 80 && angle < 100)
{
- isRectangle = false;
- break;
+ angleCounter++;
}
}
+ isRectangle = angleCounter > 1;
if (isRectangle)
{
@@ -220,22 +377,33 @@ private void ProcessImage(Mat img)
// Validate width
roiRectangle.Width = (roiRectangle.X + roiRectangle.Width) <= img.Width ? roiRectangle.Width : roiRectangle.Width - (roiRectangle.X + roiRectangle.Width - img.Width);
- crop = new Mat(img, roiRectangle);
- RoiImage = crop.ToBitmap();
- _eventAggregator.GetEvent().Publish();
- ProcessImageOCR(crop);
+ if (roiRectangle.Width > 0 && roiRectangle.Y > 0)
+ {
+ crop = new Mat(img, roiRectangle);
+ RoiImage = crop.ToBitmap();
+ _eventAggregator.GetEvent().Publish();
+ ProcessImageOCR(crop);
+ }
+ else
+ {
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
+ }
}
catch (Exception ex)
{
_logger.LogError(ex, MethodBase.GetCurrentMethod()?.Name);
- _eventAggregator.GetEvent().Publish();
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
}
}
else
{
- _eventAggregator.GetEvent().Publish();
+ //_eventAggregator.GetEvent().Publish();
+ result = false;
}
+ return result;
}
private void ProcessImageCount(Mat img)
diff --git a/NewWorldCompanion.Services/StorageManager.cs b/NewWorldCompanion.Services/StorageManager.cs
index 3a0a7f9..f749820 100644
--- a/NewWorldCompanion.Services/StorageManager.cs
+++ b/NewWorldCompanion.Services/StorageManager.cs
@@ -1,4 +1,5 @@
using NewWorldCompanion.Entities;
+using NewWorldCompanion.Events;
using NewWorldCompanion.Interfaces;
using Prism.Events;
using System;
@@ -84,6 +85,20 @@ public void SaveStorage()
using FileStream stream = File.Create(fileName);
var options = new JsonSerializerOptions { WriteIndented = true };
JsonSerializer.Serialize(stream, Items, options);
+
+ _eventAggregator.GetEvent().Publish();
+ }
+
+ public string GetItemStorageInfo(string itemName)
+ {
+ string storageInfo = string.Empty;
+ var items = _items.ToList().FindAll(i => i.Localisation.ToLower().Equals(itemName.ToLower()));
+ foreach ( var item in items)
+ {
+ storageInfo = storageInfo.Length > 0 ? $"{storageInfo}, {item.Storage}" : item.Storage;
+ }
+
+ return storageInfo.Length > 0 ? storageInfo : "Missing";
}
#endregion
diff --git a/NewWorldCompanion/App.xaml.cs b/NewWorldCompanion/App.xaml.cs
index 157ab39..f120f09 100644
--- a/NewWorldCompanion/App.xaml.cs
+++ b/NewWorldCompanion/App.xaml.cs
@@ -21,7 +21,7 @@ namespace NewWorldCompanion
///
public partial class App : PrismApplication
{
- private static Mutex _mutex = null;
+ private static Mutex? _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
diff --git a/NewWorldCompanion/ViewModels/MainWindowViewModel.cs b/NewWorldCompanion/ViewModels/MainWindowViewModel.cs
index ff89975..bb8e3bc 100644
--- a/NewWorldCompanion/ViewModels/MainWindowViewModel.cs
+++ b/NewWorldCompanion/ViewModels/MainWindowViewModel.cs
@@ -72,6 +72,7 @@ public MainWindowViewModel(IEventAggregator eventAggregator, ILogger _newWorldDataStore.LoadStatusCraftingRecipes;
public string DataStatusHouseItems => _newWorldDataStore.LoadStatusHouseItems;
public string DataStatusLocalisation => _newWorldDataStore.LoadStatusLocalisation;
+ public string DataStatusLocalisationNamed => _newWorldDataStore.LoadStatusLocalisationNamed;
#endregion
@@ -124,6 +125,7 @@ private void HandleDataStatusUpdatedEvent()
RaisePropertyChanged(nameof(DataStatusCraftingRecipes));
RaisePropertyChanged(nameof(DataStatusHouseItems));
RaisePropertyChanged(nameof(DataStatusLocalisation));
+ RaisePropertyChanged(nameof(DataStatusLocalisationNamed));
}
#endregion
diff --git a/NewWorldCompanion/ViewModels/Tabs/Config/ConfigOverlayViewModel.cs b/NewWorldCompanion/ViewModels/Tabs/Config/ConfigOverlayViewModel.cs
index 2fe68be..2efcaef 100644
--- a/NewWorldCompanion/ViewModels/Tabs/Config/ConfigOverlayViewModel.cs
+++ b/NewWorldCompanion/ViewModels/Tabs/Config/ConfigOverlayViewModel.cs
@@ -29,6 +29,7 @@ public class ConfigOverlayViewModel : BindableBase
private OverlayResource _selectedOverlayResource = new OverlayResource();
private bool _toggleTooltip = false;
private bool _toggleExtendedTooltip = false;
+ private bool _toggleNamedItemsTooltip = false;
private int _serverIndex = 0;
private string _itemNameFilter = string.Empty;
@@ -106,6 +107,19 @@ public bool ToggleExtendedTooltip
}
}
+ public bool ToggleNamedItemsTooltip
+ {
+ get => _toggleNamedItemsTooltip;
+ set
+ {
+ _toggleNamedItemsTooltip = value;
+ RaisePropertyChanged();
+
+ _settingsManager.Settings.NamedItemsTooltipEnabled = value;
+ _settingsManager.SaveSettings();
+ }
+ }
+
public int ServerIndex
{
get
@@ -215,6 +229,7 @@ private void InitOverlayResources()
// Load extended tooltip toggle
ToggleTooltip = _settingsManager.Settings.TooltipEnabled;
ToggleExtendedTooltip = _settingsManager.Settings.ExtendedTooltipEnabled;
+ ToggleNamedItemsTooltip = _settingsManager.Settings.NamedItemsTooltipEnabled;
// Get interesting resources for extended overlay
var overlayResources = _newWorldDataStore.GetOverlayResources();
diff --git a/NewWorldCompanion/ViewModels/Tabs/Items/NamedItemsViewModel.cs b/NewWorldCompanion/ViewModels/Tabs/Items/NamedItemsViewModel.cs
new file mode 100644
index 0000000..d186f8f
--- /dev/null
+++ b/NewWorldCompanion/ViewModels/Tabs/Items/NamedItemsViewModel.cs
@@ -0,0 +1,453 @@
+using NewWorldCompanion.Entities;
+using NewWorldCompanion.Events;
+using NewWorldCompanion.Interfaces;
+using Prism.Commands;
+using Prism.Events;
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace NewWorldCompanion.ViewModels.Tabs.Items
+{
+ public class NamedItemsViewModel : BindableBase
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private readonly ISettingsManager _settingsManager;
+ private readonly INewWorldDataStore _newWorldDataStore;
+ private readonly IStorageManager _storageManager;
+
+ private ObservableCollection _items = new ObservableCollection();
+
+ private string _itemNameFilter = string.Empty;
+ private NamedItem _selectedItem = new();
+ private bool _toggleFilter = false;
+
+ // Start of Constructor region
+
+ #region Constructor
+
+ public NamedItemsViewModel(IEventAggregator eventAggregator, ISettingsManager settingsManager, INewWorldDataStore newWorldDataStore, IStorageManager storageManager)
+ {
+ // Init IEventAggregator
+ _eventAggregator = eventAggregator;
+ _eventAggregator.GetEvent().Subscribe(HandleNewWorldDataStoreUpdatedEvent);
+ _eventAggregator.GetEvent().Subscribe(HandleStorageManagerUpdatedEvent);
+
+ // Init services
+ _settingsManager = settingsManager;
+ _newWorldDataStore = newWorldDataStore;
+ _storageManager = storageManager;
+
+ // Init View commands
+ VisitNwdbCommand = new DelegateCommand