package blusunrize.immersiveengineering.common.blocks.metal; import blusunrize.immersiveengineering.ImmersiveEngineering; import blusunrize.immersiveengineering.api.IEEnums.SideConfig; import blusunrize.immersiveengineering.api.Lib; import blusunrize.immersiveengineering.api.energy.immersiveflux.FluxStorage; import blusunrize.immersiveengineering.api.tool.ITeslaEntity; import blusunrize.immersiveengineering.common.Config.IEConfig; import blusunrize.immersiveengineering.common.blocks.IEBlockInterfaces.IBlockBounds; import blusunrize.immersiveengineering.common.blocks.IEBlockInterfaces.IDirectionalTile; import blusunrize.immersiveengineering.common.blocks.IEBlockInterfaces.IHammerInteraction; import blusunrize.immersiveengineering.common.blocks.IEBlockInterfaces.IHasDummyBlocks; import blusunrize.immersiveengineering.common.blocks.TileEntityIEBase; import blusunrize.immersiveengineering.common.util.ChatUtils; import blusunrize.immersiveengineering.common.util.EnergyHelper.IEForgeEnergyWrapper; import blusunrize.immersiveengineering.common.util.EnergyHelper.IIEInternalFluxHandler; import blusunrize.immersiveengineering.common.util.IEDamageSources; import blusunrize.immersiveengineering.common.util.IEDamageSources.TeslaDamageSource; import blusunrize.immersiveengineering.common.util.IEPotions; import blusunrize.immersiveengineering.common.util.IESounds; import blusunrize.immersiveengineering.common.util.network.MessageTileSync; import com.google.common.collect.ArrayListMultimap; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing.Axis; import net.minecraft.util.ITickable; import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.stream.Collectors; public class TileEntityTeslaCoil extends TileEntityIEBase implements ITickable, IIEInternalFluxHandler, IHasDummyBlocks, IDirectionalTile, IBlockBounds, IHammerInteraction { public boolean dummy = false; public FluxStorage energyStorage = new FluxStorage(48000); public boolean redstoneControlInverted = false; public EnumFacing facing = EnumFacing.UP; public boolean lowPower = false; private Vec3d soundPos = null; @SideOnly(Side.CLIENT) public static ArrayListMultimap<BlockPos,LightningAnimation> effectMap; @Override public void update() { if(dummy) return; synchronized (this) { if (worldObj.isRemote && soundPos!=null) { worldObj.playSound(soundPos.xCoord,soundPos.yCoord,soundPos.zCoord, IESounds.tesla, SoundCategory.BLOCKS, 2.5F,0.5F+worldObj.rand.nextFloat(), true); soundPos = null; } } int timeKey = getPos().getX()^getPos().getZ(); int energyDrain = IEConfig.Machines.teslacoil_consumption; if (lowPower) energyDrain/=2; if(worldObj.getTotalWorldTime()%32==(timeKey&31) && canRun(energyDrain)) { if(!worldObj.isRemote) this.energyStorage.extractEnergy(energyDrain,false); double radius = 6; if (lowPower) radius/=2; AxisAlignedBB aabbSmall = new AxisAlignedBB(getPos().getX()+.5-radius,getPos().getY()+.5-radius,getPos().getZ()+.5-radius, getPos().getX()+.5+radius,getPos().getY()+.5+radius,getPos().getZ()+.5+radius); AxisAlignedBB aabb = aabbSmall.expand(radius/2, radius/2, radius/2); List<Entity> targetsAll = worldObj.getEntitiesWithinAABB(Entity.class, aabb); if (!worldObj.isRemote) for (Entity e:targetsAll) if (e instanceof ITeslaEntity) ((ITeslaEntity) e).onHit(this, lowPower); List<Entity> targets = targetsAll.stream().filter((e)->(e instanceof EntityLivingBase&&aabbSmall.intersectsWith(e.getEntityBoundingBox()))).collect(Collectors.toList()); if(!targets.isEmpty()) { TeslaDamageSource dmgsrc = IEDamageSources.causeTeslaDamage(IEConfig.Machines.teslacoil_damage, lowPower); int randomTarget = worldObj.rand.nextInt(targets.size()); EntityLivingBase target = (EntityLivingBase) targets.get(randomTarget); if(target!=null) { if(!worldObj.isRemote) { energyDrain = IEConfig.Machines.teslacoil_consumption_active; if (lowPower) energyDrain/=2; if(energyStorage.extractEnergy(energyDrain,true)==energyDrain) { energyStorage.extractEnergy(energyDrain,false); if (dmgsrc.apply(target)) { int prevFire = target.fire; target.fire = 1; target.addPotionEffect(new PotionEffect(IEPotions.stunned,128)); target.fire = prevFire; } this.sendRenderPacket(target); } } } } else if(worldObj.isRemote && worldObj.getTotalWorldTime()%128==(timeKey&127)) { //target up to 4 blocks away double tV = (worldObj.rand.nextDouble()-.5)*8; double tH = (worldObj.rand.nextDouble()-.5)*8; if (lowPower) { tV/=2; tH/=2; } //Minimal distance to the coil is 2 blocks tV += tV<0?-2:2; tH += tH<0?-2:2; BlockPos targetBlock = getPos().add(facing.getAxis()==Axis.X?0:tH, facing.getAxis()==Axis.Y?0:tV, facing.getAxis()==Axis.Y?tV:facing.getAxis()==Axis.X?tH:0); double tL=0; boolean targetFound = false; if(!worldObj.isAirBlock(targetBlock)) { IBlockState state = worldObj.getBlockState(targetBlock); AxisAlignedBB blockBounds = state.getBoundingBox(worldObj, targetBlock); // ty = (blockY-getPos().getY())+state.getBlock().getBlockBoundsMaxY(); if(facing==EnumFacing.UP) tL = targetBlock.getY()-getPos().getY() + blockBounds.maxY; else if(facing==EnumFacing.DOWN) tL = targetBlock.getY()-getPos().getY() + blockBounds.minY; else if(facing==EnumFacing.NORTH) tL = targetBlock.getZ()-getPos().getZ() + blockBounds.minZ; else if(facing==EnumFacing.SOUTH) tL = targetBlock.getZ()-getPos().getZ() + blockBounds.maxZ; else if(facing==EnumFacing.WEST) tL = targetBlock.getX()-getPos().getX() + blockBounds.minX; else tL = targetBlock.getX()-getPos().getX() + blockBounds.maxX; targetFound = true; } else { boolean positiveFirst = worldObj.rand.nextBoolean(); for(int i=0; i<2; i++) { for(int ll=0;ll<=6;ll++) { BlockPos targetBlock2 = targetBlock.offset(positiveFirst?facing:facing.getOpposite(), ll); if(!worldObj.isAirBlock(targetBlock2)) { IBlockState state = worldObj.getBlockState(targetBlock2); AxisAlignedBB blockBounds = state.getBoundingBox(worldObj, targetBlock2); tL = facing.getAxis()==Axis.Y?(targetBlock2.getY()-getPos().getY()): facing.getAxis()==Axis.Z?(targetBlock2.getZ()-getPos().getZ()): (targetBlock2.getZ()-getPos().getZ()); EnumFacing tempF = positiveFirst?facing:facing.getOpposite(); if(tempF==EnumFacing.UP) tL += blockBounds.maxY; else if(tempF==EnumFacing.DOWN) tL += blockBounds.minY; else if(tempF==EnumFacing.NORTH) tL += blockBounds.minZ; else if(tempF==EnumFacing.SOUTH) tL += blockBounds.maxZ; else if(tempF==EnumFacing.WEST) tL += blockBounds.minX; else tL += blockBounds.maxX; targetFound = true; break; } } if(targetFound) break; positiveFirst = !positiveFirst; } } if(targetFound) { double tx = facing.getAxis()==Axis.X?tL:tH; double ty = facing.getAxis()==Axis.Y?tL:tV; double tz = facing.getAxis()==Axis.Y?tV:facing.getAxis()==Axis.X?tH:tL; EnumFacing f = null; if(facing.getAxis()==Axis.Y) { if(Math.abs(tz)>Math.abs(tx)) f = tz<0?EnumFacing.NORTH:EnumFacing.SOUTH; else f = tx<0?EnumFacing.WEST:EnumFacing.EAST; } else if(facing.getAxis()==Axis.Z) { if(Math.abs(ty)>Math.abs(tx)) f = ty<0?EnumFacing.DOWN:EnumFacing.UP; else f = tx<0?EnumFacing.WEST:EnumFacing.EAST; } else { if(Math.abs(ty)>Math.abs(tz)) f = ty<0?EnumFacing.DOWN:EnumFacing.UP; else f = tz<0?EnumFacing.NORTH:EnumFacing.SOUTH; } double verticalOffset = 1+worldObj.rand.nextDouble()*.25; Vec3d coilPos = new Vec3d(getPos()).addVector(.5,.5,.5); //Vertical offset coilPos = coilPos.addVector(facing.getFrontOffsetX()*verticalOffset, facing.getFrontOffsetY()*verticalOffset, facing.getFrontOffsetZ()*verticalOffset); //offset to direction if(f!=null) { coilPos = coilPos.addVector(f.getFrontOffsetX()*.375, f.getFrontOffsetY()*.375, f.getFrontOffsetZ()*.375); //random side offset f = f.rotateAround(facing.getAxis()); double dShift = (worldObj.rand.nextDouble()-.5)*.75; coilPos = coilPos.addVector(f.getFrontOffsetX()*dShift, f.getFrontOffsetY()*dShift, f.getFrontOffsetZ()*dShift); } effectMap.put(getPos(), new LightningAnimation(coilPos, new Vec3d(getPos()).addVector(tx,ty,tz))); worldObj.playSound(coilPos.xCoord,coilPos.yCoord,coilPos.zCoord, IESounds.tesla, SoundCategory.BLOCKS, 2.5F,0.5F+worldObj.rand.nextFloat(), true); } } this.markDirty(); } } protected void sendRenderPacket(Entity target) { NBTTagCompound tag = new NBTTagCompound(); tag.setInteger("targetEntity", target.getEntityId()); ImmersiveEngineering.packetHandler.sendToAll(new MessageTileSync(this, tag)); } @Override public void receiveMessageFromServer(NBTTagCompound message) { if(message.hasKey("targetEntity")) { Entity target = worldObj.getEntityByID(message.getInteger("targetEntity")); if(target instanceof EntityLivingBase) { double dx = target.posX-getPos().getX(); double dy = target.posY-getPos().getY(); double dz = target.posZ-getPos().getZ(); EnumFacing f = null; if(facing.getAxis()==Axis.Y) { if(Math.abs(dz)>Math.abs(dx)) f = dz<0?EnumFacing.NORTH:EnumFacing.SOUTH; else f = dx<0?EnumFacing.WEST:EnumFacing.EAST; } else if(facing.getAxis()==Axis.Z) { if(Math.abs(dy)>Math.abs(dx)) f = dy<0?EnumFacing.DOWN:EnumFacing.UP; else f = dx<0?EnumFacing.WEST:EnumFacing.EAST; } else { if(Math.abs(dy)>Math.abs(dz)) f = dy<0?EnumFacing.DOWN:EnumFacing.UP; else f = dz<0?EnumFacing.NORTH:EnumFacing.SOUTH; } double verticalOffset = 1+worldObj.rand.nextDouble()*.25; Vec3d coilPos = new Vec3d(getPos()).addVector(.5,.5,.5); //Vertical offset coilPos = coilPos.addVector(facing.getFrontOffsetX()*verticalOffset, facing.getFrontOffsetY()*verticalOffset, facing.getFrontOffsetZ()*verticalOffset); //offset to direction if(f!=null) { coilPos = coilPos.addVector(f.getFrontOffsetX()*.375, f.getFrontOffsetY()*.375, f.getFrontOffsetZ()*.375); //random side offset f = f.rotateAround(facing.getAxis()); double dShift = (worldObj.rand.nextDouble()-.5)*.75; coilPos = coilPos.addVector(f.getFrontOffsetX()*dShift, f.getFrontOffsetY()*dShift, f.getFrontOffsetZ()*dShift); } effectMap.put(getPos(), new LightningAnimation(coilPos,(EntityLivingBase)target)); synchronized (this) { soundPos = coilPos; } } } } @Override public void readCustomNBT(NBTTagCompound nbt, boolean descPacket) { dummy = nbt.getBoolean("dummy"); redstoneControlInverted = nbt.getBoolean("redstoneInverted"); lowPower = nbt.getBoolean("lowPower"); facing = EnumFacing.getFront(nbt.getInteger("facing")); energyStorage.readFromNBT(nbt); } @Override public void writeCustomNBT(NBTTagCompound nbt, boolean descPacket) { nbt.setBoolean("dummy", dummy); nbt.setBoolean("redstoneInverted", redstoneControlInverted); nbt.setBoolean("lowPower", lowPower); if(facing!=null) nbt.setInteger("facing", facing.ordinal()); energyStorage.writeToNBT(nbt); } @Override public float[] getBlockBounds() { if(!dummy) return null; switch(facing) { case DOWN: return new float[]{.125f,.125f,.125f, .875f,1,.875f}; case UP: return new float[]{.125f,0,.125f, .875f,.875f,.875f}; case NORTH: return new float[]{.125f,.125f,.125f, .875f,.875f,1}; case SOUTH: return new float[]{.125f,.125f,0, .875f,.875f,.875f}; case WEST: return new float[]{.125f,.125f,.125f, 1,.875f,.875f}; case EAST: return new float[]{0,.125f,.125f, .875f,.875f,.875f}; } return null; } AxisAlignedBB renderBB; @Override @SideOnly(Side.CLIENT) public AxisAlignedBB getRenderBoundingBox() { if(renderBB==null) renderBB = new AxisAlignedBB(getPos().add(-8,-8,-8),getPos().add(8,8,8)); return renderBB; } @Override public boolean hammerUseSide(EnumFacing side, EntityPlayer player, float hitX, float hitY, float hitZ) { if(dummy) { TileEntity te = worldObj.getTileEntity(getPos().offset(facing,-1)); if(te instanceof TileEntityTeslaCoil) return ((TileEntityTeslaCoil)te).hammerUseSide(side, player, hitX, hitY, hitZ); return false; } if (player.isSneaking()) { int energyDrain = IEConfig.Machines.teslacoil_consumption; if (lowPower) energyDrain/=2; if (canRun(energyDrain)) player.attackEntityFrom(IEDamageSources.causeTeslaPrimaryDamage(), Float.MAX_VALUE); else { lowPower = !lowPower; ChatUtils.sendServerNoSpamMessages(player, new TextComponentTranslation(Lib.CHAT_INFO+"tesla."+(lowPower?"lowPower":"highPower"))); markDirty(); } } else { redstoneControlInverted = !redstoneControlInverted; ChatUtils.sendServerNoSpamMessages(player, new TextComponentTranslation(Lib.CHAT_INFO+"rsControl."+(redstoneControlInverted?"invertedOn":"invertedOff"))); markDirty(); this.markContainingBlockForUpdate(null); } return true; } @Override public EnumFacing getFacing() { return facing; } @Override public void setFacing(EnumFacing facing) { this.facing = facing; } @Override public int getFacingLimitation() { return 0; } @Override public boolean mirrorFacingOnPlacement(EntityLivingBase placer) { return false; } @Override public boolean canHammerRotate(EnumFacing side, float hitX, float hitY, float hitZ, EntityLivingBase entity) { return false; } @Override public boolean canRotate(EnumFacing axis) { return false; } @Override public boolean isDummy() { return dummy; } @Override public void placeDummies(BlockPos pos, IBlockState state, EnumFacing side, float hitX, float hitY, float hitZ) { worldObj.setBlockState(pos.offset(facing), state); ((TileEntityTeslaCoil)worldObj.getTileEntity(pos.offset(facing))).dummy = true; ((TileEntityTeslaCoil)worldObj.getTileEntity(pos.offset(facing))).facing = facing; } @Override public void breakDummies(BlockPos pos, IBlockState state) { for(int i=0; i<=1; i++) if(worldObj.getTileEntity(getPos().offset(facing, dummy?-1:0).offset(facing, i)) instanceof TileEntityTeslaCoil) worldObj.setBlockToAir(getPos().offset(facing, dummy?-1:0).offset(facing, i)); } @Nonnull @Override public FluxStorage getFluxStorage() { if(dummy) { TileEntity te = worldObj.getTileEntity(getPos().offset(facing,-1)); if(te instanceof TileEntityTeslaCoil) return ((TileEntityTeslaCoil)te).getFluxStorage(); } return energyStorage; } @Nonnull @Override public SideConfig getEnergySideConfig(EnumFacing facing) { return !dummy?SideConfig.INPUT:SideConfig.NONE; } IEForgeEnergyWrapper[] wrappers = IEForgeEnergyWrapper.getDefaultWrapperArray(this); @Override public IEForgeEnergyWrapper getCapabilityWrapper(EnumFacing facing) { if(!dummy) return wrappers[facing==null?0:facing.ordinal()]; return null; } public boolean canRun(int energyDrain) { return (worldObj.isBlockIndirectlyGettingPowered(getPos())>0^redstoneControlInverted) && energyStorage.getEnergyStored()>=energyDrain; } public static class LightningAnimation { public Vec3d startPos; public EntityLivingBase targetEntity; public Vec3d targetPos; public int timer = 40; public List<Vec3d> subPoints = new ArrayList<>(); private Vec3d prevTarget; public LightningAnimation(Vec3d startPos, EntityLivingBase targetEntity) { this.startPos = startPos; this.targetEntity = targetEntity; } public LightningAnimation(Vec3d startPos, Vec3d targetPos) { this.startPos = startPos; this.targetPos = targetPos; } public boolean shoudlRecalculateLightning() { if(subPoints.isEmpty()||timer%8==0) return true; boolean b = false; Vec3d end = targetEntity!=null?targetEntity.getPositionVector():targetPos; if(prevTarget!=null) b = prevTarget.distanceTo(end)>1; prevTarget = end; return b; } public void createLightning(Random rand) { subPoints.clear(); Vec3d end = targetEntity!=null?targetEntity.getPositionVector():targetPos; Vec3d dist = end.subtract(startPos); double points = 12; for(int i=0; i<points; i++) { Vec3d sub = startPos.addVector(dist.xCoord/points*i, dist.yCoord/points*i, dist.zCoord/points*i); //distance to the middle point and by that, distance from the start and end. -1 is start, 1 is end double fixPointDist= (i-points/2)/(points/2); //Randomization modifier, closer to start/end means smaller divergence double mod = 1-.75*Math.abs(fixPointDist); double offX = (rand.nextDouble()-.5)*mod; double offY = (rand.nextDouble()-.5)*mod; double offZ = (rand.nextDouble()-.5)*mod; if(fixPointDist<0) { offY+=.75*mod*(.75+fixPointDist);//Closer to the coil should arc upwards offX = (sub.xCoord-startPos.xCoord)<0?-Math.abs(offX):Math.abs(offX); offZ = (sub.zCoord-startPos.zCoord)<0?-Math.abs(offZ):Math.abs(offZ); } else { offY = Math.min(end.yCoord+1*(1-fixPointDist)*-Math.signum(dist.yCoord), offY);//final points should be higher/lower than end, depending on if lightning goes up or down offX = Math.abs(offX)*(end.xCoord-sub.xCoord); offZ = Math.abs(offZ)*(end.zCoord-sub.zCoord); } subPoints.add(sub.addVector(offX,offY,offZ)); } } } }