/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.entity.projectile;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockButton;
import net.minecraft.block.BlockLever;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.item.EntityXPOrb;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.BlockPos;
import net.minecraft.util.DamageSource;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.world.World;
import zeldaswordskills.api.block.IBoomerangBlock;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceBaseIndirect;
import zeldaswordskills.item.ZSSItems;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.PlayerUtils;
import zeldaswordskills.util.WorldUtils;
public class EntityBoomerang extends EntityMobThrowable
{
/** Watchable object index for the boomerang ItemStack */
private static final int ITEM_DATAWATCHER_INDEX = 22;
/** Watchable object index for target entity's id */
private static final int TARGET_DATAWATCHER_INDEX = 23;
/** Distance this boomerang has traveled; starts out equal to the maximum range */
private int distance = 12;
/** Ticks allowed before the boomerang falls to the ground if it can't reach its owner */
private static final int LIFESPAN = 100;
/** The original inventory slot occupied by the boomerang item */
private int slot;
/** Whether this boomerang can carry multiple items */
private boolean captureAll = false;
/** ItemStack version of captured drops is more efficient for NBT storage */
private List<ItemStack> capturedItems = new ArrayList<ItemStack>();
/** The amount of xp captured */
private int xp = 0;
public EntityBoomerang(World world) {
super(world);
}
public EntityBoomerang(World world, EntityLivingBase entity) {
super(world, entity);
}
public EntityBoomerang(World world, double x, double y, double z) {
super(world, x, y, z);
}
public EntityBoomerang(World world, EntityLivingBase shooter, EntityLivingBase target, float velocity, float wobble) {
super(world, shooter, target, velocity, wobble);
setTarget(target);
}
@Override
protected void entityInit() {
super.entityInit();
dataWatcher.addObject(TARGET_DATAWATCHER_INDEX, -1);
// data type 5 is ItemStack, as seen in EntityItem's entityInit()
dataWatcher.addObjectByDataType(ITEM_DATAWATCHER_INDEX, 5);
}
/** Sets the boomerang's original itemstack and inventory slot index */
public EntityBoomerang setInvStack(ItemStack stack, int slot) {
dataWatcher.updateObject(ITEM_DATAWATCHER_INDEX, stack);
this.slot = slot;
return this;
}
/** Returns the boomerang itemstack */
public ItemStack getBoomerang() {
return dataWatcher.getWatchableObjectItemStack(ITEM_DATAWATCHER_INDEX);
}
/** Sets the distance this boomerang can travel before it must return */
public EntityBoomerang setRange(int range) {
this.distance = range;
return this;
}
/** Sets this boomerang to capture all dropped items */
public EntityBoomerang setCaptureAll(boolean captureAll) {
this.captureAll = captureAll;
return this;
}
/** Returns the current target, if any */
protected EntityLivingBase getTarget() {
int id = dataWatcher.getWatchableObjectInt(TARGET_DATAWATCHER_INDEX);
return (id > 0 ? (EntityLivingBase) worldObj.getEntityByID(id) : null);
}
/** Sets this the current target */
public void setTarget(EntityLivingBase target) {
dataWatcher.updateObject(TARGET_DATAWATCHER_INDEX, target != null ? target.getEntityId() : -1);
}
/** Returns a boomerang damage source */
protected DamageSource getDamageSource() {
return new DamageSourceBaseIndirect("boomerang", this, getThrower()).setStunDamage(200, 5, true).setProjectile();
}
@Override
protected float getVelocity() {
return 1.0F;
}
@Override
protected float getGravityVelocity() {
return 0.0F;
}
@Override
public void onUpdate() {
--distance;
if (shouldDrop() && !worldObj.isRemote) {
worldObj.spawnEntityInWorld(new EntityItem(worldObj, posX, posY, posZ, getBoomerang()));
dropXpOrbs();
releaseDrops(null);
setDead();
} else {
if (ticksExisted % 4 == 0) {
WorldUtils.playSoundAtEntity(this, Sounds.SWORD_MISS, 0.4F, 0.5F);
}
captureDrops();
captureXpOrbs();
destroyVines();
updateMotion();
super.onUpdate();
}
}
/**
* Whether the boomerang should drop as an item this tick
*/
private boolean shouldDrop() {
return distance < -LIFESPAN || getThrower() == null || !getThrower().isEntityAlive();
}
/**
* Adjusts boomerang's motion
*/
protected void updateMotion() {
if (distance < 0 && getTarget() != getThrower()) {
setTarget(getThrower());
}
EntityLivingBase target = getTarget();
if (target != null) {
double d0 = target.posX - this.posX;
double d1 = target.getEntityBoundingBox().minY + (double)(target.height) - this.posY;
double d2 = target.posZ - this.posZ;
/*
// TODO make the boomerang curve
if (distance > -20) {
float yaw = (float)(Math.atan2(d0, d2) * 180.0D / Math.PI);
float pitch = (float)(Math.atan2(d1, Math.sqrt(d0 * d0 + d2 * d2)) * 180.0D / Math.PI);
float f = 0.4F;
rotationYaw += (yaw - rotationYaw) / 90.0F;
rotationPitch += (pitch - rotationPitch) / 90.0F;
motionX = (double)(-MathHelper.sin(rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(rotationPitch / 180.0F * (float) Math.PI) * f);
motionZ = (double)(MathHelper.cos(rotationYaw / 180.0F * (float) Math.PI) * MathHelper.cos(rotationPitch / 180.0F * (float) Math.PI) * f);
motionY = (double)(-MathHelper.sin(rotationPitch / 180.0F * (float) Math.PI) * f);
setThrowableHeading(motionX, motionY, motionZ, getVelocity(), 0.0F);
} else {
double d = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
double d3 = Math.pow(d, 3);
double k = 0.1D;
motionX += k / d3 * d0;
motionY += k / d3 * d1;
motionZ += k / d3 * d2;
double velocity = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
motionX *= getVelocity() / velocity;
motionY *= getVelocity() / velocity;
motionZ *= getVelocity() / velocity;
}
*/
setThrowableHeading(d0, d1, d2, getVelocity(), 0.0F);
}
}
@Override
public void onCollideWithPlayer(EntityPlayer player) {
if (distance < 0 && !worldObj.isRemote) {
if (player.inventory.getStackInSlot(slot) == null) {
player.inventory.setInventorySlotContents(slot, getBoomerang());
} else {
int i = player.inventory.getFirstEmptyStack();
if (i >= 0) {
player.inventory.setInventorySlotContents(i, player.inventory.getStackInSlot(slot));
player.inventory.setInventorySlotContents(slot, getBoomerang());
} else {
PlayerUtils.addItemToInventory(player, getBoomerang());
}
}
dropXpOrbs();
releaseDrops(player);
setDead();
}
}
@Override
protected void onImpact(MovingObjectPosition mop) {
if (mop.typeOfHit == MovingObjectType.ENTITY) {
if (mop.entityHit != getThrower() && mop.entityHit.attackEntityFrom(getDamageSource(), getDamage())) {
playSound(Sounds.DAMAGE_SUCCESSFUL_HIT, 1.0F, 1.2F / (rand.nextFloat() * 0.2F + 0.9F));
if (mop.entityHit instanceof EntityLivingBase && getThrower() != null) {
EnchantmentHelper.applyThornEnchantments((EntityLivingBase) mop.entityHit, getThrower());
EnchantmentHelper.applyArthropodEnchantments(getThrower(), mop.entityHit);
}
}
} else {
BlockPos pos = mop.getBlockPos();
IBlockState state = worldObj.getBlockState(pos);
Block block = state.getBlock();
boolean flag = block.getMaterial().blocksMovement();
if (block instanceof IBoomerangBlock) {
flag = ((IBoomerangBlock) block).onBoomerangCollided(worldObj, pos, state, this);
} else {
block.onEntityCollidedWithBlock(worldObj, pos, this);
float hardness = block.getBlockHardness(worldObj, pos);
if (block.getMaterial() != Material.air && hardness >= 0.0F && hardness < 0.1F && !worldObj.isRemote) {
worldObj.destroyBlock(pos, true);
} else if (block instanceof BlockButton || (block instanceof BlockLever &&
getBoomerang() != null && getBoomerang().getItem() == ZSSItems.boomerangMagic)) {
WorldUtils.activateButton(worldObj, state, pos);
flag = true;
}
}
if (flag && !noClip) {
noClip = true;
distance = Math.min(distance, 0);
setThrowableHeading(-motionX, -motionY, -motionZ, getVelocity(), 1.0F);
}
}
}
/**
* Attempts to add the item either as the currently riding entity, or as a
* captured drop (in which case the item entity is set to dead)
* @return true if the item was captured in one form or another
*/
public boolean captureItem(EntityItem item) {
if (item.isEntityAlive()) {
if (riddenByEntity == null) {
item.mountEntity(this);
return true;
} else if (captureAll && item != riddenByEntity) {
capturedItems.add(item.getEntityItem());
item.setDead();
return true;
}
}
return false;
}
/**
* Scans for and captures nearby EntityItems
*/
private void captureDrops() {
if (riddenByEntity == null || captureAll) {
List<EntityItem> items = worldObj.getEntitiesWithinAABB(EntityItem.class, getEntityBoundingBox().expand(1.0D, 1.0D, 1.0D));
for (EntityItem item : items) {
if (captureItem(item) && !captureAll) {
return;
}
}
}
}
/**
* Releases all captured drops either into the player's inventory or on the ground, if player is null
*/
private void releaseDrops(EntityPlayer player) {
for (ItemStack stack : capturedItems) {
if (player != null) {
PlayerUtils.addItemToInventory(player, stack);
} else {
WorldUtils.spawnItemWithRandom(worldObj, stack, posX, posY, posZ);
}
}
}
/**
* Scans for and captures nearby XP Orbs
*/
private void captureXpOrbs() {
List<EntityXPOrb> orbs = worldObj.getEntitiesWithinAABB(EntityXPOrb.class, getEntityBoundingBox().expand(1.0D, 1.0D, 1.0D));
for (EntityXPOrb orb : orbs) {
xp += orb.getXpValue();
worldObj.playSoundAtEntity(this, Sounds.XP_ORB, 0.1F, 0.5F * ((rand.nextFloat() - rand.nextFloat()) * 0.7F + 1.8F));
orb.setDead();
}
}
/**
* Drops all captured xp as orbs
*/
private void dropXpOrbs() {
int i = xp;
while (i > 0) {
int j = EntityXPOrb.getXPSplit(i);
i -= j;
worldObj.spawnEntityInWorld(new EntityXPOrb(worldObj, posX, posY, posZ, j));
worldObj.playSoundAtEntity(this, Sounds.XP_ORB, 0.1F, 0.5F * ((rand.nextFloat() - rand.nextFloat()) * 0.7F + 1.8F));
}
}
/**
* Checks for and destroys vines each update tick
*/
private void destroyVines() {
int i = MathHelper.floor_double(posX);
int j = MathHelper.floor_double(posY);
int k = MathHelper.floor_double(posZ);
BlockPos pos = new BlockPos(i, j, k);
if (worldObj.getBlockState(pos).getBlock().getMaterial() == Material.vine) {
worldObj.destroyBlock(pos, true);
}
}
@Override
public void writeEntityToNBT(NBTTagCompound compound) {
super.writeEntityToNBT(compound);
compound.setInteger("distance", distance);
NBTTagCompound item = new NBTTagCompound();
getBoomerang().writeToNBT(item);
compound.setTag("item", item);
compound.setInteger("invSlot", slot);
compound.setInteger("target", getTarget() != null ? getTarget().getEntityId() : -1);
compound.setBoolean("captureAll", captureAll);
if (captureAll) {
NBTTagList items = new NBTTagList();
for (ItemStack stack : capturedItems) {
NBTTagCompound dropNBT = new NBTTagCompound();
stack.writeToNBT(dropNBT);
items.appendTag(dropNBT);
}
compound.setTag("items", items);
}
compound.setInteger("capturedXP", xp);
}
@Override
public void readEntityFromNBT(NBTTagCompound compound) {
super.readEntityFromNBT(compound);
distance = compound.getInteger("distance");
dataWatcher.updateObject(ITEM_DATAWATCHER_INDEX, ItemStack.loadItemStackFromNBT(compound.getCompoundTag("item")));
slot = compound.getInteger("invSlot");
dataWatcher.updateObject(TARGET_DATAWATCHER_INDEX, compound.hasKey("target") ? compound.getInteger("target") : -1);
captureAll = compound.getBoolean("captureAll");
if (captureAll) {
NBTTagList items = compound.getTagList("items", compound.getId());
for (int i = 0; i < items.tagCount(); ++i) {
capturedItems.add(ItemStack.loadItemStackFromNBT((NBTTagCompound) items.getCompoundTagAt(i)));
}
}
xp = compound.getInteger("capturedXP");
}
}