All users will need to merge their Minecraft Forum account with a new or existing Twitch account starting October 23rd. You can merge your accounts by clicking here. Have questions? Learn more here.

I am trying to increase the range of skeleton archers, and/or replace their shooting AI with a different one, or create a new mob if necessary.

So far i am removing the "EntityAIAttackRangedBow" task every "UpdateLiving" event (absolutly garbage, just to see if the rest works), since the method "AbstractSkeleton.setCombatTask()" adds the original task occasionly, and i have added a new version of it (new class that doesnt extend the original but same code with minor changes) to the tasklist, with a higher "maxAttackDistance".

skeletons attack properly, but dont if i remove the "addtask" line, however the aggro distance has not changed...

any ideas why that is?

code:

public static void UpdateLiving(LivingEvent.LivingUpdateEvent event){
if(event.getEntity() instanceof EntitySkeleton) {
EntitySkeleton skeleton =((EntitySkeleton)event.getEntity());
for (Object entry : skeleton.tasks.taskEntries.toArray()) {
EntityAIBase ai = ((EntityAITasks.EntityAITaskEntry) entry).action;
if (ai instanceof EntityAIAttackRangedBow) {
skeleton.tasks.removeTask(ai);
System.out.println("removeTask: AttackRangedBow");

final EntityAISnipeBow SnipeAI = new EntityAISnipeBow(skeleton, 1.0D, 20, 256.0F);

int i = 20;
if (skeleton.world.getDifficulty() != EnumDifficulty.HARD)
{
i = 40;
}
SnipeAI.setAttackCooldown(i);

public class EntityAISnipeBow<T extends EntityMob & IRangedAttackMob> extends EntityAIBase
{
private final T entity;
private final double moveSpeedAmp;
private int attackCooldown;
private final float maxAttackDistance;
private int attackTime = -1;
private int seeTime;
private boolean strafingClockwise;
private boolean strafingBackwards;
private int strafingTime = -1;

/**
* Returns whether an in-progress EntityAIBase should continue executing
*/
public boolean shouldContinueExecuting()
{
return (this.shouldExecute() || !this.entity.getNavigator().noPath()) && this.isBowInMainhand();
}

/**
* Execute a one shot task or start executing a continuous task
*/
public void startExecuting()
{
super.startExecuting();
((IRangedAttackMob)this.entity).setSwingingArms(true);
}

/**
* Reset the task's internal state. Called when this task is interrupted by another one
*/
public void resetTask()
{
super.resetTask();
((IRangedAttackMob)this.entity).setSwingingArms(false);
this.seeTime = 0;
this.attackTime = -1;
this.entity.resetActiveHand();
}

/**
* Keep ticking a continuous task that has already been started
*/
public void updateTask()
{
EntityLivingBase entitylivingbase = this.entity.getAttackTarget();

if (this.entity.isHandActive())
{
if (!flag && this.seeTime < -60)
{
this.entity.resetActiveHand();
}
else if (flag)
{
int i = this.entity.getItemInUseMaxCount();

next would be tweaking the EntityAISnipeBowI-task to properly account for gravity (not just aiming 0.2 blocks higher every block distance...) and increasing the arrow velocity to obtain a similar reach as players.

skeletons fire at 1.6 velocity, and the max value that a player can shoot at is 3, or at leat that is what ItemBow.onPlayerStoppedUsing suggests, though im not clear on the max value obtained in practice since that the parameter "timeLeft" is obscure to me...

i could determine the value experimentally, since i found the value for the "gravitational-accceleration" in EntityArrow.OnUpdate():

this.motionY -= 0.05000000074505806D;

(probably in blocks/tick -> 0.05m x20² -> 20m/s² ?!)

edit: experiments give me 2,38 for player shot arrows.

but i would rather figure out the math behind the code.

Well, while you could find the value based on a closed-form formula, it is probably easier to "brute force" it by simulating various values. You could probably just simulate it in the game where you create a flat world, and make a config to change the values and see where things land.

However, one thing that helps is I don't think the arrow code slows the arrow down in flight. It only applies a pseudo-gravitational constant. Since it doesn't slow down, I think that the distance might simply change based on the proportion you change the arrow speed without changing the gravity. If you think about it, an arrow takes a certain number of ticks to hit the ground in vanilla so if you speed it up then it should cover a proportional amount of distance in that same number of ticks.

So play around with it on a flat world. You can also try changing the gravitational constant.

the arrow does slow down, but only when it is flying upward. it will reach max height, at which point vertical speed is 0, and then it accelerates back down...

the fact that the arrow speed is decremented each tick by a constant value is perfectly equivalent to gravitational acceleration, and as such the laws of free fall apply.

also this constant is part of entityArrow, so changing this would change every arrow. that and it is not a variable, but an immediat...

it would be possible however, to add drag to the arrow, since the loss of kinetic-energy is more or less geometric, but thats a different mod alltogether, since i dont want to change arrow behaviour.

I just want some Archers to put on my ramperts that will actually shoot enemies when they are in reach of their bows.

concerning projectile reach, it doesnt scale linearly with initial velocity but with the velocity squared and that only on flat ground. as soon as shooter and target are at different levels, the formula becomes a bit more complicated. Wiki artticle

I am currently testing this solution(Link),more specificly the one with constant speed projectile and moving target.

When I said it doesn't slow down, I mean it doesn't have any air friction resistance. It does have gravity. However, that only works in the Y direction whereas air friction (in real world arrows) applies opposite the velocity no matter what the velocity vector is.

This means that the distance the arrow will fly per tick in X. Z is proportional to the speed it is shot. If you shoot it at different angles, it will of course fly different distances, but for any given angle if you shoot it at twice the speed it will go twice as far horizontally per tick. And this is constant for the X and Z for the full flight of the arrow.

The time it is in the air though will be different (assuming you don't change the gravity acceleration) because it will have an initial vertical speed based on the Y component of the initial velocity which will be proportional to the change in speed. Then the gravity has to decrease that velocity over a number of ticks until is starts coming down and eventually hits the ground. If the velocity is higher then the number of ticks it is in the air is more.

This is why shooting at 45 degrees when there is no air resistance results in the farthest shot.

We're used to real-world projectiles with air resistance so our feeling for this Minecraft-type trajectory isn't quite right. But it means that in you double the initial velocity it will more than double the distance because it will both stay in the air longer and travel at a faster rate. In other words, every angle will give you a different result in terms of how much further proportionally it will travel.

If you're going to do it with a formula, you need to solve for two equations related to the time in the air for a given angle. You have the time in the air equal to the vertical component of the initial velocity as modified by the gravity, and then multiply that by the velocity in the horizontal X, Z plane to get the distance traveled.

Remember too that Minecraft is a discrete system, not continuous, so the math won't match up exactly unless you count in ticks. Let's do the math...

If initial arrow speed is 1.0 at 45 degrees:

In one second you only get 20 calculations to apply gravity. So if initial arrow speed was 1.0 shot at 45 degrees with 0.05 gravity, then the vertical component would be 1.0*sin(45deg) = 0.71. Then one tick later it would be 0.66, then 0.61 and so forth. So it would take 0.71 / 0.05 = 14 ticks to get to top (where vertical velocity = 0) and equal number of ticks to come back down. So this is 28 ticks = about 1.4 seconds. The horizontal component of the initial velocity is 1.0* cos(45deg) = 0.71 and this does not slow down so it will travel horizontal distance of 0.71 * 28 = 20.

If initial arrow speed is 2.3 at 45 degrees:

In one second you only get 20 calculations to apply gravity. So if initial arrow speed was 2.3 shot at 45 degrees with 0.05 gravity, then the vertical component would be 2.3*sin(45deg) = 1.6. Then one tick later it would be 1.55, then 1.50 and so forth. So it would take 1.55 / 0.05 = 31 ticks to get to top (where vertical velocity = 0) and equal number of ticks to come back down. So this is 62 ticks = about 3 seconds. The horizontal component of the initial velocity is 2.3*cos(45deg) = 1.6 and this does not slow down so it will travel horizontal distance of 1.6 * 62 = 99.

So you see that by increasing the velocity by factor of 2.3 you increase the distance by 5 times!

Okay, so think through the math it should be as follows:

this makes sense because you multiply the horizontal speed by the number of ticks which equals the vertical divided by gravitational acceleration times two (up and then back down).

Note though this is the flat distance and assume angle is at least upwards a bit. If you're shooting downwards, or if you're shooting at upwards at a target also above you you'll have to go through the math to handle those cases as well.

The interesting thing is how the distance is proportional to the SQUARE of the initial speed. So if you want it to go twice as far you'll want to only increase the speed by square root of two.

Please check my math -- I just did it quickly way past my bedtime.

which is exactly the formula from the Wiki article that i mentioned.

it also works for high-arc trajectories.

Otherwise, the math(Link) for finding the x,y,z components for a a projectile with a predefined initial velocity and a target moving at constant speed and direction seems to be working correctly.

i have only tested it with shooter and target at similar levels (the eyeheight of golems is is higher than the inital y-pos of the arrows shot by a skeleton after all...), and with an initial velocity of 3, the skeletons manage to shoot golems up to 72 blocks away, which is not their full range, and their arrows fall short of them if the distance increases from these 72blocks, eventhough they are shooting further...

public void attackEntityWithSnipeAttack(EntityLivingBase shooter,EntityLivingBase target, float distanceFactor)
{
EntityArrow entityarrow = getArrow(shooter, distanceFactor);
double G = 0.05000000074505806D;
double A = entityarrow.posX;
double B = entityarrow.posY;
double C = entityarrow.posZ;
double d; //entityarrow.motionX;
double e; //entityarrow.motionY;
double f; //entityarrow.motionZ;
double M = target.posX;
double N = target.getEntityBoundingBox().minY + (double)target.getEyeHeight();
double O = target.posZ;
//relative motion
double P = target.motionX - shooter.motionX;
double Q = target.motionY - shooter.motionY;
double R = target.motionZ - shooter.motionZ;
//projectile_speed
double S = 3D;
double H = M - A;
double J = O - C;
double K = N - B;
double L = -0.5F * G;
//Quartic Coefficients
double c0 = L*L;
double c1 = 2*Q*L;
double c2 = Q*Q + 2*K*L - S*S +P*P +R*R;
double c3 = 2*K*Q + 2*H*P + 2*J*R;
double c4 = K*K + H*H + J*J;
//solve Quartic
double[] t = solveQuartic(c0, c1, c2, c3, c4);
double low_t = 99999999999999D; //impossibly high in this context
if (t != null) {
for (double entry : t) {
System.out.println("entry:" + entry);
int retval = Double.compare(entry, low_t);
if (retval < 0 && entry > 0) {
low_t = entry;
}
}
System.out.println("low_t:" + low_t);
}
else{
return;
}
d= ((H+P*low_t)/low_t);
e= ((K+Q*low_t-L*low_t*low_t)/low_t);
f= ((J+R*low_t)/low_t);
entityarrow.shoot(d, e, f, (float)S, (float)(9 - shooter.world.getDifficulty().getDifficultyId() * 3));
entityarrow.motionX += shooter.motionX;
entityarrow.motionZ += shooter.motionZ;
if (!shooter.onGround)
{
entityarrow.motionY += shooter.motionY;
}
shooter.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (shooter.getRNG().nextFloat() * 0.4F + 0.8F));
shooter.world.spawnEntity(entityarrow);
//System.out.println("Arrow: "+entityarrow.motionX+","+entityarrow.motionY+","+entityarrow.motionZ);
}

this solveQuartic method is scavanged (from sunflower api) and only returns real roots.

/**
* Solve a quartic equation of the form ax^4+bx^3+cx^2+dx^1+e=0. The roots
* are returned in a sorted array of doubles in increasing order.
*
* @param a coefficient of x^4
* @param b coefficient of x^3
* @param c coefficient of x^2
* @param d coefficient of x^1
* @param e coefficient of x^0
* @return a sorted array of roots, or <code>null</code> if no solutions
* exist
*/
public static double[] solveQuartic(double a, double b, double c, double d, double e) {
double inva = 1 / a;
double c1 = b * inva;
double c2 = c * inva;
double c3 = d * inva;
double c4 = e * inva;
// cubic resolvant
double c12 = c1 * c1;
double p = -0.375 * c12 + c2;
double q = 0.125 * c12 * c1 - 0.5 * c1 * c2 + c3;
double r = -0.01171875 * c12 * c12 + 0.0625 * c12 * c2 - 0.25 * c1 * c3 + c4;
double z = solveCubicForQuartic(-0.5 * p, -r, 0.5 * r * p - 0.125 * q * q);
double d1 = 2.0 * z - p;
if (d1 < 0) {
if (d1 > 1.0e-10)
d1 = 0;
else
return null;
}
double d2;
if (d1 < 1.0e-10) {
d2 = z * z - r;
if (d2 < 0)
return null;
d2 = Math.sqrt(d2);
} else {
d1 = Math.sqrt(d1);
d2 = 0.5 * q / d1;
}
// setup usefull values for the quadratic factors
double q1 = d1 * d1;
double q2 = -0.25 * c1;
double pm = q1 - 4 * (z - d2);
double pp = q1 - 4 * (z + d2);
if (pm >= 0 && pp >= 0) {
// 4 roots (!)
pm = Math.sqrt(pm);
pp = Math.sqrt(pp);
double[] results = new double[4];
results[0] = -0.5 * (d1 + pm) + q2;
results[1] = -0.5 * (d1 - pm) + q2;
results[2] = 0.5 * (d1 + pp) + q2;
results[3] = 0.5 * (d1 - pp) + q2;
// tiny insertion sort
for (int i = 1; i < 4; i++) {
for (int j = i; j > 0 && results[j - 1] > results[j]; j--) {
double t = results[j];
results[j] = results[j - 1];
results[j - 1] = t;
}
}
return results;
} else if (pm >= 0) {
pm = Math.sqrt(pm);
double[] results = new double[2];
results[0] = -0.5 * (d1 + pm) + q2;
results[1] = -0.5 * (d1 - pm) + q2;
return results;
} else if (pp >= 0) {
pp = Math.sqrt(pp);
double[] results = new double[2];
results[0] = 0.5 * (d1 - pp) + q2;
results[1] = 0.5 * (d1 + pp) + q2;
return results;
}
return null;
}
/**
* Return only one root for the specified cubic equation. This routine is
* only meant to be called by the quartic solver. It assumes the cubic is of
* the form: x^3+px^2+qx+r.
*
* @param p
* @param q
* @param r
* @return
*/
private static final double solveCubicForQuartic(double p, double q, double r) {
double A2 = p * p;
double Q = (A2 - 3.0 * q) / 9.0;
double R = (p * (A2 - 4.5 * q) + 13.5 * r) / 27.0;
double Q3 = Q * Q * Q;
double R2 = R * R;
double d = Q3 - R2;
double an = p / 3.0;
if (d >= 0) {
d = R / Math.sqrt(Q3);
double theta = Math.acos(d) / 3.0;
double sQ = -2.0 * Math.sqrt(Q);
return sQ * Math.cos(theta) - an;
} else {
double sQ = Math.pow(Math.sqrt(R2 - Q3) + Math.abs(R), 1.0 / 3.0);
if (R < 0)
return (sQ + Q / sQ) - an;
else
return -(sQ + Q / sQ) - an;
}
}

I will have to create a new "EntityAINearestAttackableTarget" Task for targets that are more than just a few blocks below/above the shooter.

Rollback Post to RevisionRollBack

To post a comment, please login or register a new account.

Hi.

I am trying to increase the range of skeleton archers, and/or replace their shooting AI with a different one, or create a new mob if necessary.

So far i am removing the "EntityAIAttackRangedBow" task every "UpdateLiving" event (absolutly garbage, just to see if the rest works), since the method "AbstractSkeleton.setCombatTask()" adds the original task occasionly, and i have added a new version of it (new class that doesnt extend the original but same code with minor changes) to the tasklist, with a higher "maxAttackDistance".

skeletons attack properly, but dont if i remove the "addtask" line, however the aggro distance has not changed...

any ideas why that is?

code:

if(event.getEntity() instanceof EntitySkeleton) {

EntitySkeleton skeleton =((EntitySkeleton)event.getEntity());

for (Object entry : skeleton.tasks.taskEntries.toArray()) {

EntityAIBase ai = ((EntityAITasks.EntityAITaskEntry) entry).action;

if (ai instanceof EntityAIAttackRangedBow) {

skeleton.tasks.removeTask(ai);

System.out.println("removeTask: AttackRangedBow");

final EntityAISnipeBow SnipeAI = new EntityAISnipeBow(skeleton, 1.0D, 20, 256.0F);

int i = 20;

if (skeleton.world.getDifficulty() != EnumDifficulty.HARD)

{

i = 40;

}

SnipeAI.setAttackCooldown(i);

skeleton.tasks.addTask(4,SnipeAI);

}

}

}

}

class EntityAISnipeBow :

import net.minecraft.entity.EntityLivingBase;

import net.minecraft.entity.IRangedAttackMob;

import net.minecraft.entity.ai.EntityAIBase;

import net.minecraft.entity.monster.EntityMob;

import net.minecraft.entity.projectile.EntityArrow;

import net.minecraft.entity.projectile.EntityTippedArrow;

import net.minecraft.init.Items;

import net.minecraft.init.SoundEvents;

import net.minecraft.item.ItemBow;

import net.minecraft.util.EnumHand;

import net.minecraft.util.math.MathHelper;

public class EntityAISnipeBow<T extends EntityMob & IRangedAttackMob> extends EntityAIBase

{

private final T entity;

private final double moveSpeedAmp;

private int attackCooldown;

private final float maxAttackDistance;

private int attackTime = -1;

private int seeTime;

private boolean strafingClockwise;

private boolean strafingBackwards;

private int strafingTime = -1;

public EntityAISnipeBow(T p_i47515_1_, double p_i47515_2_, int p_i47515_4_, float p_i47515_5_)

{

this.entity = p_i47515_1_;

this.moveSpeedAmp = p_i47515_2_;

this.attackCooldown = p_i47515_4_;

this.maxAttackDistance = p_i47515_5_ * p_i47515_5_;

this.setMutexBits(3);

}

public void setAttackCooldown(int p_189428_1_)

{

this.attackCooldown = p_189428_1_;

}

/**

* Returns whether the EntityAIBase should begin execution.

*/

public boolean shouldExecute()

{

return this.entity.getAttackTarget() == null ? false : this.isBowInMainhand();

}

protected boolean isBowInMainhand()

{

return !this.entity.getHeldItemMainhand().isEmpty() && this.entity.getHeldItemMainhand().getItem() == Items.BOW;

}

/**

* Returns whether an in-progress EntityAIBase should continue executing

*/

public boolean shouldContinueExecuting()

{

return (this.shouldExecute() || !this.entity.getNavigator().noPath()) && this.isBowInMainhand();

}

/**

* Execute a one shot task or start executing a continuous task

*/

public void startExecuting()

{

super.startExecuting();

((IRangedAttackMob)this.entity).setSwingingArms(true);

}

/**

* Reset the task's internal state. Called when this task is interrupted by another one

*/

public void resetTask()

{

super.resetTask();

((IRangedAttackMob)this.entity).setSwingingArms(false);

this.seeTime = 0;

this.attackTime = -1;

this.entity.resetActiveHand();

}

/**

* Keep ticking a continuous task that has already been started

*/

public void updateTask()

{

EntityLivingBase entitylivingbase = this.entity.getAttackTarget();

if (entitylivingbase != null)

{

double d0 = this.entity.getDistanceSq(entitylivingbase.posX, entitylivingbase.getEntityBoundingBox().minY, entitylivingbase.posZ);

boolean flag = this.entity.getEntitySenses().canSee(entitylivingbase);

boolean flag1 = this.seeTime > 0;

if (flag != flag1)

{

this.seeTime = 0;

}

if (flag)

{

++this.seeTime;

}

else

{

--this.seeTime;

}

if (d0 <= (double)this.maxAttackDistance && this.seeTime >= 20)

{

this.entity.getNavigator().clearPath();

++this.strafingTime;

}

else

{

this.entity.getNavigator().tryMoveToEntityLiving(entitylivingbase, this.moveSpeedAmp);

this.strafingTime = -1;

}

if (this.strafingTime >= 20)

{

if ((double)this.entity.getRNG().nextFloat() < 0.3D)

{

this.strafingClockwise = !this.strafingClockwise;

}

if ((double)this.entity.getRNG().nextFloat() < 0.3D)

{

this.strafingBackwards = !this.strafingBackwards;

}

this.strafingTime = 0;

}

if (this.strafingTime > -1)

{

if (d0 > (double)(this.maxAttackDistance * 0.75F))

{

this.strafingBackwards = false;

}

else if (d0 < (double)(this.maxAttackDistance * 0.25F))

{

this.strafingBackwards = true;

}

this.entity.getMoveHelper().strafe(this.strafingBackwards ? -0.5F : 0.5F, this.strafingClockwise ? 0.5F : -0.5F);

this.entity.faceEntity(entitylivingbase, 30.0F, 30.0F);

}

else

{

this.entity.getLookHelper().setLookPositionWithEntity(entitylivingbase, 30.0F, 30.0F);

}

if (this.entity.isHandActive())

{

if (!flag && this.seeTime < -60)

{

this.entity.resetActiveHand();

}

else if (flag)

{

int i = this.entity.getItemInUseMaxCount();

if (i >= 20)

{

this.entity.resetActiveHand();

attackEntityWithSnipeAttack(this.entity, entitylivingbase, ItemBow.getArrowVelocity(i)); <---------CHANGE

this.attackTime = this.attackCooldown;

}

}

}

else if (--this.attackTime <= 0 && this.seeTime >= -60)

{

this.entity.setActiveHand(EnumHand.MAIN_HAND);

}

}

}

public void attackEntityWithSnipeAttack(EntityLivingBase shooter,EntityLivingBase target, float distanceFactor) <--------NEW

{

EntityArrow entityarrow = getArrow(shooter, distanceFactor);

double d0 = target.posX - shooter.posX;

double d1 = target.getEntityBoundingBox().minY + (double)(target.height / 3.0F) - entityarrow.posY;

double d2 = target.posZ - shooter.posZ;

double d3 = (double) MathHelper.sqrt(d0 * d0 + d2 * d2);

entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float)(14 - shooter.world.getDifficulty().getDifficultyId() * 4));

shooter.playSound(SoundEvents.ENTITY_SKELETON_SHOOT, 1.0F, 1.0F / (shooter.getRNG().nextFloat() * 0.4F + 0.8F));

shooter.world.spawnEntity(entityarrow);

}

protected EntityArrow getArrow(EntityLivingBase shooter,float p_190726_1_) <--------NEW

{

EntityTippedArrow entitytippedarrow = new EntityTippedArrow(shooter.world, shooter);

entitytippedarrow.setEnchantmentEffectsFromEntity(shooter, p_190726_1_);

return entitytippedarrow;

}

}

The aggro distance is usually set by the targeting task (separate task).

Found it through: EntityAINearestAttackableTarget

SharedMonsterAttributes.FOLLOW_RANGEThanks alot!

[/pre]

works great:

inal AttributeModifier Sniping_Aggro_Boost = (new AttributeModifier("Sniping Aggro Boost", 45.0D, 2)).setSaved(false);

skeleton.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).applyModifier(Sniping_Aggro_Boost);

next would be tweaking the EntityAISnipeBowI-task to properly account for gravity (not just aiming 0.2 blocks higher every block distance...) and increasing the arrow velocity to obtain a similar reach as players.

skeletons fire at 1.6 velocity, and the max value that a player can shoot at is 3, or at leat that is what ItemBow.onPlayerStoppedUsing suggests, though im not clear on the max value obtained in practice since that the parameter "timeLeft" is obscure to me...

i could determine the value experimentally, since i found the value for the "gravitational-accceleration" in EntityArrow.OnUpdate():

this.motionY -= 0.05000000074505806D;

(probably in blocks/tick -> 0.05m x20² -> 20m/s² ?!)

edit: experiments give me 2,38 for player shot arrows.

but i would rather figure out the math behind the code.

Well, while you could find the value based on a closed-form formula, it is probably easier to "brute force" it by simulating various values. You could probably just simulate it in the game where you create a flat world, and make a config to change the values and see where things land.

However, one thing that helps is I don't think the arrow code slows the arrow down in flight. It only applies a pseudo-gravitational constant. Since it doesn't slow down, I think that the distance might simply change based on the proportion you change the arrow speed without changing the gravity. If you think about it, an arrow takes a certain number of ticks to hit the ground in vanilla so if you speed it up then it should cover a proportional amount of distance in that same number of ticks.

So play around with it on a flat world. You can also try changing the gravitational constant.

the arrow does slow down, but only when it is flying upward. it will reach max height, at which point vertical speed is 0, and then it accelerates back down...

the fact that the arrow speed is decremented each tick by a constant value is perfectly equivalent to gravitational acceleration, and as such the laws of free fall apply.

also this constant is part of entityArrow, so changing this would change every arrow. that and it is not a variable, but an immediat...

it would be possible however, to add drag to the arrow, since the loss of kinetic-energy is more or less geometric, but thats a different mod alltogether, since i dont want to change arrow behaviour.

I just want some Archers to put on my ramperts that will actually shoot enemies when they are in reach of their bows.

concerning projectile reach, it doesnt scale linearly with initial velocity but with the velocity squared and that only on flat ground. as soon as shooter and target are at different levels, the formula becomes a bit more complicated. Wiki artticle

I am currently testing this solution(Link),more specificly the one with constant speed projectile and moving target.

When I said it doesn't slow down, I mean it doesn't have any air friction resistance. It does have gravity. However, that only works in the Y direction whereas air friction (in real world arrows) applies opposite the velocity no matter what the velocity vector is.

This means that the distance the arrow will fly per tick in X. Z is proportional to the speed it is shot. If you shoot it at different angles, it will of course fly different distances, but for any given angle if you shoot it at twice the speed it will go twice as far horizontally per tick. And this is constant for the X and Z for the full flight of the arrow.

The time it is in the air though will be different (assuming you don't change the gravity acceleration) because it will have an initial vertical speed based on the Y component of the initial velocity which will be proportional to the change in speed. Then the gravity has to decrease that velocity over a number of ticks until is starts coming down and eventually hits the ground. If the velocity is higher then the number of ticks it is in the air is more.

This is why shooting at 45 degrees when there is no air resistance results in the farthest shot.

We're used to real-world projectiles with air resistance so our feeling for this Minecraft-type trajectory isn't quite right. But it means that in you double the initial velocity it will more than double the distance because it will both stay in the air longer and travel at a faster rate. In other words, every angle will give you a different result in terms of how much further proportionally it will travel.

If you're going to do it with a formula, you need to solve for two equations related to the time in the air for a given angle. You have the time in the air equal to the vertical component of the initial velocity as modified by the gravity, and then multiply that by the velocity in the horizontal X, Z plane to get the distance traveled.

Remember too that Minecraft is a discrete system, not continuous, so the math won't match up exactly unless you count in ticks. Let's do the math...

If initial arrow speed is 1.0 at 45 degrees:In one second you only get 20 calculations to apply gravity. So if initial arrow speed was 1.0 shot at 45 degrees with 0.05 gravity, then the vertical component would be 1.0*sin(45deg) = 0.71. Then one tick later it would be 0.66, then 0.61 and so forth. So it would take 0.71 / 0.05 = 14 ticks to get to top (where vertical velocity = 0) and equal number of ticks to come back down. So this is 28 ticks = about 1.4 seconds. The horizontal component of the initial velocity is 1.0* cos(45deg) = 0.71 and this does not slow down so it will travel horizontal distance of 0.71 * 28 = 20.

If initial arrow speed is 2.3 at 45 degrees:In one second you only get 20 calculations to apply gravity. So if initial arrow speed was 2.3 shot at 45 degrees with 0.05 gravity, then the vertical component would be 2.3*sin(45deg) = 1.6. Then one tick later it would be 1.55, then 1.50 and so forth. So it would take 1.55 / 0.05 = 31 ticks to get to top (where vertical velocity = 0) and equal number of ticks to come back down. So this is 62 ticks = about 3 seconds. The horizontal component of the initial velocity is 2.3*cos(45deg) = 1.6 and this does not slow down so it will travel horizontal distance of 1.6 * 62 = 99.

So you see that by increasing the velocity by factor of 2.3 you increase the distance by 5 times!

Okay, so think through the math it should be as follows:

distanceHorizontal = initialSpeed * cosDegrees(angleInDegrees) * initialSpeed * sinDegrees(angleInDegrees) / ab s(gravityConstant) * 2this makes sense because you multiply the horizontal speed by the number of ticks which equals the vertical divided by gravitational acceleration times two (up and then back down).

Note though this is the flat distance and assume angle is at least upwards a bit. If you're shooting downwards, or if you're shooting at upwards at a target also above you you'll have to go through the math to handle those cases as well.

The interesting thing is how the distance is proportional to the SQUARE of the initial speed. So if you want it to go twice as far you'll want to only increase the speed by square root of two.

Please check my math -- I just did it quickly way past my bedtime.

i agree completely.

your math seems correct and can be simplified to the formula:

distanceHorizontal = initialSpeed * initialSpeed * sinDegrees(2 * angleInDegrees) / abs(gravityConstant)

which is exactly the formula from the Wiki article that i mentioned.

it also works for high-arc trajectories.

Otherwise, the math(Link) for finding the x,y,z components for a a projectile with a predefined initial velocity and a target moving at constant speed and direction seems to be working correctly.

i have only tested it with shooter and target at similar levels (the eyeheight of golems is is higher than the inital y-pos of the arrows shot by a skeleton after all...), and with an initial velocity of 3, the skeletons manage to shoot golems up to 72 blocks away, which is not their full range, and their arrows fall short of them if the distance increases from these 72blocks, eventhough they are shooting further...

this solveQuartic method is scavanged (from sunflower api) and only returns real roots.

I will have to create a new "EntityAINearestAttackableTarget" Task for targets that are more than just a few blocks below/above the shooter.