Синхронизация позиции entity [1.7.10]

2,502
78
380
Я уже давненько сделал Entity в своих целях для projectile. Еще тогда, я столкнулся с проблемой, что клиентские координаты сущности почему-то меняются при попадании в землю. На самом деле, стрела находится в дереве. Но в клиенте она меняет свое положение.


Я временно решил проблему костылем, переопределив такой метод.
Код:
@Override
@SideOnly(Side.CLIENT)
public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int par) {}

Проблема в тот момент решилась, но сегодня она вернулась... Теперь происходит рассинхрон  с полетом сущности, если задать большой motion.
На самом деле, стрелы застревают в дереве. (Не обращайте внимание на то, что они не падают, так и должно быть) Если, например выстрелить в воздух, они могут лететь по дуге :silly:. Эта проблема решается возвращением того переопределенного метода.

Что я делаю не так? Вот код сущности. Он почти аналогичен EntityArrow.
Код:
public class EntityProjectile extends Entity implements IThrowableEntity, IProjectile
{
    protected int xTile;
    protected int yTile;
    protected int zTile;
    protected Block inTile;
    protected int inData;
    protected boolean inGround;
    protected boolean beenInGround;
    protected int ticksAlive;
    public int untouch;
    
    public EntityLivingBase thrower;
    protected String throwerName;
    public float magicDamage;
    
    public EntityProjectile(World world)
    {
        super(world);
        renderDistanceWeight = 10.0D;
        setSize(0.5F, 0.5F);
        ticksExisted = 1200;  
        untouch = getMaxUntouchability();
    }

    public EntityProjectile(World world, double x, double y, double z)
    {
        this(world);
        setPosition(x, y, z);
        yOffset = 0.0F;
    }
    
