564 lines
18 KiB
Java

package com.hbm.entity.effect;
import java.util.ArrayList;
import com.hbm.util.TrackerUtil;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
/*
* Toroidial Convection Simulation Explosion Effect
* Tor Ex
*/
public class EntityNukeTorex extends Entity {
public double coreHeight = 3;
public double convectionHeight = 3;
public double torusWidth = 3;
public double rollerSize = 1;
public double heat = 1;
public double lastSpawnY = - 1;
public ArrayList<Cloudlet> cloudlets = new ArrayList();
//public static int cloudletLife = 200;
public EntityNukeTorex(World world) {
super(world);
this.ignoreFrustumCheck = true;
this.setSize(1F, 50F);
}
@Override
protected void entityInit() {
this.dataWatcher.addObject(10, new Float(1));
this.dataWatcher.addObject(11, new Integer(0));
}
@Override
@SideOnly(Side.CLIENT)
public int getBrightnessForRender(float p_70070_1_) {
return 15728880;
}
@Override
public float getBrightness(float p_70013_1_) {
return 1.0F;
}
@Override
public void onUpdate() {
double s = this.getScale();
double cs = 1.5;
int maxAge = this.getMaxAge();
if(worldObj.isRemote) {
if(ticksExisted == 1) this.setScale((float) s);
if(lastSpawnY == -1) {
lastSpawnY = posY - 3;
}
int spawnTarget = Math.max(worldObj.getHeightValue((int) Math.floor(posX), (int) Math.floor(posZ)) - 3, 1);
double moveSpeed = 0.5D;
if(Math.abs(spawnTarget - lastSpawnY) < moveSpeed) {
lastSpawnY = spawnTarget;
} else {
lastSpawnY += moveSpeed * Math.signum(spawnTarget - lastSpawnY);
}
// spawn mush clouds
double range = (torusWidth - rollerSize) * 0.25;
double simSpeed = getSimulationSpeed();
int toSpawn = (int) Math.ceil(10 * simSpeed * simSpeed);
int lifetime = Math.min((ticksExisted * ticksExisted) + 200, maxAge - ticksExisted + 200);
for(int i = 0; i < toSpawn; i++) {
double x = posX + rand.nextGaussian() * range;
double z = posZ + rand.nextGaussian() * range;
Cloudlet cloud = new Cloudlet(x, lastSpawnY, z, (float)(rand.nextDouble() * 2D * Math.PI), 0, lifetime);
cloud.setScale(1F + this.ticksExisted * 0.005F * (float) s, 5F * (float) cs);
cloudlets.add(cloud);
}
// spawn shock clouds
if(ticksExisted < 100) {
int cloudCount = ticksExisted * 5;
int shockLife = Math.max(300 - ticksExisted * 20, 50);
for(int i = 0; i < cloudCount; i++) {
Vec3 vec = Vec3.createVectorHelper((ticksExisted * 2 + rand.nextDouble()) * 2, 0, 0);
float rot = (float) (Math.PI * 2 * rand.nextDouble());
vec.rotateAroundY(rot);
this.cloudlets.add(new Cloudlet(vec.xCoord + posX, worldObj.getHeightValue((int) (vec.xCoord + posX) + 1, (int) (vec.zCoord + posZ)), vec.zCoord + posZ, rot, 0, shockLife)
.setScale(5F, 2F)
.setMotion(0));
}
}
// spawn ring clouds
if(ticksExisted < 200) {
lifetime *= s;
for(int i = 0; i < 2; i++) {
Cloudlet cloud = new Cloudlet(posX, posY + coreHeight, posZ, (float)(rand.nextDouble() * 2D * Math.PI), 0, lifetime, TorexType.RING);
cloud.setScale(1F + this.ticksExisted * 0.0025F * (float) (cs * s), 3F * (float) (cs * s));
cloudlets.add(cloud);
}
}
// spawn condensation clouds
if(ticksExisted > 200 && ticksExisted < 600) {
for(int i = 0; i < 20; i++) {
for(int j = 0; j < 4; j++) {
float angle = (float) (Math.PI * 2 * rand.nextDouble());
Vec3 vec = Vec3.createVectorHelper(torusWidth + rollerSize * 3, 0, 0);
vec.rotateAroundZ((float) (Math.PI / 45 * j));
vec.rotateAroundY(angle);
Cloudlet cloud = new Cloudlet(posX + vec.xCoord, posY + coreHeight - 5 + j * s, posZ + vec.zCoord, angle, 0, (int) ((20 + ticksExisted / 10) * (1 + rand.nextDouble() * 0.1)), TorexType.CONDENSATION);
cloud.setScale(0.125F * (float) (cs * s), 3F * (float) (cs * s));
cloudlets.add(cloud);
}
}
}
if(ticksExisted > 300 && ticksExisted < 600) {
for(int i = 0; i < 20; i++) {
for(int j = 0; j < 4; j++) {
float angle = (float) (Math.PI * 2 * rand.nextDouble());
Vec3 vec = Vec3.createVectorHelper(torusWidth + rollerSize * 2, 0, 0);
vec.rotateAroundZ((float) (Math.PI / 45 * j));
vec.rotateAroundY(angle);
Cloudlet cloud = new Cloudlet(posX + vec.xCoord, posY + coreHeight + 25 + j * s, posZ + vec.zCoord, angle, 0, (int) ((20 + ticksExisted / 10) * (1 + rand.nextDouble() * 0.1)), TorexType.CONDENSATION);
cloud.setScale(0.125F * (float) (cs * s), 3F * (float) (cs * s));
cloudlets.add(cloud);
}
}
}
for(Cloudlet cloud : cloudlets) {
cloud.update();
}
coreHeight += 0.15/* * s*/;
torusWidth += 0.05/* * s*/;
rollerSize = torusWidth * 0.35;
convectionHeight = coreHeight + rollerSize;
int maxHeat = (int) (50 * s);
heat = maxHeat - Math.pow((maxHeat * this.ticksExisted) / maxAge, 1);
cloudlets.removeIf(x -> x.isDead);
}
if(!worldObj.isRemote && this.ticksExisted > maxAge) {
this.setDead();
}
}
public EntityNukeTorex setScale(float scale) {
if(!worldObj.isRemote) getDataWatcher().updateObject(10, scale);
this.coreHeight = this.coreHeight / 1.5D * scale;
this.convectionHeight = this.convectionHeight / 1.5D * scale;
this.torusWidth = this.torusWidth / 1.5D * scale;
this.rollerSize = this.rollerSize / 1.5D * scale;
return this;
}
public EntityNukeTorex setType(int type) {
this.dataWatcher.updateObject(11, type);
return this;
}
public double getSimulationSpeed() {
int lifetime = getMaxAge();
int simSlow = lifetime / 4;
int simStop = lifetime / 2;
int life = EntityNukeTorex.this.ticksExisted;
if(life > simStop) {
return 0D;
}
if(life > simSlow) {
return 1D - ((double)(life - simSlow) / (double)(simStop - simSlow));
}
return 1.0D;
}
public double getScale() {
return this.dataWatcher.getWatchableObjectFloat(10);
}
public double getSaturation() {
double d = (double) this.ticksExisted / (double) this.getMaxAge();
return 1D - (d * d * d * d);
}
public double getGreying() {
int lifetime = getMaxAge();
int greying = lifetime * 3 / 4;
if(ticksExisted > greying) {
return 1 + ((double)(ticksExisted - greying) / (double)(lifetime - greying));
}
return 1D;
}
public float getAlpha() {
int lifetime = getMaxAge();
int fadeOut = lifetime * 3 / 4;
int life = EntityNukeTorex.this.ticksExisted;
if(life > fadeOut) {
float fac = (float)(life - fadeOut) / (float)(lifetime - fadeOut);
return 1F - fac;
}
return 1.0F;
}
public int getMaxAge() {
double s = this.getScale();
return (int) (45 * 20 * s);
}
public class Cloudlet {
public double posX;
public double posY;
public double posZ;
public double prevPosX;
public double prevPosY;
public double prevPosZ;
public double motionX;
public double motionY;
public double motionZ;
public int age;
public int cloudletLife;
public float angle;
public boolean isDead = false;
float rangeMod = 1.0F;
public float colorMod = 1.0F;
public Vec3 color;
public Vec3 prevColor;
public TorexType type;
public Cloudlet(double posX, double posY, double posZ, float angle, int age, int maxAge) {
this(posX, posY, posZ, angle, age, maxAge, TorexType.STANDARD);
}
public Cloudlet(double posX, double posY, double posZ, float angle, int age, int maxAge, TorexType type) {
this.posX = posX;
this.posY = posY;
this.posZ = posZ;
this.age = age;
this.cloudletLife = maxAge;
this.angle = angle;
this.rangeMod = 0.3F + rand.nextFloat() * 0.7F;
this.colorMod = 0.8F + rand.nextFloat() * 0.2F;
this.type = type;
this.updateColor();
}
private void update() {
age++;
if(age > cloudletLife) {
this.isDead = true;
}
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
Vec3 simPos = Vec3.createVectorHelper(EntityNukeTorex.this.posX - this.posX, 0, EntityNukeTorex.this.posZ - this.posZ);
double simPosX = EntityNukeTorex.this.posX + simPos.lengthVector();
double simPosZ = EntityNukeTorex.this.posZ + 0D;
if(this.type == TorexType.STANDARD) {
Vec3 convection = getConvectionMotion(simPosX, simPosZ);
Vec3 lift = getLiftMotion(simPosX, simPosZ);
double factor = MathHelper.clamp_double((this.posY - EntityNukeTorex.this.posY) / EntityNukeTorex.this.coreHeight, 0, 1);
this.motionX = convection.xCoord * factor + lift.xCoord * (1D - factor);
this.motionY = convection.yCoord * factor + lift.yCoord * (1D - factor);
this.motionZ = convection.zCoord * factor + lift.zCoord * (1D - factor);
} else if(this.type == TorexType.RING) {
Vec3 motion = getRingMotion(simPosX, simPosZ);
this.motionX = motion.xCoord;
this.motionY = motion.yCoord;
this.motionZ = motion.zCoord;
} else if(this.type == TorexType.CONDENSATION) {
Vec3 motion = getCondensationMotion();
this.motionX = motion.xCoord;
this.motionY = motion.yCoord;
this.motionZ = motion.zCoord;
}
double mult = this.motionMult * getSimulationSpeed();
this.posX += this.motionX * mult;
this.posY += this.motionY * mult;
this.posZ += this.motionZ * mult;
this.updateColor();
}
private Vec3 getCondensationMotion() {
Vec3 delta = Vec3.createVectorHelper(posX - EntityNukeTorex.this.posX, 0, posZ - EntityNukeTorex.this.posZ);
double speed = 0.00002 * EntityNukeTorex.this.getScale() * EntityNukeTorex.this.ticksExisted;
delta.xCoord *= speed;
//delta.yCoord *= speed * 0.75 * EntityNukeTorex.this.getScale();
delta.zCoord *= speed;
return delta;
}
private Vec3 getRingMotion(double simPosX, double simPosZ) {
/*Vec3 targetPos = Vec3.createVectorHelper(
(EntityNukeTorex.this.posX + torusWidth * 1),
(EntityNukeTorex.this.posY + coreHeight * 0.5),
EntityNukeTorex.this.posZ);
Vec3 delta = Vec3.createVectorHelper(targetPos.xCoord - simPosX, targetPos.yCoord - this.posY, targetPos.zCoord - simPosZ);
double speed = 0.125D;
delta.xCoord *= speed;
delta.yCoord *= speed;
delta.zCoord *= speed;
delta.rotateAroundY(this.angle);
return delta;*/
if(simPosX > EntityNukeTorex.this.posX + torusWidth * 2)
return Vec3.createVectorHelper(0, 0, 0);
/* the position of the torus' outer ring center */
Vec3 torusPos = Vec3.createVectorHelper(
(EntityNukeTorex.this.posX + torusWidth),
(EntityNukeTorex.this.posY + coreHeight * 0.5),
EntityNukeTorex.this.posZ);
/* the difference between the cloudlet and the torus' ring center */
Vec3 delta = Vec3.createVectorHelper(torusPos.xCoord - simPosX, torusPos.yCoord - this.posY, torusPos.zCoord - simPosZ);
/* the distance this cloudlet wants to achieve to the torus' ring center */
double roller = EntityNukeTorex.this.rollerSize * this.rangeMod * 0.25;
/* the distance between this cloudlet and the torus' outer ring perimeter */
double dist = delta.lengthVector() / roller - 1D;
/* euler function based on how far the cloudlet is away from the perimeter */
double func = 1D - Math.pow(Math.E, -dist); // [0;1]
/* just an approximation, but it's good enough */
float angle = (float) (func * Math.PI * 0.5D); // [0;90°]
/* vector going from the ring center in the direction of the cloudlet, stopping at the perimeter */
Vec3 rot = Vec3.createVectorHelper(-delta.xCoord / dist, -delta.yCoord / dist, -delta.zCoord / dist);
/* rotate by the approximate angle */
rot.rotateAroundZ(angle);
/* the direction from the cloudlet to the target position on the perimeter */
Vec3 motion = Vec3.createVectorHelper(
torusPos.xCoord + rot.xCoord - simPosX,
torusPos.yCoord + rot.yCoord - this.posY,
torusPos.zCoord + rot.zCoord - simPosZ);
double speed = 0.001D;
motion.xCoord *= speed;
motion.yCoord *= speed;
motion.zCoord *= speed;
motion = motion.normalize();
motion.rotateAroundY(this.angle);
return motion;
}
/* simulated on a 2D-plane along the X/Y axis */
private Vec3 getConvectionMotion(double simPosX, double simPosZ) {
if(simPosX > EntityNukeTorex.this.posX + torusWidth * 2)
return Vec3.createVectorHelper(0, 0, 0);
/* the position of the torus' outer ring center */
Vec3 torusPos = Vec3.createVectorHelper(
(EntityNukeTorex.this.posX + torusWidth),
(EntityNukeTorex.this.posY + coreHeight),
EntityNukeTorex.this.posZ);
/* the difference between the cloudlet and the torus' ring center */
Vec3 delta = Vec3.createVectorHelper(torusPos.xCoord - simPosX, torusPos.yCoord - this.posY, torusPos.zCoord - simPosZ);
/* the distance this cloudlet wants to achieve to the torus' ring center */
double roller = EntityNukeTorex.this.rollerSize * this.rangeMod;
/* the distance between this cloudlet and the torus' outer ring perimeter */
double dist = delta.lengthVector() / roller - 1D;
/* euler function based on how far the cloudlet is away from the perimeter */
double func = 1D - Math.pow(Math.E, -dist); // [0;1]
/* just an approximation, but it's good enough */
float angle = (float) (func * Math.PI * 0.5D); // [0;90°]
/* vector going from the ring center in the direction of the cloudlet, stopping at the perimeter */
Vec3 rot = Vec3.createVectorHelper(-delta.xCoord / dist, -delta.yCoord / dist, -delta.zCoord / dist);
/* rotate by the approximate angle */
rot.rotateAroundZ(angle);
/* the direction from the cloudlet to the target position on the perimeter */
Vec3 motion = Vec3.createVectorHelper(
torusPos.xCoord + rot.xCoord - simPosX,
torusPos.yCoord + rot.yCoord - this.posY,
torusPos.zCoord + rot.zCoord - simPosZ);
motion = motion.normalize();
motion.rotateAroundY(this.angle);
return motion;
}
private Vec3 getLiftMotion(double simPosX, double simPosZ) {
double scale = MathHelper.clamp_double(1D - (simPosX - (EntityNukeTorex.this.posX + torusWidth)), 0, 1);
Vec3 motion = Vec3.createVectorHelper(EntityNukeTorex.this.posX - this.posX, (EntityNukeTorex.this.posY + convectionHeight) - this.posY, EntityNukeTorex.this.posZ - this.posZ);
motion = motion.normalize();
motion.xCoord *= scale;
motion.yCoord *= scale;
motion.zCoord *= scale;
return motion;
}
private void updateColor() {
this.prevColor = this.color;
double exX = EntityNukeTorex.this.posX;
double exY = EntityNukeTorex.this.posY + EntityNukeTorex.this.coreHeight;
double exZ = EntityNukeTorex.this.posZ;
double distX = exX - posX;
double distY = exY - posY;
double distZ = exZ - posZ;
double distSq = distX * distX + distY * distY + distZ * distZ;
distSq /= EntityNukeTorex.this.heat;
double dist = Math.sqrt(distSq);
dist = Math.max(dist, 1);
double col = 2D / dist;
int type = EntityNukeTorex.this.dataWatcher.getWatchableObjectInt(11);
if(type == 1) {
this.color = Vec3.createVectorHelper(
Math.max(col * 1, 0.25),
Math.max(col * 2, 0.25),
Math.max(col * 0.5, 0.25)
);
} else {
this.color = Vec3.createVectorHelper(
Math.max(col * 2, 0.25),
Math.max(col * 1.5, 0.25),
Math.max(col * 0.5, 0.25)
);
}
}
public Vec3 getInterpPos(float interp) {
return Vec3.createVectorHelper(
prevPosX + (posX - prevPosX) * interp,
prevPosY + (posY - prevPosY) * interp,
prevPosZ + (posZ - prevPosZ) * interp);
}
public Vec3 getInterpColor(float interp) {
if(this.type == TorexType.CONDENSATION) {
return Vec3.createVectorHelper(1F, 1F, 1F);
}
double greying = EntityNukeTorex.this.getGreying();
if(this.type == TorexType.RING) {
greying += 1;
}
return Vec3.createVectorHelper(
(prevColor.xCoord + (color.xCoord - prevColor.xCoord) * interp) * greying,
(prevColor.yCoord + (color.yCoord - prevColor.yCoord) * interp) * greying,
(prevColor.zCoord + (color.zCoord - prevColor.zCoord) * interp) * greying);
}
public float getAlpha() {
float alpha = (1F - ((float)age / (float)cloudletLife)) * EntityNukeTorex.this.getAlpha();
if(this.type == TorexType.CONDENSATION) alpha *= 0.25;
return alpha;
}
private float startingScale = 1;
private float growingScale = 5F;
public float getScale() {
return startingScale + ((float)age / (float)cloudletLife) * growingScale;
}
public Cloudlet setScale(float start, float grow) {
this.startingScale = start;
this.growingScale = grow;
return this;
}
private double motionMult = 1F;
public Cloudlet setMotion(double mult) {
this.motionMult = mult;
return this;
}
}
public static enum TorexType {
STANDARD,
RING,
CONDENSATION
}
@Override protected void writeEntityToNBT(NBTTagCompound nbt) { }
@Override public boolean writeToNBTOptional(NBTTagCompound nbt) { return false; }
@Override public void readEntityFromNBT(NBTTagCompound nbt) { this.setDead(); }
@Override
@SideOnly(Side.CLIENT)
public boolean isInRangeToRenderDist(double distance) {
return true;
}
public static void statFac(World world, double x, double y, double z, float scale) {
EntityNukeTorex torex = new EntityNukeTorex(world).setScale(MathHelper.clamp_float(scale * 0.01F, 0.5F, 5F));
torex.setPosition(x, y, z);
world.spawnEntityInWorld(torex);
TrackerUtil.setTrackingRange(world, torex, 1000);
}
public static void statFacBale(World world, double x, double y, double z, float scale) {
EntityNukeTorex torex = new EntityNukeTorex(world).setScale(MathHelper.clamp_float(scale * 0.01F, 0.5F, 5F)).setType(1);
torex.setPosition(x, y, z);
world.spawnEntityInWorld(torex);
TrackerUtil.setTrackingRange(world, torex, 1000);
}
}