diff --git a/DamageCalculator/DamageCalculator/DamageCalculator.csproj b/DamageCalculator/DamageCalculator/DamageCalculator.csproj
index 5e1e7d9..5fb23f2 100644
--- a/DamageCalculator/DamageCalculator/DamageCalculator.csproj
+++ b/DamageCalculator/DamageCalculator/DamageCalculator.csproj
@@ -11,8 +11,8 @@
27.ico
- 1.3.0.1
- 1.3.0.1
+ 1.3.0.3
+ 1.3.0.3
diff --git a/DamageCalculator/DamageCalculator/MainWindow.xaml.cs b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
index 2453e1f..6316e5c 100644
--- a/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
+++ b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs
@@ -32,6 +32,12 @@ namespace Damage_Calculator
private readonly string placeholderText = "None";
+ private static readonly double eyeLevelStanding = 64.093811;
+ private static readonly double eyeLevelCrouching = 46.076218;
+ private static readonly int heightStanding = 72;
+ private static readonly int heightCrouching = 54;
+ private static readonly int playerWidth = 32;
+
///
/// Holds current in-game mouse coordinates when hovering over the map
///
@@ -1026,6 +1032,7 @@ namespace Damage_Calculator
// left and right point for the X and Y coordinates (in pixels) so we gotta convert those
Point[] points = this.connectingLine.Tag as Point[];
+ // These are not in-game coordinates yet, but the offset from the top left -- but in units instead of pixels
double leftX = this.getUnitsFromPixels(points[0].X);
double leftY = this.getUnitsFromPixels(points[0].Y);
double leftZ;
@@ -1034,10 +1041,29 @@ namespace Damage_Calculator
double rightY = this.getUnitsFromPixels(points[1].Y);
double rightZ;
+ // Make the left and right point in-game coordinates accessible to everyone outside this function
+ Point playerCoords = this.getGameCoordsFromPoint(points[1].X, points[1].Y);
+ this.playerPoint.X = playerCoords.X;
+ this.playerPoint.Y = playerCoords.Y;
+
+ Point targetOrBombCoords = this.getGameCoordsFromPoint(points[0].X, points[0].Y);
+ if (this.bombPoint != null)
+ {
+ this.bombPoint.X = targetOrBombCoords.X;
+ this.bombPoint.Y = targetOrBombCoords.Y;
+ }
+ if(this.targetPoint != null)
+ {
+ this.targetPoint.X = targetOrBombCoords.X;
+ this.targetPoint.Y = targetOrBombCoords.Y;
+ }
+
+ // Manage height
if (this.playerPoint.AssociatedAreaID < 0 ||
((this.DrawMode == eDrawMode.Shooting && this.targetPoint.AssociatedAreaID < 0)
|| (this.DrawMode == eDrawMode.Bomb && this.bombPoint.AssociatedAreaID < 0)))
{
+ // One of the points has no area ID, and thus no Z coordinate
leftZ = 0;
rightZ = 0;
}
@@ -1051,13 +1077,14 @@ namespace Damage_Calculator
double diffPixels2D = Math.Sqrt(Math.Pow(leftX - rightX, 2) + Math.Pow(leftY - rightY, 2));
double unitsDifference2D = this.getUnitsFromPixels(diffPixels2D);
+
if (this.DrawMode == eDrawMode.Bomb)
{
// Add the appropriate eye level
if (radioPlayerStanding.IsChecked == true)
- rightZ += 64.093811;
+ rightZ += eyeLevelStanding;
else if(radioPlayerCrouched.IsChecked == true)
- rightZ += 46.076218;
+ rightZ += eyeLevelCrouching;
}
// Add Z height to calculation, unless a point has no area ID associated, then it stays 2D
@@ -1172,11 +1199,46 @@ namespace Damage_Calculator
private void calculateAndUpdateBombDamage()
{
+ // This is the maximum damage, and the height of the bell curve
+ double flDamage = this.loadedMap.BombDamage; // 500 is hard-coded as the default, and also the default in the hammer editor, can be overridden as a map creator
+ double flBombRadius = flDamage * 3.5d;
+
+ // First we need to check if the player is within the bomb radius, this isn't done with a circle, but with a box that has a side length of 2r
+ // So basically it's the bounding box, so if you're not directly above, below, left or right of the bomb, the radius increases a bit
+ // Also its calculated via the bounding box of the player which is 32x32 units in the horizontal axes
+
+ // Get mins and maxs of player hitbox
+
+ // Mins is X - 16, Y - 16, Z
+ Vector3 playerMins = new Vector3 { X = (float)(this.playerPoint.X - (playerWidth / 2d)), Y = (float)(this.playerPoint.Y - (playerWidth / 2d)), Z = (float)this.playerPoint.Z };
+
+ // Head height is not eye level, crouching is smaller, otherwise use standing height
+ float headHeight = (float)(this.radioPlayerCrouched.IsChecked == true ? heightCrouching : heightStanding);
+
+ // Maxs is X + 16, Y + 16, Z + head height
+ Vector3 playerMaxs = new Vector3 { X = (float)(this.playerPoint.X + (playerWidth / 2d)), Y = (float)(this.playerPoint.Y + (playerWidth / 2d)), Z = (float)this.playerPoint.Z + headHeight };
+
+ // Get mins and maxs of bomb radius
+ Vector3 bombMins = new Vector3 { X = (float)(this.bombPoint.X - flBombRadius), Y = (float)(this.bombPoint.Y - flBombRadius), Z = (float)(this.bombPoint.Z - flBombRadius) };
+
+ Vector3 bombMaxs = new Vector3 { X = (float)(this.bombPoint.X + flBombRadius), Y = (float)(this.bombPoint.Y + flBombRadius), Z = (float)(this.bombPoint.Z + flBombRadius) };
+
+ // Check if the two boxes intersect
+ bool playerIsInRange = SpatialPartitioningHelper.BoxIntersects(bombMins, bombMaxs, playerMins, playerMaxs);
+
+ if (!playerIsInRange)
+ {
+ txtResult.Text = txtResultArmor.Text = "0";
+ return;
+ }
+
+ // Now we can calculate the damage...
+
const double damagePercentage = 1.0d;
- double flDamage = this.loadedMap.BombDamage; // 500 - default, if radius is not written on the map https://i.imgur.com/mUSaTHj.png
- double flBombRadius = flDamage * 3.5d;
+ // From player origin + eye level, to the bomb
double flDistanceToLocalPlayer = (double)this.unitsDistance;// ((c4bomb origin + viewoffset) - (localplayer origin + viewoffset))
+ // This defines the width of the curve, a smaller value gives a steeper curve and a faster falloff
double fSigma = flBombRadius / 3.0d;
double fGaussianFalloff = Math.Exp(-flDistanceToLocalPlayer * flDistanceToLocalPlayer / (2.0d * fSigma * fSigma));
double flAdjustedDamage = flDamage * fGaussianFalloff * damagePercentage;
diff --git a/DamageCalculator/DamageCalculator/SpatialPartitioningHelper.cs b/DamageCalculator/DamageCalculator/SpatialPartitioningHelper.cs
new file mode 100644
index 0000000..1909a79
--- /dev/null
+++ b/DamageCalculator/DamageCalculator/SpatialPartitioningHelper.cs
@@ -0,0 +1,150 @@
+using SteamShared.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.RightsManagement;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Damage_Calculator
+{
+ internal class Voxel
+ {
+ private uint x;
+ private uint y;
+ private uint z;
+ private uint voxelId;
+
+ public uint X
+ {
+ get => this.x;
+ set
+ {
+ this.x = value & 0x7FF; // Maximum value is 11 bits, make double sure
+ this.updateVoxelId();
+ }
+ }
+
+ public uint Y
+ {
+ get => this.y;
+ set
+ {
+ this.y = value & 0x7FF; // Maximum value is 11 bits, make double sure
+ this.updateVoxelId();
+ }
+ }
+
+ public uint Z
+ {
+ get => this.z;
+ set
+ {
+ this.z = value & 0x3FF; // Maximum value is 10 bits, make double sure
+ this.updateVoxelId();
+ }
+ }
+
+ public uint VoxelId
+ {
+ get => this.voxelId;
+ set
+ {
+ this.voxelId = value;
+
+ // VoxelID consists of X=11, Y=11 and Z=10 bits in little endian like this ZZZZZZZZZZYYYYYYYYYYYXXXXXXXXXXX
+ // so extract them
+
+ // No need to bitshift since it's already on the lowest bit, but & it so that Y and Z are 0
+ this.x = value & 0x7FF;
+
+ // & it to only get the Y value in the middle and then shift it to the rightmost spot
+ this.y = (value & 0x3FF800) >> 11;
+
+ // This has no values higher than it so no & is needed, but shift it to the rightmost spot, the lower values will be discarded anyways
+ this.z = value >> 22;
+ }
+ }
+
+ private void updateVoxelId()
+ {
+ this.voxelId = 0;
+
+ this.voxelId |= this.x;
+
+ this.voxelId |= this.y << 11;
+
+ this.voxelId |= this.z << 22;
+ }
+ }
+
+ internal static class SpatialPartitioningHelper
+ {
+ const int MAX_COORD_INTEGER = 16384;
+ const int MIN_COORD_INTEGER = -MAX_COORD_INTEGER;
+ const float MAX_COORD_FLOAT = MAX_COORD_INTEGER;
+ const float MIN_COORD_FLOAT = -MAX_COORD_FLOAT;
+ const int COORD_EXTENT = 2 * MAX_COORD_INTEGER;
+
+ const int SPHASH_LEVEL_SKIP = 2;
+
+ const int SPHASH_VOXEL_SIZE = 256; // Must be power of 2
+ const int SPHASH_VOXEL_SHIFT = 8;
+
+ const float SPHASH_EPS = 0.03125f;
+
+ static readonly Vector3 voxelOrigin = new Vector3 { X = MIN_COORD_FLOAT, Y = MIN_COORD_FLOAT, Z = MIN_COORD_FLOAT };
+
+ static int levelShift;
+ static int levelCount;
+
+ static int GetVoxelSize(int level)
+ {
+ return SPHASH_VOXEL_SIZE << (SPHASH_LEVEL_SKIP * level);
+ }
+
+ static void UpdateLevelShift(int level)
+ {
+ levelShift = SPHASH_VOXEL_SHIFT + (SPHASH_LEVEL_SKIP * level);
+ }
+
+ static void UpdateLevelCount()
+ {
+ levelCount = 0;
+ while (ComputeVoxelCountAtLevel(levelCount) > 2)
+ {
+ // From level 0 to 3 it will be 128, 32, 8, 2
+ ++levelCount;
+ }
+
+ // And then add one to have the count instead of the maximum level index
+ ++levelCount;
+ }
+
+ static int ComputeVoxelCountAtLevel(int level)
+ {
+ int nVoxelCount = COORD_EXTENT >> SPHASH_VOXEL_SHIFT;
+ nVoxelCount >>= (SPHASH_LEVEL_SKIP * level);
+
+ return (nVoxelCount > 0) ? nVoxelCount : 1;
+ }
+
+ static Vector3 VoxelIndexFromPoint(Vector3 worldPoint)
+ {
+ Vector3 voxel = new Vector3();
+
+ voxel.X = (int)(worldPoint.X - voxelOrigin.X) >> levelShift;
+ voxel.Y = (int)(worldPoint.Y - voxelOrigin.Y) >> levelShift;
+ voxel.Z = (int)(worldPoint.Z - voxelOrigin.Z) >> levelShift;
+
+ return voxel;
+ }
+
+ public static bool BoxIntersects(Vector3 boxMins, Vector3 boxMaxs, Vector3 otherMins, Vector3 otherMaxs)
+ {
+ return (otherMins.X <= boxMaxs.X) && (otherMaxs.X >= boxMins.X) &&
+ (otherMins.Y <= boxMaxs.Y) && (otherMaxs.Y >= boxMins.Y) &&
+ (otherMins.Z <= boxMaxs.Z) && (otherMaxs.Z >= boxMins.Z);
+ }
+ }
+}
diff --git a/SteamShared/SteamShared/SteamShared/Models/MapPoint.cs b/SteamShared/SteamShared/SteamShared/Models/MapPoint.cs
index 1ccb0d6..b6f5fc7 100644
--- a/SteamShared/SteamShared/SteamShared/Models/MapPoint.cs
+++ b/SteamShared/SteamShared/SteamShared/Models/MapPoint.cs
@@ -14,6 +14,19 @@ namespace SteamShared.Models
public double PercentageY { get; set; }
+ ///
+ /// The in-game X-coordinate.
+ ///
+ public double X { get; set; }
+
+ ///
+ /// The in-game Y-coordinate.
+ ///
+ public double Y { get; set; }
+
+ ///
+ /// The in-game Z-coordinate.
+ ///
public double Z { get; set; }
public int AssociatedAreaID { get; set; } = -1;