/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of the AcademyCraft mod.
* https://github.com/LambdaInnovation/AcademyCraft
* Licensed under GPLv3, see project root for more information.
*/
package cn.academy.vanilla.meltdowner.entity;
import cn.academy.core.client.ACRenderingHelper;
import cn.academy.core.Resources;
import cn.lambdalib.annoreg.core.Registrant;
import cn.lambdalib.annoreg.mc.RegEntity;
import cn.lambdalib.template.client.render.entity.RenderIcon;
import cn.lambdalib.util.client.RenderUtils;
import cn.lambdalib.util.client.shader.ShaderSimple;
import cn.lambdalib.util.entityx.EntityAdvanced;
import cn.lambdalib.util.entityx.EntityCallback;
import cn.lambdalib.util.generic.MathUtils;
import cn.lambdalib.util.generic.RandUtils;
import cn.lambdalib.util.helper.GameTimer;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
/**
* @author WeAthFolD
*/
@Registrant
@RegEntity
@RegEntity.HasRender
public class EntityMdBall extends EntityAdvanced {
@SideOnly(Side.CLIENT)
@RegEntity.Render
public static R renderer;
static final int MAX_TETXURES = 5;
static final float RANGE_FROM = 0.8f, RANGE_TO = 1.3f;
//Synced states
EntityPlayer spawner;
float subX = 0, subY = 0, subZ = 0;
//Ctor init data
int life = 50;
//Client-side data
int texID;
long spawnTime;
long lastTime;
long burstTime = 400;
double alphaWiggle = 0.8;
double accel;
double offsetX, offsetY, offsetZ;
public EntityMdBall(EntityPlayer player) {
this(player, 2333333, null);
}
public EntityMdBall(EntityPlayer player, int life) {
this(player, life, null);
}
public EntityMdBall(EntityPlayer player, int life, final EntityCallback<EntityMdBall> callback) {
super(player.worldObj);
this.spawner = player;
// Calc the sub-offset
float theta = -player.rotationYaw / 180 * MathUtils.PI_F +
RandUtils.rangef(-MathUtils.PI_F * 0.45f, MathUtils.PI_F * 0.45f);
float range = RandUtils.rangef(RANGE_FROM, RANGE_TO);
subX = MathHelper.sin(theta) * range;
subZ = MathHelper.cos(theta) * range;
subY = RandUtils.rangef(-1.2f, 0.2f);
// Pos init
updatePosition();
this.life = life;
this.executeAfter(new EntityCallback<EntityMdBall>() {
@Override
public void execute(EntityMdBall target) {
target.setDead();
}
}, life);
if(callback != null)
this.executeAfter(callback, life - 2);
}
public EntityMdBall(World world) {
super(world);
spawnTime = GameTimer.getTime();
ignoreFrustumCheck = true; // Small variation in render tick posupdate will cause problem
}
@Override
public void entityInit() {
dataWatcher.addObject(3, Integer.valueOf(0));
dataWatcher.addObject(4, Float.valueOf(0));
dataWatcher.addObject(5, Float.valueOf(0));
dataWatcher.addObject(6, Float.valueOf(0));
dataWatcher.addObject(7, Integer.valueOf(0));
}
@Override
public void onFirstUpdate() {
if(!worldObj.isRemote) {
dataWatcher.updateObject(3, Integer.valueOf(spawner.getEntityId()));
dataWatcher.updateObject(4, Float.valueOf(subX));
dataWatcher.updateObject(5, Float.valueOf(subY));
dataWatcher.updateObject(6, Float.valueOf(subZ));
dataWatcher.updateObject(7, Integer.valueOf(life));
}
}
@Override
public void onUpdate() {
super.onUpdate();
if(worldObj.isRemote) {
if(getSpawner() == null) {
int eid = dataWatcher.getWatchableObjectInt(3);
Entity e = worldObj.getEntityByID(eid);
if(e instanceof EntityPlayer) {
spawner = (EntityPlayer) e;
}
} else {
if(subX == 0 && subY == 0 && subZ == 0) {
subX = dataWatcher.getWatchableObjectFloat(4);
subY = dataWatcher.getWatchableObjectFloat(5);
subZ = dataWatcher.getWatchableObjectFloat(6);
life = dataWatcher.getWatchableObjectInt(7);
} else {
updatePosition();
}
}
} else {
updatePosition();
}
}
protected EntityPlayer getSpawner() {
return spawner;
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass == 1;
}
@Override
protected void readEntityFromNBT(NBTTagCompound tag) {
setDead();
}
@Override
protected void writeEntityToNBT(NBTTagCompound tag) {}
@SideOnly(Side.CLIENT)
private boolean updateRenderTick() {
if(spawner == null || (subX == 0 && subY == 0 && subZ == 0))
return false;
final double maxAccel = 4;
long time = GameTimer.getTime();
long life = time - spawnTime;
//Alpha wiggling
if(lastTime != 0) {
long dt = time - lastTime;
if(rand.nextInt(8) < 3) {
accel = RandUtils.ranged(-maxAccel, maxAccel);
//System.out.println("AccelChange=>" + accel);
}
//System.out.println("AV=>" + alphaVel);
alphaWiggle += accel * dt / 1000.0;
if(alphaWiggle > 1) alphaWiggle = 1;
if(alphaWiggle < 0) alphaWiggle = 0;
}
lastTime = time;
//Texture wiggling
if(rand.nextInt(8) < 2) {
texID = rand.nextInt(MAX_TETXURES);
}
//Surrounding
float phase = life / 300.0f;
offsetX = 0.03 * MathHelper.sin(phase);
offsetZ = 0.03 * MathHelper.cos(phase);
offsetY = 0.04 * MathHelper.cos((float) (phase * 1.4 + Math.PI / 3.5));
updatePosition();
return true;
}
private double getAlpha() {
int lifeMS = life * 50;
long time = GameTimer.getTime();
long dt = time - spawnTime;
final int blendTime = 150;
if(dt > lifeMS - blendTime)
return Math.max(0, MathUtils.lerpf(1, 0, (float) (dt - (lifeMS - blendTime)) / blendTime));
if(dt > lifeMS - burstTime)
return MathUtils.lerp(0.6, 1.0, (double) (dt - (lifeMS - burstTime)) / (burstTime - blendTime));
if(dt < 300)
return MathUtils.lerp(0, 0.6, (double) dt / 300);
return 0.6;
}
private float getSize() {
int lifeMS = life * 50;
long time = GameTimer.getTime();
long dt = time - spawnTime;
if(dt > lifeMS - 100)
return Math.max(0, MathUtils.lerpf(1.5f, 0, (float) (dt - (lifeMS - 100)) / 100));
if(dt > lifeMS - 300)
return MathUtils.lerpf(1, 1.5f, (float) (dt - (lifeMS - 300)) / 200);
return 1;
}
private void updatePosition() {
posX = spawner.posX + subX;
posY = spawner.posY + subY + (worldObj.isRemote ? 0 : 1.6); //Fix for different sides
posZ = spawner.posZ + subZ;
}
@SideOnly(Side.CLIENT)
public static class R extends RenderIcon {
ResourceLocation[] textures;
ResourceLocation glowTexture;
public R() {
super(null);
textures = Resources.getEffectSeq("mdball", MAX_TETXURES);
glowTexture = Resources.getTexture("effects/mdball/glow");
//this.minTolerateAlpha = 0.05f;
this.shadowOpaque = 0;
}
@Override
public void doRender(Entity par1Entity, double x, double y,
double z, float par8, float par9) {
if(RenderUtils.isInShadowPass()) {
return;
}
EntityMdBall ent = (EntityMdBall) par1Entity;
if(!ent.updateRenderTick())
return;
EntityPlayer clientPlayer = Minecraft.getMinecraft().thePlayer;
//HACK: Force set the render pos to prevent glitches
{
x = ent.posX - clientPlayer.posX;
y = ent.posY - clientPlayer.posY;
z = ent.posZ - clientPlayer.posZ;
if(!ACRenderingHelper.isThePlayer(ent.getSpawner()))
y += 1.6;
}
GL11.glPushMatrix();
{
ShaderSimple.instance().useProgram();
GL11.glTranslated(ent.offsetX, ent.offsetY, ent.offsetZ);
double alpha = ent.getAlpha();
float size = ent.getSize();
//Glow texture
this.color.a = alpha * (0.3 + ent.alphaWiggle * 0.7);
this.icon = glowTexture;
this.setSize(0.7f * size);
super.doRender(par1Entity, x, y, z, par8, par9);
//Core
this.color.a = alpha * (0.8 + 0.2 * ent.alphaWiggle);
this.icon = textures[ent.texID];
this.setSize(0.5f * size);
super.doRender(par1Entity, x, y, z, par8, par9);
GL20.glUseProgram(0);
}
GL11.glPopMatrix();
}
}
}