Minecraft Projectile Launch Angle Calculation

What Other People Did

For one of my projects, I needed some way to determine the launch angle of a projectile in Minecraft that would hit a specific point, so I recalled that the Wurst client, which is open source, had a bow aimbot. Looking at their code, they calculated arrow launch angle like this:

double hDistance = Math.sqrt(posX * posX + posZ * posZ);
double hDistanceSq = hDistance * hDistance;
float g = 0.006F;
float velocitySq = velocity * velocity;
float velocityPow4 = velocitySq * velocitySq;
float neededPitch = (float)-Math.toDegrees(Math.atan((velocitySq - Math.sqrt(velocityPow4 - g * (g * hDistanceSq + 2 * posY * velocitySq))) / (g * hDistance)));

From what I can tell, this is some form or another of the equation found on Wikipedia for the angle θ required to hit coordinate (x, y), which is \(\tan\theta=\frac{v^{2}\pm\sqrt{v^{4}-g\left(gx^{2}+2yv^{2}\right)}}{gx}\). Unfortunately, this equation assumes that there is no air resistance and that movement is continuous. Minecraft, however, does have air resistance and updates position and velocity every 1/20 second (one game tick) rather than continuously, so this equation serves only as an approximation.

As travel time or air resistance increases, this approximation becomes more and more inaccurate. I tested this out in a graphing calculator, and when I simulated the launch of an arrow traveling at one block per tick, the resulting landing point of the arrow with air resistance was a couple of blocks off from the landing point without air resistance. For a fully charged bow, which launches an arrow at a velocity over two blocks per tick, this inaccuracy would be even greater.

In Wurst’s code, they seem to play around with the parameters to compensate for these inaccuracies. g is set to 0.006 instead of 0.05 and velocity is divided by 3, which seems to work well enough for their bow aimbot. However, I wanted a more generalized solution that could work with a greater variety of projectiles and launch velocities.

I looked online for more solutions, but everything I could find seemed to assume a parabolic trajectory in some way or another.

New Approach

Discarding the idea of deriving a purely analytical solution, I went for a new approach: getting an accurate approximation for the angle. After all, simulating Minecraft projectile trajectories is quite straightforward, so I can just make a guess for the angle and refine that guess until it hits the target.

In order to simulate the projectile trajectory, I need to know how Minecraft does its physics. According to the Minecraft wiki, there is different gravitational acceleration and drag for different projectiles, and drag is applied before acceleration in each tick. They even supplied a table of the air resistance and gravitation acceleration for various entity types. In their footnotes, however, they had the following equation for vertical velocity at time t given a certain starting velocity: V(t) = ((1-drag)x)-(acceleration((1-(1-drag)^t)/drag)). Looking at this equation, it makes no sense, since that would mean that at 0 acceleration, velocity would stay constant even if there was drag.

Since I was no longer confident about the accuracy of this section, I cross-referenced the information in it with a decompiled version of Minecraft’s code that I had handy. Indeed, there were other inconsistencies that I started to find, such as the fact that potions had a gravitational acceleration of 0.05 rather than 0.03. However, most of the general details like the order of drag and acceleration were correct. In the code, they do entity physics in this order for every in-game tick:

  1. increment position by the velocity
  2. apply drag
  3. apply gravity

With this information, I came up with the following equation for velocity at time t:
$$v(t)=\left(1-d\right)^{t}\left(v-\frac{a}{d}\right)+\frac{a}{d}$$
where v is initial velocity, a is acceleration, d is drag, and t is time in ticks.

Looking at this equation, I realized that it was simply a geometric series with an added constant value. That meant that position could be calculated as the sum of a finite geometric series plus t times the constant. That produces the following equation for position:
$$p(t)=\frac{(v-T)(1-(1-d)^{t})}{d}-\frac{gt}{d}$$

There’s no horizontal acceleration, so my equation for horizontal position becomes
$$x(t)=\frac{v_x\left(1-\left(1-d\right)^{t}\right)}{d}$$

And vertical acceleration is just -g since gravity is just acceleration in the negative y direction, so my equation for vertical position becomes
$$y(t)=\frac{\left(v_y+\frac{g}{d}\right)\left(1-\left(1-d\right)^{t}\right)}{d}-\frac{gt}{d}$$

Solving for t in terms of x I got
$$t=\frac{\ln\left(1-\frac{xd}{v_{x}}\right)}{\ln\left(1-d\right)}$$

Plugging that into my equation for y(t) I get
$$y=\frac{x\left(\frac{g}{d}+v_{y}\right)}{v_{x}}-\frac{g\ln\left(1-\frac{xd}{v_{x}}\right)}{d\ln\left(1-d\right)}$$

Then substituting \(v\cos\left(\theta\right)\) for x and \(v\sin\left(\theta\right)\) for y, I get
$$y=\frac{x*(\frac{g}{d}+v\sin\left(x)\right)}{v\cos x}-\frac{g\ln\left(1-\frac{xd}{v\cos x}\right)}{d\ln\left(1-d\right)}$$

