aliens

Author Topic: [DONE][Suggestion] Uniform shooting spread option  (Read 1966 times)

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #15 on: April 21, 2025, 12:21:45 pm »
That distribution doesn't look quite right. In real life, recoil is usually up, not left and right. Also, accuracy bias in novice shooters is usually to the bottom-left (for right-handed people) and bottom-right (for left-handed ones) from the target, again, not straight left and right from the target.

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #16 on: April 21, 2025, 02:25:23 pm »
That distribution doesn't look quite right. In real life, recoil is usually up, not left and right. Also, accuracy bias in novice shooters is usually to the bottom-left (for right-handed people) and bottom-right (for left-handed ones) from the target, again, not straight left and right from the target.
I hope you're joking. The core of vanilla algorithm is untouched, I just made dispersion more uniform, and reduced the count of most annoying, extreme off-target shots.

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #17 on: April 21, 2025, 04:38:49 pm »
I just made dispersion more uniform

What's this dark area then?

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #18 on: April 21, 2025, 08:02:18 pm »
What's this dark area then?
First of all, this is top-down view.
Every point isn't final, where a shot stops. They are "targeting" points. Green ones mean that shot will hit target unit. Gray ones - shot will fly by in. Yellow - shot will fly over/below the unit.

Points which create that darker / higher density zone are moved from "initial" square dispersion corners, from (potentially) most distant places closer to target, inside 0.85 of dispersion area diameter. That way, most shots fly reasonably close to target.

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #19 on: April 21, 2025, 09:25:57 pm »
So, why are the target points moved into that higher density zone instead of being evenly distributed with the rest of the points? The way I see it, this higher density zone shouldn't appear.

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #20 on: April 21, 2025, 10:39:41 pm »
So, why are the target points moved into that higher density zone instead of being evenly distributed with the rest of the points? The way I see it, this higher density zone shouldn't appear.
Because they shouldn't be evenly distributed. Initially, these points were misses (most probably). So, after secondary distribution, they should remain misses as much as possible. Even distribution makes them hit more than distribution along the narrow line through the target. Area in front of target unit and behind it is mostly "hit area".

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #21 on: April 22, 2025, 12:04:17 am »
For BrutalOXCE, I've made three spread option. The third one just uses uniform part of distribution from vanilla (which occurs roughtly 30% of times) but for 100% shots. Well, that's... beautiful)

New BOXCE release will be at the end of the week, probably. Until then, I'll be testing two new modes in different scenarios by myself.

On gif - there are 2% accuracy shots from laser pistol, 168 for each of three modes
« Last Edit: April 22, 2025, 12:06:55 am by jnarical »

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #22 on: April 22, 2025, 01:00:01 am »
Even distribution makes them hit more

And you couldn't figure out a way to distribute the targets in a way that would be an even distribution, while remaining consistent with the original hit rate?

Like, you called this option "Uniform" shooting spread, but no matter how I look at it, with that high density zone there it's anything but uniform. I know, I'm being too critical. However, with the distribution being like that, secondary targets standing in that high density zone are going to get hit a lot more than they were in the original game. As a player, I would turn this option on due to this advantage :D

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #23 on: April 22, 2025, 03:34:02 am »
And you couldn't figure out a way to distribute the targets in a way that would be an even distribution, while remaining consistent with the original hit rate?
Yep. That’s impossible without increasing dispersion area. And that’s against the very idea of my change.

Like, you called this option "Uniform" shooting spread, but no matter how I look at it, with that high density zone there it's anything but uniform.
You either didn’t read the PR or trolling. “Uniform” relates to drastically different hitchance/dispersion angle for different shots’ directions in vanilla.

However, with the distribution being like that, secondary targets standing in that high density zone are going to get hit a lot more than they were in the original game. As a player, I would turn this option on due to this advantage :D
Imagine a ray which crosses dispersion circle away from target. There’s a point where it enters the circle, point where it leaves, and line between them. Imagine that you have target dispersion to any point of that line. There are many possible points for target - but all of them create just one single shot trajectory. That’s why it doesn’t matter that dispersion LOOKS non-uniform, it creates uniform shots distribution anyway. Not exactly “uniform” but with desired bell-kinda-shape distribution of deviation angles. It all seems obvious from simulation images tbh.

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #24 on: April 22, 2025, 06:41:14 pm »
Yep. That’s impossible without increasing dispersion area.

What would happen if you changed
Code: [Select]
double exprX = target->x + dX - origin.x;
double exprY = target->y + dY - origin.y;
double deviateDist2D = sqrt(exprX * exprX + exprY * exprY); // Distance from origin to deviation point

if (resultShifted &&                  // point is on inner ring and should be a "miss"
deviateDist2D > targetDist2D-2 &&
deviateDist2D < targetDist2D+2)
break;
to
Code: [Select]
if (resultShifted && sqrt(dX * dX + dY * dY) > 3) // point is 3 voxels distance away from target
break;
?

That’s why it doesn’t matter that dispersion LOOKS non-uniform

I think it does matter because there's a difference in the distribution over the Z axis. However, we can't see it because all the views you show are top-down.
« Last Edit: April 22, 2025, 07:14:50 pm by Delian »

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #25 on: April 22, 2025, 07:52:01 pm »
What would happen if you changed
You remove target itself from secondary distribution, but area ahead and behind it still remains, so any shot deviated to one of these areas - still hits the target. These areas usually much larger than target, so excluding only the target doesn’t do much in term of hit chances.

