diff --git a/src/main/java/cr0s/warpdrive/block/movement/BlockTransporterScanner.java b/src/main/java/cr0s/warpdrive/block/movement/BlockTransporterScanner.java index 6eb4b4ec..5996b75e 100644 --- a/src/main/java/cr0s/warpdrive/block/movement/BlockTransporterScanner.java +++ b/src/main/java/cr0s/warpdrive/block/movement/BlockTransporterScanner.java @@ -12,6 +12,9 @@ import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; +import java.util.ArrayList; +import java.util.Collection; + public class BlockTransporterScanner extends BlockAbstractBase { @SideOnly(Side.CLIENT) @@ -69,7 +72,9 @@ public class BlockTransporterScanner extends BlockAbstractBase { return iconBuffer[metadata == 0 ? 2 : 3]; } - public boolean isValid(final World worldObj, final VectorI vScanner) { + // return null or empty collection if it's invalid + public Collection getValidContainment(final World worldObj, final VectorI vScanner) { + final ArrayList vContainments = new ArrayList<>(8); boolean isScannerPosition = true; for (int x = vScanner.x - 1; x <= vScanner.x + 1; x++) { for (int z = vScanner.z - 1; z <= vScanner.z + 1; z++) { @@ -77,19 +82,24 @@ public class BlockTransporterScanner extends BlockAbstractBase { final Block blockBase = worldObj.getBlock(x, vScanner.y, z); if ( !(blockBase instanceof BlockTransporterContainment) && (!isScannerPosition || !(blockBase instanceof BlockTransporterScanner)) ) { - return false; + return null; } isScannerPosition = !isScannerPosition; // check 2 above blocks are air if (!worldObj.isAirBlock(x, vScanner.y + 1, z)) { - return false; + return null; } if (!worldObj.isAirBlock(x, vScanner.y + 2, z)) { - return false; + return null; + } + + // save containment position + if (blockBase instanceof BlockTransporterContainment) { + vContainments.add(new VectorI(x, vScanner.y, z)); } } } - return true; + return vContainments; } } \ No newline at end of file diff --git a/src/main/java/cr0s/warpdrive/block/movement/TileEntityTransporterCore.java b/src/main/java/cr0s/warpdrive/block/movement/TileEntityTransporterCore.java index 6c17b078..c87e1aa9 100644 --- a/src/main/java/cr0s/warpdrive/block/movement/TileEntityTransporterCore.java +++ b/src/main/java/cr0s/warpdrive/block/movement/TileEntityTransporterCore.java @@ -32,6 +32,7 @@ import li.cil.oc.api.machine.Callback; import li.cil.oc.api.machine.Context; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -42,7 +43,6 @@ import java.util.Map.Entry; import java.util.UUID; import net.minecraft.block.Block; -import net.minecraft.client.particle.EntityFX; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; import net.minecraft.entity.EntityLivingBase; @@ -53,6 +53,9 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.Packet; +import net.minecraft.network.play.server.S35PacketUpdateTileEntity; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionEffect; import net.minecraft.tileentity.TileEntity; @@ -82,6 +85,7 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen private EnumTransporterState transporterState = EnumTransporterState.DISABLED; // computed properties + private ArrayList vLocalContainments = null; private AxisAlignedBB aabbLocalScanners = null; private boolean isBlockUpdated = false; private int tickUpdateRegistry = 0; @@ -558,6 +562,7 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen final int zMax = zCoord + WarpDriveConfig.TRANSPORTER_SCANNER_GRAB_XZ_BLOCKS; final ArrayList vScanners = new ArrayList<>(16); + final HashSet vContainments = new HashSet<>(64); for (int x = xMin; x <= xMax; x++) { for (int y = yMin; y <= yMax; y++) { if (y < 0 || y > 254) { @@ -570,29 +575,31 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen // only accept valid ones, spawn particles on others final VectorI vScanner = new VectorI(x, y, z); - final boolean isValid = ((BlockTransporterScanner) block).isValid(worldObj, vScanner); - if (isValid) { - vScanners.add(vScanner); - worldObj.setBlockMetadataWithNotify(x, y, z, 1, 2); - } else { + final Collection vValidContainments = ((BlockTransporterScanner) block).getValidContainment(worldObj, vScanner); + if (vValidContainments == null || vValidContainments.isEmpty()) { worldObj.setBlockMetadataWithNotify(x, y, z, 0, 2); PacketHandler.sendSpawnParticlePacket(worldObj, "jammed", (byte) 5, new Vector3(vScanner.x + 0.5D, vScanner.y + 1.5D, vScanner.z + 0.5D), new Vector3(0.0D, 0.0D, 0.0D), 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 32); + } else { + vScanners.add(vScanner); + vContainments.addAll(vValidContainments); + worldObj.setBlockMetadataWithNotify(x, y, z, 1, 2); } } } } } - setLocalScanners(vScanners); + setLocalScanners(vScanners, vContainments); } - private void setLocalScanners(final ArrayList vScanners) { + private void setLocalScanners(final ArrayList vScanners, final Collection vContainments) { // no scanner defined => force null if (vScanners == null || vScanners.isEmpty()) { vLocalScanners = null; + vLocalContainments = null; aabbLocalScanners = null; return; } @@ -611,11 +618,17 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen // save values vLocalScanners = vScanners; + vLocalContainments = new ArrayList<>(vContainments); aabbLocalScanners = AxisAlignedBB.getBoundingBox( vMin.x, vMin.y, vMin.z, vMax.x + 1.0D, vMax.y + 1.0D, vMax.z + 1.0D); } + @SuppressWarnings("unchecked") + public Collection getContainments() { + return vLocalContainments; + } + private static class FocusValues { ArrayList vScanners; int countRangeUpgrades; @@ -1207,10 +1220,7 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen final Entity entity = (Entity) object; - // skip particle effects - if (entity instanceof EntityFX) { - continue; - } + // (particle effects are client side only, no need to filter them out) // skip blacklisted ids final String entityId = EntityList.getEntityString(entity); @@ -1262,10 +1272,7 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen final Entity entity = (Entity) object; - // skip particle effects - if (entity instanceof EntityFX) { - continue; - } + // (particle effects are client side only, no need to filter them out) // skip blacklisted ids final String entityId = EntityList.getEntityString(entity); @@ -1304,13 +1311,21 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen tagCompound.setLong("uuidLeast", uuid.getLeastSignificantBits()); } - final NBTTagList tagListScanners = new NBTTagList(); - if (vLocalScanners != null) { - for (VectorI vScanner : vLocalScanners) { + if ( vLocalScanners != null + && vLocalContainments != null ) { + final NBTTagList tagListScanners = new NBTTagList(); + for (final VectorI vScanner : vLocalScanners) { final NBTTagCompound tagCompoundScanner = vScanner.writeToNBT(new NBTTagCompound()); tagListScanners.appendTag(tagCompoundScanner); } tagCompound.setTag("scanners", tagListScanners); + + final NBTTagList tagListContainments = new NBTTagList(); + for (final VectorI vContainment : vLocalContainments) { + final NBTTagCompound tagCompoundContainment = vContainment.writeToNBT(new NBTTagCompound()); + tagListContainments.appendTag(tagCompoundContainment); + } + tagCompound.setTag("containments", tagListContainments); } tagCompound.setInteger(IBeamFrequency.BEAM_FREQUENCY_TAG, beamFrequency); @@ -1346,14 +1361,22 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen uuid = UUID.randomUUID(); } - if (tagCompound.hasKey("scanners", Constants.NBT.TAG_LIST)) { + if ( tagCompound.hasKey("scanners", Constants.NBT.TAG_LIST) + && tagCompound.hasKey("containments", Constants.NBT.TAG_LIST)) { final NBTTagList tagListScanners = (NBTTagList) tagCompound.getTag("scanners"); final ArrayList vScanners = new ArrayList<>(tagListScanners.tagCount()); for (int indexScanner = 0; indexScanner < tagListScanners.tagCount(); indexScanner++) { final VectorI vScanner = VectorI.createFromNBT(tagListScanners.getCompoundTagAt(indexScanner)); vScanners.add(vScanner); } - setLocalScanners(vScanners); + + final NBTTagList tagListContainments = (NBTTagList) tagCompound.getTag("containments"); + final ArrayList vContainments = new ArrayList<>(tagListContainments.tagCount()); + for (int indexContainment = 0; indexContainment < tagListContainments.tagCount(); indexContainment++) { + final VectorI vContainment = VectorI.createFromNBT(tagListContainments.getCompoundTagAt(indexContainment)); + vContainments.add(vContainment); + } + setLocalScanners(vScanners, vContainments); } beamFrequency = tagCompound.getInteger(IBeamFrequency.BEAM_FREQUENCY_TAG); @@ -1390,6 +1413,20 @@ public class TileEntityTransporterCore extends TileEntityAbstractEnergy implemen } } + @Override + public Packet getDescriptionPacket() { + final NBTTagCompound tagCompound = new NBTTagCompound(); + writeToNBT(tagCompound); + + return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, -1, tagCompound); + } + + @Override + public void onDataPacket(final NetworkManager networkManager, final S35PacketUpdateTileEntity packet) { + final NBTTagCompound tagCompound = packet.func_148857_g(); + readFromNBT(tagCompound); + } + // Common OC/CC methods @Override public String[] transporterName(final Object[] arguments) { diff --git a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java index 935f7487..4ae31981 100644 --- a/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java +++ b/src/main/java/cr0s/warpdrive/config/WarpDriveConfig.java @@ -203,6 +203,7 @@ public class WarpDriveConfig { public static boolean LOGGING_RENDERING = false; public static boolean LOGGING_CHUNK_HANDLER = false; public static boolean LOGGING_CHUNK_LOADING = true; + public static boolean LOGGING_ENTITY_FX = false; public static boolean LOGGING_CLIENT_SYNCHRONIZATION = false; // Starmap @@ -687,6 +688,7 @@ public class WarpDriveConfig { LOGGING_RENDERING = config.get("logging", "enable_rendering_logs", LOGGING_RENDERING, "Detailed rendering logs to help debug the mod.").getBoolean(false); LOGGING_CHUNK_HANDLER = config.get("logging", "enable_chunk_handler_logs", LOGGING_CHUNK_HANDLER, "Detailed chunk data logs to help debug the mod.").getBoolean(false); LOGGING_CHUNK_LOADING = config.get("logging", "enable_chunk_loading_logs", LOGGING_CHUNK_LOADING, "Chunk loading logs, enable it to report chunk loaders updates").getBoolean(false); + LOGGING_ENTITY_FX = config.get("logging", "enable_entity_fx_logs", LOGGING_ENTITY_FX, "EntityFX logs, enable it to dump entityFX registry updates").getBoolean(false); // Starmap registry STARMAP_REGISTRY_UPDATE_INTERVAL_SECONDS = Commons.clamp(0, 300, diff --git a/src/main/java/cr0s/warpdrive/data/EntityFXRegistry.java b/src/main/java/cr0s/warpdrive/data/EntityFXRegistry.java new file mode 100644 index 00000000..0a3b8c4f --- /dev/null +++ b/src/main/java/cr0s/warpdrive/data/EntityFXRegistry.java @@ -0,0 +1,184 @@ +package cr0s.warpdrive.data; + +import cr0s.warpdrive.WarpDrive; +import cr0s.warpdrive.config.WarpDriveConfig; +import cr0s.warpdrive.render.AbstractEntityFX; +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Registry of all active entity FX on this client + * + * @author LemADEC + */ +public class EntityFXRegistry { + + private static final HashMap>> REGISTRY = new HashMap<>(); + private static int countAdd = 0; + private static int countRemove = 0; + private static int countRead = 0; + + public EntityFXRegistry() { + } + + private static int computeHashcode(final AbstractEntityFX entityFX) { + return computeHashcode(entityFX.worldObj.provider.dimensionId, + MathHelper.floor_double(entityFX.posX), + MathHelper.floor_double(entityFX.posY), + MathHelper.floor_double(entityFX.posZ)); + } + + private static int computeHashcode(final World world, final Vector3 v3Position) { + return computeHashcode(world.provider.dimensionId, + MathHelper.floor_double(v3Position.x), + MathHelper.floor_double(v3Position.y), + MathHelper.floor_double(v3Position.z)); + } + + private static int computeHashcode(final int dimensionId, final int x, final int y, final int z) { + return (dimensionId << 24) ^ ((x & 0xFFFF) << 8) ^ (y << 16) ^ (z & 0xFFFF); + } + + private static void logStats(final int trigger) { + if ((trigger & 0x3FF) != 0) { + return; + } + + int sizeTotal = 0; + int sizeClusterMax = 0; + for (final CopyOnWriteArraySet> items : REGISTRY.values()) { + final int size = items.size(); + sizeTotal += size; + sizeClusterMax = Math.max(sizeClusterMax, size); + } + + WarpDrive.logger.info(String.format("AbstractEntityFX REGISTRY stats: read %d add %d remove %d => %.3f read, currently holding %d items %d hashes %d maxCluster", + countRead, countAdd, countRemove, + ((float) countRead) / (countRemove + countRead + countAdd), + sizeTotal, REGISTRY.size(), sizeClusterMax)); + } + + public static AbstractEntityFX get(final World world, final Vector3 v3Position, final double rangeMax) { + countRead++; + if (WarpDriveConfig.LOGGING_ENTITY_FX) { + logStats(countRead); + } + + // get by hashcode + final Integer hashcode = computeHashcode(world, v3Position); + final CopyOnWriteArraySet> setRegistryItems = REGISTRY.get(hashcode); + if (setRegistryItems == null) { + return null; + } + + final double rangeMaxSquare = rangeMax * rangeMax; + + // get the exact match + for (final WeakReference weakEntityFX : setRegistryItems) { + if (weakEntityFX == null) { + countRemove++; + setRegistryItems.remove(null); + continue; + } + final AbstractEntityFX entityFX = weakEntityFX.get(); + if ( entityFX == null + || entityFX.isDead ) { + countRemove++; + setRegistryItems.remove(weakEntityFX); + continue; + } + final double rangeSquared = v3Position.distanceTo_square(entityFX); + if (rangeSquared < rangeMaxSquare) { + return entityFX; + } + } + return null; + } + + public static boolean add(final AbstractEntityFX entityFX) { + countRead++; + if (WarpDriveConfig.LOGGING_ENTITY_FX) { + logStats(countRead); + } + + // get by hashcode + final Integer hashcode = computeHashcode(entityFX); + CopyOnWriteArraySet> setRegistryItems = REGISTRY.get(hashcode); + if (setRegistryItems == null) { + setRegistryItems = new CopyOnWriteArraySet<>(); + REGISTRY.put(hashcode, setRegistryItems); + } else { + // get the exact match + final Vector3 v3Position = new Vector3(entityFX); + for (final WeakReference weakEntityFX : setRegistryItems) { + if (weakEntityFX == null) { + countRemove++; + setRegistryItems.remove(null); + continue; + } + final AbstractEntityFX entityFX_existing = weakEntityFX.get(); + if ( entityFX_existing == null + || entityFX_existing.isDead ) { + countRemove++; + setRegistryItems.remove(weakEntityFX); + continue; + } + if (entityFX.getEntityId() == entityFX_existing.getEntityId()) { + if (WarpDriveConfig.LOGGING_ENTITY_FX) { + printRegistry("already registered"); + } + return false; + } + if (v3Position.distanceTo_square(entityFX) < 0.01D) { + if (WarpDriveConfig.LOGGING_ENTITY_FX) { + printRegistry("existing entity at location"); + } + return false; + } + } + } + + // not found => add + countAdd++; + setRegistryItems.add(new WeakReference<>(entityFX)); + if (WarpDriveConfig.LOGGING_ENTITY_FX) { + printRegistry("added"); + } + return true; + } + + private static void printRegistry(final String trigger) { + WarpDrive.logger.info("AbstractEntityFX REGISTRY (" + REGISTRY.size() + " entries after " + trigger + "):"); + + for (final Entry>> entryRegistryItems : REGISTRY.entrySet()) { + StringBuilder message = new StringBuilder(); + final Iterator> iterator = entryRegistryItems.getValue().iterator(); + while (iterator.hasNext()) { + final WeakReference weakEntityFX = iterator.next(); + if (weakEntityFX == null) { + countRemove++; + iterator.remove(); + continue; + } + final AbstractEntityFX entityFX = weakEntityFX.get(); + if (entityFX == null) { + countRemove++; + iterator.remove(); + continue; + } + message.append(String.format("\n- %s", + entityFX)); + } + WarpDrive.logger.info(String.format("- %d entries with hashcode 0x%8X: %s", + entryRegistryItems.getValue().size(), + entryRegistryItems.getKey(), + message.toString())); + } + } +} diff --git a/src/main/java/cr0s/warpdrive/network/MessageTransporterEffect.java b/src/main/java/cr0s/warpdrive/network/MessageTransporterEffect.java index c4b48c01..76881786 100644 --- a/src/main/java/cr0s/warpdrive/network/MessageTransporterEffect.java +++ b/src/main/java/cr0s/warpdrive/network/MessageTransporterEffect.java @@ -2,13 +2,16 @@ package cr0s.warpdrive.network; import cr0s.warpdrive.Commons; import cr0s.warpdrive.WarpDrive; +import cr0s.warpdrive.block.movement.TileEntityTransporterCore; import cr0s.warpdrive.config.WarpDriveConfig; +import cr0s.warpdrive.data.EntityFXRegistry; import cr0s.warpdrive.data.GlobalPosition; import cr0s.warpdrive.data.MovingEntity; import cr0s.warpdrive.data.Vector3; import cr0s.warpdrive.data.VectorI; -import cr0s.warpdrive.render.EntityFXBeam; +import cr0s.warpdrive.render.AbstractEntityFX; import cr0s.warpdrive.render.EntityFXDot; +import cr0s.warpdrive.render.EntityFXEnergizing; import io.netty.buffer.ByteBuf; import net.minecraft.block.Block; @@ -16,6 +19,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.particle.EntityFX; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.MathHelper; import net.minecraft.world.World; @@ -143,6 +147,10 @@ public class MessageTransporterEffect implements IMessage, IMessageHandler vContainments = ((TileEntityTransporterCore) tileEntity).getContainments(); + if (vContainments == null) { + WarpDrive.logger.error(String.format("No containments blocks identified for transporter core at %s", + vTransporter)); + return; + } + for (final VectorI vContainment : vContainments) { + if (world.rand.nextFloat() < 0.85F) { + continue; + } + + // start preferably from top or bottom side + double y; + if (isFalling) { + y = vContainment.y + 0.5D - Math.pow(world.rand.nextDouble(), 3.0D) * yRange; + } else { + y = vContainment.y + 0.5D + Math.pow(world.rand.nextDouble(), 3.0D) * yRange; + } + final Vector3 v3Position = new Vector3( + vContainment.x + world.rand.nextDouble(), + y, + vContainment.z + world.rand.nextDouble()); + + // add particle + final EntityFX effect = new EntityFXDot(world, v3Position, + v3Motion, v3Acceleration, 0.98D, + 30); + effect.setRBGColorF(redBase + redFactor * world.rand.nextFloat(), + greenBase + greenFactor * world.rand.nextFloat(), + blueBase + blueFactor * world.rand.nextFloat() ); + effect.setAlphaF(1.0F); + FMLClientHandler.instance().getClient().effectRenderer.addEffect(effect); + } + } + @Override @SideOnly(Side.CLIENT) public IMessage onMessage(MessageTransporterEffect messageSpawnParticle, MessageContext context) { diff --git a/src/main/java/cr0s/warpdrive/render/AbstractEntityFX.java b/src/main/java/cr0s/warpdrive/render/AbstractEntityFX.java new file mode 100644 index 00000000..c5044119 --- /dev/null +++ b/src/main/java/cr0s/warpdrive/render/AbstractEntityFX.java @@ -0,0 +1,20 @@ +package cr0s.warpdrive.render; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.world.World; + +@SideOnly(Side.CLIENT) +public abstract class AbstractEntityFX extends EntityFX { + + public AbstractEntityFX(final World world, final double x, final double y, final double z, + final double xSpeed, final double ySpeed, final double zSpeed) { + super(world, x, y, z, xSpeed, ySpeed, zSpeed); + } + + // extend current life + public void refresh() { + particleMaxAge = Math.max(particleMaxAge, particleAge + 20); + } +} \ No newline at end of file diff --git a/src/main/java/cr0s/warpdrive/render/EntityFXDot.java b/src/main/java/cr0s/warpdrive/render/EntityFXDot.java index 9fe86f5a..35cc7dd9 100644 --- a/src/main/java/cr0s/warpdrive/render/EntityFXDot.java +++ b/src/main/java/cr0s/warpdrive/render/EntityFXDot.java @@ -68,6 +68,11 @@ public class EntityFXDot extends EntityFX { return layer; } + @Override + public int getBrightnessForRender(float p_70070_1_) { + return 0xF00000; + } + @Override public void renderParticle(final Tessellator tessellator, final float partialTick, final float cosYaw, final float cosPitch, final float sinYaw, final float sinSinPitch, final float cosSinPitch) { diff --git a/src/main/java/cr0s/warpdrive/render/EntityFXEnergizing.java b/src/main/java/cr0s/warpdrive/render/EntityFXEnergizing.java new file mode 100644 index 00000000..0e0aa3cb --- /dev/null +++ b/src/main/java/cr0s/warpdrive/render/EntityFXEnergizing.java @@ -0,0 +1,222 @@ +package cr0s.warpdrive.render; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import cr0s.warpdrive.data.Vector3; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.MathHelper; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; + +@SideOnly(Side.CLIENT) +public class EntityFXEnergizing extends AbstractEntityFX { + + private static final ResourceLocation TEXTURE = new ResourceLocation("warpdrive", "textures/particle/energy_grey.png"); + + private double radius; + private double length; + private final int countSteps; + private float rotYaw; + private float rotPitch; + private float prevYaw; + private float prevPitch; + + public EntityFXEnergizing(final World world, final Vector3 position, final Vector3 target, + final float red, final float green, final float blue, + final int age, final float radius) { + super(world, position.x, position.y, position.z, 0.0D, 0.0D, 0.0D); + setRBGColorF(red, green, blue); + setSize(0.02F, 0.02F); + noClip = true; + motionX = 0.0D; + motionY = 0.0D; + motionZ = 0.0D; + + this.radius = radius; + + final float xd = (float) (posX - target.x); + final float yd = (float) (posY - target.y); + final float zd = (float) (posZ - target.z); + length = new Vector3(this).distanceTo(target); + final double lengthXZ = MathHelper.sqrt_double(xd * xd + zd * zd); + rotYaw = (float) (Math.atan2(xd, zd) * 180.0D / Math.PI); + rotPitch = (float) (Math.atan2(yd, lengthXZ) * 180.0D / Math.PI); + prevYaw = rotYaw; + prevPitch = rotPitch; + particleMaxAge = age; + + // kill the particle if it's too far away + // reduce cylinder resolution when fancy graphic are disabled + final EntityLivingBase entityRender = Minecraft.getMinecraft().renderViewEntity; + int visibleDistance = 300; + + if (!Minecraft.getMinecraft().gameSettings.fancyGraphics) { + visibleDistance = 100; + countSteps = 1; + } else { + countSteps = 6; + } + + if (entityRender.getDistance(posX, posY, posZ) > visibleDistance) { + particleMaxAge = 0; + } + } + + @Override + public void onUpdate() { + prevPosX = posX; + prevPosY = posY; + prevPosZ = posZ; + prevYaw = rotYaw; + prevPitch = rotPitch; + + if (particleAge++ >= particleMaxAge) { + setDead(); + } + } + + @Override + public void renderParticle(final Tessellator tessellator, final float partialTick, + final float cosYaw, final float cosPitch, final float sinYaw, final float sinSinPitch, final float cosSinPitch) { + tessellator.draw(); + GL11.glPushMatrix(); + + final double factorFadeIn = Math.min((particleAge + partialTick) / 20.0F, 1.0F); + + // alpha starts at 50%, vanishing to 10% during last ticks + float alpha = 0.5F; + if (particleMaxAge - particleAge <= 4) { + alpha = 0.5F - (4 - (particleMaxAge - particleAge)) * 0.1F; + } else { + // add random flickering + final double timeAlpha = ((getEntityId() ^ 0x47C8) & 0xFFFF) + particleAge + partialTick + 0.0167D; + alpha += Math.pow(Math.sin(timeAlpha * 0.37D) + Math.sin(0.178D + timeAlpha * 0.17D), 2.0D) * 0.05D; + } + + // texture clock is offset to de-synchronize particles + final double timeTexture =(getEntityId() & 0xFFFF) + particleAge + partialTick; + + // repeated a pixel column, changing periodically, to animate the texture + final double uOffset = ((int) Math.floor(timeTexture * 0.5D) % 16) / 16.0D; + + // add vertical noise + final double vOffset = Math.pow(Math.sin(timeTexture * 0.20D), 2.0D) * 0.005D; + + + // bind our texture, repeating on both axis + FMLClientHandler.instance().getClient().renderEngine.bindTexture(TEXTURE); + GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); + GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); + + // rendering on both sides + GL11.glDisable(GL11.GL_CULL_FACE); + + // alpha transparency, don't update depth mask + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + GL11.glDepthMask(false); + + // animated translation + final float xx = (float)(prevPosX + (posX - prevPosX) * partialTick - interpPosX); + final float yy = (float)(prevPosY + (posY - prevPosY) * partialTick - interpPosY); + final float zz = (float)(prevPosZ + (posZ - prevPosZ) * partialTick - interpPosZ); + GL11.glTranslated(xx, yy, zz); + + // animated rotation + final float rotYaw = prevYaw + (this.rotYaw - prevYaw) * partialTick; + final float rotPitch = prevPitch + (this.rotPitch - prevPitch) * partialTick; + final float rotSpin = 0.0F; + GL11.glRotatef(90.0F, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(180.0F + rotYaw, 0.0F, 0.0F, -1.0F); + GL11.glRotatef(rotPitch, 1.0F, 0.0F, 0.0F); + GL11.glRotatef(rotSpin, 0.0F, 1.0F, 0.0F); + + // actual parameters + final double radius = this.radius * factorFadeIn; + final double yMin = length * (0.5D - factorFadeIn / 2.0D); + final double yMax = length * (0.5D + factorFadeIn / 2.0D); + final double uMin = uOffset; + final double uMax = uMin + 1.0D / 32.0D; + + final double vMin = -1.0D + vOffset; + final double vMax = vMin + length * factorFadeIn; + + // start drawing + tessellator.startDrawingQuads(); + tessellator.setBrightness(200); + tessellator.setColorRGBA_F(particleRed, particleGreen, particleBlue, alpha); + + // loop covering 45 deg, using symmetry to cover a full circle + final double angleMax = Math.PI / 4.0D; + final double angleStep = angleMax / countSteps; + double angle = 0.0D; + double cosPrev = radius * Math.cos(angle); + double sinPrev = radius * Math.sin(angle); + for (int indexStep = 1; indexStep <= countSteps; indexStep++) { + angle += angleStep; + final double cosNext = radius * Math.cos(angle); + final double sinNext = radius * Math.sin(angle); + + // cos sin + tessellator.addVertexWithUV( cosPrev, yMax, sinPrev, uMax, vMax); + tessellator.addVertexWithUV( cosPrev, yMin, sinPrev, uMax, vMin); + tessellator.addVertexWithUV( cosNext, yMin, sinNext, uMin, vMin); + tessellator.addVertexWithUV( cosNext, yMax, sinNext, uMin, vMax); + + tessellator.addVertexWithUV(-cosPrev, yMax, sinPrev, uMax, vMax); + tessellator.addVertexWithUV(-cosPrev, yMin, sinPrev, uMax, vMin); + tessellator.addVertexWithUV(-cosNext, yMin, sinNext, uMin, vMin); + tessellator.addVertexWithUV(-cosNext, yMax, sinNext, uMin, vMax); + + tessellator.addVertexWithUV( cosPrev, yMax, -sinPrev, uMax, vMax); + tessellator.addVertexWithUV( cosPrev, yMin, -sinPrev, uMax, vMin); + tessellator.addVertexWithUV( cosNext, yMin, -sinNext, uMin, vMin); + tessellator.addVertexWithUV( cosNext, yMax, -sinNext, uMin, vMax); + + tessellator.addVertexWithUV(-cosPrev, yMax, -sinPrev, uMax, vMax); + tessellator.addVertexWithUV(-cosPrev, yMin, -sinPrev, uMax, vMin); + tessellator.addVertexWithUV(-cosNext, yMin, -sinNext, uMin, vMin); + tessellator.addVertexWithUV(-cosNext, yMax, -sinNext, uMin, vMax); + + // sin cos + tessellator.addVertexWithUV( sinPrev, yMax, cosPrev, uMax, vMax); + tessellator.addVertexWithUV( sinPrev, yMin, cosPrev, uMax, vMin); + tessellator.addVertexWithUV( sinNext, yMin, cosNext, uMin, vMin); + tessellator.addVertexWithUV( sinNext, yMax, cosNext, uMin, vMax); + + tessellator.addVertexWithUV(-sinPrev, yMax, cosPrev, uMax, vMax); + tessellator.addVertexWithUV(-sinPrev, yMin, cosPrev, uMax, vMin); + tessellator.addVertexWithUV(-sinNext, yMin, cosNext, uMin, vMin); + tessellator.addVertexWithUV(-sinNext, yMax, cosNext, uMin, vMax); + + tessellator.addVertexWithUV( sinPrev, yMax, -cosPrev, uMax, vMax); + tessellator.addVertexWithUV( sinPrev, yMin, -cosPrev, uMax, vMin); + tessellator.addVertexWithUV( sinNext, yMin, -cosNext, uMin, vMin); + tessellator.addVertexWithUV( sinNext, yMax, -cosNext, uMin, vMax); + + tessellator.addVertexWithUV(-sinPrev, yMax, -cosPrev, uMax, vMax); + tessellator.addVertexWithUV(-sinPrev, yMin, -cosPrev, uMax, vMin); + tessellator.addVertexWithUV(-sinNext, yMin, -cosNext, uMin, vMin); + tessellator.addVertexWithUV(-sinNext, yMax, -cosNext, uMin, vMax); + + cosPrev = cosNext; + sinPrev = sinNext; + } + + // draw + tessellator.draw(); + + // restore OpenGL state + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + GL11.glDepthMask(true); + GL11.glDisable(GL11.GL_BLEND); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glPopMatrix(); + tessellator.startDrawingQuads(); + FMLClientHandler.instance().getClient().renderEngine.bindTexture(new ResourceLocation("textures/particle/particles.png")); + } +} \ No newline at end of file