package com.flansmod.common.driveables; import java.util.ArrayList; import java.util.List; import io.netty.buffer.ByteBuf; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.BlockPos; import net.minecraft.util.DamageSource; import net.minecraft.util.EnumParticleTypes; import net.minecraft.util.MathHelper; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.MovingObjectPosition.MovingObjectType; import net.minecraft.util.Vec3; import net.minecraft.world.World; import net.minecraft.world.WorldSettings.GameType; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.fml.common.network.ByteBufUtils; import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; //import cofh.api.energy.IEnergyContainerItem; import com.flansmod.api.IControllable; import com.flansmod.api.IExplodeable; import com.flansmod.client.EntityCamera; import com.flansmod.client.FlansModClient; import com.flansmod.client.debug.EntityDebugDot; import com.flansmod.client.debug.EntityDebugVector; import com.flansmod.common.FlansMod; import com.flansmod.common.RotatedAxes; import com.flansmod.common.driveables.DriveableType.ParticleEmitter; import com.flansmod.common.guns.BulletType; import com.flansmod.common.guns.EntityBullet; import com.flansmod.common.guns.EntityShootable; import com.flansmod.common.guns.EnumFireMode; import com.flansmod.common.guns.GunType; import com.flansmod.common.guns.InventoryHelper; import com.flansmod.common.guns.ItemBullet; import com.flansmod.common.guns.ItemGun; import com.flansmod.common.guns.ItemShootable; import com.flansmod.common.guns.ShootableType; import com.flansmod.common.guns.ShotData; import com.flansmod.common.guns.ShotData.InstantShotData; import com.flansmod.common.guns.ShotData.SpawnEntityShotData; import com.flansmod.common.guns.raytracing.FlansModRaytracer; import com.flansmod.common.guns.raytracing.FlansModRaytracer.BulletHit; import com.flansmod.common.guns.raytracing.FlansModRaytracer.DriveableHit; import com.flansmod.common.network.PacketDriveableDamage; import com.flansmod.common.network.PacketDriveableKeyHeld; import com.flansmod.common.network.PacketPlaySound; import com.flansmod.common.parts.EnumPartCategory; import com.flansmod.common.parts.ItemPart; import com.flansmod.common.parts.PartType; import com.flansmod.common.teams.TeamsManager; import com.flansmod.common.vector.Vector3f; public abstract class EntityDriveable extends Entity implements IControllable, IExplodeable, IEntityAdditionalSpawnData { public boolean syncFromServer = true; /** Ticks since last server update. Use to smoothly transition to new position */ public int serverPositionTransitionTicker; /** Server side position, as synced by PacketVehicleControl packets */ public double serverPosX, serverPosY, serverPosZ; /** Server side rotation, as synced by PacketVehicleControl packets */ public double serverYaw, serverPitch, serverRoll; /** The driveable data which contains the inventory, the engine and the fuel */ public DriveableData driveableData; /** The shortName of the driveable type, used to obtain said type */ public String driveableType; /** The throttle, in the range -1, 1 is multiplied by the maxThrottle (or maxNegativeThrottle) from the plane type to obtain the thrust */ public float throttle; /** The wheels on this plane */ public EntityWheel[] wheels; public boolean fuelling; /** Extra prevRoation field for smoothness in all 3 rotational axes */ public float prevRotationRoll; /** Angular velocity */ public Vector3f angularVelocity = new Vector3f(0F, 0F, 0F); /** Whether each mouse button is held */ public boolean leftMouseHeld = false, rightMouseHeld = false; /** Shoot delay variables */ public float shootDelayPrimary, shootDelaySecondary; /** Minigun speed variables */ public float minigunSpeedPrimary, minigunSpeedSecondary; /** Current gun variables for alternating weapons. */ public int currentGunPrimary, currentGunSecondary; /** Angle of harvester aesthetic piece */ public float harvesterAngle; public RotatedAxes prevAxes; public RotatedAxes axes; public EntitySeat[] seats; private float yOffset; @SideOnly(Side.CLIENT) public EntityLivingBase camera; private int[] emitterTimers; public int animCount = 0; public int animFrame = 0; public EntityDriveable(World world) { super(world); axes = new RotatedAxes(); prevAxes = new RotatedAxes(); preventEntitySpawning = true; setSize(1F, 1F); yOffset = 6F / 16F; ignoreFrustumCheck = true; renderDistanceWeight = 200D; } public EntityDriveable(World world, DriveableType t, DriveableData d) { this(world); driveableType = t.shortName; driveableData = d; } protected void initType(DriveableType type, boolean clientSide) { seats = new EntitySeat[type.numPassengers + 1]; for(int i = 0; i < type.numPassengers + 1; i++) { if(!clientSide) { seats[i] = new EntitySeat(worldObj, this, i); worldObj.spawnEntityInWorld(seats[i]); } } wheels = new EntityWheel[type.wheelPositions.length]; for(int i = 0; i < wheels.length; i++) { if(!clientSide) { wheels[i] = new EntityWheel(worldObj, this, i); worldObj.spawnEntityInWorld(wheels[i]); } } stepHeight = type.wheelStepHeight; yOffset = type.yOffset; emitterTimers = new int[type.emitters.size()]; for(int i = 0; i < type.emitters.size(); i++) { emitterTimers[i] = rand.nextInt(type.emitters.get(i).emitRate); } //Register Plane to Radar on Spawning //if(type.onRadar == true) // RadarRegistry.register(this); } @Override protected void writeEntityToNBT(NBTTagCompound tag) { driveableData.writeToNBT(tag); tag.setString("Type", driveableType); tag.setFloat("RotationYaw", axes.getYaw()); tag.setFloat("RotationPitch", axes.getPitch()); tag.setFloat("RotationRoll", axes.getRoll()); } @Override protected void readEntityFromNBT(NBTTagCompound tag) { driveableType = tag.getString("Type"); driveableData = new DriveableData(tag); initType(DriveableType.getDriveable(driveableType), false); prevRotationYaw = tag.getFloat("RotationYaw"); prevRotationPitch = tag.getFloat("RotationPitch"); prevRotationRoll = tag.getFloat("RotationRoll"); axes = new RotatedAxes(prevRotationYaw, prevRotationPitch, prevRotationRoll); } @Override public void writeSpawnData(ByteBuf data) { ByteBufUtils.writeUTF8String(data, driveableType); NBTTagCompound tag = new NBTTagCompound(); driveableData.writeToNBT(tag); ByteBufUtils.writeTag(data, tag); data.writeFloat(axes.getYaw()); data.writeFloat(axes.getPitch()); data.writeFloat(axes.getRoll()); //Write damage for(EnumDriveablePart ep : EnumDriveablePart.values()) { DriveablePart part = getDriveableData().parts.get(ep); data.writeShort((short)part.health); data.writeBoolean(part.onFire); } } @Override public void readSpawnData(ByteBuf data) { try { driveableType = ByteBufUtils.readUTF8String(data); driveableData = new DriveableData(ByteBufUtils.readTag(data)); initType(getDriveableType(), true); axes.setAngles(data.readFloat(), data.readFloat(), data.readFloat()); prevRotationYaw = axes.getYaw(); prevRotationPitch = axes.getPitch(); prevRotationRoll = axes.getRoll(); //Read damage for(EnumDriveablePart ep : EnumDriveablePart.values()) { DriveablePart part = getDriveableData().parts.get(ep); part.health = data.readShort(); part.onFire = data.readBoolean(); } } catch(Exception e) { FlansMod.log("Failed to retreive plane type from server."); super.setDead(); e.printStackTrace(); } camera = new EntityCamera(worldObj, this); worldObj.spawnEntityInWorld(camera); } /** * Called with the movement of the mouse. Used in controlling vehicles if need be. * @param deltaY * @param deltaX * @return if mouse movement was handled. */ @Override public abstract void onMouseMoved(int deltaX, int deltaY); @Override @SideOnly(Side.CLIENT) public EntityLivingBase getCamera() { return camera; } protected boolean canSit(int seat) { return getDriveableType().numPassengers >= seat && seats[seat].riddenByEntity == null; } @Override protected boolean canTriggerWalking() { return false; } @Override protected void entityInit() { } @Override public AxisAlignedBB getCollisionBox(Entity entity) { return null;//entity.boundingBox; } @Override public boolean canBePushed() { return false; } @Override public double getMountedYOffset() { return -0.3D; } @Override public double getYOffset() { return yOffset; } @Override /** Pass generic damage to the core */ public boolean attackEntityFrom(DamageSource damagesource, float i) { return worldObj.isRemote || isDead || attackPart(EnumDriveablePart.core, damagesource, i); } @Override public void setDead() { super.setDead(); //Unregister to Radar //RadarRegistry.unregister(this); if(worldObj.isRemote) camera.setDead(); for(EntitySeat seat : seats) if(seat != null) seat.setDead(); } @Override public void onCollideWithPlayer(EntityPlayer par1EntityPlayer) { //Do nothing. Like a boss. // TODO: perhaps send the player flying?? //Sounds good. ^ } @Override public boolean canBeCollidedWith() { return !isDead; } @Override public void applyEntityCollision(Entity entity) { if(!isPartOfThis(entity)) super.applyEntityCollision(entity); } @Override public void setPositionAndRotation2(double d, double d1, double d2, float f, float f1, int i, boolean b) { if(ticksExisted > 1) return; if(riddenByEntity instanceof EntityPlayer && FlansMod.proxy.isThePlayer((EntityPlayer)riddenByEntity)) { } else { if(syncFromServer) { serverPositionTransitionTicker = i + 5; } else { double var10 = d - posX; double var12 = d1 - posY; double var14 = d2 - posZ; double var16 = var10 * var10 + var12 * var12 + var14 * var14; if (var16 <= 1.0D) { return; } serverPositionTransitionTicker = 3; } serverPosX = d; serverPosY = d1; serverPosZ = d2; serverYaw = f; serverPitch = f1; } } public void setPositionRotationAndMotion(double x, double y, double z, float yaw, float pitch, float roll, double motX, double motY, double motZ, float velYaw, float velPitch, float velRoll, float throt, float steeringYaw) { if(worldObj.isRemote) { serverPosX = x; serverPosY = y; serverPosZ = z; serverYaw = yaw; serverPitch = pitch; serverRoll = roll; serverPositionTransitionTicker = 5; } else { setPosition(x, y, z); prevRotationYaw = yaw; prevRotationPitch = pitch; prevRotationRoll = roll; setRotation(yaw, pitch, roll); } //Set the motions regardless of side. motionX = motX; motionY = motY; motionZ = motZ; angularVelocity = new Vector3f(velYaw, velPitch, velRoll); throttle = throt; } @Override public void setVelocity(double d, double d1, double d2) { motionX = d; motionY = d1; motionZ = d2; } @Override public boolean pressKey(int key, EntityPlayer player) { if(!worldObj.isRemote && key == 9 && getDriveableType().modePrimary == EnumFireMode.SEMIAUTO) //Primary { shoot(false); return true; } else if(!worldObj.isRemote && key == 8 && getDriveableType().modeSecondary == EnumFireMode.SEMIAUTO) //Secondary { shoot(true); return true; } return false; } @Override public void updateKeyHeldState(int key, boolean held) { if(worldObj.isRemote) { FlansMod.getPacketHandler().sendToServer(new PacketDriveableKeyHeld(key, held)); } switch(key) { case 9 : leftMouseHeld = held; break; case 8 : rightMouseHeld = held; break; } } /** Shoot method called by pressing / holding shoot buttons */ public void shoot(boolean secondary) { DriveableType type = getDriveableType(); if(seats[0] == null && !(seats[0].riddenByEntity instanceof EntityLivingBase)) return; //Check shoot delay while(getShootDelay(secondary) <= 0.0f) { //We can shoot, so grab the available shoot points and the weaponType ArrayList<DriveablePosition> shootPoints = type.shootPoints(secondary); EnumWeaponType weaponType = type.weaponType(secondary); //If there are no shoot points, return if(shootPoints.size() == 0) return; //For alternating guns, move on to the next one int currentGun = getCurrentGun(secondary); if(type.alternate(secondary)) { currentGun = (currentGun + 1) % shootPoints.size(); setCurrentGun(currentGun, secondary); shootEach(type, shootPoints.get(currentGun), currentGun, secondary, weaponType); } else for(int i = 0; i < shootPoints.size(); i++) shootEach(type, shootPoints.get(i), i, secondary, weaponType); } } private boolean driverIsCreative() { return seats != null && seats[0] != null && seats[0].riddenByEntity instanceof EntityPlayer && ((EntityPlayer)seats[0].riddenByEntity).capabilities.isCreativeMode; } private EntityPlayer GetDriver() { if(seats != null && seats[0] != null && seats[0].riddenByEntity instanceof EntityPlayer) return ((EntityPlayer)seats[0].riddenByEntity); else return null; } private void shootEach(DriveableType type, DriveablePosition shootPoint, int currentGun, boolean secondary, EnumWeaponType weaponType) { //Rotate the gun vector to global axes Vector3f gunVec = getOrigin(shootPoint); Vector3f lookVector = getLookVector(shootPoint); //If its a pilot gun, then it is using a gun type, so do the following if(shootPoint instanceof PilotGun) { PilotGun pilotGun = (PilotGun)shootPoint; //Get the gun from the plane type and the ammo from the data GunType gunType = pilotGun.type; ItemStack shootableStack = driveableData.ammo[getDriveableType().numPassengerGunners + currentGun]; EntityPlayer driver = GetDriver(); // For each ItemShootable shootableItem = (ItemShootable)shootableStack.getItem(); ShootableType shootableType = shootableItem.type; float spread = 0.005f * gunType.bulletSpread * shootableType.bulletSpread; lookVector.x += (float)worldObj.rand.nextGaussian() * spread; lookVector.y += (float)worldObj.rand.nextGaussian() * spread; lookVector.z += (float)worldObj.rand.nextGaussian() * spread; lookVector.scale(500.0f); // Instant bullets. Do a raytrace if(gunType.bulletSpeed == 0.0f) { for(int i = 0; i < gunType.numBullets * shootableType.numBullets; i++) { List<BulletHit> hits = FlansModRaytracer.Raytrace(worldObj, driver, false, null, gunVec, lookVector, 0); Entity victim = null; Vector3f hitPos = Vector3f.add(gunVec, lookVector, null); BulletHit firstHit = null; if(!hits.isEmpty()) { firstHit = hits.get(0); hitPos = Vector3f.add(gunVec, (Vector3f)lookVector.scale(firstHit.intersectTime), null); victim = firstHit.GetEntity(); } if(FlansMod.DEBUG) { worldObj.spawnEntityInWorld(new EntityDebugDot(worldObj, gunVec, 100, 1.0f, 1.0f, 1.0f)); } ShotData shotData = new InstantShotData(-1, type, shootableType, driver, gunVec, firstHit, hitPos, gunType.damage, i < gunType.numBullets * shootableType.numBullets - 1, false); ((ItemGun)gunType.item).ServerHandleShotData(null, -1, worldObj, this, false, shotData); } } // Else, spawn an entity else { ShotData shotData = new SpawnEntityShotData(-1, type, shootableType, driver, lookVector); ((ItemGun)gunType.item).ServerHandleShotData(null, -1, worldObj, this, false, shotData); } //Reset the shoot delay setShootDelay(type.shootDelay(secondary), secondary); } else //One of the other modes { switch(weaponType) { case BOMB : { if(TeamsManager.bombsEnabled) { int slot = -1; for(int i = driveableData.getBombInventoryStart(); i < driveableData.getBombInventoryStart() + type.numBombSlots; i++) { ItemStack bomb = driveableData.getStackInSlot(i); if(bomb != null && bomb.getItem() instanceof ItemBullet && type.isValidAmmo(((ItemBullet)bomb.getItem()).type, weaponType)) { slot = i; } } if(slot != -1) { int spread = 0; int damageMultiplier = secondary ? type.damageModifierSecondary : type.damageModifierPrimary; float shellSpeed = 0F; ItemStack bulletStack = driveableData.getStackInSlot(slot); ItemBullet bulletItem = (ItemBullet)bulletStack.getItem(); EntityShootable bulletEntity = bulletItem.getEntity(worldObj, new Vec3(posX + gunVec.x, posY + gunVec.y, posZ + gunVec.z), axes.getYaw(), axes.getPitch(), motionX, motionY, motionZ, (EntityLivingBase)seats[0].riddenByEntity, damageMultiplier, type); worldObj.spawnEntityInWorld(bulletEntity); if(type.shootSound(secondary) != null) PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.shootSound(secondary), false); if(!driverIsCreative()) { bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); if(bulletStack.getItemDamage() == bulletStack.getMaxDamage()) { bulletStack.setItemDamage(0); bulletStack.stackSize--; if(bulletStack.stackSize == 0) bulletStack = null; } driveableData.setInventorySlotContents(slot, bulletStack); } setShootDelay(type.shootDelay(secondary), secondary); } } break; } case MISSILE : //These two are actually almost identical case SHELL : { if(TeamsManager.shellsEnabled) { int slot = -1; for(int i = driveableData.getMissileInventoryStart(); i < driveableData.getMissileInventoryStart() + type.numMissileSlots; i++) { ItemStack shell = driveableData.getStackInSlot(i); if(shell != null && shell.getItem() instanceof ItemBullet && type.isValidAmmo(((ItemBullet)shell.getItem()).type, weaponType)) { slot = i; } } if(slot != -1) { int spread = 0; int damageMultiplier = secondary ? type.damageModifierSecondary : type.damageModifierPrimary; float shellSpeed = 3F; ItemStack bulletStack = driveableData.getStackInSlot(slot); ItemBullet bulletItem = (ItemBullet)bulletStack.getItem(); EntityShootable bulletEntity = bulletItem.getEntity(worldObj, Vector3f.add(new Vector3f(posX, posY, posZ), gunVec, null), lookVector, (EntityLivingBase)seats[0].riddenByEntity, spread, damageMultiplier, shellSpeed, type); worldObj.spawnEntityInWorld(bulletEntity); if(type.shootSound(secondary) != null) PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.shootSound(secondary), false); if(!driverIsCreative()) { bulletStack.setItemDamage(bulletStack.getItemDamage() + 1); if(bulletStack.getItemDamage() == bulletStack.getMaxDamage()) { bulletStack.setItemDamage(0); bulletStack.stackSize--; if(bulletStack.stackSize == 0) bulletStack = null; } driveableData.setInventorySlotContents(slot, bulletStack); } setShootDelay(type.shootDelay(secondary), secondary); } } break; } case GUN: //Handled above break; case MINE: break; case NONE: break; } } } public Vector3f getOrigin(DriveablePosition dp) { //Rotate the gun vector to global axes Vector3f localGunVec = new Vector3f(dp.position); if(dp.part == EnumDriveablePart.turret) { //Untranslate by the turret origin, to get the rotation about the right point Vector3f.sub(localGunVec, getDriveableType().turretOrigin, localGunVec); //Rotate by the turret angles localGunVec = seats[0].looking.findLocalVectorGlobally(localGunVec); //Translate by the turret origin Vector3f.add(localGunVec, getDriveableType().turretOrigin, localGunVec); } return rotate(localGunVec); } public Vector3f getLookVector(DriveablePosition dp) { return axes.getXAxis(); } @Override public void onUpdate() { super.onUpdate(); DriveableType type = getDriveableType(); if(!worldObj.isRemote) { for(int i = 0; i < getDriveableType().numPassengers + 1; i++) { if(seats[i] == null || !seats[i].addedToChunk) { seats[i] = new EntitySeat(worldObj, this, i); worldObj.spawnEntityInWorld(seats[i]); } } for(int i = 0; i < type.wheelPositions.length; i++) { if(wheels[i] == null || !wheels[i].addedToChunk) { wheels[i] = new EntityWheel(worldObj, this, i); worldObj.spawnEntityInWorld(wheels[i]); } } } //Harvest stuff //Aesthetics if(hasEnoughFuel()) { harvesterAngle += throttle / 5F; } //Actual harvesting if(type.harvestBlocks && type.health.get(EnumDriveablePart.harvester) != null) { CollisionBox box = type.health.get(EnumDriveablePart.harvester); for(float x = box.x; x <= box.x + box.w; x++) { for(float y = box.y; y <= box.y + box.h; y++) { for(float z = box.z; z <= box.z + box.d; z++) { Vector3f v = axes.findLocalVectorGlobally(new Vector3f(x, y, z)); int blockX = (int)Math.round(posX + v.x); int blockY = (int)Math.round(posY + v.y); int blockZ = (int)Math.round(posZ + v.z); Block block = worldObj.getBlockState(new BlockPos(blockX, blockY, blockZ)).getBlock(); boolean cancelled = false; if(seats[0] != null && seats[0].riddenByEntity instanceof EntityPlayerMP) { int eventOutcome = ForgeHooks.onBlockBreakEvent(worldObj, ((EntityPlayerMP)seats[0].riddenByEntity).capabilities.isCreativeMode ? GameType.CREATIVE : ((EntityPlayerMP)seats[0].riddenByEntity).capabilities.allowEdit ? GameType.SURVIVAL : GameType.ADVENTURE, (EntityPlayerMP)seats[0].riddenByEntity, new BlockPos(blockX, blockY, blockZ)); cancelled = eventOutcome == -1; } if(!cancelled) { if(type.materialsHarvested.contains(block.getMaterial()) && block.getBlockHardness(worldObj, new BlockPos(blockX, blockY, blockZ)) >= 0F) { //Add the itemstack to mecha inventory List<ItemStack> stacks = block.getDrops(worldObj, new BlockPos(blockX, blockY, blockZ), worldObj.getBlockState(new BlockPos(blockX, blockY, blockZ)), 0); for(int i = 0; i < stacks.size(); i++) { ItemStack stack = stacks.get(i); if(!InventoryHelper.addItemStackToInventory(driveableData, stack, driverIsCreative()) && !worldObj.isRemote && worldObj.getGameRules().getBoolean("doTileDrops")) { worldObj.spawnEntityInWorld(new EntityItem(worldObj, blockX + 0.5F, blockY + 0.5F, blockZ + 0.5F, stack)); } } //Destroy block worldObj.destroyBlock(new BlockPos(blockX, blockY, blockZ), false); } } } } } } for(DriveablePart part : getDriveableData().parts.values()) { if(part.box != null) { part.update(this); //Client side particles if(worldObj.isRemote) { if(part.onFire) { //Pick a random position within the bounding box and spawn a flame there Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + rand.nextFloat() * part.box.w, part.box.y + rand.nextFloat() * part.box.h, part.box.z + rand.nextFloat() * part.box.d)); worldObj.spawnParticle(EnumParticleTypes.FLAME, posX + pos.x, posY + pos.y, posZ + pos.z, 0, 0, 0); } if(part.health > 0 && part.health < part.maxHealth / 2) { //Pick a random position within the bounding box and spawn a flame there Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + rand.nextFloat() * part.box.w, part.box.y + rand.nextFloat() * part.box.h, part.box.z + rand.nextFloat() * part.box.d)); worldObj.spawnParticle(part.health < part.maxHealth / 4 ? EnumParticleTypes.SMOKE_LARGE : EnumParticleTypes.SMOKE_NORMAL, posX + pos.x, posY + pos.y, posZ + pos.z, 0, 0, 0); } } //Server side fire handling if(part.onFire) { //Rain can put out fire if(worldObj.isRaining() && rand.nextInt(40) == 0) part.onFire = false; //Also water blocks //Get the centre point of the part Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x + part.box.w / 2F, part.box.y + part.box.h / 2F, part.box.z + part.box.d / 2F)); if(worldObj.getBlockState(new BlockPos(MathHelper.floor_double(posX + pos.x), MathHelper.floor_double(posY + pos.y), MathHelper.floor_double(posZ + pos.z))).getBlock().getMaterial() == Material.water) { part.onFire = false; } } else { Vector3f pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x / 16F + part.box.w / 32F, part.box.y / 16F + part.box.h / 32F, part.box.z / 16F + part.box.d / 32F)); if(worldObj.getBlockState(new BlockPos(MathHelper.floor_double(posX + pos.x), MathHelper.floor_double(posY + pos.y), MathHelper.floor_double(posZ + pos.z))).getBlock().getMaterial() == Material.lava) { part.onFire = true; } } } } for(int i = 0; i < type.emitters.size(); i++) { ParticleEmitter emitter = type.emitters.get(i); emitterTimers[i]--; boolean canEmit = false; DriveablePart part = getDriveableData().parts.get(EnumDriveablePart.getPart(emitter.part)); float healthPercentage = (float)part.health / (float)part.maxHealth; if(isPartIntact(EnumDriveablePart.getPart(emitter.part)) && healthPercentage >= emitter.minHealth && healthPercentage <= emitter.maxHealth){ canEmit = true; } else { canEmit = false; } if(emitterTimers[i] <= 0) { if(throttle >= emitter.minThrottle && throttle <= emitter.maxThrottle && canEmit){ //Emit! Vector3f velocity = new Vector3f(0,0,0);; Vector3f pos = new Vector3f(0,0,0); if(seats != null && seats[0] != null){ if(EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.turret && EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.head){ Vector3f localPosition = new Vector3f(emitter.origin.x + rand.nextFloat() * emitter.extents.x - emitter.extents.x * 0.5f, emitter.origin.y + rand.nextFloat() * emitter.extents.y - emitter.extents.y * 0.5f, emitter.origin.z + rand.nextFloat() * emitter.extents.z - emitter.extents.z * 0.5f); pos = axes.findLocalVectorGlobally(localPosition); velocity = axes.findLocalVectorGlobally(emitter.velocity); } else if(EnumDriveablePart.getPart(emitter.part) == EnumDriveablePart.turret || EnumDriveablePart.getPart(emitter.part) != EnumDriveablePart.head){ Vector3f localPosition2 = new Vector3f(emitter.origin.x + rand.nextFloat() * emitter.extents.x - emitter.extents.x * 0.5f, emitter.origin.y + rand.nextFloat() * emitter.extents.y - emitter.extents.y * 0.5f, emitter.origin.z + rand.nextFloat() * emitter.extents.z - emitter.extents.z * 0.5f); RotatedAxes yawOnlyLooking = new RotatedAxes(seats[0].looking.getYaw() + axes.getYaw(), axes.getPitch(), axes.getRoll()); pos = yawOnlyLooking.findLocalVectorGlobally(localPosition2); velocity = yawOnlyLooking.findLocalVectorGlobally(emitter.velocity); } worldObj.spawnParticle(emitter.effectType, posX + pos.x, posY + pos.y, posZ + pos.z, velocity.x, velocity.y, velocity.z); } } emitterTimers[i] = emitter.emitRate; } } checkParts(); prevRotationYaw = axes.getYaw(); prevRotationPitch = axes.getPitch(); prevRotationRoll = axes.getRoll(); prevAxes = axes.clone(); if(riddenByEntity != null && riddenByEntity.isDead) { riddenByEntity = null; } if(riddenByEntity != null && isDead) { riddenByEntity.mountEntity(null); } if(riddenByEntity != null) riddenByEntity.fallDistance = 0F; boolean canThrust = driverIsCreative() || driveableData.fuelInTank > 0; //If there's no player in the driveable or it cannot thrust, slow the plane and turn off mouse held actions if((seats[0] != null && seats[0].riddenByEntity == null) || !canThrust && getDriveableType().maxThrottle != 0 && getDriveableType().maxNegativeThrottle != 0) { throttle *= 0.98F; rightMouseHeld = leftMouseHeld = false; } //Check if shooting if(shootDelayPrimary > 0) shootDelayPrimary--; if(shootDelaySecondary > 0) shootDelaySecondary--; if(!worldObj.isRemote) { if(leftMouseHeld && getDriveableType().modePrimary == EnumFireMode.FULLAUTO) shoot(false); if(rightMouseHeld && getDriveableType().modeSecondary == EnumFireMode.FULLAUTO) shoot(true); minigunSpeedPrimary *= 0.9F; minigunSpeedSecondary *= 0.9F; if(leftMouseHeld && getDriveableType().modePrimary == EnumFireMode.MINIGUN) { minigunSpeedPrimary += 0.1F; if(minigunSpeedPrimary > 1F) shoot(false); } if(rightMouseHeld && getDriveableType().modeSecondary == EnumFireMode.MINIGUN) { minigunSpeedSecondary += 0.1F; if(minigunSpeedSecondary > 1F) shoot(true); } } //Handle fuel int fuelMultiplier = 2; //The tank is currently full, so do nothing if(getDriveableData().fuelInTank >= type.fuelTankSize) return; //Look through the entire inventory for fuel cans, buildcraft fuel buckets and RedstoneFlux power sources for(int i = 0; i < getDriveableData().getSizeInventory(); i++) { ItemStack stack = getDriveableData().getStackInSlot(i); if(stack == null || stack.stackSize <= 0) continue; Item item = stack.getItem(); //If we have an electric engine, look for RedstoneFlux power source items, such as power cubes /* if(getDriveableData().engine.useRFPower) { if(item instanceof IEnergyContainerItem) { IEnergyContainerItem energy = (IEnergyContainerItem)item; getDriveableData().fuelInTank += (fuelMultiplier * energy.extractEnergy(stack, getDriveableData().engine.RFDrawRate, false)) / getDriveableData().engine.RFDrawRate; } } else */ { //Check for Flan's Mod fuel items if(item instanceof ItemPart) { PartType part = ((ItemPart)item).type; //Check it is a fuel item if(part.category == EnumPartCategory.FUEL) { //Put 2 points of fuel getDriveableData().fuelInTank += fuelMultiplier; //Damage the fuel item to indicate being used up int damage = stack.getItemDamage(); stack.setItemDamage(damage + 1); //If we have finished this fuel item if(damage >= stack.getMaxDamage()) { //Reset the damage to 0 stack.setItemDamage(0); //Consume one item stack.stackSize--; //If we consumed the last one, destroy the stack if(stack.stackSize <= 0) getDriveableData().setInventorySlotContents(i, null); } //We found a fuel item and consumed some, so we are done break; } } //Check for Buildcraft oil and fuel buckets else if(FlansMod.hooks.BuildCraftLoaded && stack.isItemEqual(FlansMod.hooks.BuildCraftOilBucket) && getDriveableData().fuelInTank + 1000 * fuelMultiplier <= type.fuelTankSize) { getDriveableData().fuelInTank += 1000 * fuelMultiplier; getDriveableData().setInventorySlotContents(i, new ItemStack(Items.bucket)); } else if(FlansMod.hooks.BuildCraftLoaded && stack.isItemEqual(FlansMod.hooks.BuildCraftFuelBucket) && getDriveableData().fuelInTank + 2000 * fuelMultiplier <= type.fuelTankSize) { getDriveableData().fuelInTank += 2000 * fuelMultiplier; getDriveableData().setInventorySlotContents(i, new ItemStack(Items.bucket)); } } } } public void checkForCollisions() { boolean crashInWater = false; double speed = getSpeedXYZ(); for(DriveablePosition p : getDriveableType().collisionPoints) { if(driveableData.parts.get(p.part).dead) continue; Vector3f lastRelPos = prevAxes.findLocalVectorGlobally(p.position); Vec3 lastPos = new Vec3(prevPosX + lastRelPos.x, prevPosY + lastRelPos.y, prevPosZ + lastRelPos.z); Vector3f currentRelPos = axes.findLocalVectorGlobally(p.position); Vec3 currentPos = new Vec3(posX + currentRelPos.x, posY + currentRelPos.y, posZ + currentRelPos.z); if(FlansMod.DEBUG && worldObj.isRemote) { worldObj.spawnEntityInWorld(new EntityDebugVector(worldObj, new Vector3f(lastPos), Vector3f.sub(currentRelPos, lastRelPos, null), 10, 1F, 0F, 0F)); } MovingObjectPosition hit = worldObj.rayTraceBlocks(lastPos, currentPos, crashInWater); if(hit != null && hit.typeOfHit == MovingObjectType.BLOCK) { BlockPos pos = hit.getBlockPos(); IBlockState state = worldObj.getBlockState(pos); Block blockHit = state.getBlock(); float blockHardness = blockHit.getBlockHardness(worldObj, pos); //Attack the part if(!attackPart(p.part, DamageSource.inWall, blockHardness * blockHardness * (float)speed) && TeamsManager.driveablesBreakBlocks) { //And if it didn't die from the attack, break the block worldObj.playAuxSFXAtEntity(null, 2001, pos, Block.getStateId(state)); if(!worldObj.isRemote) { blockHit.dropBlockAsItem(worldObj, pos, state, 1); worldObj.setBlockToAir(pos); } } else { //The part died! worldObj.createExplosion(this, currentPos.xCoord, currentPos.yCoord, currentPos.zCoord, 1F, false); } } } } @Override public void fall(float distance, float damageMultiplier) { if (distance <= 0) return; //super.fall(k); int i = MathHelper.ceiling_float_int(distance - 10F); if(i > 0) attackPart(EnumDriveablePart.core, DamageSource.fall, damageMultiplier * i / 5); } /** Attack a certain part of a driveable and return whether it broke or not */ public boolean attackPart(EnumDriveablePart ep, DamageSource source, float damage) { DriveablePart part = driveableData.parts.get(ep); return part.attack(damage, source.isFireDamage()); } /** Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates */ public Vector3f rotate(Vector3f inVec) { return axes.findLocalVectorGlobally(inVec); } /** Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates */ public Vector3f rotate(Vec3 inVec) { return rotate(inVec.xCoord, inVec.yCoord, inVec.zCoord); } /** Takes a vector (such as the origin of a seat / gun) and translates it from local coordinates to global coordinates */ public Vector3f rotate(double x, double y, double z) { return rotate(new Vector3f((float)x, (float)y, (float)z)); } //Rotate the plane locally by some angle about the yaw axis public void rotateYaw(float rotateBy) { if(Math.abs(rotateBy) < 0.01F) return; axes.rotateLocalYaw(rotateBy); updatePrevAngles(); } //Rotate the plane locally by some angle about the pitch axis public void rotatePitch(float rotateBy) { if(Math.abs(rotateBy) < 0.01F) return; axes.rotateLocalPitch(rotateBy); updatePrevAngles(); } //Rotate the plane locally by some angle about the roll axis public void rotateRoll(float rotateBy) { if(Math.abs(rotateBy) < 0.01F) return; axes.rotateLocalRoll(rotateBy); updatePrevAngles(); } public void updatePrevAngles() { //Correct angles that crossed the +/- 180 line, so that rendering doesnt make them swing 360 degrees in one tick. double dYaw = axes.getYaw() - prevRotationYaw; if(dYaw > 180) prevRotationYaw += 360F; if(dYaw < -180) prevRotationYaw -= 360F; double dPitch = axes.getPitch() - prevRotationPitch; if(dPitch > 180) prevRotationPitch += 360F; if(dPitch < -180) prevRotationPitch -= 360F; double dRoll = axes.getRoll() - prevRotationRoll; if(dRoll > 180) prevRotationRoll += 360F; if(dRoll < -180) prevRotationRoll -= 360F; } public void setRotation(float rotYaw, float rotPitch, float rotRoll) { axes.setAngles(rotYaw, rotPitch, rotRoll); } //Used to stop self collision public boolean isPartOfThis(Entity ent) { for(EntitySeat seat : seats) { if(seat == null) continue; if(ent == seat) return true; if(seat.riddenByEntity == ent) return true; } return ent == this; } public DriveableType getDriveableType() { return DriveableType.getDriveable(driveableType); } public DriveableData getDriveableData() { return driveableData; } @Override public boolean isDead() { return isDead; } @Override public Entity getControllingEntity() { return seats[0].getControllingEntity(); } @Override public ItemStack getPickedResult(MovingObjectPosition target) { ItemStack stack = new ItemStack(getDriveableType().item, 1, 0); NBTTagCompound tags = new NBTTagCompound(); stack.setTagCompound(tags); driveableData.writeToNBT(tags); return stack; } public boolean hasFuel() { if (seats == null || seats[0] == null || seats[0].riddenByEntity == null) return false; return driverIsCreative() || driveableData.fuelInTank > 0; } public boolean hasEnoughFuel() { if (seats == null || seats[0] == null || seats[0].riddenByEntity == null) return false; return driverIsCreative() || driveableData.fuelInTank > driveableData.engine.fuelConsumption * throttle; } //Physics time! Oooh yeah public double getSpeedXYZ() { return Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ); } public double getSpeedXZ() { return Math.sqrt(motionX * motionX + motionZ * motionZ); } /** To be overriden by vehicles to get alternate collision system */ public boolean landVehicle() { return false; } /** Overriden by planes for wheel parts */ public boolean gearDown() { return true; } /** Whether or not the plane is on the ground * TODO : Replace with proper check based on wheels * */ public boolean onGround() { return onGround; } /** Attack method called by bullets hitting the plane. Does advanced raytracing to detect which part of the plane is hit */ public ArrayList<BulletHit> attackFromBullet(Vector3f origin, Vector3f motion) { //Make an array to contain the hits ArrayList<BulletHit> hits = new ArrayList<BulletHit>(); //Get the position of the bullet origin, relative to the centre of the plane, and then rotate the vectors onto local co-ordinates Vector3f relativePosVector = Vector3f.sub(origin, new Vector3f((float)posX, (float)posY, (float)posZ), null); Vector3f rotatedPosVector = axes.findGlobalVectorLocally(relativePosVector); Vector3f rotatedMotVector = axes.findGlobalVectorLocally(motion); //Check each part for(DriveablePart part : getDriveableData().parts.values()) { //Ray trace the bullet DriveableHit hit = part.rayTrace(this, rotatedPosVector, rotatedMotVector); if(hit != null) hits.add(hit); } return hits; } /** Called if the bullet actually hit the part returned by the raytrace * @param penetratingPower */ public float bulletHit(BulletType bulletType, float damage, DriveableHit hit, float penetratingPower) { DriveablePart part = getDriveableData().parts.get(hit.part); part.hitByBullet(bulletType, damage); //This is server side bsns if(!worldObj.isRemote) { checkParts(); //If it hit, send a damage update packet FlansMod.getPacketHandler().sendToAllAround(new PacketDriveableDamage(this), posX, posY, posZ, 100, dimension); } return penetratingPower - 5F; } /** A simple raytracer for the driveable. Called by tools */ public DriveablePart raytraceParts(Vector3f origin, Vector3f motion) { //Get the position of the bullet origin, relative to the centre of the plane, and then rotate the vectors onto local co-ordinates Vector3f relativePosVector = Vector3f.sub(origin, new Vector3f((float)posX, (float)posY, (float)posZ), null); Vector3f rotatedPosVector = axes.findGlobalVectorLocally(relativePosVector); Vector3f rotatedMotVector = axes.findGlobalVectorLocally(motion); //Check each part for(DriveablePart part : getDriveableData().parts.values()) { //Ray trace the bullet if(part.rayTrace(this, rotatedPosVector, rotatedMotVector) != null) { return part; } } return null; } /** For overriding for toggles such as gear up / down on planes */ public boolean canHitPart(EnumDriveablePart part) { return true; } /** Internal method for checking that all parts are ok, destroying broken ones, dropping items and making sure that child parts are destroyed when their parents are */ public void checkParts() { for(DriveablePart part : getDriveableData().parts.values()) { if(part != null && !part.dead && part.health <= 0 && part.maxHealth > 0) { killPart(part); } } for(EntitySeat seat : seats) { } //If the core was destroyed, kill the driveable if(getDriveableData().parts.get(EnumDriveablePart.core).dead) { if(!worldObj.isRemote) { for(DriveablePart part : driveableData.parts.values()) { if(part.health > 0 && !part.dead) killPart(part); } } setDead(); } } /** Internal method for killing driveable parts */ private void killPart(DriveablePart part) { if(part.dead) return; part.health = 0; part.dead = true; //Drop items DriveableType type = getDriveableType(); if(!worldObj.isRemote) { Vector3f pos = new Vector3f(0, 0, 0); //Get the midpoint of the part if(part.box != null) pos = axes.findLocalVectorGlobally(new Vector3f(part.box.x / 16F + part.box.w / 32F, part.box.y / 16F + part.box.h / 32F, part.box.z / 16F + part.box.d / 32F)); ArrayList<ItemStack> drops = type.getItemsRequired(part, getDriveableData().engine); if(drops != null) { //Drop each itemstack for(ItemStack stack : drops) { worldObj.spawnEntityInWorld(new EntityItem(worldObj, posX + pos.x, posY + pos.y, posZ + pos.z, stack.copy())); } } dropItemsOnPartDeath(pos, part); //Inventory is in the core, so drop it if the core is broken if(part.type == EnumDriveablePart.core) { for(int i = 0; i < getDriveableData().getSizeInventory(); i++) { ItemStack stack = getDriveableData().getStackInSlot(i); if(stack != null) { worldObj.spawnEntityInWorld(new EntityItem(worldObj, posX + rand.nextGaussian(), posY + rand.nextGaussian(), posZ + rand.nextGaussian(), stack)); } } } } //Kill all child parts to stop things floating unconnected for(EnumDriveablePart child : part.type.getChildren()) { killPart(getDriveableData().parts.get(child)); } } /** Method for planes, vehicles and whatnot to drop their own specific items if they wish */ protected abstract void dropItemsOnPartDeath(Vector3f midpoint, DriveablePart part); @Override public float getPlayerRoll() { return axes.getRoll(); } @Override public float getPrevPlayerRoll() { return prevAxes.getRoll(); } @Override public void explode() { } @Override public float getCameraDistance() { return getDriveableType().cameraDistance; } public boolean isPartIntact(EnumDriveablePart part) { DriveablePart thisPart = getDriveableData().parts.get(part); return thisPart.maxHealth == 0 || thisPart.health > 0; } public abstract boolean hasMouseControlMode(); public abstract String getBombInventoryName(); public abstract String getMissileInventoryName(); public boolean rotateWithTurret(Seat seat) { return seat.part == EnumDriveablePart.turret; } @Override public String getName() { return getDriveableType().name; } @SideOnly(Side.CLIENT) public boolean showInventory(int seat) { return seat != 0 || !FlansModClient.controlModeMouse; } //------------------------------------- // Getters and setters for dual fields //------------------------------------- public float getShootDelay(boolean secondary) { return secondary ? shootDelaySecondary : shootDelayPrimary; } public float getMinigunSpeed(boolean secondary) { return secondary ? minigunSpeedSecondary : minigunSpeedPrimary; } public int getCurrentGun(boolean secondary) { return secondary ? currentGunSecondary : currentGunPrimary; } public void setShootDelay(float f, boolean secondary) { if(secondary) shootDelaySecondary = f; else shootDelayPrimary = f; } public void setMinigunSpeed(float f, boolean secondary) { if(secondary) minigunSpeedSecondary = f; else minigunSpeedPrimary = f; } public void setCurrentGun(int i, boolean secondary) { if(secondary) currentGunSecondary = i; else currentGunPrimary = i; } }