On the other hand, you’re bringing artificial non-uniformity, chance for a shot to fly through the centre of dispersion area becomes lower, than slightly to the side.

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #26 on: April 22, 2025, 07:58:51 pm »
Quote from: Delian
I think it does matter because there's a difference in the distribution over the Z axis. However, we can't see it because all the views you show are top-down.
This difference is close to non-existent for close range shots (below 10 tiles). Anyway, when we’re speaking about shots which don’t cross target boundary, z-shift doesn’t matter in terms of hitting the target itself, it’s already miss in XY projection. In terms of hitting other units - it DOES matter, but to a what extent?

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
[DONE][Suggestion] Uniform shooting spread option
« Reply #27 on: April 22, 2025, 08:54:39 pm »
I’m not at home now. Here’s the picture I drew before work. I’m highly doubt it’s self-explanatory but

Offline Delian

  • Commander
  • *****
  • Posts: 707
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #28 on: April 24, 2025, 02:37:03 am »
Ok, can you show me the scatter plot with this code?

Code: [Select]
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.
« Last Edit: April 26, 2025, 03:12:56 pm by Delian »

Offline jnarical

  • Colonel
  • ****
  • Posts: 129
    • View Profile
Re: [DONE][Suggestion] Uniform shooting spread option
« Reply #29 on: April 24, 2025, 08:04:29 pm »
Ok, can you show me the scatter plot with this code?
Simulation tool is written in python, and the function there is bare minimum, without any mention of Options:: etc.
To show you scatter plot - I need to understand what you gave me here, and then convert it to python. It's too much of a work, honestly.
If you want, you can modify python function with LLM - here it is:
Spoiler:
Code: [Select]
def applyAccuracyNew(origin: Position, target_initial: Position, accuracy: float) -> Position:
"""
NEW/EXPERIMENTAL accuracy function.
Applies an accuracy modifier to the target coordinates.
Accuracy is expected as a fraction (e.g., 0.45 for 45%).
"""
target = Position(target_initial.x, target_initial.y, target_initial.z)

xDist = abs(origin.x - target.x)
yDist = abs(origin.y - target.y)
zDist = abs(origin.z - target.z)

xyShift = 0
zShift = 0

if xDist <= yDist:
xyShift = xDist / 4 + yDist
else:
xyShift = yDist / 4 + xDist

xyShift *= 0.839

if xyShift <= zDist:
zShift = xyShift / 2 + zDist
else:
zShift = xyShift + zDist / 2

deviation_raw = random.randint(0, 100) - (float(accuracy) * 100.0)

target_dist = math.sqrt(xDist * xDist + yDist * yDist)

if deviation_raw >= 0:
deviation_adjusted = deviation_raw + 50
else:
deviation_adjusted = deviation_raw + 10

if abs(zShift) < EPSILON:
zShift = EPSILON

deviation = max(1, int(round(float(zShift) * deviation_adjusted / 200.0)))

OVERALL_SPREAD_COEFF = 1.0
INNER_SPREAD_COEFF = 0.85

result_shifted = False

for i in range(1, 10):

dx = random.randint(0, deviation) - deviation // 2
dy = random.randint(0, deviation) - deviation // 2

deviate_dist = math.sqrt((target.x + dx - origin.x)**2 + (target.y + dy - origin.y)**2)

if (result_shifted is True
and deviate_dist > target_dist - 2
and deviate_dist < target_dist + 2):
break

if result_shifted is False:

radius_sq = dx*dx + dy*dy
if (radius_sq <= (OVERALL_SPREAD_COEFF * deviation // 2) ** 2):
break

result_shifted = True
deviation = int(deviation * INNER_SPREAD_COEFF)

target.x += dx
target.y += dy

z_deviation_range = max(0, deviation // 2)
z_deviation_offset = z_deviation_range // 2
if z_deviation_range > 0:
target.z += random.randint(0, z_deviation_range) - z_deviation_offset

target.x = int(round(target.x))
target.y = int(round(target.y))
target.z = int(round(target.z))
return target

I'll try to make my script work under Windows, but it's not guaranteed.

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 this option gives them a noticable buff.
Valid point. Bear in mind, that shooting with arcing weapon along NE side of a map (30% of all possible shooting directions) gives you more noticable buff than my algorithm, and always was that way. Other than that - of course, turning square dispersion into round with the same size (and into smaller round in case there' second try) buffes explosives, just like making explosives fly in the target directions instead of somewhere else. This algorithm inherently buffs HE shooting rounds. That's what I personally like about it the most ))

If there's an issue with arcing/throwing - we could just enlarge external radius of dispersion circle for these types of attacks. But we need to make sure this is needed, in the first place. I'll be glad if someone will test this, as I myself don't know all corner cases, to be said.

UPD:
In terms of additional shooting dispersion along the line, applied to arcing explosives - there's no particular buff to hitting the target itself, but explosions become predictable, which is not good. I think maybe I should enlarge secondary dispersion radius and make it uniform across all area, instead of reducing the radius. I'll look into it.

UPD2:
Created new repo with Accuracy simulator, published version for Windows (in "Releases" section):
https://github.com/narical/OpenXcom-Accuracy-Simulator
« Last Edit: April 24, 2025, 11:40:08 pm by jnarical »