package com.robowars.core.entity.projectile; import java.util.Random; import org.lwjgl.util.vector.Vector3f; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.IProjectile; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.BlockPos; import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; import static java.lang.Math.*; public class EntityLaser extends Entity { /**synched between c-s so bullets fly the same direction*/ private Random r; public float dmg; public double speed0=0; /**For not adding to v[1] when on the ground*/ public boolean wasCollided=false; public boolean isFirstCollision=true; public Entity owner; public boolean firstTimeSpawning=true; public double[] v= new double[3]; public EntityLaser(World worldIn) { super(worldIn); r= worldIn.rand; } public EntityLaser(Entity aimer,/*Barrel barrel,*/double s, double deviation, float dmg){ this(aimer.worldObj); this.ignoreFrustumCheck= true; this.preventEntitySpawning = false; this.owner= aimer; //barrel.calculatePosAndLook(); //setPos(barrel.muzzleEndTransformed.x, barrel.muzzleEndTransformed.y, barrel.muzzleEndTransformed.z); setPos(aimer.posX, aimer.posY, aimer.posZ); // this.rotationYaw= barrel.aimer.rotationYaw; // this.rotationPitch= -barrel.aimer.rotationPitch; this.rotationYaw= aimer.rotationYaw; this.rotationPitch= -aimer.rotationPitch; float cosp= MathHelper.cos(rotationPitch); Vector3f look= new Vector3f(MathHelper.cos(rotationYaw)*cosp, MathHelper.sin(rotationPitch), MathHelper.sin(rotationYaw)*cosp ); this.v[0]= look.x*s; this.v[1]= look.y*s; this.v[2]= look.z*s; v[0]+= aimer.motionX; v[1]+= aimer.motionX; v[2]+= aimer.motionZ; this.deviate(deviation); // this.v[0]= barrel.lookdir.x*s; // this.v[1]= barrel.lookdir.y*s; // this.v[2]= barrel.lookdir.z*s; // v[0]+= barrel.getEntityMountedTo().motionX; // v[1]+= barrel.getEntityMountedTo().motionX; // v[2]+= barrel.getEntityMountedTo().motionZ; this.speed0= s; this.dmg=dmg; // if(this.getClass().equals(EntityProjectile.class)) // worldObj.spawnEntityInWorld(this); // if(worldObj.isRemote) // posY-=1.75;//client has the wrong position for some reason this.playSound("robowars:mob.bot.shoot", 1.0F, 1.0F / ((float)Math.random() * 0.4F + 0.8F)); } @Override protected void entityInit(){} @Override protected void readEntityFromNBT(NBTTagCompound nbtTagCompound) { } @Override protected void writeEntityToNBT(NBTTagCompound nbtTagCompound) { } @Override public boolean isInRangeToRenderDist(double par1){return par1<200*200;} // @Override // @Deprecated // public void setPosition(double x, double y, double z){ // super.setPosition(x, y, z); // } // @Override // @Deprecated // public void moveEntity(double vx, double vy, double vz){} public void setPos(double x, double y, double z){ // this.prevPosX= this.posX; // this.prevPosY= this.posY; // this.prevPosZ= this.posZ; // this.posX= x; // this.posY= y; // this.posZ= z; setPosition(x, y, z); //this.getBoundingBox()...setBounds(-.25, -.25, -.25, .25, .25, .25); //this.getBoundingBox().offset(posX, posY, posZ); } @Override public boolean canBeCollidedWith(){ return false; } /**The block ID this is trying to enter, or if done moving, has entered*/ Block entering; /**Amount of movement still needing to be done this tick*/ Vector3f remain= new Vector3f(); /**one of the 3 will be distance to the next block*/ Vector3f step= new Vector3f(); /**move the projectile along no more than 1 block at a time * so as to not get stuck in walls and stuff*/ protected void moveEntity(){ noClip= true; if(this.noClip){ this.posX+=v[0]; this.posY+=v[1]; this.posZ+=v[2]; setPos(posX, posY, posZ); return; } remain.x= (float) this.v[0]; remain.y= (float) this.v[1]; remain.z= (float) this.v[2]; //find distance to next block being moved into //find out the magnitude of the step and calculate step vector //do not assume that a bullet never rests perfectly on the edge of a block, you will crash //step vector < remaining ? continue : move directly using remain (will never enter new voxel); break //get id of block whos edge this just moved onto //add step vector to position, subtract from remaining motion //check for effects (ricochet, slow, pierce), apply them, not to the current step //remember when reversing a motion component to reverse all of its factors, except step //when in doubt use abs() while(true){ //prevent /0 if(v[0]==0) v[0]=1e-9; if(v[1]==0) v[1]=1e-9; if(v[2]==0) v[2]=1e-9; //distance to next block final double[] dist= new double[3]; dist[0]= ( v[0]>0 ? ceil(posX):floor(posX) ) - posX; dist[1]= ( v[1]>0 ? ceil(posY):floor(posY) ) - posY; dist[2]= ( v[2]>0 ? ceil(posZ):floor(posZ) ) - posZ; final double ta= posX, tb= posY, tc= posZ; // //t=d/s; time to enter the next block final double[] t= new double[3]; t[0]= abs(dist[0]/v[0]); t[1]= abs(dist[1]/v[1]); t[2]= abs(dist[2]/v[2]); // // // System.out.println(t[1]+" "+t[2]); // // //find which time is smallest, thus block being moved into /**0 x 1 y 2 z*/ final int dir; if(t[0]<t[1] && t[0]<t[2]) dir=0; else if(t[1]<t[0] && t[1]<t[2]) dir=1; else if(t[2]<t[0] && t[2]<t[1]) dir=2; else{dir=0;} //is not moving onto an edge //thus not considered entering a new block; considered to be moving into a new block while on its edge //thus is done with move looping float remdir= dir==0? remain.x : dir==1? remain.y : remain.z; if(v[dir]>0? remdir<=0 : remdir>=0){ // if(v[dir]>0? remain[dir]<dist[dir] : remain[dir]>dist[dir]){ // posX+=remain[0]; // posY+=remain[1]; // posZ+=remain[2]; break; } step= new Vector3f((float)v[0],(float)v[1],(float)v[2]); step.normalise(); step.scale(.03f); posX+= step.x; posY+= step.y; posZ+= step.z; remain.x-= step.x; remain.y-= step.y; remain.z-= step.z; // switch(dir){//make sure it is moved perfectly onto the edge // case 0: // posX= (int) floor(posX+0.1);//nudge it to make sure its truncated properly // break; // case 1: // posY= (int) floor(posY+0.1); // break; // case 2: // posZ= (int) floor(posZ+0.1); // break; // } entering= worldObj.getBlockState(new BlockPos(this)).getBlock(); //check for effects (ricochet, slow, pierce, spall), apply them //remember when reversing a dironent to reverse all of its factors except step //does not affect the current step wasCollided=false; switch(getBlockBulletEffect(entering)){ case 0://air isCollided=false; break; case 1://solid isCollided=true; ricochet(dir, entering); break; case 2://liquid isCollided=false; remain.x*=0.9; remain.y*=0.9; remain.z*=0.9; v[0]*=0.9; v[1]*=0.9; v[2]*=0.9; break; case 3://breakable //TODO allow bots to break blocks? // if(owner!=null && owner instanceof EntityPlayer // && getSpeed() > 0.4 // && worldObj..canMineBlock( (EntityPlayer)this.owner, (int)(posX+step[0]),(int)(posY+step[1]),(int)(posZ+step[2]))) // worldObj.setBlock((int)(posX+step[0]),(int)(posY+step[1]),(int)(posZ+step[2]), Blocks.air); // isCollided=true; // remain[0]*=0.4;//reduce speed // remain[1]*=0.4; // remain[2]*=0.4; // v[0]*=0.4; // v[1]*=0.4; // v[2]*=0.4; // return; } if(isCollided && isFirstCollision){ // id= worldObj.getBlockId((int)(posX),(int)(posY),(int)(posZ)); // if(dir!=1) // System.out.println(dir); isFirstCollision=false; onFirstCollision(); } //get off of the edge and into the next (or incase ricochet, back into the previous) block // final double a= copySign(0.01, v[0]); // posX+= copySign(0.001, v[0]); // posY+= copySign(0.001, v[1]); // posZ+= copySign(0.001, v[2]); } setPos(posX, posY, posZ); if(posY<-2){ System.out.println("SHIT WENT WRONG"); this.setDead();} } /**@return 0 air 1 solid 2 viscous*/ public static int getBlockBulletEffect(Block block){ if(block==Blocks.air) return 0; if(block==Blocks.water || block==Blocks.lava)//viscous return 2; if(block==Blocks.glass || block==Blocks.glowstone || block==Blocks.redstone_lamp || block==Blocks.torch)//breakable return 3; return 1;//solidblock } protected void ricochet(int dir, Block block){ float resist= block.getExplosionResistance(owner); double hitSpeed=0;//speed orthogonal to face hit //undo movement into the block //posX-= step[0]*4; //posY-= step[1]*4; //posZ-= step[2]*4; // // remain[0]+= step[0]; // remain[1]+= step[1]; // remain[2]+= step[2]; hitSpeed=v[dir]; //deflect v[dir]*=-1; if(dir==0) remain.x*=-1; else if(dir==1) remain.y*=-1; else remain.z*=-1; if(abs(v[1])<.1){//friction from the ground, prevents undue rolling v[0]*=.95; v[2]*=.95; //also 0 the y motion v[1]=0; remain.y=0; } final float p=penetration(resist, hitSpeed); if(p==-1){ //TODO explode fx remain.set(0, 0, 0); this.isDead=true; this.setDead(); } else{ //if(abs(v[0])+abs(v[1])+abs(v[2])>0.1){ remain.scale(p); v[0]*= p; v[1]*= p; v[2]*= p; }//} } /**@return -1 if penetrating, otherwise speed multiplier*/ protected float penetration(float resist, double speed){ speed= abs(speed); //System.out.println(speed/resist); //System.out.println(1/(10/resist +1)); //constant is penetrabilit[1]. Higher = less bouncy bullets if(speed/resist < 1.0) return 1/(7/resist +1);//TODO calibrate else return -1; } protected void slow(){ final double s=0.40; v[0]*=s; v[1]*=s; v[2]*=s; } public void deviate(double d){ d/=2; this.v[0]+= r.nextGaussian()*d; this.v[1]+= r.nextGaussian()*d; this.v[2]+= r.nextGaussian()*d; } private int lastTickSpeedCrunched=-1; private double speed; public double getSpeed(){ if(lastTickSpeedCrunched!=this.ticksExisted) speed= sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); return speed; } protected void checkHit(){} public void pew(){ worldObj.playSoundAtEntity(this, "t1a:"+ (entering.getExplosionResistance(this)>2? "ricochetA":"ricochetB")//TODO update ricochet sound , 1, 1); } /**gets called before applying ricochet, etc*/ protected void onFirstCollision(){ pew(); } // @Override // protected void readEntityFromNBT(NBTTagCompound var1) { // setPos(posX, posY, posZ); // var1.setBoolean("first", false); // speed0= var1.getDouble("s0"); // this.v[0]=motionX; // this.v[1]=motionY; // this.v[2]=motionZ; // this.setDead(); // } // @Override // protected void writeEntityToNBT(NBTTagCompound var1) { // firstTimeSpawning= var1.getBoolean("first"); // var1.setDouble("s0", speed0); // } // // public void writeSpawnData(ByteBuf data){ //// //// data.writeDouble(v[0]); //// data.writeDouble(v[1]); //// data.writeDouble(v[2]); //// data.writeBoolean(firstTimeSpawning); //// if(this.owner!=null) //// data.writeInt(this.owner.entityId); //// else //// data.writeInt(-1); // } // public void readSpawnData(ByteBuf data){ //// v[0]= data.readDouble(); //// v[1]= data.readDouble(); //// v[2]= data.readDouble(); //// //// final boolean doRecoil= data.readBoolean(); //// final int id= data.readInt(); //// if(id!=-1) //// this.owner=worldObj.getEntityByID(id); //// if(doRecoil && id!=-1 && owner instanceof EntityPlayer) //// T1A.commproxy.doRecoil((EntityPlayer) owner, r); // } }