Ok, can you show me the scatter plot with this code?
int dX = RNG::generate(0, deviation) - deviation / 2;
int dY = RNG::generate(0, deviation) - deviation / 2;
int dZ = RNG::generate(0, deviation / 2) / 2 - deviation / 8;
if (Options::oxceUniformShootingSpread)
{
// function which calculates if an infinite line generated from two points intersects a cubeoid
auto intersects = [](const Position& pointA, const Position& pointB, const Position& tileMin, const Position& tileMax)
{
std::array<double, 3> pA{pointA.x, pointA.y, pointA.z};
std::array<double, 3> pB{pointB.x, pointB.y, pointB.z};
std::array<double, 3> cubeMin{tileMin.x, tileMin.y, tileMin.z};
std::array<double, 3> cubeMax{tileMax.x, tileMax.y, tileMax.z};
double t_min = -std::numeric_limits<double>::infinity();
double t_max = std::numeric_limits<double>::infinity();
for (int i = 0; i < 3; ++i) // Iterate over x, y, z axes
{
if (pA[i] != pB[i])
{
double t1 = (cubeMin[i] - pA[i]) / (pB[i] - pA[i]);
double t2 = (cubeMax[i] - pA[i]) / (pB[i] - pA[i]);
if (t1 > t2)
std::swap(t1, t2); // Ensure t1 is the smaller value
t_min = std::max(t_min, t1); // Update entry point
t_max = std::min(t_max, t2); // Update exit point
}
else if (pA[i] < cubeMin[i] || pA[i] > cubeMax[i])
return false; // Line is parallel to this axis and outside the cube
}
return t_min <= t_max; // true if there is an intersection
};
Position originalTargetTileMin(target->toTile().toVoxel());
Position originalTargetTileMax(originalTargetTileMin.x + Position::TileXY - 1, originalTargetTileMin.y + Position::TileXY - 1, originalTargetTileMin.z + Position::TileZ - 1);
Position deviatedTarget(target->x + dX, target->y + dY, target->z + dZ);
bool hit = keepRange ? //keepRange == true for arcing shots
deviatedTarget.toTile() == target->toTile() : //an arcing shot that targets the original target tile space
intersects(origin, deviatedTarget, originalTargetTileMin, originalTargetTileMax); //a non-arcing shot that intersects the original target tile space
if (!hit)
{ // If vanilla was a miss, then convert area of possible deviation from square to a circle
// Average distance of a random point from the centre of a square: 0.7651957; Average distance of a random point from the centre of a circle: 0.6666667
// So first we need to expand the circle's radius by about 15% if we want the average distance to remain the same
deviation *= 1.147793574696;
// Try (20 times) to generate a new target in polar coordinates
for (int i = 0; i < 20; ++i)
{
double radius = RNG::generate(0.0, (double)deviation / 2);
double radians = RNG::generate(0.0, 1.0) * 2 * M_PI;
dX = radius * std::cos(radians);
dY = radius * std::sin(radians);
deviatedTarget.x = target->x + dX;
deviatedTarget.y = target->y + dY;
hit = keepRange ? deviatedTarget.toTile() == target->toTile() : intersects(origin, deviatedTarget, originalTargetTileMin, originalTargetTileMax);
if (!hit)
break;
}
}
}
target->x += dX;
target->y += dY;
target->z += dZ;
Basically, this checks if vanilla attack was a hit or miss. And if it was a miss, it converts the square area of possible deviation to a circular one and tries to generate a miss. This should guarantee hit rate that's equal to vanilla, no matter the distance.
BTW, you didn't consider arcing shots and throws, did you? Arcing shots aren't rays. Also, arcing shots and throws are usually explosives, so with your code as it is the new option gives them a noticable buff.
Edit: Made code simpler, and added a 15% increase to circle's radius.