And so the derivative of y with respect to θ is
$$\frac{dy}{d\theta}=x+\frac{xg\tan x}{\left(-xd+v\cos x\right)\ln\left(1-d\right)}+\frac{xg\tan x}{dv\cos x}+x\tan^2\left(x\right)$$

From there, I just pick the angle that points straight toward my target x and y with Math.atan2(ty, tx) to get a rough estimate for my desired angle and use newton approximation from there to get the correct launch angle. It will always solve for the lowest possible angle since my starting angle (pointing straight at the target) will always undershoot the target.

public static Double getNeededAngle(double tx, double ty, double v, double d, double g) {
    // If it's near the asymptotes, just return a vertical angle
    if (tx < ty * 0.001) {
        return ty>0 ? Math.PI/2.0 : -Math.PI/2.0;
    }
    
    double md = 1.0-d;
    double log_md = Math.log(md);
    double g_d = g/d; // This is terminal velocity
    double theta = Math.atan2(ty, tx);
    double prev_abs_ydif = Double.POSITIVE_INFINITY;
    
    // 20 iterations max, although it usually converges in 3 iterations
    for (int i=0; i<20; i++) {
        System.out.println(i);
        double cost = Math.cos(theta);
        double sint = Math.sin(theta);
        double tant = sint/cost;
        double vx = v * cost;
        double vy = v * sint;
        double y = tx*(g_d+vy)/vx - g_d*Math.log(1-d*tx/vx)/log_md;
        double ydif = y-ty;
        double abs_ydif = Math.abs(ydif);
        
        // If it's getting farther away, there's probably no solution
        if (abs_ydif>prev_abs_ydif) {
            return null;
        }
        else if (abs_ydif < 0.0001) {
            return theta;
        }
        
        double dy_dtheta = tx + g*tx*tant / ((-d*tx+v*cost)*log_md) + g*tx*tant/(d*v*cost) + tx*tant*tant;
        theta -= ydif/dy_dtheta;
        prev_abs_ydif = abs_ydif;
    }
    
    // If exceeded max iterations, return null
    return null;
}

To get the launch angle to shoot an arrow with a fully charged bow from position1 to position2, for example, you would just have to do something like

double dx = position2.x-position1.x;
double dy = position2.y-position1.y;
double dz = position2.z-position1.z;
double h = Math.sqrt(dx*dx + dz*dz);
double yaw = (360 - Math.toDegrees(Math.atan2(dx, dz))) % 360;
double pitch = Math.toDegrees(
    getNeededAngle(h, dy, 3, 0.01, 0.05));

7 comments

  1. Absolutely love that post, it helps massively with a little project of mine. I just have 2 questions: From what direction (North, East, South, West) does your Yaw start counting?
    Does the calculation work with targets higher AND lower than the shooting position?

    1. Sorry for not responding, I wasn’t expecting for this blog post to get comments so I never checked.

      The yaw is based on how Minecraft represents yaw, which is a little weird since it doesn’t follow convention:
      yaw = 0 -> positive z
      yaw = 90 -> negative x
      yaw = 180 -> negative z
      yaw = 270 -> positive x
      Here’s an image if it helps: https://wiki.vg/images/1/1e/Minecraft-trig-yaw.png

      The calculation will work for both targets that are higher and targets that are lower than the shooting position.

  2. Hi, in my project I need to calculate the trajectory of a projectile in water. Do you know what the hydrodynamic resistance is? I tried to find it, but I didn’t find anything and the only solution I see is to simply test the deviation of your algorithm in water and based on that find the correct d…

    1. Yeah that is probably your best bet, unless you want to try my approach which was to manually go through the source code to find the correct constants for each projectile I wanted to use (which can be tedious due to the fact that they don’t keep these values in one place). I might make something to find these values automatically in the future though.

      If it helps, I believe it is 0.4 for arrows in water.

    1. The Minecraft physics engine internally has different drag and gravity values for different entities, so that’s what (d) and (g) mean respectively.

      In my blog post, I linked to the following Minecraft Wiki page, which contains a table of these values: https://minecraft.fandom.com/wiki/Entity#Motion_of_entities

      They seem to mostly be correct, but I found that some of the values were wrong unfortunately. The 0.01 (d) and 0.05 (g) values I used in my article is for arrows, which I confirmed are correct.

      If the projectile you’re working with turns out to not have the correct value in the table, I unfortunately do not have an easy way to find it. What I personally did was to go digging through Minecraft’s source code to get the values, which was quite tedious due to how their code is structured.

      1. I actually fixed the issue, I was able to graph the trajectory of arrows and most projectiles (excluding ender pearls). There was a repository from Michael Zhang on trajectories for 1.8 Minecraft, so I adapted it for 1.8.9 here
        https://github.com/listingclown3/AimBow/tree/master

        The BowRayData and ThrowableRayData have the code to simulate projectile trajectories. Check it out some time.

Leave a comment

Your email address will not be published. Required fields are marked *