package com.flansmod.common.guns;
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.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.BlockPos;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSourceIndirect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.ByteBufUtils;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import com.flansmod.client.FlansModClient;
import com.flansmod.common.FlansMod;
import com.flansmod.common.FlansModExplosion;
import com.flansmod.common.PlayerHandler;
import com.flansmod.common.RotatedAxes;
import com.flansmod.common.driveables.EntityDriveable;
import com.flansmod.common.network.PacketFlak;
import com.flansmod.common.network.PacketPlaySound;
import com.flansmod.common.teams.ItemTeamArmour;
import com.flansmod.common.teams.Team;
import com.flansmod.common.teams.TeamsManager;
import com.flansmod.common.types.InfoType;
import com.flansmod.common.vector.Vector3f;
public class EntityGrenade extends EntityShootable implements IEntityAdditionalSpawnData
{
public GrenadeType type;
/** The entity that threw them */
public EntityLivingBase thrower;
/** This is to avoid players grenades teamkilling after they switch team */
public Team teamOfThrower;
/** Yeah, I want my grenades to have fancy physics */
public RotatedAxes axes = new RotatedAxes();
public Vector3f angularVelocity = new Vector3f(0F, 0F, 0F);
public float prevRotationRoll = 0F;
/** Set to the smoke amount when the grenade detonates and decremented every tick after that */
public int smokeTime = 0;
/** Set to true when smoke grenade detonates */
public boolean smoking = false;
/** Set to true when a sticky grenade sticks. Impedes further movement */
public boolean stuck = false;
/** Stores the position of the block this grenade is stuck to. Used to determine when to unstick */
public int stuckToX, stuckToY, stuckToZ;
/** Stop repeat detonations */
public boolean detonated = false;
/** For deployable bags */
public int numUsesRemaining = 0;
public EntityGrenade(World w)
{
super(w);
}
public EntityGrenade(World w, GrenadeType g, EntityLivingBase t)
{
this(w);
setPosition(t.posX, t.posY + t.getEyeHeight(), t.posZ);
type = g;
numUsesRemaining = type.numUses;
thrower = t;
if(thrower instanceof EntityPlayer && PlayerHandler.getPlayerData((EntityPlayer)thrower) != null)
{
teamOfThrower = PlayerHandler.getPlayerData((EntityPlayer)thrower).team;
}
setSize(g.hitBoxSize, g.hitBoxSize);
//Set the grenade to be facing the way the player is looking
axes.setAngles(t.rotationYaw + 90F, g.spinWhenThrown ? t.rotationPitch : 0F, 0F);
rotationYaw = prevRotationYaw = g.spinWhenThrown ? t.rotationYaw + 90F : 0F;
rotationPitch = prevRotationPitch = t.rotationPitch;
//Give the grenade velocity in the direction the player is looking
float speed = 0.5F * type.throwSpeed;
motionX = axes.getXAxis().x * speed;
motionY = axes.getXAxis().y * speed;
motionZ = axes.getXAxis().z * speed;
if(type.spinWhenThrown)
angularVelocity = new Vector3f(0F, 0F, 10F);
if(type.throwSound != null)
PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.throwSound, true);
}
@Override
public void onUpdate()
{
super.onUpdate();
//Quiet despawning
if(type.despawnTime > 0 && ticksExisted > type.despawnTime)
{
detonated = true;
setDead();
return;
}
//Visuals
if(worldObj.isRemote)
{
if(type.trailParticles)
{
double dX = (posX - prevPosX) / 10;
double dY = (posY - prevPosY) / 10;
double dZ = (posZ - prevPosZ) / 10;
for (int i = 0; i < 10; i++)
{
EntityFX particle = FlansModClient.getParticle(type.trailParticleType, worldObj, prevPosX + dX * i, prevPosY + dY * i, prevPosZ + dZ * i);
if(particle != null && Minecraft.getMinecraft().gameSettings.fancyGraphics)
particle.renderDistanceWeight = 100D;
//worldObj.spawnEntityInWorld(particle);
}
}
}
//Smoke
if(smoking)
{
//Send flak packet to spawn particles
FlansMod.getPacketHandler().sendToAllAround(new PacketFlak(posX, posY, posZ, 50, type.smokeParticleType), posX, posY, posZ, 30, dimension);
//
List list = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, getEntityBoundingBox().expand(type.smokeRadius, type.smokeRadius, type.smokeRadius));
for(Object obj : list)
{
EntityLivingBase entity = ((EntityLivingBase)obj);
if(entity.getDistanceToEntity(this) < type.smokeRadius)
{
//Do some checks first
boolean smokeThem = true;
for(int i = 0; i < 5; i++)
{
//If any currently equipped item has smoke protection (gas masks), stop the effects
ItemStack stack = entity.getEquipmentInSlot(i);
if(stack != null && stack.getItem() instanceof ItemTeamArmour)
{
if(((ItemTeamArmour)stack.getItem()).type.smokeProtection)
smokeThem = false;
}
}
if(smokeThem)
for(PotionEffect effect : type.smokeEffects)
entity.addPotionEffect(new PotionEffect(effect));
}
}
smokeTime--;
if(smokeTime == 0)
setDead();
}
//Detonation conditions
if(!worldObj.isRemote)
{
if(ticksExisted > type.fuse && type.fuse > 0)
detonate();
//If this grenade has a proximity trigger, check for living entities within it's range
if(type.livingProximityTrigger > 0 || type.driveableProximityTrigger > 0)
{
float checkRadius = Math.max(type.livingProximityTrigger, type.driveableProximityTrigger);
List list = worldObj.getEntitiesWithinAABBExcludingEntity(this, getEntityBoundingBox().expand(checkRadius, checkRadius, checkRadius));
for(Object obj : list)
{
if(obj == thrower && ticksExisted < 10)
continue;
if(obj instanceof EntityLivingBase && getDistanceToEntity((Entity)obj) < type.livingProximityTrigger)
{
//If we are in a gametype and both thrower and triggerer are playing, check for friendly fire
if(TeamsManager.getInstance() != null && TeamsManager.getInstance().currentRound != null && obj instanceof EntityPlayerMP && thrower instanceof EntityPlayer)
{
if(!TeamsManager.getInstance().currentRound.gametype.playerAttacked((EntityPlayerMP)obj, new EntityDamageSourceGun(type.shortName, this, (EntityPlayer)thrower, type, false)))
continue;
}
if(type.damageToTriggerer > 0)
((EntityLivingBase)obj).attackEntityFrom(getGrenadeDamage(), type.damageToTriggerer);
detonate();
break;
}
if(obj instanceof EntityDriveable && getDistanceToEntity((Entity)obj) < type.driveableProximityTrigger)
{
if(type.damageToTriggerer > 0)
((EntityDriveable)obj).attackEntityFrom(getGrenadeDamage(), type.damageToTriggerer);
detonate();
break;
}
}
}
}
//If the block we were stuck to is gone, unstick
if(stuck && worldObj.isAirBlock(new BlockPos(stuckToX, stuckToY, stuckToZ)))
stuck = false;
//Physics and motion (Don't move if stuck)
if(!stuck && !type.stickToThrower)
{
prevRotationYaw = axes.getYaw();
prevRotationPitch = axes.getPitch();
prevRotationRoll = axes.getRoll();
if(angularVelocity.lengthSquared() > 0.00000001F)
axes.rotateLocal(angularVelocity.length(), angularVelocity.normalise(null));
Vector3f posVec = new Vector3f(posX, posY, posZ);
Vector3f motVec = new Vector3f(motionX, motionY, motionZ);
Vector3f nextPosVec = Vector3f.add(posVec, motVec, null);
//Raytrace the motion of this grenade
MovingObjectPosition hit = worldObj.rayTraceBlocks(posVec.toVec3(), nextPosVec.toVec3());
//If we hit block
if(hit != null && hit.typeOfHit == MovingObjectType.BLOCK)
{
//Get the blockID and block material
Block block = worldObj.getBlockState(hit.getBlockPos()).getBlock();
Material mat = block.getMaterial();
//If this grenade detonates on impact, do so
if(type.explodeOnImpact)
detonate();
//If we hit glass and can break it, do so
else if(type.breaksGlass && mat == Material.glass && TeamsManager.canBreakGlass)
{
worldObj.setBlockToAir(hit.getBlockPos());
FlansMod.proxy.playBlockBreakSound(hit.getBlockPos().getX(), hit.getBlockPos().getY(), hit.getBlockPos().getZ(), block);
}
//If this grenade does not penetrate blocks, hit the block instead
//The grenade cannot bounce if it detonated on impact, so hence the "else" condition
else if(!type.penetratesBlocks)
{
Vector3f hitVec = new Vector3f(hit.hitVec);
//Motion of the grenade pre-hit
Vector3f preHitMotVec = Vector3f.sub(hitVec, posVec, null);
//Motion of the grenade post-hit
Vector3f postHitMotVec = Vector3f.sub(motVec, preHitMotVec, null);
//Reflect postHitMotVec based on side hit
EnumFacing sideHit = hit.sideHit;
switch(sideHit)
{
case UP : case DOWN : postHitMotVec.setY(-postHitMotVec.getY()); break;
case EAST : case WEST : postHitMotVec.setX(-postHitMotVec.getX()); break;
case NORTH : case SOUTH : postHitMotVec.setZ(-postHitMotVec.getZ()); break;
//TODO : Check the compass directions are correct
}
//Calculate the time interval spent post reflection
float lambda = Math.abs(motVec.lengthSquared()) < 0.00000001F ? 1F : postHitMotVec.length() / motVec.length();
//Scale the post hit motion by the bounciness of the grenade
postHitMotVec.scale(type.bounciness / 2);
//Move the grenade along the new path including reflection
posX += preHitMotVec.x + postHitMotVec.x;
posY += preHitMotVec.y + postHitMotVec.y;
posZ += preHitMotVec.z + postHitMotVec.z;
//Set the motion
motionX = postHitMotVec.x / lambda;
motionY = postHitMotVec.y / lambda;
motionZ = postHitMotVec.z / lambda;
//Reset the motion vector
motVec = new Vector3f(motionX, motionY, motionZ);
//Give it a random spin
float randomSpinner = 90F;
Vector3f.add(angularVelocity, new Vector3f(rand.nextGaussian() * randomSpinner, rand.nextGaussian() * randomSpinner, rand.nextGaussian() * randomSpinner), angularVelocity);
//Slow the spin based on the motion
angularVelocity.scale(motVec.lengthSquared());
//Play the bounce sound
if(motVec.lengthSquared() > 0.01D)
playSound(type.bounceSound, 1.0F, 1.2F / (this.rand.nextFloat() * 0.2F + 0.9F));
//If this grenade is sticky, stick it to the block
if(type.sticky)
{
//Move the grenade to the point of contact
posX = hitVec.x;
posY = hitVec.y;
posZ = hitVec.z;
//Stop all motion of the grenade
motionX = motionY = motionZ = 0;
angularVelocity.set(0F, 0F, 0F);
float yaw = axes.getYaw();
switch(hit.sideHit)
{
case DOWN : axes.setAngles(yaw, 180F, 0F); break;
case UP : axes.setAngles(yaw, 0F, 0F); break;
case NORTH : axes.setAngles(270F, 90F, 0F); axes.rotateLocalYaw(yaw); break;
case SOUTH : axes.setAngles(90F, 90F, 0F); axes.rotateLocalYaw(yaw); break;
case WEST : axes.setAngles(180F, 90F, 0F); axes.rotateLocalYaw(yaw); break;
case EAST : axes.setAngles(0F, 90F, 0F); axes.rotateLocalYaw(yaw); break;
}
//Set the stuck flag on
stuck = true;
stuckToX = hit.getBlockPos().getX();
stuckToY = hit.getBlockPos().getY();
stuckToZ = hit.getBlockPos().getZ();
}
}
}
//We didn't hit a block, continue as normal
else
{
posX += motionX;
posY += motionY;
posZ += motionZ;
}
//Update the grenade position
setPosition(posX, posY, posZ);
}
if(type.stickToThrower)
{
if(thrower == null || thrower.isDead)
setDead();
else setPosition(thrower.posX, thrower.posY, thrower.posZ);
}
//If throwing this grenade at an entity should hurt them, this bit checks for entities in the way and does so
//(Don't attack entities when stuck to stuff)
if(type.damageVsLiving > 0 && !stuck)
{
Vector3f motVec = new Vector3f(motionX, motionY, motionZ);
List list = worldObj.getEntitiesWithinAABBExcludingEntity(this, getEntityBoundingBox());
for(Object obj : list)
{
if(obj == thrower && ticksExisted < 10 || motVec.lengthSquared() < 0.01D)
continue;
if(obj instanceof EntityLivingBase)
((EntityLivingBase)obj).attackEntityFrom(getGrenadeDamage(), type.damageVsLiving * motVec.lengthSquared() * 3);
}
}
//Apply gravity
motionY -= 9.81D / 400D * type.fallSpeed;
//Temporary fire glitch fix
if(worldObj.isRemote)
extinguish();
}
@Override
public boolean attackEntityFrom(DamageSource source, float f)
{
if(type.detonateWhenShot)
detonate();
return type.detonateWhenShot;
}
public void detonate()
{
//Do not detonate before grenade is primed
if(ticksExisted < type.primeDelay)
return;
//Stop repeat detonations
if(detonated)
return;
detonated = true;
//Play detonate sound
PacketPlaySound.sendSoundPacket(posX, posY, posZ, FlansMod.soundRange, dimension, type.detonateSound, true);
//Explode
if(!worldObj.isRemote && type.explosionRadius > 0.1F)
{
if((thrower instanceof EntityPlayer))
new FlansModExplosion(worldObj, this, (EntityPlayer)thrower, type, posX, posY, posZ, type.explosionRadius, type.fireRadius > 0, type.smokeRadius > 0, type.explosionBreaksBlocks);
else
worldObj.createExplosion(this, posX, posY, posZ, type.explosionRadius, TeamsManager.explosions && type.explosionBreaksBlocks);
}
//Make fire
if(type.fireRadius > 0.1F)
{
for(float i = -type.fireRadius; i < type.fireRadius; i++)
{
for(float j = -type.fireRadius; j < type.fireRadius; j++)
{
for(float k = -type.fireRadius; k < type.fireRadius; k++)
{
int x = MathHelper.floor_double(i + posX);
int y = MathHelper.floor_double(j + posY);
int z = MathHelper.floor_double(k + posZ);
if(i * i + j * j + k * k <= type.fireRadius * type.fireRadius && worldObj.getBlockState(new BlockPos(x, y, z)).getBlock() == Blocks.air && rand.nextBoolean())
{
/*
if(!worldObj.getBlockState(new BlockPos(x + 1, y, z)).getBlock(). || !worldObj.isAirBlock(new BlockPos(x - 1, y, z))
|| !worldObj.isAirBlock(new BlockPos(x, y + 1, z)) || !worldObj.isAirBlock(new BlockPos(x, y - 1, z))
|| !worldObj.isAirBlock(new BlockPos(x, y, z + 1)) || !worldObj.isAirBlock(new BlockPos(x, y, z - 1)))
*/
{
worldObj.setBlockState(new BlockPos(x, y, z), Blocks.fire.getDefaultState(), 2);
worldObj.markBlockForUpdate(new BlockPos(x, y, z));
}
}
}
}
}
}
//Make explosion particles
if(worldObj.isRemote)
{
for(int i = 0; i < type.explodeParticles; i++)
{
worldObj.spawnParticle(FlansModClient.getParticleType(type.explodeParticleType), posX, posY, posZ, rand.nextGaussian(), rand.nextGaussian(), rand.nextGaussian());
}
}
//Drop item upon detonation, after explosions and whatnot
if(!worldObj.isRemote && type.dropItemOnDetonate != null)
{
String itemName = type.dropItemOnDetonate;
int damage = 0;
if (itemName.contains("."))
{
damage = Integer.parseInt(itemName.split("\\.")[1]);
itemName = itemName.split("\\.")[0];
}
ItemStack dropStack = InfoType.getRecipeElement(itemName, damage);
entityDropItem(dropStack, 1.0F);
}
//Start smoke counter
if(type.smokeTime > 0)
{
smoking = true;
smokeTime = type.smokeTime;
}
else
{
setDead();
}
}
@Override
public void setPositionAndRotation2(double x, double y, double z, float yaw, float pitch, int i, boolean b)
{
}
private DamageSource getGrenadeDamage()
{
if(thrower instanceof EntityPlayer)
return (new EntityDamageSourceGun(type.shortName, this, (EntityPlayer)thrower, type, false)).setProjectile();
else return (new EntityDamageSourceIndirect(type.shortName, this, thrower)).setProjectile();
}
@Override
protected void entityInit()
{
}
@Override
protected void readEntityFromNBT(NBTTagCompound tags)
{
type = GrenadeType.getGrenade(tags.getString("Type"));
thrower = worldObj.getPlayerEntityByName(tags.getString("Thrower"));
rotationYaw = tags.getFloat("RotationYaw");
rotationPitch = tags.getFloat("RotationPitch");
axes.setAngles(rotationYaw, rotationPitch, 0F);
}
@Override
protected void writeEntityToNBT(NBTTagCompound tags)
{
if(type == null)
setDead();
else
{
tags.setString("Type", type.shortName);
if(thrower != null)
tags.setString("Thrower", thrower.getName());
tags.setFloat("RotationYaw", axes.getYaw());
tags.setFloat("RotationPitch", axes.getPitch());
}
}
@Override
public void writeSpawnData(ByteBuf data)
{
ByteBufUtils.writeUTF8String(data, type.shortName);
data.writeInt(thrower == null ? 0 : thrower.getEntityId());
data.writeFloat(axes.getYaw());
data.writeFloat(axes.getPitch());
}
@Override
public void readSpawnData(ByteBuf data)
{
type = GrenadeType.getGrenade(ByteBufUtils.readUTF8String(data));
thrower = (EntityLivingBase)worldObj.getEntityByID(data.readInt());
setRotation(data.readFloat(), data.readFloat());
prevRotationYaw = rotationYaw;
prevRotationPitch = rotationPitch;
axes.setAngles(rotationYaw, rotationPitch, 0F);
if(type.spinWhenThrown)
angularVelocity = new Vector3f(0F, 0F, 10F);
}
@Override
public boolean isBurning()
{
return false;
}
@Override
public boolean canBeCollidedWith()
{
return !isDead && type.isDeployableBag;
}
@Override
public boolean interactFirst(EntityPlayer player)
{
// Player right clicked on grenade
//For deployable bags, give player rewards
if(type.isDeployableBag && !worldObj.isRemote)
{
boolean used = false;
//Handle healing
if(type.healAmount > 0 && player.getHealth() < player.getMaxHealth())
{
player.heal(type.healAmount);
FlansMod.getPacketHandler().sendToAllAround(new PacketFlak(player.posX, player.posY, player.posZ, 5, "heart"), new NetworkRegistry.TargetPoint(player.dimension, player.posX, player.posY, player.posZ, 50F));
used = true;
}
//Handle potion effects
for(PotionEffect effect : type.potionEffects)
{
player.addPotionEffect(new PotionEffect(effect));
used = true;
}
//Handle ammo
if(type.numClips > 0 && player.getCurrentEquippedItem() != null && player.getCurrentEquippedItem().getItem() instanceof ItemGun)
{
GunType gun = ((ItemGun)player.getCurrentEquippedItem().getItem()).GetType();
if(gun.ammo.size() > 0)
{
ShootableType bulletToGive = gun.ammo.get(0);
int numToGive = Math.min(bulletToGive.maxStackSize, type.numClips * gun.numAmmoItemsInGun);
if(player.inventory.addItemStackToInventory(new ItemStack(bulletToGive.item, numToGive)))
{
used = true;
}
}
}
//If the bag is all used up, get rid of it
if(used)
{
numUsesRemaining--;
if(numUsesRemaining <= 0)
setDead();
}
}
return true;
}
}