From 3e94f4c62238c930efba1d0ca9d8be320a6e57fb Mon Sep 17 00:00:00 2001 From: Mathias Lui Date: Sun, 27 Nov 2022 22:03:13 +0100 Subject: [PATCH] Fix bomb damage radius * Change version to 1.3.0.3 * Fix bomb radius not accurate by using a box * When creating a point somewhere we now store its in-game coordinates for the bomb intersection test * SpatialPartitioningHelper.cs is not yet used, except for the BoxIntersects-Method * --- .../DamageCalculator/DamageCalculator.csproj | 4 +- .../DamageCalculator/MainWindow.xaml.cs | 70 +++++++- .../SpatialPartitioningHelper.cs | 150 ++++++++++++++++++ .../SteamShared/Models/MapPoint.cs | 13 ++ 4 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 DamageCalculator/DamageCalculator/SpatialPartitioningHelper.cs 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;