    public EntityProjectile(World world, EntityLivingBase thrower, float speed, float deviation)
    {
        this(world);
        this.thrower = thrower;
        
        setLocationAndAngles(thrower.posX, thrower.posY + thrower.getEyeHeight(), thrower.posZ, thrower.rotationYaw, thrower.rotationPitch);
        posX -= MathHelper.cos(rotationYaw / 180.0F * (float)Math.PI) * 0.16F;
        posY -= 0.1;
        posZ -= MathHelper.sin(rotationYaw / 180.0F * (float)Math.PI) * 0.16F;
        
        Vec3 vec = thrower.getLook(1F);
        setPosition(posX + vec.xCoord, posY + vec.yCoord, posZ + vec.zCoord);
        
        yOffset = 0.0F;
        motionX = -MathHelper.sin(rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(rotationPitch / 180.0F * (float)Math.PI);
        motionZ =  MathHelper.cos(rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(rotationPitch / 180.0F * (float)Math.PI);
        motionY = (-MathHelper.sin(rotationPitch / 180.0F * (float)Math.PI));
        setThrowableHeading(motionX, motionY, motionZ, speed, deviation);
    }

    public EntityProjectile(World world, EntityLivingBase thrower, EntityLivingBase target, float speed, float deviation)
    {
        this(world);
        this.thrower = thrower;

        posY = thrower.posY + thrower.getEyeHeight() - 0.1;
        double d0 = target.posX - thrower.posX;
        double d1 = target.boundingBox.minY + target.height / 3.0F - posY;
        double d2 = target.posZ - thrower.posZ;
        double d3 = MathHelper.sqrt_double(d0 * d0 + d2 * d2);

        if (d3 >= 1.0E-7D) {
            float f2 = (float)(Math.atan2(d2, d0) * 180.0D / Math.PI) - 90.0F;
            float f3 = (float)(-(Math.atan2(d1, d3) * 180.0D / Math.PI));
            double d4 = d0 / d3;
            double d5 = d2 / d3;
            setLocationAndAngles(thrower.posX + d4, posY, thrower.posZ + d5, f2, f3);
            yOffset = 0.0F;
            float f4 = (float)d3 * 0.2F;
            setThrowableHeading(d0, d1 + f4, d2, speed, deviation);
        }
    }
    
    @Override
    public void entityInit() {}
    
    @Override
    public void setThrowableHeading(double x, double y, double z, float speed, float deviation)
    {
        float f2 = MathHelper.sqrt_double(x * x + y * y + z * z);
        x /= f2;
        y /= f2;
        z /= f2;
        x += rand.nextGaussian() * (rand.nextBoolean() ? -1 : 1) * 0.0075 * deviation;
        y += rand.nextGaussian() * (rand.nextBoolean() ? -1 : 1) * 0.0075 * deviation;
        z += rand.nextGaussian() * (rand.nextBoolean() ? -1 : 1) * 0.0075 * deviation;
        x *= speed;
        y *= speed;
        z *= speed;
        motionX = x;
        motionY = y;
        motionZ = z;
        float f3 = MathHelper.sqrt_double(x * x + z * z);
        prevRotationYaw = rotationYaw = (float)(Math.atan2(x, z) * 180.0D / Math.PI);
        prevRotationPitch = rotationPitch = (float)(Math.atan2(y, f3) * 180.0D / Math.PI);
        ticksAlive = 0;
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int par)
    {
        //super.setPositionAndRotation2(x, y, z, yaw, pitch, par);
    }

    @Override
    @SideOnly(Side.CLIENT)
    public void setVelocity(double x, double y, double z)
    {
        motionX = x;
        motionY = y;
        motionZ = z;

        if (prevRotationPitch == 0.0F && prevRotationYaw == 0.0F) {
            float f = MathHelper.sqrt_double(x * x + z * z);
            prevRotationYaw = rotationYaw = (float)(Math.atan2(x, z) * 180.0D / Math.PI);
            prevRotationPitch = rotationPitch = (float)(Math.atan2(y, f) * 180.0D / Math.PI);
            prevRotationPitch = rotationPitch;
            prevRotationYaw = rotationYaw;
            setLocationAndAngles(posX, posY, posZ, rotationYaw, rotationPitch);
        }
    }
    
    @Override
    public void onUpdate()
    {
        onEntityUpdate();
    }
    
    @Override
    public void onEntityUpdate()
    {
        super.onEntityUpdate();
        
        if (needAimRotation()) {
            makeAimRotation();
        }
        
        if (canRotation()) {
            rotationYaw += getRotationOnYaw();
            rotationPitch += getRotationOnPitch();
        }
        
        Block i = worldObj.getBlock(xTile, yTile, zTile);
        if (i != null) {
            i.setBlockBoundsBasedOnState(worldObj, xTile, yTile, zTile);
            AxisAlignedBB axisalignedbb = i.getCollisionBoundingBoxFromPool(worldObj, xTile, yTile, zTile);
            if (axisalignedbb != null && axisalignedbb.isVecInside(Vec3.createVectorHelper(posX, posY, posZ))) {
                inGround = true;
            }
        }
        
        if (untouch > 0) {
            untouch--;
        }
        
        if (inGround) {
            Block j = worldObj.getBlock(xTile, yTile, zTile);
            int k = worldObj.getBlockMetadata(xTile, yTile, zTile);
            if (j == inTile && k == inData) {}
            else {
                inGround = false;
                motionX *= rand.nextFloat() * 0.2F;
                motionY *= rand.nextFloat() * 0.2F;
                motionZ *= rand.nextFloat() * 0.2F;
            }
            return;
        }
        
        if (++ticksAlive >= ticksExisted) {
            setDead();
            return;
        }
        
        MovingObjectPosition mop = findMovingObjectPosition();
        if (mop != null) {
            if (mop.entityHit != null) {
                onEntityHit((EntityLivingBase) mop.entityHit);
                if (dieAfterEntityHit()) {
                    setDead();
                }
            }
            else {
                onGroundHit(mop);
                if (dieAfterGroundHit()) {
                    setDead();
                }
            }
            untouch = getMaxUntouchability();
        }
        
        posX += motionX;
        posY += motionY;
        posZ += motionZ;        
        motionX *= getAirResistance();
        motionY *= getAirResistance();
        motionZ *= getAirResistance();
        motionY -= getGravity();
        
        if (isInWater()) {
            onCollideWithWater();    
        }    
        
        setPosition(posX, posY, posZ);
        func_145775_I();
    }
    
    public void makeAimRotation()
    {
        float f = MathHelper.sqrt_double(motionX * motionX + motionZ * motionZ);
        prevRotationYaw = rotationYaw = (float) ((Math.atan2(motionX, motionZ) * 180D) / Math.PI);
        prevRotationPitch = rotationPitch = (float) ((Math.atan2(motionY, f) * 180D) / Math.PI);
    }
    
    public MovingObjectPosition findMovingObjectPosition()
    {     
        Vec3 vec31 = Vec3.createVectorHelper(posX, posY, posZ);
        Vec3 vec32 = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ);
        MovingObjectPosition mop = worldObj.func_147447_a(vec31, vec32, false, true, false);
        vec31 = Vec3.createVectorHelper(posX, posY, posZ);
        vec32 = Vec3.createVectorHelper(posX + motionX, posY + motionY, posZ + motionZ);
        if (mop != null) {
            vec32 = Vec3.createVectorHelper(mop.hitVec.xCoord, mop.hitVec.yCoord, mop.hitVec.zCoord);
        }
        
        Entity entity = null;
        @SuppressWarnings("unchecked")
        List<Entity> list = worldObj.getEntitiesWithinAABBExcludingEntity(this, boundingBox.addCoord(motionX, motionY, motionZ).expand(1.0D, 1.0D, 1.0D));
        double d = 0.0D;
        for (Entity iterEntity : list) {
            if (!iterEntity.canBeCollidedWith() || !(iterEntity instanceof EntityLivingBase) || iterEntity == thrower && ticksAlive < 5) {
                continue;
            }
            float f4 = 0.3F;
            AxisAlignedBB aabb = iterEntity.boundingBox.expand(f4, f4, f4);
            MovingObjectPosition newMop = aabb.calculateIntercept(vec31, vec32);
            if (newMop == null) {
                continue;
            }
            double d1 = vec31.distanceTo(newMop.hitVec);
            if (d1 < d || d == 0.0D) {
                entity = iterEntity;
                d = d1;
            }
        }
        
        if (entity != null) {
            mop = new MovingObjectPosition(entity);
            if (mop.entityHit instanceof EntityPlayer) {
                EntityPlayer entityplayer = (EntityPlayer) mop.entityHit;
                if (entityplayer.capabilities.disableDamage || thrower instanceof EntityPlayer && thrower != null && !((EntityPlayer)thrower).canAttackPlayer(entityplayer)) {
                    mop = null;
                }
            }
        }
        
        return mop;
    }
    
    public void onEntityHit(EntityLivingBase entity)
    {
        if (!worldObj.isRemote) {
            applyEntityHitEffects(entity);
        }
        bounceBack();
    }
    
    public void applyEntityHitEffects(EntityLivingBase entity)
    {
        if (isBurning() && !(entity instanceof EntityEnderman)) {
            entity.setFire(5);
        }
        
        if (thrower != null) {
            EnchantmentHelper.func_151384_a(entity, thrower);
            EnchantmentHelper.func_151385_b(thrower, entity);
            
            if (thrower instanceof EntityPlayerMP && thrower != entity && entity instanceof EntityPlayer) {
                ((EntityPlayerMP) thrower).playerNetServerHandler.sendPacket(new S2BPacketChangeGameState(6, 0));
            }
            
            int knockback = EnchantmentHelper.getKnockbackModifier(thrower, entity);
            if (knockback != 0) {
                entity.addVelocity(-MathHelper.sin(rotationYaw * (float) Math.PI / 180.0F) * knockback * 0.5F, 0.1D, MathHelper.cos(rotationYaw * (float) Math.PI / 180.0F) * knockback * 0.5F);
                this.motionX *= 0.6D;
                this.motionZ *= 0.6D;
                this.setSprinting(false);
            }
            
            int fire = EnchantmentHelper.getFireAspectModifier(thrower);
            if (fire > 0 && !entity.isBurning()) {
                entity.setFire(1);
            }
        }
        
        DamageSource dmgSource = DamageSource.causeIndirectMagicDamage(this, (thrower == null) ? this : thrower);
        entity.attackEntityFrom(dmgSource, magicDamage);
    }
    
    public void onGroundHit(MovingObjectPosition mop)
    {
        xTile = mop.blockX;
        yTile = mop.blockY;
        zTile = mop.blockZ;
        inTile = worldObj.getBlock(xTile, yTile, zTile);
        inData = worldObj.getBlockMetadata(xTile, yTile, zTile);
        motionX = mop.hitVec.xCoord - posX;
        motionY = mop.hitVec.yCoord - posY;
        motionZ = mop.hitVec.zCoord - posZ;
        float f1 = MathHelper.sqrt_double(motionX * motionX + motionY * motionY + motionZ * motionZ);
        posX -= motionX / f1 * 0.05D;
        posY -= motionY / f1 * 0.05D;
        posZ -= motionZ / f1 * 0.05D;
        inGround = true;
        beenInGround = true;
        untouch = getMaxUntouchability();
        playHitSound();
        
        if (inTile != null) {
            inTile.onEntityCollidedWithBlock(worldObj, xTile, yTile, zTile, this);
        }
    }
    
    public void onCollideWithWater()
    {
        for (int i = 0; i < 4; ++i) {
            worldObj.spawnParticle("bubble", posX - motionX * 0.25F, posY - motionY * 0.25F, posZ - motionZ * 0.25F, motionX, motionY, motionZ);
        }    
        motionX *= getWaterResistance();
        motionY *= getWaterResistance();
        motionZ *= getWaterResistance();
        beenInGround = true;
    }
    
    @Override
    public void onCollideWithPlayer(EntityPlayer player) {}
    
    protected void bounceBack()
    {
        motionX *= -0.05D;
        motionY *= -0.05D;
        motionZ *= -0.05D;
    }    
    
    @Override
    public Entity getThrower()
    {
        if (thrower == null && throwerName != null && throwerName.length() > 0) {
            thrower = worldObj.getPlayerEntityByName(throwerName);
        }
        return thrower;
    }

    @Override
    public void setThrower(Entity entity) {
        if (entity instanceof EntityLivingBase) {
            thrower = (EntityLivingBase) entity;
        }
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public float getShadowSize()
    {
        return 0.0F;
    }
    
    @Override
    protected boolean canTriggerWalking()
    {
        return false;
    }
    
    public float getAirResistance()
    {
        return 1F;
    }
    
    public float getWaterResistance()
    {
        return 1F;
    }
    
    public float getGravity()
    {
        return 0F;
    }
    
    public int getMaxUntouchability()
    {
        return 7;
    }
    
    public boolean dieAfterEntityHit()
    {
        return true;
    }
    
    public boolean dieAfterGroundHit()
    {
        return true;
    }
    
    public boolean canRotation()
    {
        return !beenInGround;
    }
    
    public float getRotationOnPitch()
    {
        return 0.0F;
    }
    
    public float getRotationOnYaw()
    {
        return 0.0F;
    }
    
    public boolean needAimRotation()
    {
        return !canRotation() || (getRotationOnPitch() == 0 && getRotationOnYaw() == 0);
    }
    
    public void playHitSound() {}
    
    @Override
    public void writeEntityToNBT(NBTTagCompound nbt)
    {
        nbt.setShort("xTile", (short) xTile);
        nbt.setShort("yTile", (short) yTile);
        nbt.setShort("zTile", (short) zTile);
        nbt.setByte("inTile", (byte) Block.getIdFromBlock(inTile));
        nbt.setByte("inData", (byte) inData);
        nbt.setBoolean("inGround", inGround);
        nbt.setBoolean("beenInGround", beenInGround);
        nbt.setInteger("ticksAlive", ticksAlive);
        nbt.setByte("untouch", (byte) untouch);
        nbt.setFloat("magicDamage", magicDamage);
        
        if ((throwerName == null || throwerName.length() == 0) && thrower != null && thrower instanceof EntityPlayer) {
            throwerName = thrower.getCommandSenderName();
        }
        nbt.setString("throwerName", throwerName == null ? "" : throwerName);
    }
    
    @Override
    public void readEntityFromNBT(NBTTagCompound nbt)
    {
        xTile = nbt.getShort("xTile");
        yTile = nbt.getShort("yTile");
        zTile = nbt.getShort("zTile");
        inTile = Block.getBlockById(nbt.getByte("inTile") & 0xFF);
        inData = nbt.getByte("inData") & 0xFF;
        inGround = nbt.getBoolean("inGround");
        beenInGround = nbt.getBoolean("beenInGrond");
        ticksAlive = nbt.getInteger("ticksAlive");
        untouch = nbt.getByte("untouch") & 0xFF;
        magicDamage = nbt.getFloat("magicDamage");
        
        throwerName = nbt.getString("throwerName");
        if (throwerName != null && throwerName.length() == 0) {
            throwerName = null;
        }
        thrower = (EntityLivingBase) getThrower();
    }
}
 
1,137
5
2
Конь говорил, что проблема в интерполяции. Якобы она делается к точке 0.0.0.
 
608
5
15
Мне помогло if(life > 1) *Код* (не только движения, всего, в том числе и super.onUpdate())
[merge_posts_bbcode]Добавлено: 13.07.2016 20:16:22[/merge_posts_bbcode]

Ну, не совсем помогло, но вроде норм, попробуй.
 
2,502
78
380
Кароче, основная проблема в motion. Он почему-то криво передается клиенту...



Почему-то округляется до 3.9...
[merge_posts_bbcode]Добавлено: 13.07.2016 21:58:10[/merge_posts_bbcode]

svk2140 написал(а):
Мне помогло if(life > 1) *Код* (не только движения, всего, в том числе и super.onUpdate())
[merge_posts_bbcode]Добавлено: 13.07.2016 20:16:22[/merge_posts_bbcode]

Ну, не совсем помогло, но вроде норм, попробуй.
Костыль костылей. Да и как это вообще поможет? life по-любому будет больше 1, когда сущность сущность упадет
 

tox1cozZ

aka Agravaine
Модератор
7,533
485
2,343
Ага, там когда пакет приходит, то моушен ограничен в 3.9.
Не знаю, как с этим бороться.
 
608
5
15
Dahaka написал(а):
Кароче, основная проблема в motion. Он почему-то криво передается клиенту...



Почему-то округляется до 3.9...
[merge_posts_bbcode]Добавлено: 13.07.2016 21:58:10[/merge_posts_bbcode]

svk2140 написал(а):
Мне помогло if(life > 1) *Код* (не только движения, всего, в том числе и super.onUpdate())
[merge_posts_bbcode]Добавлено: 13.07.2016 20:16:22[/merge_posts_bbcode]

Ну, не совсем помогло, но вроде норм, попробуй.
Костыль костылей. Да и как это вообще поможет? life по-любому будет больше 1, когда сущность сущность упадет
Меньше вопросов, когда я раньше стрелял из своего оружия, пули при высокой скорости летели криво, а точнее всегда правее (Возможно внутри кода майна какае-то неправильные расчёты, раз ВСЕГДА правее, вне зависимости от направления взгляда). Но когда я поставил это условие - всё нормализовалось.
 
2,502
78
380
Я сейчас хук туда запилю и готово
[merge_posts_bbcode]Добавлено: 13.07.2016 23:28:51[/merge_posts_bbcode]

Вот этот великий код
Код:
public S0EPacketSpawnObject(Entity p_i45166_1_, int p_i45166_2_, int p_i45166_3_)
{
    this.field_149018_a = p_i45166_1_.getEntityId();
    this.field_149016_b = MathHelper.floor_double(p_i45166_1_.posX * 32.0D);
    this.field_149017_c = MathHelper.floor_double(p_i45166_1_.posY * 32.0D);
    this.field_149014_d = MathHelper.floor_double(p_i45166_1_.posZ * 32.0D);
    this.field_149021_h = MathHelper.floor_float(p_i45166_1_.rotationPitch * 256.0F / 360.0F);
    this.field_149022_i = MathHelper.floor_float(p_i45166_1_.rotationYaw * 256.0F / 360.0F);
    this.field_149019_j = p_i45166_2_;
    this.field_149020_k = p_i45166_3_;

    if (p_i45166_3_ > 0)
    {
        double d0 = p_i45166_1_.motionX;
        double d1 = p_i45166_1_.motionY;
        double d2 = p_i45166_1_.motionZ;
        double d3 = 3.9D;

        if (d0 < -d3)
        {
            d0 = -d3;
        }

        if (d1 < -d3)
        {
            d1 = -d3;
        }

        if (d2 < -d3)
        {
            d2 = -d3;
        }

        if (d0 > d3)
        {
            d0 = d3;
        }

        if (d1 > d3)
        {
            d1 = d3;
        }

        if (d2 > d3)
        {
            d2 = d3;
        }

        this.field_149015_e = (int)(d0 * 8000.0D);
        this.field_149012_f = (int)(d1 * 8000.0D);
        this.field_149013_g = (int)(d2 * 8000.0D);
    }
}
 
6,095
226
1,174
Сюда прямо, плиз
 
2,502
78
380
Кароче, поиском по всем файлам в итоге нашел 4 места, где режется motion.

  • Конструктор S0EPacketSpawnObject
  • Конструктор S0FPacketSpawnMob
  • Конструктор S12PacketEntityVelocity
  • Метод toBytes в EntitySpawnMessage

В конструкторы я вставил хуки, они работают. В методе toBytes значение обрезается, если entity instanceof IThrowableEntity, поэтому я просто убрал эту имплементацию из своего класса. А еще, туда хук не вставить :)
Код:
if (entity instanceof IThrowableEntity)
{
    Entity owner = ((IThrowableEntity)entity).getThrower();
    buf.writeInt(owner == null ? entity.getEntityId() : owner.getEntityId());
    double maxVel = 3.9D;
    double mX = entity.motionX;
    double mY = entity.motionY;
    double mZ = entity.motionZ;
    if (mX < -maxVel) mX = -maxVel;
    if (mY < -maxVel) mY = -maxVel;
    if (mZ < -maxVel) mZ = -maxVel;
    if (mX >  maxVel) mX =  maxVel;
    if (mY >  maxVel) mY =  maxVel;
    if (mZ >  maxVel) mZ =  maxVel;
    buf.writeInt((int)(mX * 8000D));
    buf.writeInt((int)(mY * 8000D));
    buf.writeInt((int)(mZ * 8000D));
}

Проблема в том, что motion все еще где-то обрезается...
[merge_posts_bbcode]Добавлено: 14.07.2016 22:32:29[/merge_posts_bbcode]

Agravaine написал(а):
Ага, там когда пакет приходит, то моушен ограничен в 3.9.
Не знаю, как с этим бороться.
Где ты это видел, не подскажешь? Может я что-то упускаю?

[merge_posts_bbcode]Добавлено: 14.07.2016 22:33:01[/merge_posts_bbcode]

Хуки скинуть могу, но зачем, если толку от них пока ноль.
 
2,502
78
380
Вернул своей сущности имплементацию IThrowableEntity и все-таки смог запилить хук в метод toBytes. Моушены правильно передаются клиенту, правильно устанавливаются в саму сущность, но потом все равно где-то режутся... В onUpdate motion уже кривой.
 
608
5
15
Попробуй условие впихнуть, мне же помогло...
 
2,502
78
380
Ок, куда пихать? Я не особо понял, кроме onUpdate
 
608
5
15
Просто заведи переменную жизни (или она есть в энтити, хз) и поставь это условие для всего апдейта.
 
2,502
78
380
svk2140 написал(а):
Просто заведи переменную жизни (или она есть в энтити, хз) и поставь это условие для всего апдейта.
Код:
@Override
public void onUpdate()
{
    if (++ticksAlive >= ticksExisted) {
        setDead();
        return;
    }

    if (ticksAlive <= 2) {
        return;
    }
    ....
}
Как я и думал, эта хрень влияет только на лаги в начале жизни...
 
608
5
15
Не знаю, может попробовать отключить ванильные пакеты и пользоваться своими?
[merge_posts_bbcode]Добавлено: 15.07.2016 16:31:27[/merge_posts_bbcode]

В любом случае надеюсь ты решишь эту проблему и поделишься с нами ^_^

[merge_posts_bbcode]Добавлено: 15.07.2016 16:34:17[/merge_posts_bbcode]

Хотя, если в onUpdate всё возвращается, может проблема где-то в рендере, когда он интерполирует?

P.S. Не умею пользоваться брейкпоинтом :D

Попробуй что-ли провести поиск по "3.9" :)
 
164
1
4
А что если убрать это всё и просто вместо Entity наследовать EntityArrow?
P. S. А ещё в инете пишут, что последние 3 параметра регистрации Энтити стрелы должны быть такими:
Код:
EntityRegistry.registerModEntity(EntityArrowCustom.class, "arrowcustom", ++modEntityIndex, ZSSMain.instance, 64, 20, true);
 
Сверху