/**
Copyright (C) <2017> <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.item;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentProtection;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.MathHelper;
import net.minecraft.util.StatCollector;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.ZSSMain;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceFireIndirect;
import zeldaswordskills.api.item.ISacredFlame;
import zeldaswordskills.block.BlockSacredFlame;
import zeldaswordskills.creativetab.ZSSCreativeTabs;
import zeldaswordskills.entity.ZSSEntityInfo;
import zeldaswordskills.entity.buff.Buff;
import zeldaswordskills.entity.player.ZSSPlayerInfo;
import zeldaswordskills.network.PacketDispatcher;
import zeldaswordskills.network.client.PacketISpawnParticles;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.PlayerUtils;
import zeldaswordskills.util.WarpPoint;
import zeldaswordskills.util.WorldUtils;
/**
*
* The three Spirit Crystals, i.e. spells, from Ocarina of Time
*
* Each use consumes part of the spirit; when used up entirely, only the base crystal remains.
*
* Din's Fire: AoE centered on player engulfs all nearby targets in flames
* Farore's Wind: Sneak + right-click to mark a location, use to teleport to a marked location in same dimension
* Nayru's Love: Become impervious to damage until magic is depleted or certain amount of time passes;
* constantly drains magic points and prevents the use of other magic skills
*
*/
public class ItemSpiritCrystal extends BaseModItem implements ISacredFlame, ISpawnParticles
{
/** The spirit's id, from BlockSacredFlame */
private final BlockSacredFlame.EnumType spiritType;
/** Cost (in damage) for each use of this item*/
private final int costToUse;
/** Amount of time required before the crystal's effects activate */
private final int timeToUse;
public ItemSpiritCrystal(BlockSacredFlame.EnumType flame, int cost, int time) {
super();
spiritType = flame;
costToUse = cost;
timeToUse = time;
setMaxDamage(128);
setMaxStackSize(1);
setCreativeTab(ZSSCreativeTabs.tabTools);
}
@Override
public int getMaxItemUseDuration(ItemStack stack) {
return timeToUse;
}
@Override
public EnumAction getItemUseAction(ItemStack stack) {
return EnumAction.BLOCK;
}
@Override
public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity entity) {
return true;
}
@Override
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
if (!ZSSPlayerInfo.get(player).canUseMagic()) {
player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F);
return stack;
}
int cost = 0;
if (spiritType == BlockSacredFlame.EnumType.FARORE && player.isSneaking()) {
if (ZSSPlayerInfo.get(player).useMagic(2.0F)) { // magic_cost / 5
mark(stack, world, player);
cost = 1;
} else {
player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F);
}
} else if (spiritType == BlockSacredFlame.EnumType.NAYRU) {
cost = handleNayru(stack, world, player);
} else {
player.setItemInUse(stack, getMaxItemUseDuration(stack));
String sound = (spiritType == BlockSacredFlame.EnumType.DIN ? Sounds.SUCCESS_MAGIC : Sounds.FLAME_ABSORB);
player.playSound(sound, 1.0F, 1.0F);
}
if (damageStack(stack, player, cost)) {
stack = new ItemStack(ZSSItems.crystalSpirit);
}
return stack;
}
@Override
public ItemStack onItemUseFinish(ItemStack stack, World world, EntityPlayer player) {
int cost = 0;
switch(spiritType) {
case DIN: cost = handleDin(stack, world, player); break;
case FARORE: cost = handleFarore(stack, world, player); break;
case NAYRU: break;
default: ZSSMain.logger.warn("Invalid spirit type " + spiritType + " while using spirit crystal");
}
if (damageStack(stack, player, cost)) {
return new ItemStack(ZSSItems.crystalSpirit);
}
return stack;
}
@Override
public boolean onActivatedSacredFlame(ItemStack stack, World world, EntityPlayer player, BlockSacredFlame.EnumType flame, boolean isActive) {
return false;
}
@Override
public boolean onClickedSacredFlame(ItemStack stack, World world, EntityPlayer player, BlockSacredFlame.EnumType flame, boolean isActive) {
if (world.isRemote) {
return false;
} else if (stack.getItemDamage() == 0) {
PlayerUtils.sendTranslatedChat(player, "chat.zss.spirit_crystal.sacred_flame.full");
} else if (isActive) {
if (spiritType == flame) {
int originalDamage = stack.getItemDamage();
stack.setItemDamage(0);
world.playSoundAtEntity(player, Sounds.SUCCESS_MAGIC, 1.0F, 1.0F);
return (world.rand.nextInt(stack.getMaxDamage()) < originalDamage);
} else {
PlayerUtils.sendTranslatedChat(player, "chat.zss.spirit_crystal.sacred_flame.mismatch");
}
} else {
PlayerUtils.sendTranslatedChat(player, "chat.zss.sacred_flame.inactive");
}
return false;
}
/**
* Damages the stack for amount, returning true if the stack size is zero
*/
private boolean damageStack(ItemStack stack, EntityPlayer player, int amount) {
if (amount > 0) {
stack.damageItem(amount, player);
return stack.stackSize == 0;
}
return false;
}
/**
* Returns true if there is enough charge remaining to use the item
*/
private boolean canUse(ItemStack stack) {
return (stack.getMaxDamage() - stack.getItemDamage() >= costToUse);
}
/**
* Processes right-click for Din's Fire; returns amount of damage to apply to stack
*/
private int handleDin(ItemStack stack, World world, EntityPlayer player) {
if (!ZSSPlayerInfo.get(player).useMagic(40.0F)) {
player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F);
return 0;
}
float radius = 5.0F;
if (!world.isRemote) {
PacketDispatcher.sendToAllAround(new PacketISpawnParticles(player, radius), player, 64.0D);
affectDinBlocks(world, player, radius);
}
affectDinEntities(world, player, radius);
world.playSoundAtEntity(player, Sounds.EXPLOSION, 4.0F, (1.0F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.2F) * 0.7F);
return costToUse;
}
/**
* Affects all blocks in the radius with the effects of Din's Fire
*/
private void affectDinBlocks(World world, EntityPlayer player, float radius) {
List<BlockPos> affectedBlockPositions = new ArrayList<BlockPos>(WorldUtils.getAffectedBlocksList(world, world.rand, radius, player.posX, player.posY, player.posZ, null));
Block block;
BlockPos pos;
Iterator<BlockPos> iterator = affectedBlockPositions.iterator();
while (iterator.hasNext()) {
pos = iterator.next();
block = world.getBlockState(pos).getBlock();
if (block.getMaterial() == Material.air && Config.isDinIgniteEnabled()) {
Block block1 = world.getBlockState(pos.down()).getBlock();
if (block1.isFullBlock() && world.rand.nextInt(8) == 0) {
world.setBlockState(pos, Blocks.fire.getDefaultState());
}
} else if (WorldUtils.canMeltBlock(world, block, pos)) {
world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D,
Sounds.FIRE_FIZZ, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
world.setBlockToAir(pos);
}
}
}
/**
* Affects all entities within the radius with the effects of Din's Fire
*/
private void affectDinEntities(World world, EntityPlayer player, float radius) {
List<Entity> list = world.getEntitiesWithinAABBExcludingEntity(player, player.getEntityBoundingBox().expand(radius, radius / 2F, radius));
Vec3 vec3 = new Vec3(player.posX, player.posY, player.posZ);
for (int k2 = 0; k2 < list.size(); ++k2) {
Entity entity = list.get(k2);
double d0 = entity.posX - player.posX;
double d1 = entity.posY + (double) entity.getEyeHeight() - player.posY;
double d2 = entity.posZ - player.posZ;
double d8 = (double) MathHelper.sqrt_double(d0 * d0 + d1 * d1 + d2 * d2);
if (d8 != 0.0D) {
d0 /= d8;
d1 /= d8;
d2 /= d8;
double d10 = (double) world.getBlockDensity(vec3, entity.getEntityBoundingBox());
float amount = 32.0F * (float) d10;
if (entity.isImmuneToFire()) {
amount *= 0.25F;
}
if (entity.attackEntityFrom(new DamageSourceFireIndirect("magic.din", player, player, true).setMagicDamage(), amount) && !entity.isImmuneToFire()) {
if (world.rand.nextFloat() < d10) {
entity.setFire(10);
}
}
double d11 = EnchantmentProtection.func_92092_a(entity, d10);
entity.motionX += d0 * d11;
entity.motionY += d1 * d11;
entity.motionZ += d2 * d11;
}
}
}
@Override
@SideOnly(Side.CLIENT)
public void spawnParticles(World world, EntityPlayer player, ItemStack stack, double posX, double posY, double posZ, float radius) {
int i1 = MathHelper.floor_double(posX + radius);
int j1 = MathHelper.floor_double(posY + radius);
int k1 = MathHelper.floor_double(posZ + radius);
for (int i = MathHelper.floor_double(posX - radius); i < i1; ++i) {
for (int j = MathHelper.floor_double(posY - radius + 1.0D); j < j1; ++j) {
for (int k = MathHelper.floor_double(posZ - radius); k < k1; ++k) {
double d0 = (double)((float) i + world.rand.nextFloat());
double d1 = (double)((float) j + world.rand.nextFloat());
double d2 = (double)((float) k + world.rand.nextFloat());
double d3 = d0 - posX;
double d4 = d1 - posY;
double d5 = d2 - posZ;
double d6 = (double) MathHelper.sqrt_double(d3 * d3 + d4 * d4 + d5 * d5);
d3 /= d6;
d4 /= d6;
d5 /= d6;
double d7 = 0.5D / (d6 / (double) radius + 0.1D);
d7 *= (double)(world.rand.nextFloat() * world.rand.nextFloat() + 0.3F);
d3 *= d7;
d4 *= d7;
d5 *= d7;
world.spawnParticle(EnumParticleTypes.FLAME, (d0 + posX * 1.0D) / 2.0D, (d1 + posY * 1.0D) / 2.0D, (d2 + posZ * 1.0D) / 2.0D, d3, d4, d5);
world.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, d0, d1, d2, d3, d4, d5);
}
}
}
}
/**
* Processes right-click for Farore's Wind; returns amount of damage to apply to stack
*/
private int handleFarore(ItemStack stack, World world, EntityPlayer player) {
String fail = null;
if (canUse(stack)) {
WarpPoint warp = recall(stack);
if (warp == null) {
fail = "chat.zss.spirit_crystal.farore.fail.mark";
} else if (warp.dimensionId != player.worldObj.provider.getDimensionId()) {
fail = "chat.zss.spirit_crystal.farore.fail.dimension";
} else if (ZSSPlayerInfo.get(player).useMagic(10.0F)) {
player.setPositionAndUpdate(warp.pos.getX() + 0.5D, warp.pos.getY() + 0.5D, warp.pos.getZ() + 0.5D);
player.playSound(Sounds.SUCCESS_MAGIC, 1.0F, 1.0F);
return costToUse;
} // else 'magic fail' sound played below
}
player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F);
if (fail != null && !world.isRemote) {
PlayerUtils.sendTranslatedChat(player, fail);
}
return 0;
}
/**
* Processes right-click for Nayru's Love; returns amount of damage to apply to stack
*/
private int handleNayru(ItemStack stack, World world, EntityPlayer player) {
if (!Config.allowUnlimitedNayru() && ZSSEntityInfo.get(player).isBuffActive(Buff.UNLIMITED_MAGIC)) {
player.playSound(Sounds.MAGIC_FAIL, 1.0F, 1.0F);
if (!world.isRemote) {
PlayerUtils.sendTranslatedChat(player, "chat.zss.spirit_crystal.nayru.unlimited.fail");
}
} else if (canUse(stack) && ZSSPlayerInfo.get(player).useMagic(25.0F)) {
ZSSPlayerInfo.get(player).activateNayru();
world.playSoundAtEntity(player, Sounds.SUCCESS_MAGIC, 1.0F, 1.0F);
return costToUse;
}
return 0;
}
/**
* Saves the player's current position and dimension for Farore's Wind
*/
private void mark(ItemStack stack, World world, EntityPlayer player) {
if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); }
WarpPoint warp = new WarpPoint(world.provider.getDimensionId(), new BlockPos(player));
stack.getTagCompound().setTag("zssRecallPoint", warp.writeToNBT());
world.playSoundAtEntity(player, Sounds.SUCCESS_MAGIC, 1.0F, 1.0F);
}
/**
* Returns the saved warp coordinates for Farore's Wind
*/
private WarpPoint recall(ItemStack stack) {
if (stack.hasTagCompound() && stack.getTagCompound().hasKey("zssRecallPoint", Constants.NBT.TAG_COMPOUND)) {
return WarpPoint.readFromNBT(stack.getTagCompound().getCompoundTag("zssRecallPoint"));
}
// for backwards compatibility:
else if (stack.hasTagCompound() && stack.getTagCompound().hasKey("zssFWdimension")) {
int dimension = stack.getTagCompound().getInteger("zssFWdimension");
double x = stack.getTagCompound().getDouble("zssFWposX");
double y = stack.getTagCompound().getDouble("zssFWposY");
double z = stack.getTagCompound().getDouble("zssFWposZ");
return new WarpPoint(dimension, new BlockPos(x, y, z));
}
return null;
}
@Override
@SideOnly(Side.CLIENT)
public boolean hasEffect(ItemStack stack) {
return true;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, EntityPlayer player, List<String> list, boolean advanced) {
list.add(EnumChatFormatting.ITALIC + StatCollector.translateToLocal("tooltip." + getUnlocalizedName().substring(5) + ".desc.0"));
list.add(EnumChatFormatting.ITALIC + StatCollector.translateToLocal("tooltip." + getUnlocalizedName().substring(5) + ".desc.1"));
}
}