diff --git a/DamageCalculator/DamageCalculator/DamageCalculator.csproj b/DamageCalculator/DamageCalculator/DamageCalculator.csproj
index 1460ca4..b36596a 100644
--- a/DamageCalculator/DamageCalculator/DamageCalculator.csproj
+++ b/DamageCalculator/DamageCalculator/DamageCalculator.csproj
@@ -11,8 +11,8 @@
27.ico
- 1.2.0.0
- 1.2.0.0
+ 1.3.0.0
+ 1.3.0.0
@@ -42,6 +42,7 @@
+
@@ -51,4 +52,9 @@
+
+
+ $(DefaultXamlRuntime)
+
+
\ No newline at end of file
diff --git a/DamageCalculator/DamageCalculator/Globals.cs b/DamageCalculator/DamageCalculator/Globals.cs
new file mode 100644
index 0000000..8f76e25
--- /dev/null
+++ b/DamageCalculator/DamageCalculator/Globals.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+
+namespace Damage_Calculator
+{
+ public static class Globals
+ {
+ public static Settings Settings { get; set; } = new Settings();
+
+ public static void LoadSettings()
+ {
+ // Get path
+ string pathToFile = System.IO.Path.Combine(MainWindow.FilesPath, Settings.SettingsFileName);
+
+ if (!System.IO.File.Exists(pathToFile))
+ {
+ Globals.SaveSettings();
+ return;
+ }
+
+ XmlSerializer serializer = new XmlSerializer(typeof(Settings));
+ using (var fs = new System.IO.FileStream(pathToFile, System.IO.FileMode.Open))
+ {
+ Globals.Settings = (Settings)serializer.Deserialize(fs);
+ }
+ }
+
+ public static void SaveSettings()
+ {
+ // Get path
+ string pathToFile = System.IO.Path.Combine(MainWindow.FilesPath, Settings.SettingsFileName);
+
+ if (!System.IO.Directory.Exists(MainWindow.FilesPath))
+ // Make sure the folder exists before attempting to save the file
+ System.IO.Directory.CreateDirectory(MainWindow.FilesPath);
+
+ XmlSerializer serializer = new XmlSerializer(typeof(Settings));
+ using (var fs = new System.IO.FileStream(pathToFile, System.IO.FileMode.Create))
+ {
+ serializer.Serialize(fs, Globals.Settings);
+ }
+ }
+ }
+}
diff --git a/DamageCalculator/DamageCalculator/Help.xaml b/DamageCalculator/DamageCalculator/Help.xaml
index 5547628..87517ea 100644
--- a/DamageCalculator/DamageCalculator/Help.xaml
+++ b/DamageCalculator/DamageCalculator/Help.xaml
@@ -13,23 +13,35 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
-
+
diff --git a/DamageCalculator/DamageCalculator/MainWindow.xaml b/DamageCalculator/DamageCalculator/MainWindow.xaml
index 9e1846a..599ad86 100644
--- a/DamageCalculator/DamageCalculator/MainWindow.xaml
+++ b/DamageCalculator/DamageCalculator/MainWindow.xaml
@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Damage_Calculator"
mc:Ignorable="d"
- Title="CS:GO Damage Calculator" Height="566" Width="877" MinHeight="700" MinWidth="700"
+ Title="CS:GO Damage Calculator" Height="566" Width="1030" MinHeight="700" MinWidth="1000"
Style="{DynamicResource CustomWindowStyle}"
WindowStartupLocation="CenterScreen" Icon="27.ico"
WindowState="Maximized"
@@ -15,28 +15,8 @@
PreviewMouseDown="Window_PreviewMouseDown">
diff --git a/DamageCalculator/DamageCalculator/MainWindow.xaml.cs b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
index d00318a..c000b2a 100644
--- a/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
+++ b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
@@ -18,6 +18,7 @@ using Shared.Models;
using Shared.ZatVdfParser;
using System.Xml.Serialization;
using System.Globalization;
+using System.Collections.ObjectModel;
namespace Damage_Calculator
{
@@ -26,21 +27,44 @@ namespace Damage_Calculator
///
public partial class MainWindow : Window
{
+ public static string FilesPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "CSGO Damage Calculator");
+ public static readonly string WeaponsFileExtension = ".wpd";
+
///
- /// Gets or sets the point that will be there when left-clicking a map.
+ /// Holds current in-game mouse coordinates when hovering over the map
///
- private MapPoint leftPoint = new MapPoint();
- private Color leftPointColour = Color.FromArgb(140, 255, 0, 0);
+ private Vector3 currentMouseCoord = Vector3.Zero;
+
+ ///
+ /// Gets or sets the point that will be there when left-clicking a map in shooting mode.
+ ///
+ private MapPoint targetPoint = new MapPoint();
+
+ ///
+ /// Gets or sets the point that will be there when left-clicking a map in bomb mode.
+ ///
+ private MapPoint bombPoint = new MapPoint();
///
/// Gets or sets the point that will be there when right-clicking a map.
///
- private MapPoint rightPoint = new MapPoint();
- private Color rightPointColour = Color.FromArgb(140, 0, 255, 0);
+ private MapPoint playerPoint = new MapPoint();
- private Line connectingLine = new Line();
+ // Point and line colours
+ private Color leftClickPointColour = Color.FromArgb(140, 255, 0, 0);
+ private Color rightClickPointColour = Color.FromArgb(140, 0, 255, 0);
+ private Color connectingLineColour = Color.FromArgb(140, 255, 255, 255);
- private MapPoint bombCircle = new MapPoint();
+ ///
+ /// The height layer if several NAV areas overlap at the mouse position (0 is the bottom most (in UI should show this + 1), -1 if there is no area at the mouse)
+ ///
+ private int currentHeightLayer = -1;
+ private List hoveredNavAreas = null;
+ private bool userChangedLayer = false;
+ private int userHeightLayerOffset = 0;
+
+ private System.Windows.Shapes.Path connectingLine = new System.Windows.Shapes.Path();
+ private bool redrawLine = false;
private eDrawMode DrawMode = eDrawMode.Shooting;
@@ -66,6 +90,7 @@ namespace Damage_Calculator
public MainWindow()
{
InitializeComponent();
+ Globals.LoadSettings();
Shared.Globals.Settings.CsgoHelper.CsgoPath = Shared.Globals.Settings.SteamHelper.GetGamePathFromExactName("Counter-Strike: Global Offensive");
if (Shared.Globals.Settings.CsgoHelper.CsgoPath == null)
@@ -83,6 +108,54 @@ namespace Damage_Calculator
bgWorker.RunWorkerAsync();
}
+ #region Canvas Helper Methods
+ private bool canvasContains(UIElement element)
+ {
+ return this.mapCanvas.Children.Contains(element);
+ }
+
+ private bool canvasContains(Predicate predicate)
+ {
+ foreach (UIElement child in this.mapCanvas.Children)
+ {
+ if (predicate(child))
+ return true;
+ }
+
+ return false;
+ }
+
+ private void canvasAdd(UIElement element)
+ {
+ this.mapCanvas.Children.Add(element);
+ }
+
+ private void canvasRemove(UIElement element)
+ {
+ this.mapCanvas.Children.Remove(element);
+ }
+
+ private void canvasRemoveWhere(Predicate predicate)
+ {
+ // Mark elements to remove because you can't continue iterating after you deleted an item
+ var elementsToRemove = new List();
+ foreach (UIElement child in this.mapCanvas.Children)
+ {
+ if(predicate(child))
+ elementsToRemove.Add(child);
+ }
+
+ // Remove marked elements
+ foreach (UIElement element in elementsToRemove)
+ this.mapCanvas.Children.Remove(element);
+ }
+
+ private void canvasClear()
+ {
+ this.mapCanvas.Children.Clear();
+ }
+ #endregion
+
private static string calculateMD5(string filename)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
@@ -100,6 +173,8 @@ namespace Damage_Calculator
if (!this.IsInitialized)
return;
+ string prevSelectedMapName = ((this.comboBoxMaps.SelectedItem as ComboBoxItem)?.Tag as CsgoMap)?.MapFileName;
+
// Add maps
var newMaps = new List();
@@ -111,21 +186,21 @@ namespace Damage_Calculator
string mapNameWithPrefix = System.IO.Path.GetFileNameWithoutExtension(map.MapImagePath).ToLower();
// Filter map type
- if (mapNameWithPrefix.StartsWith("de") && mnuShowDefusalMaps.IsChecked == false)
+ if (mapNameWithPrefix.StartsWith("de") && Globals.Settings.ShowDefusalMaps == false)
continue;
- if (mapNameWithPrefix.StartsWith("cs") && mnuShowHostageMaps.IsChecked == false)
+ if (mapNameWithPrefix.StartsWith("cs") && Globals.Settings.ShowHostageMaps == false)
continue;
- if (mapNameWithPrefix.StartsWith("ar") && mnuShowArmsRaceMaps.IsChecked == false)
+ if (mapNameWithPrefix.StartsWith("ar") && Globals.Settings.ShowArmsRaceMaps == false)
continue;
- if (mapNameWithPrefix.StartsWith("dz") && mnuShowDangerZoneMaps.IsChecked == false)
+ if (mapNameWithPrefix.StartsWith("dz") && Globals.Settings.ShowDangerZoneMaps == false)
continue;
// Filter file existence
- if (map.BspFilePath == null && mnuShowMapsMissingBsp.IsChecked == false)
+ if (map.BspFilePath == null && Globals.Settings.ShowMapsMissingBsp == false)
continue;
- if (map.NavFilePath == null && mnuShowMapsMissingNav.IsChecked == false)
+ if (map.NavFilePath == null && Globals.Settings.ShowMapsMissingNav == false)
continue;
- if (map.AinFilePath == null && mnuShowMapsMissingAin.IsChecked == false)
+ if (map.AinFilePath == null && Globals.Settings.ShowMapsMissingAin == false)
continue;
newMap.Tag = map;
@@ -135,7 +210,28 @@ namespace Damage_Calculator
this.comboBoxMaps.ItemsSource = newMaps.OrderBy(m => m.Content);
if (newMaps.Count > 0)
- this.comboBoxMaps.SelectedIndex = 0;
+ {
+ if (prevSelectedMapName != null)
+ {
+ // Reselect the map the user had previously selected if it's still in the list
+ ComboBoxItem foundMap = newMaps.FirstOrDefault(item => (item.Tag as CsgoMap)?.MapFileName == prevSelectedMapName);
+ if (foundMap != null)
+ {
+ // Previously selected map is still in the list so select it
+ this.comboBoxMaps.SelectedItem = foundMap;
+ }
+ else
+ {
+ // Map is not in this selection anymore so select the first one
+ this.comboBoxMaps.SelectedIndex = 0;
+ }
+ }
+ else
+ {
+ // User didn't have any map selected (shouldn't be the case, however)
+ this.comboBoxMaps.SelectedIndex = 0;
+ }
+ }
}
#region background worker
@@ -160,6 +256,8 @@ namespace Damage_Calculator
this.comboBoxMaps.ItemsSource = maps.OrderBy(m => m.Content);
if (maps.Count > 0)
this.comboBoxMaps.SelectedIndex = 0;
+
+ this.canvasReload();
}
else if(e.ProgressPercentage == 1)
{
@@ -196,36 +294,30 @@ namespace Damage_Calculator
List weapons;
string itemsFile = System.IO.Path.Combine(Shared.Globals.Settings.CsgoHelper.CsgoPath, "csgo\\scripts\\items\\items_game.txt");
- string saveFileDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "CSGO Damage Calculator");
+ string saveFileDir = MainWindow.FilesPath;
string currentHash = calculateMD5(itemsFile);
if (Directory.Exists(saveFileDir))
{
string[] files = Directory.GetFiles(saveFileDir);
- if (files.Length == 1)
- {
- // Compare hashes
- string oldHash = System.IO.Path.GetFileName(files[0]);
+ string weaponsFileName = files.FirstOrDefault(file => file.ToLower().EndsWith(MainWindow.WeaponsFileExtension));
- if(currentHash == oldHash)
+ if (weaponsFileName != null)
+ {
+ // WPD file found, compare hash and file name (which is old hash)
+ string oldHash = System.IO.Path.GetFileNameWithoutExtension(weaponsFileName);
+
+ if (currentHash == oldHash)
{
- weapons = (List)serializer.Deserialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash), FileMode.Open));
+ // Weapons didn't update since last time so just load them
+ weapons = (List)serializer.Deserialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash + MainWindow.WeaponsFileExtension), FileMode.Open));
bgWorker.ReportProgress(1, weapons);
return;
}
else
{
- foreach (string file in files)
- {
- File.Delete(file);
- }
- }
- }
- else
- {
- foreach(string file in files)
- {
- File.Delete(file);
+ // Weapons need to be updated so delete any WPD files (Weapon Parse Data)
+ this.clearWeaponDataFiles(files);
}
}
}
@@ -234,36 +326,47 @@ namespace Damage_Calculator
Directory.CreateDirectory(saveFileDir);
}
+ // We didn't return cause we didn't find an up-to-date WPD file so parse new weapon data
weapons = Shared.Globals.Settings.CsgoHelper.GetWeapons();
- serializer.Serialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash), FileMode.Create), weapons);
+ serializer.Serialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash + MainWindow.WeaponsFileExtension), FileMode.Create), weapons);
bgWorker.ReportProgress(1, weapons);
}
#endregion
+ private void clearWeaponDataFiles(string[] files)
+ {
+ foreach (string file in files)
+ {
+ // Delete all files that contain weapon info (other files we wanna keep)
+ if (file.ToLower().EndsWith(MainWindow.WeaponsFileExtension))
+ File.Delete(file);
+ }
+ }
+
private void resetCanvas(bool updatedViewSettings = false)
{
if (this.IsInitialized)
{
- this.pointsCanvas.Children.Clear();
if (!updatedViewSettings)
{
- this.leftPoint = null;
- this.rightPoint = null;
+ this.targetPoint = null;
+ this.playerPoint = null;
this.connectingLine = null;
- this.bombCircle = null;
+ this.bombPoint = null;
this.unitsDistance = -1;
this.textDistanceMetres.Text = "0";
this.textDistanceUnits.Text = "0";
this.txtResult.Text = "0";
this.txtResultArmor.Text = "0";
}
+ this.UpdateLayout();
+ this.canvasClear();
+ mapCanvas.CacheMode = new BitmapCache((mapCanvas.CacheMode as BitmapCache).RenderAtScale);
}
}
private void loadMap(CsgoMap map)
{
- mapImage.Source = map.MapImage;
-
if (map.BspFilePath != null)
{
// Map radar has an actual existing BSP map file
@@ -282,6 +385,7 @@ namespace Damage_Calculator
this.resetCanvas();
this.rightZoomBorder.Reset();
+ mapImage.Source = map.MapImage;
if (map.MapType == CsgoMap.eMapType.Defusal)
{
@@ -297,11 +401,24 @@ namespace Damage_Calculator
this.txtBombMaxDamage.Text = this.txtBombRadius.Text = "None";
}
+ // Set the map's coordinates offset from the settings file in case we have a manual offset
+ var mapOffsetMapping = Globals.Settings.MapCoordinateOffsets.FirstOrDefault(m => m.DDSFileName == System.IO.Path.GetFileNameWithoutExtension(map.MapImagePath));
+ if (mapOffsetMapping != null)
+ map.MapOverwrite = mapOffsetMapping;
+
this.loadedMap = map;
}
private void parseBspData(CsgoMap map)
{
+ // Reset values so that they are filled with new values if the map is updated (prevents multiple identical spawns being drawn)
+ map.AmountPrioritySpawnsCT = 0;
+ map.AmountPrioritySpawnsT = 0;
+ map.AmountSpawnsCT = 0;
+ map.AmountSpawnsT = 0;
+ map.AmountHostages = 0;
+ map.SpawnPoints.Clear();
+
map.EntityList = Shared.Globals.Settings.CsgoHelper.ReadEntityListFromBsp(map.BspFilePath);
// Current format for one entity is:
@@ -310,7 +427,7 @@ namespace Damage_Calculator
// "property" "value"
// }
//
- // but we want this format like in VDF files, so we can use our VDF parser:
+ // but we want this format like in VDF files, so we can use our VDF parser without modifying it (laziness):
//
// "sometext"
// {
@@ -336,13 +453,13 @@ namespace Damage_Calculator
var entityRootVdf = vdf["entity"];
string className = entityRootVdf["classname"].Value;
- // Check for map parameters entity
+ // Check for map parameters entity, which contains stuff like the bomb radius, if custom
if (className == "info_map_parameters")
{
string bombRadius = entityRootVdf["bombradius"]?.Value;
if (bombRadius != null)
{
- // Map has custom bomb radius
+ // Map has custom bomb radius (which might or might not be the same as the default)
if (float.TryParse(bombRadius, out float bombRad) && bombRad >= 0)
{
// bombradius is valid and not negative
@@ -356,8 +473,8 @@ namespace Damage_Calculator
// Entity is spawn point
var spawn = new PlayerSpawn();
spawn.Team = className == "info_player_terrorist" ? ePlayerTeam.Terrorist : ePlayerTeam.CounterTerrorist;
- spawn.Origin = this.stringToVector3(entityRootVdf["origin"]?.Value) ?? Vector3.Empty;
- spawn.Angles = this.stringToVector3(entityRootVdf["angles"]?.Value) ?? Vector3.Empty;
+ spawn.Origin = this.stringToVector3(entityRootVdf["origin"]?.Value) ?? Vector3.Zero;
+ spawn.Angles = this.stringToVector3(entityRootVdf["angles"]?.Value) ?? Vector3.Zero;
// highest priority by default. if ALL spawns are high priority, then there are no priority spawns,
// in that case we will later check the amount of priority spawns and if it is the same as the total spawns.
@@ -368,7 +485,7 @@ namespace Damage_Calculator
if (priority < 1) // 0 or nothing means it's highest priority, then counts up as an integer with decreasing priority
{
spawn.IsPriority = true;
- // Count prio spawns
+ // Count into prio spawns
if (spawn.Team == ePlayerTeam.CounterTerrorist)
map.AmountPrioritySpawnsCT++;
else
@@ -393,8 +510,8 @@ namespace Damage_Calculator
{
// Entity is hostage spawn point (equivalent but latter is csgo specific)
var spawn = new PlayerSpawn();
- spawn.Origin = this.stringToVector3(entityRootVdf["origin"]?.Value) ?? Vector3.Empty;
- spawn.Angles = this.stringToVector3(entityRootVdf["angles"]?.Value) ?? Vector3.Empty;
+ spawn.Origin = this.stringToVector3(entityRootVdf["origin"]?.Value) ?? Vector3.Zero;
+ spawn.Angles = this.stringToVector3(entityRootVdf["angles"]?.Value) ?? Vector3.Zero;
spawn.Team = ePlayerTeam.CounterTerrorist; // Just for the colour
// Count all hostage spawns
@@ -413,6 +530,7 @@ namespace Damage_Calculator
if (navFilesFound.Item1)
{
map.NavFileBspPacked = true;
+ map.NavMesh = navFilesFound.Item3;
map.NavFilePath = "PACKED";
}
if (navFilesFound.Item2)
@@ -421,6 +539,12 @@ namespace Damage_Calculator
map.AinFilePath = "PACKED";
}
}
+
+ if(map.NavFilePath != null && !map.NavFileBspPacked)
+ {
+ // Nav file not packed and a file path for it is existent so parse it here
+ map.NavMesh = Shared.NavFile.Parse(new FileStream(map.NavFilePath, FileMode.Open));
+ }
}
private Vector3 stringToVector3(string coords)
@@ -448,21 +572,39 @@ namespace Damage_Calculator
return null;
}
+ #region Conversion Helpers
+ private double getUnitsFromPixels(double pixels)
+ {
+ double mapSizePixels = (this.mapImage.Source as BitmapSource).PixelWidth;
+ double mapSizeUnits = mapSizePixels * (this.loadedMap.MapOverwrite.MapScale > 0 ? this.loadedMap.MapOverwrite.MapScale : this.loadedMap.MapSizeMultiplier);
+ return Math.Abs(pixels) * mapSizeUnits / mapSizePixels;
+ }
+
private double getPixelsFromUnits(double units)
{
int mapSizePixels = (this.mapImage.Source as BitmapSource).PixelWidth;
- double mapSizeUnits = mapSizePixels * this.loadedMap.MapSizeMultiplier;
- return Math.Abs(units) * this.pointsCanvas.ActualWidth / mapSizeUnits;
+ double mapSizeUnits = mapSizePixels * (this.loadedMap.MapOverwrite.MapScale > 0 ? this.loadedMap.MapOverwrite.MapScale : this.loadedMap.MapSizeMultiplier);
+ return Math.Abs(units) * this.mapCanvas.ActualWidth / mapSizeUnits;
}
private Point getPointFromGameCoords(double x, double y)
{
Point p = new Point();
- p.X = this.getPixelsFromUnits(this.loadedMap.UpperLeftWorldXCoordinate - x);
- p.Y = this.getPixelsFromUnits(this.loadedMap.UpperLeftWorldYCoordinate - y);
+ p.X = this.getPixelsFromUnits(this.loadedMap.UpperLeftWorldXCoordinate - x - this.loadedMap.MapOverwrite.CoordOffset.X);
+ p.Y = this.getPixelsFromUnits(this.loadedMap.UpperLeftWorldYCoordinate - y + this.loadedMap.MapOverwrite.CoordOffset.Y);
return p;
}
+ private Point getGameCoordsFromPoint(double x, double y)
+ {
+ Point p = new Point();
+ p.X = this.loadedMap.UpperLeftWorldXCoordinate + this.getUnitsFromPixels(x) - this.loadedMap.MapOverwrite.CoordOffset.X;
+ p.Y = this.loadedMap.UpperLeftWorldYCoordinate - this.getUnitsFromPixels(y) + this.loadedMap.MapOverwrite.CoordOffset.Y;
+ return p;
+ }
+ #endregion
+
+ #region Get UI elements
private Ellipse getPointEllipse(Color strokeColour)
{
Ellipse circle = new Ellipse();
@@ -528,83 +670,183 @@ namespace Damage_Calculator
return spawnBlip;
}
+ #endregion
- private void updateCirclePositions()
+ private void positionNavMeshes()
{
+ var navMeshesInCanvas = new List();
+ foreach (UIElement child in this.mapCanvas.Children)
+ {
+ if(child is FrameworkElement element && element.Tag is NavArea)
+ {
+ // Child is nav mesh path so mark it for removal if the canvas hasn't settled yet
+ navMeshesInCanvas.Add(child);
+ }
+ }
+
+ if (Globals.Settings.NavDisplayMode != Shared.NavDisplayModes.None && navMeshesInCanvas.Count > 0 && this.mapCanvas.ActualWidth > 0 && this.mapCanvas.ActualHeight > 0)
+ // Canvas has already settled - no need to redraw
+ return;
+
+ // Remove all NAV meshes from canvas
+ navMeshesInCanvas.ForEach(navMesh => canvasRemove(navMesh));
+
+ if (Globals.Settings.NavDisplayMode == Shared.NavDisplayModes.None)
+ // Don't draw and NAV areas if disabled
+ return;
+
+ if (this.loadedMap?.NavMesh?.Header?.NavAreas == null)
+ return;
+
+ this.loadedMap.NavMesh.Header.NavAreas = this.loadedMap.NavMesh.Header.NavAreas.OrderBy(area => area.MedianPosition.Z).ToArray();
+
+ foreach (var area in this.loadedMap.NavMesh.Header.NavAreas)
+ {
+ // Don't draw area if it's not in the threshold that's set in the settings
+ // First get the percentage of the average area height between the min and max area
+ double heightPercentage = Shared.Globals.Map(area.MedianPosition.Z, this.loadedMap.NavMesh.MinZ ?? 0, this.loadedMap.NavMesh.MaxZ ?? 0, 0, 1);
+ if (heightPercentage < Globals.Settings.ShowNavAreasAbove || heightPercentage > Globals.Settings.ShowNavAreasBelow)
+ continue;
+
+ var path = new System.Windows.Shapes.Path();
+ path.Tag = area;
+
+ this.setNavAreaColour(path, area, isHovered: false);
+
+ path.HorizontalAlignment = HorizontalAlignment.Left;
+ path.VerticalAlignment = VerticalAlignment.Top;
+
+ path.IsHitTestVisible = false; // Make it not catch mouse down events
+
+ var pathGeometry = new PathGeometry(new List
+ {
+ new PathFigure(this.getPointFromGameCoords(area.ActualNorthWestCorner.X, area.ActualNorthWestCorner.Y), new List
+ {
+ new LineSegment(this.getPointFromGameCoords(area.ActualNorthEastCorner.X, area.ActualNorthEastCorner.Y), isStroked: true),
+ new LineSegment(this.getPointFromGameCoords(area.ActualSouthEastCorner.X, area.ActualSouthEastCorner.Y), isStroked: true),
+ new LineSegment(this.getPointFromGameCoords(area.ActualSouthWestCorner.X, area.ActualSouthWestCorner.Y), isStroked: true),
+ }, closed: true)
+ });
+
+ path.Data = pathGeometry;
+ this.canvasAdd(path);
+ }
+
+ /*NavArea? a = this.loadedMap.NavMesh.Header.NavAreas.FirstOrDefault(area => area.ID == 9);
+ if(a != null)
+ {
+ System.Diagnostics.Debug.Print(this.loadedMap.MapFileName);
+ System.Diagnostics.Debug.Print($"NorthWest: {a.ActualNorthWestCorner.X}, {a.ActualNorthWestCorner.Y}, {a.ActualNorthWestCorner.Z}");
+ System.Diagnostics.Debug.Print($"NorthEast: {a.ActualNorthEastCorner.X}, {a.ActualNorthEastCorner.Y}, {a.ActualNorthEastCorner.Z}");
+ System.Diagnostics.Debug.Print($"SouthWest: {a.ActualSouthWestCorner.X}, {a.ActualSouthWestCorner.Y}, {a.ActualSouthWestCorner.Z}");
+ System.Diagnostics.Debug.Print($"SouthEast: {a.ActualSouthEastCorner.X}, {a.ActualSouthEastCorner.Y}, {a.ActualSouthEastCorner.Z}");
+ }*/
+ }
+
+ private void addMapPointIfMissing(MapPoint point)
+ {
+ // Check if it's in the canvas
+ if (!this.canvasContains(point.Circle))
+ // Add it cause it was missing
+ this.canvasAdd(point.Circle);
+
+ // Set point scale and position
+ Canvas.SetLeft(point.Circle, (point.PercentageX * mapCanvas.ActualWidth / 100f) - (point.Circle.Width / 2));
+ Canvas.SetTop(point.Circle, (point.PercentageY * mapCanvas.ActualHeight / 100f) - (point.Circle.Height / 2));
+ point.Circle.Width = point.Circle.Height = point.PercentageScale * this.mapCanvas.ActualWidth / 100f;
+ }
+
+ private void drawPointsAndConnectingLine()
+ {
+ // Make sure line is not null
if (this.connectingLine == null)
- this.connectingLine = new Line();
+ this.connectingLine = new System.Windows.Shapes.Path();
- if (this.leftPoint?.Circle != null)
+ // Handle the positioning of the circles if they were set (by right or left clicking)
+ if (this.targetPoint?.Circle != null)
{
- if (pointsCanvas.Children.IndexOf(this.leftPoint.Circle) == -1 && this.mnuShowDrawnMarkers.IsChecked == true)
- pointsCanvas.Children.Add(this.leftPoint.Circle);
-
- Canvas.SetLeft(this.leftPoint.Circle, (this.leftPoint.PercentageX * pointsCanvas.ActualWidth / 100f) - (this.leftPoint.Circle.Width / 2));
- Canvas.SetTop(this.leftPoint.Circle, (this.leftPoint.PercentageY * pointsCanvas.ActualHeight / 100f) - (this.leftPoint.Circle.Height / 2));
- this.leftPoint.Circle.Width = this.leftPoint.Circle.Height = this.leftPoint.PercentageScale * this.pointsCanvas.ActualWidth / 100f;
+ // Add to canvas if not in there
+ this.addMapPointIfMissing(this.targetPoint);
}
- if (this.rightPoint?.Circle != null)
+ if (this.playerPoint?.Circle != null)
{
- if (pointsCanvas.Children.IndexOf(this.rightPoint.Circle) == -1 && this.mnuShowDrawnMarkers.IsChecked == true)
- pointsCanvas.Children.Add(this.rightPoint.Circle);
-
- Canvas.SetLeft(this.rightPoint.Circle, (this.rightPoint.PercentageX * pointsCanvas.ActualWidth / 100f) - (this.rightPoint.Circle.Width / 2));
- Canvas.SetTop(this.rightPoint.Circle, (this.rightPoint.PercentageY * pointsCanvas.ActualHeight / 100f) - (this.rightPoint.Circle.Height / 2));
- this.rightPoint.Circle.Width = this.rightPoint.Circle.Height = this.rightPoint.PercentageScale * this.pointsCanvas.ActualWidth / 100f;
+ // Add to canvas if not in there
+ this.addMapPointIfMissing(this.playerPoint);
}
- if (this.bombCircle?.Circle != null)
+ if (this.bombPoint?.Circle != null)
{
- if (pointsCanvas.Children.IndexOf(this.bombCircle.Circle) == -1 && this.mnuShowDrawnMarkers.IsChecked == true)
- pointsCanvas.Children.Add(this.bombCircle.Circle);
-
- Canvas.SetLeft(this.bombCircle.Circle, (this.bombCircle.PercentageX * pointsCanvas.ActualWidth / 100f) - (this.bombCircle.Circle.Width / 2));
- Canvas.SetTop(this.bombCircle.Circle, (this.bombCircle.PercentageY * pointsCanvas.ActualHeight / 100f) - (this.bombCircle.Circle.Height / 2));
- this.bombCircle.Circle.Width = this.bombCircle.Circle.Height = this.bombCircle.PercentageScale * this.pointsCanvas.ActualWidth / 100f;
+ // Add to canvas if not in there
+ this.addMapPointIfMissing(this.bombPoint);
}
- if((this.leftPoint?.Circle != null || this.bombCircle?.Circle != null) && this.rightPoint?.Circle != null)
+ if ((this.targetPoint?.Circle != null || this.bombPoint?.Circle != null) && this.playerPoint?.Circle != null)
{
- // Right click cirle exists, and left click circle or left click bomb circle exists
- // Ready to calculate damage and draw the in-between line
- if (this.DrawMode == eDrawMode.Shooting)
+ // Right click cirle exists, and left click target (or bomb) circle exists
+ // In other words: Two points were made, which means we can draw a line in between them
+ // Note: Only one left click circle should exist, depending on which draw mode we're in
+
+ // The points are on the canvas, because this was handled just before this
+
+ if (this.redrawLine)
{
- // Update line start pos to left click circle
- this.connectingLine.X1 = Canvas.GetLeft(this.leftPoint.Circle) + (this.leftPoint.Circle.Width / 2);
- this.connectingLine.Y1 = Canvas.GetTop(this.leftPoint.Circle) + (this.leftPoint.Circle.Height / 2);
+ Point leftClickPos;
+ Point rightClickPos;
+ // Ready to calculate damage and draw the in-between line
+ if (this.DrawMode == eDrawMode.Shooting)
+ {
+ // Update line start pos to left click target circle
+ leftClickPos.X = Canvas.GetLeft(this.targetPoint.Circle) + (this.targetPoint.Circle.Width / 2);
+ leftClickPos.Y = Canvas.GetTop(this.targetPoint.Circle) + (this.targetPoint.Circle.Height / 2);
+ }
+ else // We are in bomb mode. Change this if more modes are added
+ {
+ // Update line start pos to left click bomb circle
+ leftClickPos.X = Canvas.GetLeft(this.bombPoint.Circle) + (this.bombPoint.Circle.Width / 2);
+ leftClickPos.Y = Canvas.GetTop(this.bombPoint.Circle) + (this.bombPoint.Circle.Height / 2);
+ }
+ // Update line end pos to right click player circle
+ rightClickPos.X = Canvas.GetLeft(this.playerPoint.Circle) + (this.playerPoint.Circle.Width / 2);
+ rightClickPos.Y = Canvas.GetTop(this.playerPoint.Circle) + (this.playerPoint.Circle.Height / 2);
+
+ // Set visuals of the connecting line
+ this.connectingLine.Fill = null;
+ this.connectingLine.Stroke = new SolidColorBrush(this.connectingLineColour); // White, slightly transparent
+ this.connectingLine.StrokeThickness = 2;
+
+ // Make it not clickable
+ this.connectingLine.IsHitTestVisible = false;
+
+ this.connectingLine.HorizontalAlignment = HorizontalAlignment.Left;
+ this.connectingLine.VerticalAlignment = VerticalAlignment.Top;
+
+ var pathGeometry = new PathGeometry(new List
+ {
+ new PathFigure(leftClickPos, new List
+ {
+ new LineSegment(rightClickPos, isStroked: true)
+ }, closed: false)
+ });
+
+ this.connectingLine.Data = pathGeometry;
+ this.connectingLine.Tag = new Point[] { leftClickPos, rightClickPos };
+
+ if (!this.canvasContains(this.connectingLine))
+ {
+ this.canvasAdd(this.connectingLine);
+ this.lineDrawn = true;
+ }
+ this.redrawLine = false;
+ // Update top right corner distance texts
+ this.unitsDistance = this.calculateDistanceInUnits();
+
+ this.textDistanceUnits.Text = Math.Round(this.unitsDistance, 3).ToString(CultureInfo.InvariantCulture);
+ this.textDistanceMetres.Text = Math.Round(this.unitsDistance / 39.37, 3).ToString(CultureInfo.InvariantCulture);
+
+ // Recalculate and show damage
+ this.settings_Updated(null, null);
}
- else
- {
- // Update line start pos to left click bomb circle
- this.connectingLine.X1 = Canvas.GetLeft(this.bombCircle.Circle) + (this.bombCircle.Circle.Width / 2);
- this.connectingLine.Y1 = Canvas.GetTop(this.bombCircle.Circle) + (this.bombCircle.Circle.Height / 2);
- }
- // Update line end pos to right click circle
- this.connectingLine.X2 = Canvas.GetLeft(this.rightPoint.Circle) + (this.rightPoint.Circle.Width / 2);
- this.connectingLine.Y2 = Canvas.GetTop(this.rightPoint.Circle) + (this.rightPoint.Circle.Height / 2);
-
- this.connectingLine.Fill = null;
- this.connectingLine.Stroke = new SolidColorBrush(Color.FromArgb(140, 255, 255, 255)); // White, slightly transparent
- this.connectingLine.StrokeThickness = 2;
- // Make it not clickable
- this.connectingLine.IsHitTestVisible = false;
-
- int indexLine = pointsCanvas.Children.IndexOf(this.connectingLine);
- if (indexLine < 0 && this.mnuShowDrawnMarkers.IsChecked == true)
- {
- pointsCanvas.Children.Add(this.connectingLine);
- this.lineDrawn = true;
- }
-
- // Update top right corner distance texts
- this.unitsDistance = this.calculateDotDistanceInUnits();
-
- this.textDistanceUnits.Text = Math.Round(this.unitsDistance, 2).ToString();
- this.textDistanceMetres.Text = Math.Round(this.unitsDistance / 39.37, 2).ToString();
-
- // Recalculate damage
- this.settings_Updated(null, null);
}
else
{
@@ -612,13 +854,52 @@ namespace Damage_Calculator
this.lineDrawn = false;
}
- if(this.loadedMap != null)
+ if (this.loadedMap != null)
{
+ this.positionNavMeshes();
this.positionIcons();
this.positionSpawns();
}
}
+ /*private void getPlacePositions()
+ {
+ var places = new List<(string, Vector3)>();
+
+ if (this.loadedMap?.BspFilePath == null)
+ return;
+
+ foreach(var area in this.loadedMap.NavMesh.Header.NavAreas)
+ {
+ // Add the name and avg position to the list
+ if (area.PlaceID > 0 && area.PlaceID < this.loadedMap.NavMesh.Header.PlacesNames.Length)
+ places.Add((this.loadedMap.NavMesh.Header.PlacesNames[area.PlaceID - 1], new Vector3
+ {
+ X = (area.NorthWestCorner.X + area.SouthEastCorner.X) / 2,
+ Y = (area.NorthWestCorner.Y + area.SouthEastCorner.Y) / 2,
+ Z = (area.NorthWestCorner.Z + area.SouthEastCorner.Z) / 2,
+ }));
+ }
+
+ // Average all X and Y positions of every place again and put it in a new list
+ var placesCorrected = new Dictionary();
+
+ foreach(var place in places)
+ {
+ if (!placesCorrected.ContainsKey(place.Item1))
+ {
+ var correspondingPlaces = places.Where(pl => pl.Item1 == place.Item1);
+ float X = correspondingPlaces.Sum(place => place.Item2.X);
+ float Y = correspondingPlaces.Sum(place => place.Item2.Y);
+ float Z = correspondingPlaces.Sum(place => place.Item2.Z);
+ float newX = X / correspondingPlaces.Count();
+ float newY = Y / correspondingPlaces.Count();
+ float newZ = Z / correspondingPlaces.Count();
+ placesCorrected.Add(place.Item1, new Vector3 { X = newX, Y = newY, Z = newZ });
+ }
+ }
+ }*/
+
private void positionSpawns()
{
double size = this.getPixelsFromUnits(75);
@@ -626,19 +907,18 @@ namespace Damage_Calculator
{
if (spawn.Type != eSpawnType.Hostage)
{
- if (mnuAllowNonPrioritySpawns.IsChecked == false && (!spawn.IsPriority || (spawn.Team == ePlayerTeam.Terrorist && !this.loadedMap.HasPrioritySpawnsT) || (spawn.Team == ePlayerTeam.CounterTerrorist && !this.loadedMap.HasPrioritySpawnsCT)))
+ if (!Globals.Settings.AllowNonPrioritySpawns && (!spawn.IsPriority || (spawn.Team == ePlayerTeam.Terrorist && !this.loadedMap.HasPrioritySpawnsT) || (spawn.Team == ePlayerTeam.CounterTerrorist && !this.loadedMap.HasPrioritySpawnsCT)))
continue;
}
- if (spawn.Type == eSpawnType.Standard && mnuShowStandardSpawns.IsChecked == false)
+ if (spawn.Type == eSpawnType.Standard && !Globals.Settings.ShowStandardSpawns)
continue;
- else if (spawn.Type == eSpawnType.Wingman && mnuShow2v2Spawns.IsChecked == false)
+ else if (spawn.Type == eSpawnType.Wingman && !Globals.Settings.Show2v2Spawns)
continue;
- else if (spawn.Type == eSpawnType.Hostage && mnuShowHostageSpawns.IsChecked == false)
+ else if (spawn.Type == eSpawnType.Hostage && !Globals.Settings.ShowHostageSpawns)
continue;
- var existingViewBox = this.pointsCanvas.Children.OfType().FirstOrDefault(v => v.Tag == spawn);
- if (existingViewBox == null)
+ if (!canvasContains(child => child is Viewbox vb && vb.Tag == spawn))
{
Viewbox box = new Viewbox();
@@ -650,134 +930,113 @@ namespace Damage_Calculator
Point newCoords = this.getPointFromGameCoords(spawn.Origin.X, spawn.Origin.Y);
- pointsCanvas.Children.Add(box);
+ this.canvasAdd(box);
Canvas.SetLeft(box, newCoords.X - box.Width / 2);
Canvas.SetTop(box, newCoords.Y - box.Height / 2);
}
}
}
- private void positionIcons()
+ private void drawIconsIfFit(List<(string, Point)> itemsToAdd)
{
- double left;
- double top;
-
- if (mnuShowSpawnAreas.IsChecked == true)
+ foreach (var icon in itemsToAdd)
{
- // CT Icon
- if (this.CTSpawnIcon == null)
+ // Is icon already in canvas?
+ if (canvasContains(child => child is FrameworkElement element && element.Tag.ToString() == icon.Item1))
{
- this.CTSpawnIcon = new Image();
- this.CTSpawnIcon.Source = new BitmapImage(new Uri("icon_ct.png", UriKind.RelativeOrAbsolute));
- this.CTSpawnIcon.Width = 25;
- this.CTSpawnIcon.Height = 25;
- this.CTSpawnIcon.Opacity = 0.6;
- this.CTSpawnIcon.IsHitTestVisible = false;
+ // Yip, don't draw it again and check the next
+ continue;
}
- if (pointsCanvas.Children.IndexOf(CTSpawnIcon) == -1 && this.loadedMap.CTSpawnMultiplierX >= 0 && this.loadedMap.CTSpawnMultiplierY >= 0)
- pointsCanvas.Children.Add(CTSpawnIcon);
+ // Nope, create icon and add it
- left = this.loadedMap.CTSpawnMultiplierX * this.mapImage.ActualWidth - (CTSpawnIcon.ActualWidth / 2);
- top = this.loadedMap.CTSpawnMultiplierY * this.mapImage.ActualWidth - (CTSpawnIcon.ActualHeight / 2);
- if (left >= 0 && left <= this.mapImage.ActualWidth && top >= 0 && top <= this.mapImage.ActualHeight)
+ var iconImage = new Image();
+ iconImage.Source = new BitmapImage(new Uri(icon.Item1, UriKind.RelativeOrAbsolute));
+ iconImage.Width = 25;
+ iconImage.Height = 25;
+ iconImage.Opacity = 0.6;
+ iconImage.IsHitTestVisible = false;
+ iconImage.Tag = icon.Item1;
+
+ // Get absolute icon coordinates from relative ones (0.0 to 1.0)
+ double left = icon.Item2.X * this.mapImage.ActualWidth - (iconImage.ActualWidth / 2);
+ double top = icon.Item2.Y * this.mapImage.ActualWidth - (iconImage.ActualHeight / 2);
+
+ // Should icon be drawn outside the canvas?
+ if (left < 0 && left > this.mapImage.ActualWidth || top < 0 && top > this.mapImage.ActualHeight)
{
- Canvas.SetLeft(CTSpawnIcon, left);
- Canvas.SetTop(CTSpawnIcon, top);
+ // Yep, don't add it to the canvas and let the GC murder it
}
- // T Icon
- if (this.TSpawnIcon == null)
- {
- this.TSpawnIcon = new Image();
- this.TSpawnIcon.Source = new BitmapImage(new Uri("icon_t.png", UriKind.RelativeOrAbsolute));
- this.TSpawnIcon.Width = 25;
- this.TSpawnIcon.Height = 25;
- this.TSpawnIcon.Opacity = 0.6;
- this.TSpawnIcon.IsHitTestVisible = false;
- }
+ // It's in the image bounds so set position and add it
+ Canvas.SetLeft(iconImage, left);
+ Canvas.SetTop(iconImage, top);
- if (pointsCanvas.Children.IndexOf(TSpawnIcon) == -1 && this.loadedMap.TSpawnMultiplierX >= 0 && this.loadedMap.TSpawnMultiplierY >= 0)
- pointsCanvas.Children.Add(TSpawnIcon);
-
- left = this.loadedMap.TSpawnMultiplierX * this.mapImage.ActualWidth - (TSpawnIcon.ActualWidth / 2);
- top = this.loadedMap.TSpawnMultiplierY * this.mapImage.ActualWidth - (TSpawnIcon.ActualHeight / 2);
- if (left >= 0 && left <= this.mapImage.ActualWidth && top >= 0 && top <= this.mapImage.ActualHeight)
- {
- Canvas.SetLeft(TSpawnIcon, left);
- Canvas.SetTop(TSpawnIcon, top);
- }
- }
-
- if (mnuShowBombSites.IsChecked == true)
- {
- // Bomb A Icon
- if (this.ASiteIcon == null)
- {
- this.ASiteIcon = new Image();
- this.ASiteIcon.Source = new BitmapImage(new Uri("icon_a_site.png", UriKind.RelativeOrAbsolute));
- this.ASiteIcon.Width = 25;
- this.ASiteIcon.Height = 25;
- this.ASiteIcon.Opacity = 0.6;
- this.ASiteIcon.IsHitTestVisible = false;
- }
-
- if (pointsCanvas.Children.IndexOf(ASiteIcon) == -1 && this.loadedMap.BombAX >= 0 && this.loadedMap.BombAY >= 0)
- pointsCanvas.Children.Add(ASiteIcon);
-
- left = this.loadedMap.BombAX * this.mapImage.ActualWidth - (ASiteIcon.ActualWidth / 2);
- top = this.loadedMap.BombAY * this.mapImage.ActualWidth - (ASiteIcon.ActualHeight / 2);
- if (left >= 0 && left <= this.mapImage.ActualWidth && top >= 0 && top <= this.mapImage.ActualHeight)
- {
- Canvas.SetLeft(ASiteIcon, left);
- Canvas.SetTop(ASiteIcon, top);
- }
-
- // Bomb B Icon
- if (this.BSiteIcon == null)
- {
- this.BSiteIcon = new Image();
- this.BSiteIcon.Source = new BitmapImage(new Uri("icon_b_site.png", UriKind.RelativeOrAbsolute));
- this.BSiteIcon.Width = 25;
- this.BSiteIcon.Height = 25;
- this.BSiteIcon.Opacity = 0.6;
- this.BSiteIcon.IsHitTestVisible = false;
- }
-
- if (pointsCanvas.Children.IndexOf(BSiteIcon) == -1 && this.loadedMap.BombBX >= 0 && this.loadedMap.BombBY >= 0)
- pointsCanvas.Children.Add(BSiteIcon);
-
- left = this.loadedMap.BombBX * this.mapImage.ActualWidth - (BSiteIcon.ActualWidth / 2);
- top = this.loadedMap.BombBY * this.mapImage.ActualWidth - (BSiteIcon.ActualHeight / 2);
- if (left >= 0 && left <= this.mapImage.ActualWidth && top >= 0 && top <= this.mapImage.ActualHeight)
- {
- Canvas.SetLeft(BSiteIcon, left);
- Canvas.SetTop(BSiteIcon, top);
- }
+ canvasAdd(iconImage);
}
}
- private double calculateDotDistanceInUnits()
+ private void positionIcons()
{
- double leftX = this.connectingLine.X1;
- double leftY = this.connectingLine.Y1;
+ if (Globals.Settings.ShowSpawnAreas)
+ {
+ var iconsToDraw = new List<(string, Point)>
+ {
+ // The multipliers are values from 0.0 to 1.0 depending on how far right or down they are relatively to the map
+ ("icon_ct.png", new Point(this.loadedMap.CTSpawnMultiplierX, this.loadedMap.CTSpawnMultiplierY)),
+ ("icon_t.png", new Point(this.loadedMap.TSpawnMultiplierX, this.loadedMap.TSpawnMultiplierY))
+ };
- double rightX = this.connectingLine.X2;
- double rightY = this.connectingLine.Y2;
+ this.drawIconsIfFit(iconsToDraw);
+ }
- // Distance in shown pixels
- double diffPixels = Math.Sqrt(Math.Pow(Math.Abs(leftX - rightX), 2) + Math.Pow(Math.Abs(leftY - rightY), 2));
+ if (Globals.Settings.ShowBombSites)
+ {
+ var iconsToDraw = new List<(string, Point)>
+ {
+ // The multipliers are values from 0.0 to 1.0 depending on how far right or down they are relatively to the map
+ ("icon_a_site.png", new Point(this.loadedMap.BombAX, this.loadedMap.BombAY)),
+ ("icon_b_site.png", new Point(this.loadedMap.BombBX, this.loadedMap.BombBY))
+ };
- // Percentage on shown pixels
- double diffPerc = diffPixels * 100f / this.mapImage.ActualWidth;
+ this.drawIconsIfFit(iconsToDraw);
+ }
+ }
- // Distance on original pixel scales
- double diffPixelsOriginal = diffPerc * (this.mapImage.Source as BitmapSource).PixelWidth / 100f;
+ private double calculateDistanceInUnits()
+ {
+ // left and right point for the X and Y coordinates (in pixels) so we gotta convert those
+ Point[] points = this.connectingLine.Tag as Point[];
- // times scale multiplier
- double unitsDifference = diffPixelsOriginal * this.loadedMap.MapSizeMultiplier;
+ double leftX = this.getUnitsFromPixels(points[0].X);
+ double leftY = this.getUnitsFromPixels(points[0].Y);
+ double leftZ;
- return unitsDifference;
+ double rightX = this.getUnitsFromPixels(points[1].X);
+ double rightY = this.getUnitsFromPixels(points[1].Y);
+ double rightZ;
+
+ if (this.playerPoint.AssociatedAreaID < 0 ||
+ ((this.DrawMode == eDrawMode.Shooting && this.targetPoint.AssociatedAreaID < 0)
+ || (this.DrawMode == eDrawMode.Bomb && this.bombPoint.AssociatedAreaID < 0)))
+ {
+ leftZ = 0;
+ rightZ = 0;
+ }
+ else
+ {
+ leftZ = this.DrawMode == eDrawMode.Shooting ? this.targetPoint.Z : this.bombPoint.Z;
+ rightZ = this.playerPoint.Z;
+ }
+
+ // Distance in shown pixels in 2D
+ double diffPixels2D = Math.Sqrt(Math.Pow(Math.Abs(leftX - rightX), 2) + Math.Pow(Math.Abs(leftY - rightY), 2));
+ double unitsDifference2D = this.getUnitsFromPixels(diffPixels2D);
+
+ // Add Z height to calculation, unless a point has no area ID associated, then it stays 2D
+ double diffDistance3D = Math.Sqrt(Math.Pow(diffPixels2D, 2) + Math.Pow(Math.Abs(leftZ - rightZ), 2));
+
+ return diffDistance3D;
}
private void calculateAndUpdateShootingDamage()
@@ -895,6 +1154,278 @@ namespace Damage_Calculator
return flDamage;
}
+ private bool isPointInMap(Point position)
+ {
+ return position.X >= 0 && position.Y >= 0 && position.X <= mapImage.ActualWidth && position.Y <= mapImage.ActualHeight;
+ }
+
+ private void changeTheme(REghZyFramework.Themes.ThemesController.ThemeTypes newTheme)
+ {
+ REghZyFramework.Themes.ThemesController.SetTheme(newTheme);
+
+ // Additional stuff you want to change manually
+ switch (newTheme)
+ {
+ case REghZyFramework.Themes.ThemesController.ThemeTypes.Dark:
+ rectTop.Fill = rectLeftSide.Fill = rectRightSide.Fill = new SolidColorBrush(Colors.White);
+ txtEasterEggMetres.Text = "Metres:";
+ break;
+ case REghZyFramework.Themes.ThemesController.ThemeTypes.Light:
+ rectTop.Fill = rectLeftSide.Fill = rectRightSide.Fill = new SolidColorBrush(Colors.Black);
+ txtEasterEggMetres.Text = "Meters:";
+ break;
+ }
+ }
+
+ private void canvasReload()
+ {
+ // Reload map list if map filters changed
+ this.updateMapsWithCurrentFilter();
+
+ // Reload visuals
+ this.resetCanvas(true);
+ this.changeTheme(Globals.Settings.Theme);
+ }
+
+ private float getPointHeightInArea(float x, float y, NavArea area)
+ {
+ Vector3[][] groups = new Vector3[][]
+ {
+ // Order within nested array: Point that gets weighted => Point that supplies its X (origin for X weight, 0% basically) => Point that supplies its Y in the same manner
+ // So basically the second and third item is respectively the point left/right and above/under the first item (if north is up)
+ new Vector3[] { area.ActualNorthWestCorner, area.ActualNorthEastCorner, area.ActualSouthWestCorner },
+ new Vector3[] { area.ActualNorthEastCorner, area.ActualNorthWestCorner, area.ActualSouthEastCorner },
+ new Vector3[] { area.ActualSouthWestCorner, area.ActualSouthEastCorner, area.ActualNorthWestCorner },
+ new Vector3[] { area.ActualSouthEastCorner, area.ActualSouthWestCorner, area.ActualNorthEastCorner }
+ };
+
+ float resultHeight = 0;
+
+ foreach (Vector3[] group in groups)
+ {
+ float xWeight = Shared.Globals.Map(x, group[1].X, group[0].X, 0, 1);
+ float yWeight = Shared.Globals.Map(y, group[2].Y, group[0].Y, 0, 1);
+ float combinedWeight = xWeight * yWeight;
+
+ resultHeight += combinedWeight * group[0].Z;
+ }
+
+ return resultHeight;
+ }
+
+ private void recalculateCoordinates()
+ {
+ if (this.mapImage.Source == null)
+ return;
+
+ var position = Mouse.GetPosition(mapImage);
+ if (this.isPointInMap(position))
+ {
+ Point posInGame = this.getGameCoordsFromPoint(position.X, position.Y);
+
+ this.currentMouseCoord.X = (float)posInGame.X;
+ txtCursorX.Text = Math.Round(posInGame.X, 2).ToString(CultureInfo.InvariantCulture);
+
+ this.currentMouseCoord.Y = (float)posInGame.Y;
+ txtCursorY.Text = Math.Round(posInGame.Y, 2).ToString(CultureInfo.InvariantCulture);
+
+ // Height to be displayed further down, depending on area chosen
+ float newZ = 0;
+
+ if (this.loadedMap.NavMesh?.Header?.NavAreas != null)
+ {
+ var navAreasFound = new List();
+
+ foreach (var area in this.loadedMap.NavMesh.Header.NavAreas)
+ {
+ if (posInGame.X < area.ActualNorthWestCorner.X)
+ continue;
+ if (posInGame.X > area.ActualNorthEastCorner.X)
+ continue;
+ if (posInGame.Y > area.ActualNorthWestCorner.Y)
+ continue;
+ if (posInGame.Y < area.ActualSouthWestCorner.Y)
+ continue;
+
+ // HERE we found an area that the mouse is in. Here we need to count areas in case they overlap
+ navAreasFound.Add(area);
+ }
+
+ uint previousAreaID = this.getCurrentArea()?.ID ?? 0;
+
+ // Select layer closest to previous one height-wise
+ // (positionNavAreas() orders the NAV areas from bottom to top when adding so last one will be top one (based on average area height))
+ if ((this.currentHeightLayer < 0 || this.currentHeightLayer >= navAreasFound.Count || this.hoveredNavAreas.Count != navAreasFound.Count) && navAreasFound.Count > 0 && !this.userChangedLayer)
+ {
+ // Current height layer selection is undefined or bigger than the amound of areas we have
+ // Or the amount of areas hovered over has changed.
+ // In that case set it to the layer with the lowest Z difference to the previously selected one
+
+ if (navAreasFound.Count == 1 || this.hoveredNavAreas.Count == 0)
+ this.currentHeightLayer = 0;
+ else
+ {
+ int newHeightLayerIndex = 0;
+ float lowestAreaHeightDifference = -1;
+ for (int i = 0; i < navAreasFound.Count; i++)
+ {
+ float heightDiffToPrevArea = Math.Abs(navAreasFound[i].MedianPosition.Z - this.hoveredNavAreas[this.currentHeightLayer < 0 ? 0 : this.currentHeightLayer].MedianPosition.Z);
+ if (heightDiffToPrevArea < lowestAreaHeightDifference || lowestAreaHeightDifference < 0)
+ {
+ // Difference of currently looped area and last hovered area is smaller than previous loop iterations
+ newHeightLayerIndex = i;
+ lowestAreaHeightDifference = heightDiffToPrevArea;
+ }
+ }
+
+ this.currentHeightLayer = newHeightLayerIndex;
+ }
+ }
+
+ if (navAreasFound.Count == 0)
+ this.currentHeightLayer = -1;
+
+ // Update areas
+ this.hoveredNavAreas = navAreasFound;
+ this.currentHeightLayer += this.userHeightLayerOffset;
+
+ uint newAreaID = this.getCurrentArea()?.ID ?? 0;
+
+ if (this.hoveredNavAreas.Count > 0)
+ {
+ // There are areas hovered over
+ newZ = this.getPointHeightInArea(currentMouseCoord.X, currentMouseCoord.Y, this.hoveredNavAreas[this.currentHeightLayer]);
+ }
+
+ this.fillNavAreaInfo();
+ if (this.userChangedLayer || previousAreaID != newAreaID || newAreaID < 1)
+ {
+ // Hovered area changed so handle content of info box and highlighting of areas
+
+ if (newAreaID > 0)
+ {
+ if (this.hoveredNavAreas.Count > 0 && this.currentHeightLayer > -1)
+ {
+ // There's a new area to highlight
+ updateNavAreaHovered(newAreaID, setHovered: true);
+ }
+ }
+ if (previousAreaID > 0)
+ {
+ // There's an old area to de-highlight
+ updateNavAreaHovered(previousAreaID, setHovered: false);
+ }
+ }
+
+ this.userChangedLayer = false;
+ this.userHeightLayerOffset = 0;
+ }
+
+ // Display height
+ this.currentMouseCoord.Z = newZ;
+ txtCursorZ.Text = Math.Round(newZ, 2).ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ txtCursorX.Text = txtCursorY.Text = "0";
+ }
+ }
+
+ private void updateNavAreaHovered(uint areaID, bool setHovered)
+ {
+ foreach(FrameworkElement element in this.mapCanvas.Children)
+ {
+ if(element.Tag is NavArea area && area.ID == areaID)
+ {
+ setNavAreaColour(element as System.Windows.Shapes.Path, area, setHovered);
+ return;
+ }
+ }
+ }
+
+ private void setNavAreaColour(System.Windows.Shapes.Path pathOfArea, NavArea area, bool isHovered)
+ {
+ Color newColour;
+ if (isHovered)
+ {
+ newColour = Globals.Settings.NavHoverColour;
+ }
+ else
+ {
+ // Map average area height between two configurable colours
+ byte newA = (byte)Shared.Globals.Map(area.MedianPosition.Z, loadedMap.NavMesh.MinZ ?? 0, loadedMap.NavMesh.MaxZ ?? 0, Globals.Settings.NavLowColour.A, Globals.Settings.NavHighColour.A);
+ byte newR = (byte)Shared.Globals.Map(area.MedianPosition.Z, loadedMap.NavMesh.MinZ ?? 0, loadedMap.NavMesh.MaxZ ?? 0, Globals.Settings.NavLowColour.R, Globals.Settings.NavHighColour.R);
+ byte newG = (byte)Shared.Globals.Map(area.MedianPosition.Z, loadedMap.NavMesh.MinZ ?? 0, loadedMap.NavMesh.MaxZ ?? 0, Globals.Settings.NavLowColour.G, Globals.Settings.NavHighColour.G);
+ byte newB = (byte)Shared.Globals.Map(area.MedianPosition.Z, loadedMap.NavMesh.MinZ ?? 0, loadedMap.NavMesh.MaxZ ?? 0, Globals.Settings.NavLowColour.B, Globals.Settings.NavHighColour.B);
+ newColour = Color.FromArgb(newA, newR, newG, newB);
+ }
+
+ switch (Globals.Settings.NavDisplayMode)
+ {
+ case Shared.NavDisplayModes.Wireframe:
+ pathOfArea.Stroke = new SolidColorBrush(newColour);
+ pathOfArea.StrokeThickness = 1;
+ pathOfArea.Fill = null;
+ break;
+ case Shared.NavDisplayModes.Filled:
+ pathOfArea.Stroke = null;
+ pathOfArea.StrokeThickness = 0;
+ pathOfArea.Fill = new SolidColorBrush(newColour);
+ break;
+ }
+ }
+
+ private NavArea getCurrentArea()
+ {
+ if (this.hoveredNavAreas?.Count > 0 && this.currentHeightLayer > -1 && this.currentHeightLayer < this.hoveredNavAreas.Count)
+ return this.hoveredNavAreas[this.currentHeightLayer];
+
+ return null;
+ }
+
+ private void fillNavAreaInfo()
+ {
+ // Show current area not as index from 0 but starting from 1 (as in 1st layer, 2nd layer) etc.
+ if (this.hoveredNavAreas.Count > 0)
+ {
+ this.txtNavAreasAmount.Text = $"{(this.currentHeightLayer < 0 ? 0 : this.currentHeightLayer + 1)}/{this.hoveredNavAreas.Count}";
+ this.txtNavAreaHeightPercentage.Text = Math.Round(Shared.Globals.Map(this.hoveredNavAreas[this.currentHeightLayer].MedianPosition.Z, this.loadedMap.NavMesh.MinZ ?? 0, this.loadedMap.NavMesh.MaxZ ?? 0, 0, 100), 1).ToString(CultureInfo.InvariantCulture) + " %";
+ this.txtNavAreaID.Text = this.hoveredNavAreas[this.currentHeightLayer].ID.ToString();
+ this.txtNavAreaConnectionsAmount.Text = this.hoveredNavAreas[this.currentHeightLayer].ConnectionData.Sum(direction => direction.Count).ToString();
+ this.txtNavAreaPlace.Text = this.hoveredNavAreas[this.currentHeightLayer].PlaceID == 0 ? "None" : this.loadedMap.NavMesh.Header.PlacesNames[this.hoveredNavAreas[this.currentHeightLayer].PlaceID - 1];
+ }
+ else
+ {
+ this.txtNavAreasAmount.Text = "None";
+ this.txtNavAreaHeightPercentage.Text = "None";
+ this.txtNavAreaID.Text = "None";
+ this.txtNavAreaConnectionsAmount.Text = "None";
+ this.txtNavAreaPlace.Text = "None";
+ }
+ }
+
+ private void fillWeaponInfo()
+ {
+ if(this.selectedWeapon != null)
+ {
+ this.groupWeaponName.Header = this.selectedWeapon.ClassName.Replace('_','-');
+ this.txtWeaponBaseDamage.Text = this.selectedWeapon.BaseDamage.ToString(CultureInfo.InvariantCulture);
+ this.txtWeaponArmorPenetration.Text = this.selectedWeapon.ArmorPenetration.ToString(CultureInfo.InvariantCulture);
+ this.txtWeaponDamageDropoff.Text = this.selectedWeapon.DamageDropoff.ToString(CultureInfo.InvariantCulture);
+ this.txtWeaponMaxRange.Text = this.selectedWeapon.MaxBulletRange.ToString(CultureInfo.InvariantCulture);
+ this.txtWeaponHeadshotModifier.Text = this.selectedWeapon.HeadshotModifier.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ this.groupWeaponName.Header = "WEAPON-NAME";
+ this.txtWeaponBaseDamage.Text = "None";
+ this.txtWeaponArmorPenetration.Text = "None";
+ this.txtWeaponDamageDropoff.Text = "None";
+ this.txtWeaponMaxRange.Text = "None";
+ this.txtWeaponHeadshotModifier.Text = "None";
+ }
+ }
+
#region events
private void rightZoomBorder_SizeChanged(object sender, SizeChangedEventArgs e)
{
@@ -912,12 +1443,6 @@ namespace Damage_Calculator
}
}
- private void visibilityMenu_CheckChanged(object sender, RoutedEventArgs e)
- {
- this.resetCanvas(true);
- this.UpdateLayout();
- }
-
private void radioModeShooting_Checked(object sender, RoutedEventArgs e)
{
this.resetCanvas();
@@ -942,88 +1467,95 @@ namespace Damage_Calculator
private void mapImage_LayoutUpdated(object sender, EventArgs e)
{
- this.updateCirclePositions();
+ this.drawPointsAndConnectingLine();
}
private void mapImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (this.DrawMode == eDrawMode.Shooting)
{
- if (this.leftPoint == null)
- this.leftPoint = new MapPoint();
+ if (this.targetPoint == null)
+ this.targetPoint = new MapPoint();
- Point mousePos = Mouse.GetPosition(this.pointsCanvas);
- this.pointsCanvas.Children.Remove(this.leftPoint.Circle);
+ Point mousePos = Mouse.GetPosition(this.mapCanvas);
+ this.canvasRemove(this.targetPoint.Circle);
- var circle = this.getPointEllipse(this.leftPointColour);
+ var circle = this.getPointEllipse(this.leftClickPointColour);
- this.pointsCanvas.Children.Add(circle);
+ this.canvasAdd(circle);
- this.leftPoint.PercentageX = mousePos.X * 100f / this.pointsCanvas.ActualWidth;
- this.leftPoint.PercentageY = mousePos.Y * 100f / this.pointsCanvas.ActualHeight;
- this.leftPoint.PercentageScale = circle.Width * 100f / this.pointsCanvas.ActualWidth;
+ this.targetPoint.PercentageX = mousePos.X * 100f / this.mapCanvas.ActualWidth;
+ this.targetPoint.PercentageY = mousePos.Y * 100f / this.mapCanvas.ActualHeight;
+ this.targetPoint.PercentageScale = circle.Width * 100f / this.mapCanvas.ActualWidth;
+ this.targetPoint.Z = this.currentMouseCoord.Z;
+ if(this.currentHeightLayer >= 0)
+ // Associate area ID to see if we want just 2D distance in case one point has no area (and with that, Z value) with it
+ this.targetPoint.AssociatedAreaID = (int)this.hoveredNavAreas[this.currentHeightLayer].ID;
+ else
+ this.targetPoint.AssociatedAreaID = -1;
- this.leftPoint.Circle = circle;
+ this.targetPoint.Circle = circle;
+ this.redrawLine = true;
- this.updateCirclePositions();
+ this.drawPointsAndConnectingLine();
}
else if (this.DrawMode == eDrawMode.Bomb)
{
- if (this.bombCircle == null)
- this.bombCircle = new MapPoint();
+ if (this.bombPoint == null)
+ this.bombPoint = new MapPoint();
- Point mousePos = Mouse.GetPosition(this.pointsCanvas);
- this.pointsCanvas.Children.Remove(this.bombCircle.Circle);
+ Point mousePos = Mouse.GetPosition(this.mapCanvas);
+ this.canvasRemove(this.bombPoint.Circle);
- var circle = this.getBombEllipse(this.leftPointColour);
+ var circle = this.getBombEllipse(this.leftClickPointColour);
- this.pointsCanvas.Children.Add(circle);
+ this.canvasAdd(circle);
- this.bombCircle.PercentageX = mousePos.X * 100f / this.pointsCanvas.ActualWidth;
- this.bombCircle.PercentageY = mousePos.Y * 100f / this.pointsCanvas.ActualHeight;
- this.bombCircle.PercentageScale = circle.Width * 100f / this.pointsCanvas.ActualWidth;
+ this.bombPoint.PercentageX = mousePos.X * 100f / this.mapCanvas.ActualWidth;
+ this.bombPoint.PercentageY = mousePos.Y * 100f / this.mapCanvas.ActualHeight;
+ this.bombPoint.PercentageScale = circle.Width * 100f / this.mapCanvas.ActualWidth;
+ this.bombPoint.Z = this.currentMouseCoord.Z;
+ if (this.currentHeightLayer >= 0)
+ // Associate area ID to see if we want just 2D distance in case one point has no area (and with that, Z value) with it
+ this.bombPoint.AssociatedAreaID = (int)this.hoveredNavAreas[this.currentHeightLayer].ID;
+ else
+ this.bombPoint.AssociatedAreaID = -1;
- this.bombCircle.Circle = circle;
+ this.bombPoint.Circle = circle;
- this.updateCirclePositions();
+ this.redrawLine = true;
+
+ this.drawPointsAndConnectingLine();
}
}
private void mapImage_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
- if (this.rightPoint == null)
- this.rightPoint = new MapPoint();
+ if (this.playerPoint == null)
+ this.playerPoint = new MapPoint();
- Point mousePos = Mouse.GetPosition(this.pointsCanvas);
- this.pointsCanvas.Children.Remove(this.rightPoint.Circle);
+ Point mousePos = Mouse.GetPosition(this.mapCanvas);
+ this.canvasRemove(this.playerPoint.Circle);
- var circle = this.getPointEllipse(this.rightPointColour);
+ var circle = this.getPointEllipse(this.rightClickPointColour);
- this.pointsCanvas.Children.Add(circle);
+ this.canvasAdd(circle);
- this.rightPoint.PercentageX = mousePos.X * 100f / this.pointsCanvas.ActualWidth;
- this.rightPoint.PercentageY = mousePos.Y * 100f / this.pointsCanvas.ActualHeight;
- this.rightPoint.PercentageScale = circle.Width * 100f / this.pointsCanvas.ActualWidth;
+ this.playerPoint.PercentageX = mousePos.X * 100f / this.mapCanvas.ActualWidth;
+ this.playerPoint.PercentageY = mousePos.Y * 100f / this.mapCanvas.ActualHeight;
+ this.playerPoint.PercentageScale = circle.Width * 100f / this.mapCanvas.ActualWidth;
+ this.playerPoint.Z = this.currentMouseCoord.Z;
+ if (this.currentHeightLayer >= 0)
+ // Associate area ID to see if we want just 2D distance in case one point has no area (and with that, Z value) with it
+ this.playerPoint.AssociatedAreaID = (int)this.hoveredNavAreas[this.currentHeightLayer].ID;
+ else
+ this.playerPoint.AssociatedAreaID = -1;
- this.rightPoint.Circle = circle;
+ this.playerPoint.Circle = circle;
- this.updateCirclePositions();
- }
+ this.redrawLine = true;
- private void changeTheme_Click(object sender, RoutedEventArgs e)
- {
- switch (int.Parse(((MenuItem)sender).Uid))
- {
- case 0: REghZyFramework.Themes.ThemesController.SetTheme(REghZyFramework.Themes.ThemesController.ThemeTypes.Dark);
- rectTop.Fill = rectSide.Fill = new SolidColorBrush(Colors.White);
- txtEasterEggMetres.Text = "Metres:";
- break;
- case 1: REghZyFramework.Themes.ThemesController.SetTheme(REghZyFramework.Themes.ThemesController.ThemeTypes.Light);
- rectTop.Fill = rectSide.Fill = new SolidColorBrush(Colors.Black);
- txtEasterEggMetres.Text = "Meters:";
- break;
- }
- e.Handled = true;
+ this.drawPointsAndConnectingLine();
}
private void comboBoxMaps_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -1055,6 +1587,7 @@ namespace Damage_Calculator
var weapon = ((sender as ComboBox).SelectedItem as ComboBoxItem)?.Tag as CsgoWeapon;
this.selectedWeapon = weapon;
+ this.fillWeaponInfo();
settings_Updated(null, null);
}
@@ -1074,38 +1607,32 @@ namespace Damage_Calculator
private void Window_MouseMove(object sender, MouseEventArgs e)
{
- if (this.mapImage.Source == null)
- return;
-
- var position = Mouse.GetPosition(mapImage);
- if (position.X >= 0 && position.Y >= 0 && position.X <= mapImage.ActualWidth && position.Y <= mapImage.ActualHeight)
- {
- // Percentage on shown pixels
- double diffPercX = position.X * 100f / this.mapImage.ActualWidth;
- // Distance on original pixel scales
- double diffPixelsOriginalX = diffPercX * (this.mapImage.Source as BitmapSource).PixelWidth / 100f;
- // times scale multiplier
- double unitsDifferenceX = diffPixelsOriginalX * this.loadedMap.MapSizeMultiplier;
- txtCursorX.Text = Math.Round(this.loadedMap.UpperLeftWorldXCoordinate + unitsDifferenceX, 2).ToString(System.Globalization.CultureInfo.InvariantCulture);
-
- // Percentage on shown pixels
- double diffPercY = position.Y * 100f / this.mapImage.ActualWidth;
- // Distance on original pixel scales
- double diffPixelsOriginalY = diffPercY * (this.mapImage.Source as BitmapSource).PixelWidth / 100f;
- // times scale multiplier
- double unitsDifferenceY = diffPixelsOriginalY * this.loadedMap.MapSizeMultiplier;
- txtCursorY.Text = Math.Round(this.loadedMap.UpperLeftWorldYCoordinate - unitsDifferenceY, 2).ToString(System.Globalization.CultureInfo.InvariantCulture);
- }
- else
- {
- txtCursorX.Text = txtCursorY.Text = "0";
- }
+ this.recalculateCoordinates();
}
+
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
- Clipboard.SetText(txtCursorX.Text + " " + txtCursorY.Text);
+ Clipboard.SetText($"setpos_exact {this.currentMouseCoord.X.ToString(CultureInfo.InvariantCulture)} {this.currentMouseCoord.Y.ToString(CultureInfo.InvariantCulture)} {(this.currentMouseCoord.Z + 25).ToString(CultureInfo.InvariantCulture)}");
+ }
+ else if(e.Key == Key.PageUp)
+ {
+ if(this.currentHeightLayer >= 0 && this.currentHeightLayer < this.hoveredNavAreas.Count - 1)
+ {
+ this.userHeightLayerOffset = 1;
+ this.userChangedLayer = true;
+ this.recalculateCoordinates();
+ }
+ }
+ else if(e.Key == Key.PageDown)
+ {
+ if (this.currentHeightLayer > 0)
+ {
+ this.userHeightLayerOffset = -1;
+ this.userChangedLayer = true;
+ this.recalculateCoordinates();
+ }
}
// Pass it on for spacebar pan start
@@ -1125,9 +1652,19 @@ namespace Damage_Calculator
// We want space for us alone, so give no child element a piece of dat cake
e.Handled = true;
}
- private void filterMenu_CheckChanged(object sender, RoutedEventArgs e)
+
+ private void mnuOpenSettings_Click(object sender, RoutedEventArgs e)
{
- this.updateMapsWithCurrentFilter();
+ var settingsWindow = new wndSettings(this.loadedMap);
+ settingsWindow.Owner = this;
+ settingsWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ bool reloadWithNewSettings = settingsWindow.ShowDialog() == true;
+
+ if (reloadWithNewSettings)
+ {
+ // Settings *might* have changed (User pressed "Save" so just in case, reload with new settings)
+ this.canvasReload();
+ }
}
#endregion
}
diff --git a/DamageCalculator/DamageCalculator/Settings.cs b/DamageCalculator/DamageCalculator/Settings.cs
new file mode 100644
index 0000000..4b6b4fb
--- /dev/null
+++ b/DamageCalculator/DamageCalculator/Settings.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+
+namespace Damage_Calculator
+{
+ public class Settings : ICloneable
+ {
+ [XmlIgnore]
+ public static readonly string SettingsFileName = "settings.xml";
+
+ public Settings() { /* Do nothing */ }
+
+ // VISUAL SETTINGS
+
+ public REghZyFramework.Themes.ThemesController.ThemeTypes Theme { get; set; } = REghZyFramework.Themes.ThemesController.ThemeTypes.Dark;
+ public List MapCoordinateOffsets { get; set; } = new();
+ public bool ShowBombSites { get; set; } = true;
+ public bool ShowSpawnAreas { get; set; } = true;
+ public bool ShowStandardSpawns { get; set; } = true;
+ public bool Show2v2Spawns { get; set; } = true;
+ public bool ShowHostageSpawns { get; set; } = true;
+ public bool AllowNonPrioritySpawns { get; set; } = true;
+ public System.Windows.Media.Color NavLowColour { get; set; } = System.Windows.Media.Color.FromArgb(255, 20, 20, 20);
+ public System.Windows.Media.Color NavHighColour { get; set; } = System.Windows.Media.Color.FromArgb(140, 255, 255, 255);
+ public System.Windows.Media.Color NavHoverColour { get; set; } = System.Windows.Media.Color.FromArgb(140, 255, 165, 0);
+ public Shared.NavDisplayModes NavDisplayMode { get; set; } = Shared.NavDisplayModes.None;
+ public double ShowNavAreasAbove { get; set; } = 0;
+ public double ShowNavAreasBelow { get; set; } = 1;
+
+ // MAP FILTERS
+
+ public bool ShowDefusalMaps { get; set; } = true;
+ public bool ShowHostageMaps { get; set; } = true;
+ public bool ShowArmsRaceMaps { get; set; } = true;
+ public bool ShowDangerZoneMaps { get; set; } = true;
+ public bool ShowMapsMissingBsp { get; set; } = true;
+ public bool ShowMapsMissingNav { get; set; } = true;
+ public bool ShowMapsMissingAin { get; set; } = true;
+
+ public object Clone()
+ {
+ return this.MemberwiseClone();
+ }
+ }
+}
diff --git a/DamageCalculator/DamageCalculator/wndSettings.xaml b/DamageCalculator/DamageCalculator/wndSettings.xaml
new file mode 100644
index 0000000..f67a161
--- /dev/null
+++ b/DamageCalculator/DamageCalculator/wndSettings.xaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DamageCalculator/DamageCalculator/wndSettings.xaml.cs b/DamageCalculator/DamageCalculator/wndSettings.xaml.cs
new file mode 100644
index 0000000..8328000
--- /dev/null
+++ b/DamageCalculator/DamageCalculator/wndSettings.xaml.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Damage_Calculator
+{
+ ///
+ /// Interaction logic for wndSettings.xaml
+ ///
+ public partial class wndSettings : Window
+ {
+ ///
+ /// The settings of this Window. When creating the Window this is a copy of the current settings to be modified.
+ ///
+ private Settings settings;
+
+ ///
+ /// We need this to set the map coordinate offsets for each map respectively.
+ ///
+ private Shared.Models.CsgoMap currentMap = null;
+
+ private string getCurrentMapDDSName()
+ {
+ return System.IO.Path.GetFileNameWithoutExtension(currentMap.MapImagePath);
+ }
+
+ public wndSettings(Shared.Models.CsgoMap currentMap)
+ {
+ InitializeComponent();
+ this.currentMap = currentMap;
+ this.MaxHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
+ this.settings = (Settings)Globals.Settings.Clone();
+ this.fillSettings();
+ }
+
+ private void fillSettings()
+ {
+ // We'll do it the old school way because I have a headache
+
+ // Visuals
+
+ // Theme
+ switch (this.settings.Theme)
+ {
+ case REghZyFramework.Themes.ThemesController.ThemeTypes.Light:
+ this.radioLightTheme.IsChecked = true;
+ break;
+ default:
+ this.radioDarkTheme.IsChecked = true;
+ break;
+ }
+
+ var mapOverrideItem = this.settings.MapCoordinateOffsets.FirstOrDefault(map => map.DDSFileName == this.getCurrentMapDDSName());
+ if (mapOverrideItem != null)
+ {
+ this.intCurrentMapCoordsOffsetX.Value = (int)mapOverrideItem.CoordOffset.X;
+ this.intCurrentMapCoordsOffsetY.Value = (int)mapOverrideItem.CoordOffset.Y;
+ this.intCurrentMapMultiplierOverride.Value = mapOverrideItem.MapScale;
+ }
+ this.txtCurrentMapMultiplier.Content = this.currentMap.MapSizeMultiplier;
+
+ this.mnuShowBombSites.IsChecked = this.settings.ShowBombSites;
+ this.mnuShowSpawnAreas.IsChecked = this.settings.ShowSpawnAreas;
+ this.mnuShowStandardSpawns.IsChecked = this.settings.ShowStandardSpawns;
+ this.mnuShow2v2Spawns.IsChecked = this.settings.Show2v2Spawns;
+ this.mnuShowHostageSpawns.IsChecked = this.settings.ShowHostageSpawns;
+ this.mnuAllowNonPrioritySpawns.IsChecked = this.settings.AllowNonPrioritySpawns;
+
+ this.colourNavLow.SelectedColor = this.settings.NavLowColour;
+ this.colourNavHigh.SelectedColor = this.settings.NavHighColour;
+ this.colourNavHover.SelectedColor = this.settings.NavHoverColour;
+
+ foreach(string navDisplayMode in Enum.GetNames(typeof(Shared.NavDisplayModes)))
+ {
+ comboNavDisplayModes.Items.Add(navDisplayMode);
+ if (navDisplayMode == Enum.GetName(this.settings.NavDisplayMode))
+ comboNavDisplayModes.SelectedItem = navDisplayMode;
+ }
+
+ sliderNavAbove.Value = this.settings.ShowNavAreasAbove * 100f;
+ sliderNavBelow.Value = this.settings.ShowNavAreasBelow * 100f;
+
+ // Map filter
+ this.mnuShowDefusalMaps.IsChecked = this.settings.ShowDefusalMaps;
+ this.mnuShowHostageMaps.IsChecked = this.settings.ShowHostageMaps;
+ this.mnuShowArmsRaceMaps.IsChecked = this.settings.ShowArmsRaceMaps;
+ this.mnuShowDangerZoneMaps.IsChecked = this.settings.ShowDangerZoneMaps;
+ this.mnuShowMapsMissingBsp.IsChecked = this.settings.ShowMapsMissingBsp;
+ this.mnuShowMapsMissingNav.IsChecked = this.settings.ShowMapsMissingNav;
+ this.mnuShowMapsMissingAin.IsChecked = this.settings.ShowMapsMissingAin;
+ }
+
+ private void saveSettings()
+ {
+ // Visuals
+
+ // Theme
+ if ((bool)this.radioLightTheme.IsChecked)
+ this.settings.Theme = REghZyFramework.Themes.ThemesController.ThemeTypes.Light;
+ else
+ this.settings.Theme = REghZyFramework.Themes.ThemesController.ThemeTypes.Dark;
+
+ Point newCoords = new Point
+ {
+ X = this.intCurrentMapCoordsOffsetX.Value ?? 0,
+ Y = this.intCurrentMapCoordsOffsetY.Value ?? 0
+ };
+
+ var mapOffsetsItem = this.settings.MapCoordinateOffsets.FirstOrDefault(map => map.DDSFileName == this.getCurrentMapDDSName());
+ if (mapOffsetsItem != null)
+ {
+ mapOffsetsItem.CoordOffset = newCoords;
+ mapOffsetsItem.MapScale = (float)this.intCurrentMapMultiplierOverride.Value;
+ }
+ else
+ {
+ this.settings.MapCoordinateOffsets.Add(new Shared.Models.MapCustomOverwriteMapping { DDSFileName = getCurrentMapDDSName(), CoordOffset = newCoords, MapScale = (float)this.intCurrentMapMultiplierOverride.Value });
+ }
+ this.currentMap.MapOverwrite.CoordOffset = newCoords;
+ this.currentMap.MapOverwrite.MapScale = (float)this.intCurrentMapMultiplierOverride.Value;
+
+ this.settings.ShowBombSites = (bool)this.mnuShowBombSites.IsChecked;
+ this.settings.ShowSpawnAreas = (bool)this.mnuShowSpawnAreas.IsChecked;
+ this.settings.ShowStandardSpawns = (bool)this.mnuShowStandardSpawns.IsChecked;
+ this.settings.Show2v2Spawns = (bool)this.mnuShow2v2Spawns.IsChecked;
+ this.settings.ShowHostageSpawns = (bool)this.mnuShowHostageSpawns.IsChecked;
+ this.settings.AllowNonPrioritySpawns = (bool)this.mnuAllowNonPrioritySpawns.IsChecked;
+
+ this.settings.NavLowColour = this.colourNavLow.SelectedColor ?? Globals.Settings.NavLowColour;
+ this.settings.NavHighColour = this.colourNavHigh.SelectedColor ?? Globals.Settings.NavHighColour;
+ this.settings.NavHoverColour = this.colourNavHover.SelectedColor ?? Globals.Settings.NavHoverColour;
+
+ this.settings.NavDisplayMode = (Shared.NavDisplayModes)Enum.Parse(typeof(Shared.NavDisplayModes), comboNavDisplayModes.SelectedItem.ToString());
+
+ this.settings.ShowNavAreasAbove = sliderNavAbove.Value / 100f;
+ this.settings.ShowNavAreasBelow = sliderNavBelow.Value / 100f;
+
+ // Map filter
+ this.settings.ShowDefusalMaps = (bool)this.mnuShowDefusalMaps.IsChecked;
+ this.settings.ShowHostageMaps = (bool)this.mnuShowHostageMaps.IsChecked;
+ this.settings.ShowArmsRaceMaps = (bool)this.mnuShowArmsRaceMaps.IsChecked;
+ this.settings.ShowDangerZoneMaps = (bool)this.mnuShowDangerZoneMaps.IsChecked;
+ this.settings.ShowMapsMissingBsp = (bool)this.mnuShowMapsMissingBsp.IsChecked;
+ this.settings.ShowMapsMissingNav = (bool)this.mnuShowMapsMissingNav.IsChecked;
+ this.settings.ShowMapsMissingAin = (bool)this.mnuShowMapsMissingAin.IsChecked;
+
+ Globals.Settings = this.settings;
+ Globals.SaveSettings();
+ }
+
+ private void btnCancel_Click(object sender, RoutedEventArgs e)
+ {
+ this.Close();
+ }
+
+ private void btnSave_Click(object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true; // Tell main window to reload with new settings
+ this.saveSettings();
+ this.Close();
+ }
+
+ private void sliderNav_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (!this.IsInitialized)
+ return;
+
+ if(sender == sliderNavBelow && sliderNavAbove.Value > sliderNavBelow.Value)
+ sliderNavAbove.Value = sliderNavBelow.Value;
+ else if (sender == sliderNavAbove && sliderNavBelow.Value < sliderNavAbove.Value)
+ sliderNavBelow.Value = sliderNavAbove.Value;
+
+ txtNavAbove.Content = $"{this.sliderNavAbove.Value} %";
+ txtNavBelow.Content = $"{this.sliderNavBelow.Value} %";
+ }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/CsgoHelper.cs b/Shared/SteamHelpers/SteamHelpers/CsgoHelper.cs
index efc1161..d4e0925 100644
--- a/Shared/SteamHelpers/SteamHelpers/CsgoHelper.cs
+++ b/Shared/SteamHelpers/SteamHelpers/CsgoHelper.cs
@@ -422,7 +422,7 @@ namespace Shared
///
/// The absolute path to the BSP file.
/// A tuple containing whether nav or ain files were found, in that order.
- public (bool, bool) ReadIfPackedNavFilesInBsp(string bspFilePath)
+ public (bool, bool, NavMesh) ReadIfPackedNavFilesInBsp(string bspFilePath)
{
bool navFound = false;
bool ainFound = false;
@@ -446,26 +446,30 @@ namespace Shared
}
if (readZipBytes == null)
- return (false, false);
+ return (false, false, null!);
using (var stream = new MemoryStream(readZipBytes))
{
using(var zip = new ZipArchive(stream, ZipArchiveMode.Read))
{
- foreach(var entry in zip.Entries)
+ NavMesh? nav = null;
+ foreach (var entry in zip.Entries)
{
if (entry.FullName.EndsWith(".nav"))
+ {
// Found a packed NAV file
navFound = true;
+ nav = NavFile.Parse(entry.Open());
+ }
if(entry.FullName.EndsWith(".ain"))
// Found a packed AIN file
ainFound = true;
if (navFound && ainFound)
// If both already found, return prematurely
- return (true, true);
+ return (true, true, nav!);
}
- return (navFound, ainFound);
+ return (navFound, ainFound, nav!);
}
}
}
diff --git a/Shared/SteamHelpers/SteamHelpers/Globals.cs b/Shared/SteamHelpers/SteamHelpers/Globals.cs
index 451a087..2c2f54c 100644
--- a/Shared/SteamHelpers/SteamHelpers/Globals.cs
+++ b/Shared/SteamHelpers/SteamHelpers/Globals.cs
@@ -41,5 +41,10 @@ namespace Shared
}
}
}
+
+ public static float Map(float s, float a1, float a2, float b1, float b2)
+ {
+ return b1 + (s - a1) * (b2 - b1) / (a2 - a1);
+ }
}
}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/CsgoMap.cs b/Shared/SteamHelpers/SteamHelpers/Models/CsgoMap.cs
index 0d4ae79..d9cb898 100644
--- a/Shared/SteamHelpers/SteamHelpers/Models/CsgoMap.cs
+++ b/Shared/SteamHelpers/SteamHelpers/Models/CsgoMap.cs
@@ -131,7 +131,8 @@ namespace Shared.Models
public float BombBY { get; set; } = -1;
///
- /// The bomb damage in this map. By default it's 500.
+ /// The bomb damage in this map.
+ /// If not specified in a map, the default value is used, which is 500 units.
///
public float BombDamage { get; set; } = 500;
@@ -140,16 +141,39 @@ namespace Shared.Models
///
public string? EntityList { get; set; }
+ ///
+ /// Amount of CT spawns on this map, that have priority over other spawns.
+ /// (For example getting filled first when playing competitive)
+ ///
public int AmountPrioritySpawnsCT { get; set; }
+ ///
+ /// Amount of total CT spawns on this map.
+ ///
public int AmountSpawnsCT { get; set; }
+ ///
+ /// Amount of T spawns on this map, that have priority over other spawns.
+ /// (For example getting filled first when playing competitive)
+ ///
public int AmountPrioritySpawnsT { get; set; }
+ ///
+ /// Amount of total T spawns on this map.
+ ///
public int AmountSpawnsT { get; set; }
+ ///
+ /// Amount of possible hostages on this map.
+ ///
public int AmountHostages { get; set; }
+ ///
+ /// X and Y offset of the coordinates relative to the map's given coordinates as well as a new map size multiplier.
+ /// This is used to correct for inaccurate coordinates and scale in the map's associated text file.
+ ///
+ public MapCustomOverwriteMapping MapOverwrite { get; set; } = new();
+
public bool HasPrioritySpawnsT
{
get
@@ -167,5 +191,7 @@ namespace Shared.Models
}
public List SpawnPoints { get; set; } = new List();
+
+ public NavMesh? NavMesh { get; set; }
}
}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/MapCustomOverwriteMapping.cs b/Shared/SteamHelpers/SteamHelpers/Models/MapCustomOverwriteMapping.cs
new file mode 100644
index 0000000..3b7f033
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/MapCustomOverwriteMapping.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class MapCustomOverwriteMapping
+ {
+ public string? DDSFileName { get; set; } = string.Empty;
+ public System.Windows.Point CoordOffset { get; set; }
+ public float MapScale { get; set; } = 0;
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/MapPoint.cs b/Shared/SteamHelpers/SteamHelpers/Models/MapPoint.cs
index 66ac4c0..ee6e05e 100644
--- a/Shared/SteamHelpers/SteamHelpers/Models/MapPoint.cs
+++ b/Shared/SteamHelpers/SteamHelpers/Models/MapPoint.cs
@@ -14,6 +14,10 @@ namespace Shared.Models
public double PercentageY { get; set; }
+ public double Z { get; set; }
+
+ public int AssociatedAreaID { get; set; } = -1;
+
public double PercentageScale { get; set; }
}
}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavApproachSpot.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavApproachSpot.cs
new file mode 100644
index 0000000..56a2627
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavApproachSpot.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavApproachSpot
+ {
+ public NavApproachSpot() { /* Do nothing */ }
+
+ public NavApproachSpot(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.ApproachHereId = reader.ReadUInt32();
+ this.ApproachPrevId = reader.ReadUInt32();
+ this.ApproachType = reader.ReadByte();
+ this.ApproachNextId = reader.ReadUInt32();
+ this.ApproachHow = reader.ReadByte();
+ }
+
+ public uint ApproachHereId { get; set; }
+
+ public uint ApproachPrevId { get; set; }
+
+ public byte ApproachType { get; set; }
+
+ public uint ApproachNextId { get; set; }
+
+ public byte ApproachHow { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavArea.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavArea.cs
new file mode 100644
index 0000000..032f7cf
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavArea.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavArea
+ {
+ public NavArea() { /* Do nothing */ }
+
+ public NavArea(uint navVersion, BinaryReader reader)
+ {
+ this.Parse(navVersion, reader);
+ }
+
+ public void Parse(uint navVersion, BinaryReader reader)
+ {
+ this.ID = reader.ReadUInt32();
+
+ if (navVersion <= 8)
+ this.AttributeBitField = BitConverter.GetBytes(reader.ReadByte());
+ else if (navVersion <= 12)
+ this.AttributeBitField = BitConverter.GetBytes(reader.ReadUInt16());
+ else if (navVersion >= 13)
+ this.AttributeBitField = BitConverter.GetBytes(reader.ReadUInt32());
+
+ this.NorthWestCorner = new Vector3(reader);
+ this.SouthEastCorner = new Vector3(reader);
+ this.NorthEastZ = reader.ReadSingle();
+ this.SouthWestZ = reader.ReadSingle();
+
+ for(int i = 0; i < this.ConnectionData!.Length; i++)
+ {
+ this.ConnectionData[i] = new NavConnectionData(reader);
+ }
+
+ // === HIDING SPOTS === //
+
+ this.HidingSpotCount = reader.ReadByte();
+
+ this.HidingSpots = new NavHidingSpot[this.HidingSpotCount];
+ for(int i = 0; i < this.HidingSpots.Length; i++)
+ {
+ this.HidingSpots[i] = new NavHidingSpot(navVersion, reader);
+ }
+
+ // === APPROACH SPOTS === //
+
+ if (navVersion < 15)
+ {
+ this.ApproachSpotCount = reader.ReadByte();
+
+ this.ApproachSpots = new NavApproachSpot[this.ApproachSpotCount];
+ for (int i = 0; i < this.ApproachSpots.Length; i++)
+ {
+ this.ApproachSpots[i] = new NavApproachSpot(reader);
+ }
+ }
+
+ // === ENCOUNTER PATHS === //
+
+ this.EncounterPathCount = reader.ReadUInt32();
+
+ this.EncounterPaths = new NavEncounterPath[this.EncounterPathCount];
+ for (int i = 0; i < this.EncounterPaths.Length; i++)
+ {
+ this.EncounterPaths[i] = new NavEncounterPath(reader);
+ }
+
+ this.PlaceID = reader.ReadUInt16();
+
+ for(int i = 0; i < this.LadderIDSequence!.Length; i++)
+ {
+ this.LadderIDSequence[i] = new NavLadderIDSequence(reader);
+ }
+
+ for (int i = 0; i < this.EarliestOccupyTimes!.Length; i++)
+ {
+ this.EarliestOccupyTimes[i] = reader.ReadSingle();
+ }
+
+ if (navVersion >= 16)
+ {
+ for (int i = 0; i < this.LightIntensity!.Length; i++)
+ {
+ this.LightIntensity[i] = reader.ReadSingle();
+ }
+ }
+
+ // Called "Visible areas" in Valve's code
+ this.AreaBindCount = reader.ReadUInt32();
+
+ this.AreaBindSequence = new NavAreaBind[this.AreaBindCount];
+ for(int i = 0; i < this.AreaBindSequence.Length; i++)
+ {
+ this.AreaBindSequence[i] = new NavAreaBind(reader);
+ }
+
+ this.InheritVisibilityFromAreaID = reader.ReadUInt32();
+
+ //this.CustomData = new IntPtr(BitConverter.ToInt64(reader.ReadBytes(IntPtr.Size)));
+ byte garbageCount = reader.ReadByte();
+
+ reader.BaseStream.Position += garbageCount * 14;
+ }
+
+ public uint ID { get; set; }
+
+ ///
+ /// Version <= 8: unsigned char (1 byte)
+ /// Version <= 12: unsigned short (2 bytes)
+ /// Version >= 8: unsigned int (4 bytes)
+ ///
+ public byte[]? AttributeBitField { get; set; } = new byte[4];
+
+ public Vector3? MedianPosition
+ {
+ get
+ {
+ float newX = (this.ActualNorthWestCorner!.X + this.ActualNorthEastCorner!.X + this.ActualSouthEastCorner!.X + this.ActualSouthWestCorner!.X) / 4;
+ float newY = (this.ActualNorthWestCorner!.Y + this.ActualNorthEastCorner!.Y + this.ActualSouthEastCorner!.Y + this.ActualSouthWestCorner!.Y) / 4;
+ float newZ = (this.ActualNorthWestCorner!.Z + this.ActualNorthEastCorner!.Z + this.ActualSouthEastCorner!.Z + this.ActualSouthWestCorner!.Z) / 4;
+ return new Vector3 { X = newX, Y = newY, Z = newZ };
+ }
+ }
+
+ // I believe the corners are actually treated as if North was on the left of the map, I just changed the namings here to work with them more accurately
+ public Vector3? ActualNorthWestCorner { get => this.NorthEastCorner; }
+ public Vector3? ActualNorthEastCorner { get => this.SouthEastCorner; }
+ public Vector3? ActualSouthEastCorner { get => this.SouthWestCorner; }
+ public Vector3? ActualSouthWestCorner { get => this.NorthWestCorner; }
+
+ ///
+ /// Actually South West?
+ ///
+ public Vector3? NorthWestCorner { get; set; } = Vector3.Zero;
+
+ ///
+ /// Actually North East?
+ ///
+ public Vector3? SouthEastCorner { get; set; } = Vector3.Zero;
+
+ ///
+ /// Actually North West?
+ ///
+ public Vector3? NorthEastCorner
+ {
+ get
+ {
+ return new Vector3
+ {
+ X = this.NorthWestCorner?.X ?? 0,
+ Y = this.SouthEastCorner?.Y ?? 0,
+ Z = this.SouthWestZ
+ };
+ }
+ }
+
+ ///
+ /// Actually South East?
+ ///
+ public Vector3? SouthWestCorner
+ {
+ get
+ {
+ return new Vector3
+ {
+ X = this.SouthEastCorner?.X ?? 0,
+ Y = this.NorthWestCorner?.Y ?? 0,
+ Z = this.NorthEastZ
+ };
+ }
+ }
+
+ public float NorthEastZ { get; set; }
+
+ public float SouthWestZ { get; set; }
+
+ public NavConnectionData[]? ConnectionData { get; set; } = new NavConnectionData[4];
+
+ public byte HidingSpotCount { get; set; }
+
+ public NavHidingSpot[]? HidingSpots { get; set; }
+
+ ///
+ /// Version < 15
+ ///
+ public byte ApproachSpotCount { get; set; }
+
+ ///
+ /// Version < 15
+ ///
+ public NavApproachSpot[]? ApproachSpots { get; set; }
+
+ public uint EncounterPathCount { get; set; }
+
+ public NavEncounterPath[]? EncounterPaths { get; set; }
+
+ public ushort PlaceID { get; set; }
+
+ public NavLadderIDSequence[]? LadderIDSequence { get; set; } = new NavLadderIDSequence[2];
+
+ public float[]? EarliestOccupyTimes { get; set; } = new float[2];
+
+ public float[]? LightIntensity { get; set; } = new float[4];
+
+ public uint AreaBindCount { get; set; }
+
+ public NavAreaBind[]? AreaBindSequence { get; set; }
+
+ public uint InheritVisibilityFromAreaID { get; set; }
+
+ public IntPtr CustomData { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavAreaBind.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavAreaBind.cs
new file mode 100644
index 0000000..008184a
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavAreaBind.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavAreaBind
+ {
+ public NavAreaBind() { /* Do nothing */ }
+
+ public NavAreaBind(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.TargetAreaID = reader.ReadUInt32();
+ this.AttributeBitField = reader.ReadByte();
+ }
+
+ public uint TargetAreaID { get; set; }
+
+ public byte AttributeBitField { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavConnectionData.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavConnectionData.cs
new file mode 100644
index 0000000..1ac6cb2
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavConnectionData.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavConnectionData
+ {
+ public NavConnectionData(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.Count = reader.ReadUInt32();
+
+ this.AreaIDs = new uint[this.Count];
+ for(int i = 0; i < this.AreaIDs.Length; i++)
+ {
+ this.AreaIDs[i] = reader.ReadUInt32();
+ }
+ }
+
+ public uint Count { get; set; }
+
+ public uint[]? AreaIDs { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterPath.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterPath.cs
new file mode 100644
index 0000000..126bb58
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterPath.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavEncounterPath
+ {
+ public NavEncounterPath() { /* Do nothing */ }
+
+ public NavEncounterPath(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.EntryAreaID = reader.ReadUInt32();
+ this.EntryDirection = reader.ReadByte();
+ this.DestAreaID = reader.ReadUInt32();
+ this.DestDirection = reader.ReadByte();
+
+ this.EncounterSpotCount = reader.ReadByte();
+
+ this.EncounterSpots = new NavEncounterSpot[this.EncounterSpotCount];
+ for(int i = 0; i < this.EncounterSpots.Length; i++)
+ {
+ this.EncounterSpots[i] = new NavEncounterSpot(reader);
+ }
+ }
+
+ public uint EntryAreaID { get; set; }
+
+ public byte EntryDirection { get; set; }
+
+ public uint DestAreaID { get; set; }
+
+ public byte DestDirection { get; set; }
+
+
+ public byte EncounterSpotCount { get; set; }
+
+ public NavEncounterSpot[]? EncounterSpots { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterSpot.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterSpot.cs
new file mode 100644
index 0000000..e300093
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavEncounterSpot.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavEncounterSpot
+ {
+ public NavEncounterSpot() { /* Do nothing */ }
+
+ public NavEncounterSpot(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.AreaID = reader.ReadUInt32();
+ this.ParametricDistance = reader.ReadByte();
+ }
+
+ public uint AreaID { get; set; }
+
+ public byte ParametricDistance { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavHeader.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavHeader.cs
new file mode 100644
index 0000000..c6cbc5b
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavHeader.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavHeader
+ {
+ public uint MagicNumber { get; set; }
+
+ public uint Version { get; set; }
+
+ ///
+ /// Version >= 10
+ ///
+ public uint SubVersion { get; set; }
+
+ ///
+ /// Version >= 4
+ ///
+ public uint SaveBspSize { get; set; }
+
+ ///
+ /// Version >= 14
+ ///
+ public byte IsAnalyzed { get; set; }
+
+ ///
+ /// Version >= 5
+ ///
+ public ushort PlacesCount { get; set; }
+
+ ///
+ /// Version >= 5
+ ///
+ public string[]? PlacesNames { get; set; }
+
+ ///
+ /// Version > 11
+ ///
+ public byte HasUnnamedAreas { get; set; }
+
+ public uint AreaCount { get; set; }
+
+ public NavArea[]? NavAreas { get; set; }
+
+ public uint LadderCount { get; set; }
+
+ public NavLadder[]? Ladders { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavHidingSpot.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavHidingSpot.cs
new file mode 100644
index 0000000..72d4af4
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavHidingSpot.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ [Flags]
+ public enum HidingSpotAttribute
+ {
+ IN_COVER = 0x01, // In a corner with good hard cover nearby
+ GOOD_SNIPER_SPOT = 0x02, // Had at least one decent sniping corridor
+ IDEAL_SNIPER_SPOT = 0x04, // Can see either very far, or a large area, or both
+ EXPOSED = 0x08 // Spot in the open, usually on a ledge or cliff
+ }
+
+ public class NavHidingSpot
+ {
+ public NavHidingSpot() { /* Do nothing */ }
+
+ public NavHidingSpot(uint navVersion, System.IO.BinaryReader reader)
+ {
+ this.Parse(navVersion, reader);
+ }
+
+ public void Parse(uint navVersion, System.IO.BinaryReader reader)
+ {
+ if(navVersion >= 2)
+ this.ID = reader.ReadUInt32();
+
+ if (navVersion >= 1)
+ this.Position = new Vector3(reader);
+
+ if (navVersion >= 2)
+ this.Attributes = reader.ReadByte();
+ }
+
+ public uint ID { get; set; }
+
+ public Vector3? Position { get; set; }
+
+ public byte Attributes { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavLadder.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavLadder.cs
new file mode 100644
index 0000000..4c4d95e
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavLadder.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavLadder
+ {
+ public NavLadder() { /* Do nothing */ }
+
+ public NavLadder(BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(BinaryReader reader)
+ {
+ this.ID = reader.ReadUInt32();
+ this.Width = reader.ReadSingle();
+ this.Length = reader.ReadSingle();
+ this.Top = new Vector3(reader);
+ this.Bottom = new Vector3(reader);
+ this.Direction = reader.ReadInt32();
+ this.TopForwardAreaID = reader.ReadUInt32();
+ this.TopLeftAreaID = reader.ReadUInt32();
+ this.TopRightAreaID = reader.ReadUInt32();
+ this.TopBehindAreaID = reader.ReadUInt32();
+ this.BottomAreaID = reader.ReadUInt32();
+ }
+
+ public uint ID { get; set; }
+
+ public float Width { get; set; }
+
+ public float Length { get; set; }
+
+ public Vector3 Top { get; set; }
+
+ public Vector3 Bottom { get; set; }
+
+ public int Direction { get; set; }
+
+ public uint TopForwardAreaID { get; set; }
+
+ public uint TopLeftAreaID { get; set; }
+
+ public uint TopRightAreaID { get; set; }
+
+ public uint TopBehindAreaID { get; set; }
+
+ public uint BottomAreaID { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavLadderIDSequence.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavLadderIDSequence.cs
new file mode 100644
index 0000000..8691de0
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavLadderIDSequence.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavLadderIDSequence
+ {
+ public NavLadderIDSequence() { /* Do nothing */ }
+
+ public NavLadderIDSequence(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.LadderCount = reader.ReadUInt32();
+
+ this.LadderIDs = new uint[this.LadderCount];
+ for(int i = 0; i < this.LadderIDs.Length; i++)
+ {
+ this.LadderIDs[i] = reader.ReadUInt32();
+ }
+ }
+
+ public uint LadderCount { get; set; }
+
+ public uint[]? LadderIDs { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/NavMesh.cs b/Shared/SteamHelpers/SteamHelpers/Models/NavMesh.cs
new file mode 100644
index 0000000..2ce0357
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/Models/NavMesh.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shared.Models
+{
+ public class NavMesh
+ {
+ public NavHeader? Header { get; set; } = new NavHeader();
+
+ public float? MinZ { get; set; }
+
+ public float? MaxZ { get; set; }
+ }
+}
diff --git a/Shared/SteamHelpers/SteamHelpers/Models/Vector3.cs b/Shared/SteamHelpers/SteamHelpers/Models/Vector3.cs
index 0473206..fd8a084 100644
--- a/Shared/SteamHelpers/SteamHelpers/Models/Vector3.cs
+++ b/Shared/SteamHelpers/SteamHelpers/Models/Vector3.cs
@@ -8,12 +8,26 @@ namespace Shared.Models
{
public class Vector3
{
+ public Vector3() { /* Do nothing */ }
+
+ public Vector3(System.IO.BinaryReader reader)
+ {
+ this.Parse(reader);
+ }
+
+ public void Parse(System.IO.BinaryReader reader)
+ {
+ this.X = reader.ReadSingle();
+ this.Y = reader.ReadSingle();
+ this.Z = reader.ReadSingle();
+ }
+
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
- public static Vector3 Empty = new Vector3 { X = 0, Y = 0, Z = 0 };
+ public static Vector3 Zero = new Vector3 { X = 0, Y = 0, Z = 0 };
}
}
diff --git a/Shared/SteamHelpers/SteamHelpers/NavFile.cs b/Shared/SteamHelpers/SteamHelpers/NavFile.cs
new file mode 100644
index 0000000..79f683d
--- /dev/null
+++ b/Shared/SteamHelpers/SteamHelpers/NavFile.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Shared.Models;
+
+namespace Shared
+{
+ public enum NavDisplayModes { None, Wireframe, Filled }
+
+ public static class NavFile
+ {
+ public static NavMesh? Parse(byte[] navFile)
+ {
+ return NavFile.Parse(new MemoryStream(navFile));
+ }
+
+ public static NavMesh? Parse(Stream stream)
+ {
+ NavMesh mesh = new NavMesh();
+
+ // We do this because ZIP file streams are not seekable
+ using var memStream = new MemoryStream();
+ stream.CopyTo(memStream);
+ stream.Close();
+
+ using var reader = new BinaryReader(memStream);
+ reader.BaseStream.Position = 0;
+
+ // Header is created when creating the nav mesh
+ mesh.Header!.MagicNumber = reader.ReadUInt32();
+
+ if (mesh.Header.MagicNumber != 0xFEEDFACE)
+ // Not a NAV file
+ return null;
+
+ uint version = mesh.Header.Version = reader.ReadUInt32();
+
+ System.Diagnostics.Debug.WriteLine($"File is NAV file with version {version}");
+
+ if (version >= 10)
+ mesh.Header.SubVersion = reader.ReadUInt32();
+
+ if(version >= 4)
+ mesh.Header.SaveBspSize = reader.ReadUInt32();
+
+ if (version >= 14)
+ mesh.Header.IsAnalyzed = reader.ReadByte();
+
+ if(version >= 5)
+ {
+ // Callouts ("Places")
+ mesh.Header.PlacesCount = reader.ReadUInt16();
+
+ mesh.Header.PlacesNames = new string[mesh.Header.PlacesCount];
+ for (int i = 0; i < mesh.Header.PlacesNames.Length; i++)
+ {
+ ushort len = reader.ReadUInt16();
+
+ mesh.Header.PlacesNames[i] = new(reader.ReadChars(len)[..^1]);
+ }
+
+ if(version > 11)
+ {
+ mesh.Header.HasUnnamedAreas = reader.ReadByte();
+ }
+ }
+
+ // PreLoadAreas() used here? What does it do? there is no size visible (that's what she said)
+
+ mesh.Header.AreaCount = reader.ReadUInt32();
+
+ mesh.Header.NavAreas = new NavArea[mesh.Header.AreaCount];
+ for(int i = 0; i < mesh.Header.NavAreas.Length; i++)
+ {
+ mesh.Header.NavAreas[i] = new NavArea(version, reader);
+ Vector3? median = mesh.Header.NavAreas[i].MedianPosition;
+
+ // Update max and min z for mapping if applying here
+ if (mesh.MinZ == null || median!.Z < mesh.MinZ)
+ {
+ mesh.MinZ = median!.Z;
+ }
+
+ if (mesh.MaxZ == null || median!.Z > mesh.MaxZ)
+ {
+ mesh.MaxZ = median!.Z;
+ }
+ }
+
+ if(version >= 6)
+ {
+ mesh.Header.LadderCount = reader.ReadUInt32();
+
+ mesh.Header.Ladders = new NavLadder[mesh.Header.LadderCount];
+ for (int i = 0; i < mesh.Header.Ladders.Length; i++)
+ {
+ mesh.Header.Ladders[i] = new NavLadder(reader);
+ }
+ }
+
+ return mesh;
+ }
+ }
+}