/**
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.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import mods.battlegear2.api.IAllowItem;
import mods.battlegear2.api.ISheathed;
import mods.battlegear2.api.quiver.IArrowFireHandler;
import mods.battlegear2.api.quiver.ISpecialBow;
import mods.battlegear2.api.quiver.QuiverArrowRegistry;
import mods.battlegear2.api.quiver.QuiverArrowRegistry.DefaultArrowFire;
import mods.battlegear2.enchantments.BaseEnchantment;
import mods.battlegear2.items.ItemQuiver;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.passive.EntityVillager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.StatCollector;
import net.minecraft.village.MerchantRecipe;
import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.World;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.ArrowLooseEvent;
import net.minecraftforge.event.entity.player.ArrowNockEvent;
import net.minecraftforge.fml.common.Optional;
import net.minecraftforge.fml.common.Optional.Method;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.ZSSAchievements;
import zeldaswordskills.ZSSMain;
import zeldaswordskills.api.entity.BombType;
import zeldaswordskills.api.item.ArmorIndex;
import zeldaswordskills.api.item.IFairyUpgrade;
import zeldaswordskills.api.item.IMagicArrow;
import zeldaswordskills.api.item.IUnenchantable;
import zeldaswordskills.api.item.IZoom;
import zeldaswordskills.block.tileentity.TileEntityDungeonCore;
import zeldaswordskills.creativetab.ZSSCreativeTabs;
import zeldaswordskills.entity.player.ZSSPlayerInfo;
import zeldaswordskills.entity.projectile.EntityArrowBomb;
import zeldaswordskills.entity.projectile.EntityArrowCustom;
import zeldaswordskills.entity.projectile.EntityArrowElemental;
import zeldaswordskills.entity.projectile.EntityArrowElemental.ElementType;
import zeldaswordskills.handler.BattlegearEvents;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.ModInfo;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.MerchantRecipeHelper;
import zeldaswordskills.util.PlayerUtils;
import zeldaswordskills.util.TargetUtils;
/**
*
* The Hero's Bow can fire arrows even when the player has none if it has the infinity
* enchantment. It is also capable of firing different types of arrows, depending on
* its level. Standard (level 1) can fire any bomb arrow; level 2 can shoot fire and
* ice arrows; level 3 can shoot all arrow types
*
*/
@Optional.InterfaceList(value={
@Optional.Interface(iface="mods.battlegear2.api.IAllowItem", modid="battlegear2", striprefs=true),
@Optional.Interface(iface="mods.battlegear2.api.ISheathed", modid="battlegear2", striprefs=true),
@Optional.Interface(iface="mods.battlegear2.api.quiver.ISpecialBow", modid="battlegear2", striprefs=true)
})
public class ItemHeroBow extends ItemBow implements ICyclableItem, IFairyUpgrade, IModItem, IUnenchantable, IZoom,
IAllowItem, ISheathed, ISpecialBow
{
public static enum Mode {
/** Default Hero Bow behavior searches for the first usable arrow of any kind */
DEFAULT("", 0),
STANDARD("minecraft:arrow", 0),
BOMB_STANDARD(ModInfo.ID + ":arrow_bomb", 1),
BOMB_FIRE(ModInfo.ID + ":arrow_bomb_fire", 1),
BOMB_WATER(ModInfo.ID + ":arrow_bomb_water", 1),
MAGIC_FIRE(ModInfo.ID + ":arrow_fire", 2),
MAGIC_ICE(ModInfo.ID + ":arrow_ice", 2),
MAGIC_LIGHT(ModInfo.ID + ":arrow_light", 3);
private final String arrowName;
private Item arrowItem;
private final int level;
private Mode(String arrowName, int level) {
this.arrowName = arrowName;
this.level = level;
}
/**
* Returns the arrow item required for this mode
*/
public Item getArrowItem() {
if (arrowItem == null && arrowName.length() > 0) {
String[] parts = arrowName.split(":");
arrowItem = GameRegistry.findItem(parts[0], parts[1]);
}
return arrowItem;
}
/**
* Returns the next Mode by ordinal position
*/
public Mode next() {
return Mode.values()[(ordinal() + 1) % Mode.values().length];
}
/**
* Returns the next mode whose level does not exceed that given
*/
public Mode next(int level) {
return cycle(level, true);
}
/**
* Returns the previous Mode by ordinal position
*/
public Mode prev() {
return Mode.values()[((ordinal() == 0 ? Mode.values().length : ordinal()) - 1) % Mode.values().length];
}
/**
* Returns the previous mode whose level does not exceed that given
*/
public Mode prev(int level) {
return cycle(level, false);
}
private Mode cycle(int level, boolean next) {
Mode mode = this;
do {
mode = (next ? mode.next() : mode.prev());
} while (mode != this && mode.level > level);
return mode;
}
}
@SideOnly(Side.CLIENT)
private List<ModelResourceLocation> models;
/** Maps arrow IDs to arrow entity class */
private static final Map<Item, Class<? extends EntityArrowCustom>> arrowMap = new HashMap<Item, Class<? extends EntityArrowCustom>>();
/** Maps arrow IDs to bomb arrow Bomb Type */
private static BiMap<Item, BombType> bombArrowMap;
/** Maps arrow IDs to elemental arrow Element Type */
private static final Map<Item, ElementType> elementalArrowMap = new HashMap<Item, ElementType>();
/** Static list to store fire handlers; can't set type without requiring BG2 */
private static final List<IArrowFireHandler> fireHandlers = new ArrayList<IArrowFireHandler>();
public ItemHeroBow() {
super();
setFull3D();
setMaxDamage(0);
setMaxStackSize(1);
setCreativeTab(ZSSCreativeTabs.tabCombat);
}
/**
* Returns "item.zss.unlocalized_name" for translation purposes
*/
@Override
public String getUnlocalizedName() {
return super.getUnlocalizedName().replaceFirst("item.", "item.zss.");
}
@Override
public String getUnlocalizedName(ItemStack stack) {
return getUnlocalizedName();
}
/**
* Returns this bow's level for purposes of determining which arrows can be used
* Note if the NBT tag has not yet been set, it will be created automatically here
*/
public int getLevel(ItemStack bow) {
if (!bow.hasTagCompound() || !bow.getTagCompound().hasKey("zssBowLevel")) {
setLevel(bow, 1);
}
return bow.getTagCompound().getInteger("zssBowLevel");
}
/**
* Sets this bow's level, creating a new NBT tag if necessary
*/
private void setLevel(ItemStack bow, int level) {
if (!bow.hasTagCompound()) { bow.setTagCompound(new NBTTagCompound()); }
bow.getTagCompound().setInteger("zssBowLevel", level);
}
public Mode getMode(ItemStack stack) {
if (!stack.hasTagCompound() || !stack.getTagCompound().hasKey("zssItemMode")) {
setMode(stack, Mode.DEFAULT);
}
return Mode.values()[stack.getTagCompound().getInteger("zssItemMode") % Mode.values().length];
}
private void setMode(ItemStack stack, Mode mode) {
if (!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); }
stack.getTagCompound().setInteger("zssItemMode", mode.ordinal());
}
@Override
public void nextItemMode(ItemStack stack, EntityPlayer player) {
if (!player.isUsingItem()) {
setMode(stack, getMode(stack).next(getLevel(stack)));
}
}
@Override
public void prevItemMode(ItemStack stack, EntityPlayer player) {
if (!player.isUsingItem()) {
setMode(stack, getMode(stack).prev(getLevel(stack)));
}
}
@Override
public int getCurrentMode(ItemStack stack, EntityPlayer player) {
return getMode(stack).ordinal();
}
@Override
public void setCurrentMode(ItemStack stack, EntityPlayer player, int mode) {
setMode(stack, Mode.values()[mode % Mode.values().length]);
}
@Override
public ItemStack getRenderStackForMode(ItemStack stack, EntityPlayer player) {
Item item = getMode(stack).getArrowItem();
ItemStack ret = (item == null ? null : new ItemStack(item, 0));
if (ret != null) {
for (ItemStack inv : player.inventory.mainInventory) {
if (inv != null && inv.getItem() == ret.getItem()) {
ret.stackSize += inv.stackSize;
if (ret.stackSize > 98) {
ret.stackSize = 99;
break;
}
}
}
}
return ret;
}
/**
* Retrieves the currently nocked arrow (retrieved from player's extended properties)
*/
public ItemStack getArrow(EntityPlayer player) {
return ZSSPlayerInfo.get(player).getNockedArrow();
}
/**
* Stores the currently nocked arrow in the player's extended properties
*/
private void setArrow(EntityPlayer player, ItemStack arrow) {
ZSSPlayerInfo.get(player).setNockedArrow(arrow);
}
@Override
@SideOnly(Side.CLIENT)
public float getMaxZoomTime() {
return 20.0F;
}
@Override
@SideOnly(Side.CLIENT)
public float getZoomFactor() {
return 0.15F;
}
@Override
public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity entity) {
if (Config.areArrowTradesEnabled() && entity instanceof EntityVillager && !player.worldObj.isRemote) {
EntityVillager villager = (EntityVillager) entity;
MerchantRecipeList trades = villager.getRecipes(player);
if (villager.getProfession() == 2 && trades != null && trades.size() >= Config.getFriendTradesRequired()) {
int level = getLevel(stack);
MerchantRecipe trade = null;
if (level > 1) {
trade = new MerchantRecipe(new ItemStack(Items.emerald, 16), new ItemStack(ZSSItems.arrowFire, 4));
if (MerchantRecipeHelper.doesListContain(trades, trade)) {
trade = new MerchantRecipe(new ItemStack(Items.emerald, 20), new ItemStack(ZSSItems.arrowIce, 4));
if (level > 2 && MerchantRecipeHelper.doesListContain(trades, trade)) {
trade = new MerchantRecipe(new ItemStack(Items.emerald, 40), new ItemStack(ZSSItems.arrowLight, 4));
}
}
}
if (trade != null && MerchantRecipeHelper.addToListWithCheck(trades, trade)) {
PlayerUtils.sendTranslatedChat(player, "chat.zss.trade.arrow");
} else {
PlayerUtils.sendTranslatedChat(player, "chat.zss.trade.generic.sorry.1");
}
} else {
PlayerUtils.sendTranslatedChat(player, "chat.zss.trade.generic.sorry.0");
}
}
return true;
}
@Override
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
ArrowNockEvent event = new ArrowNockEvent(player, stack);
MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled()) {
return event.result;
}
// This can only be reached if BG2 is not installed or no arrow was found in the quiver
if (nockArrowFromInventory(stack, player)) {
int duration = getMaxItemUseDuration(stack);
if (ZSSMain.isBG2Enabled) {
duration -= (EnchantmentHelper.getEnchantmentLevel(BaseEnchantment.bowCharge.get().effectId, stack) * 20000);
}
player.setItemInUse(stack, duration);
}
return stack;
}
@Override
public void onPlayerStoppedUsing(ItemStack bow, World world, EntityPlayer player, int ticksRemaining) {
ZSSPlayerInfo.get(player).hasAutoBombArrow = false;
int ticksInUse = getMaxItemUseDuration(bow) - ticksRemaining;
ArrowLooseEvent event = new ArrowLooseEvent(player, bow, ticksInUse);
MinecraftForge.EVENT_BUS.post(event);
if (event.isCanceled()) {
if (getArrow(player) != null) {
setArrow(player, null); // nocked from inventory from empty quiver slot, then hot-swapped
}
return;
}
// Default behavior found usable arrow in standard player inventory
ItemStack arrowStack = getArrow(player);
if (arrowStack != null) { // can be null when hot-swapping to an empty quiver slot
if (canShootArrow(player, bow, arrowStack)) {
fireArrow(event, arrowStack, bow, player);
}
setArrow(player, null);
}
}
/**
* Called if ArrowLooseEvent was not canceled, so either the player is not using a quiver,
* or Battlegear2 is not loaded.
* Constructs and fires the arrow, if possible, and may consume the appropriate inventory item.
*/
private void fireArrow(ArrowLooseEvent event, ItemStack arrowStack, ItemStack bow, EntityPlayer player) {
boolean flag = (player.capabilities.isCreativeMode || EnchantmentHelper.getEnchantmentLevel(Enchantment.infinity.effectId, bow) > 0);
if (flag || PlayerUtils.hasItem(player, arrowStack)) {
float charge = (float) event.charge / 20.0F;
charge = Math.min((charge * charge + charge * 2.0F) / 3.0F, 1.0F);
if ((double) charge < 0.1D) {
return;
}
EntityArrow arrowEntity = getArrowEntity(arrowStack, player.worldObj, player, charge * 2.0F);
if (arrowEntity == null && ZSSMain.isBG2Enabled) { // try to construct BG2 arrow
arrowEntity = QuiverArrowRegistry.getArrowType(arrowStack, player.worldObj, player, charge * 2.0F);
}
if (arrowEntity != null && confirmArrowShot(arrowStack, player)) {
applyArrowSettings(arrowEntity, bow, charge);
if (arrowEntity instanceof EntityArrowCustom) {
applyCustomArrowSettings(event.entityPlayer, event.bow, arrowStack, (EntityArrowCustom) arrowEntity, charge);
}
player.worldObj.playSoundAtEntity(player, Sounds.BOW_RELEASE, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + charge * 0.5F);
if (flag) {
arrowEntity.canBePickedUp = 2;
} else {
PlayerUtils.consumeInventoryItem(player, arrowStack, 1);
}
if (!player.worldObj.isRemote) {
player.worldObj.spawnEntityInWorld(arrowEntity);
}
}
}
}
/**
* Returns true if the player truly can fire this arrow; used to consume MP for magic arrows
*/
private boolean confirmArrowShot(ItemStack arrow, EntityPlayer player) {
if (arrow != null && arrow.getItem() instanceof IMagicArrow) {
float mp = ((IMagicArrow) arrow.getItem()).getMagicCost(arrow, player);
return ZSSPlayerInfo.get(player).consumeMagic(mp);
}
return true;
}
/**
* Returns true if this bow's level is capable of shooting the given arrow
*/
public boolean canShootArrow(EntityPlayer player, ItemStack bow, ItemStack arrowStack) {
Item arrowItem = (arrowStack == null ? null : arrowStack.getItem());
if (arrowMap.containsKey(arrowItem)) {
if (player.capabilities.isCreativeMode) {
return true;
} else if (arrowItem instanceof IMagicArrow) {
if (!ZSSPlayerInfo.get(player).canUseMagic() || ZSSPlayerInfo.get(player).getCurrentMagic() < ((IMagicArrow) arrowItem).getMagicCost(arrowStack, player)) {
return false;
}
}
if (elementalArrowMap.containsKey(arrowItem)) {
int n = getLevel(bow);
if (n < 3) {
return (n == 2 && elementalArrowMap.get(arrowItem) != ElementType.LIGHT);
}
}
return true;
}
// allow hero's bow to fire arrows registered with BG2
return ZSSMain.isBG2Enabled && QuiverArrowRegistry.isKnownArrow(arrowStack);
}
/**
* Returns null or a valid arrow stack from the player's inventory, valid
* meaning that {@link #canShootArrow} returned true
*/
private ItemStack getArrowFromInventory(ItemStack bow, EntityPlayer player) {
Item modeArrow = getMode(bow).getArrowItem();
ItemStack arrow = null;
if (modeArrow == null && Config.enableAutoBombArrows() && player.isSneaking()) {
arrow = getAutoBombArrow(bow, player);
}
// Search specifically for the selected arrow type
if (modeArrow != null && canShootArrow(player, bow, new ItemStack(modeArrow))) {
for (ItemStack stack : player.inventory.mainInventory) {
if (stack != null && stack.getItem() == modeArrow) {
arrow = stack;
break;
}
}
}
// No mode selected or arrow could not be shot - search inventory for shootable arrow
if (arrow == null) {
for (ItemStack stack : player.inventory.mainInventory) {
if (stack != null && canShootArrow(player, bow, stack)) {
arrow = stack;
break;
}
}
}
// If still no arrow and player is in Creative Mode, nock default arrow
if (arrow == null && player.capabilities.isCreativeMode) {
arrow = new ItemStack(modeArrow == null ? Items.arrow : modeArrow);
}
return arrow;
}
/**
* Returns true if a suitable arrow to fire was found in the inventory
*/
public boolean nockArrowFromInventory(ItemStack bow, EntityPlayer player) {
setArrow(player, getArrowFromInventory(bow, player));
return getArrow(player) != null;
}
/**
* If the player is sneaking and auto-bomb arrows is enabled, this will return an
* appropriate bomb arrow, using the first bomb, bomb arrow or bomb bag encountered,
* adding a new arrow to the player's inventory while consuming both a bomb and a
* regular arrow if applicable
*/
private ItemStack getAutoBombArrow(ItemStack bow, EntityPlayer player) {
ItemStack arrow = null;
// Player must have a standard arrow to construct bomb arrow from bomb or bomb bag
boolean hasArrow = (player.capabilities.isCreativeMode || player.inventory.hasItem(Items.arrow));
// Flag whether the bomb arrow was already determined and items need not be consumed
boolean hasAutoArrow = ZSSPlayerInfo.get(player).hasAutoBombArrow;
for (int i = 0; i < player.inventory.getSizeInventory() && arrow == null; ++i) {
ItemStack stack = player.inventory.getStackInSlot(i);
if (stack != null) {
if (stack.getItem() == ZSSItems.arrowBomb || stack.getItem() == ZSSItems.arrowBombFire || stack.getItem() == ZSSItems.arrowBombWater) {
arrow = stack;
} else if (stack.getItem() instanceof ItemBombBag) {
ItemBombBag bombBag = (ItemBombBag) stack.getItem();
int bagType = bombBag.getBagBombType(stack);
if (bagType >= 0 && bombBag.getBombsHeld(stack) > 0) {
BombType type = BombType.values()[bagType % BombType.values().length];
ItemStack bombArrow = new ItemStack(bombArrowMap.inverse().get(type),1,0);
if (hasAutoArrow || player.capabilities.isCreativeMode) {
arrow = bombArrow;
} else if (hasArrow && player.inventory.addItemStackToInventory(bombArrow)) {
if (bombBag.removeBomb(stack)) {
if (player.inventory.consumeInventoryItem(Items.arrow)) {
arrow = bombArrow;
} else {
bombBag.addBombs(stack, bombArrow);
}
} else {
PlayerUtils.consumeInventoryItem(player, bombArrow, 1);
}
}
}
} else if (stack.getItem() == ZSSItems.bomb) {
ItemStack bombArrow = new ItemStack(bombArrowMap.inverse().get(ItemBomb.getType(stack)),1,0);
if (hasAutoArrow || player.capabilities.isCreativeMode) {
arrow = bombArrow;
} else if (hasArrow && player.inventory.consumeInventoryItem(Items.arrow)) {
arrow = bombArrow;
player.inventory.setInventorySlotContents(i, bombArrow);
}
}
}
}
if (arrow == null && player.capabilities.isCreativeMode) {
arrow = new ItemStack(ZSSItems.arrowBomb);
}
ZSSPlayerInfo.get(player).hasAutoBombArrow = (arrow != null);
return arrow;
}
@Override
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged) {
return !ItemStack.areItemsEqual(oldStack, newStack);
}
@Override
@SideOnly(Side.CLIENT)
public ModelResourceLocation getModel(ItemStack stack, EntityPlayer player, int ticksRemaining) {
if (!player.isUsingItem()) {
return models.get(0);
}
int ticks = stack.getMaxItemUseDuration() - ticksRemaining;
int i = (ticks > 17 ? 3 : ticks > 13 ? 2 : ticks > 0 ? 1 : 0);
ItemStack arrowStack = ZSSPlayerInfo.get(player).getNockedArrow();
if (i > 0 && arrowStack != null) {
// TODO this works, but is probably not ideal
Minecraft mc = Minecraft.getMinecraft();
GlStateManager.pushMatrix();
boolean firstPerson = (player == mc.thePlayer && mc.getRenderManager().options.thirdPersonView == 0);
if (firstPerson) {
GlStateManager.rotate(45.0F, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(-25.0F, 0.0F, 0.0F, 1.0F);
GlStateManager.translate(-0.4D, 0.4D, 0.0D);
GlStateManager.scale(1.325F, 1.325F, 1.325F);
} else {
GlStateManager.rotate(87.5F, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(45F, 0.0F, 0.0F, 1.0F);
GlStateManager.rotate(-10.0F, 1.0F, 0.0F, 0.0F);
GlStateManager.translate(0.125D, 0.125D, 0.125D);
GlStateManager.scale(0.625F, 0.625F, 0.625F);
}
GlStateManager.translate(-(-3F+i)/16F, -(-3F+i)/16F, 0.5F/16F);
mc.getRenderItem().renderItem(arrowStack, ItemCameraTransforms.TransformType.FIXED);
GlStateManager.popMatrix();
}
return models.get(i);
}
@Override
public String[] getVariants() {
String name = getUnlocalizedName();
name = ModInfo.ID + ":" + name.substring(name.lastIndexOf(".") + 1);
String[] variants = new String[4];
for (int i = 0; i < variants.length; ++i) {
variants[i] = name + "_" + i;
}
return variants;
}
@Override
@SideOnly(Side.CLIENT)
public void registerResources() {
String[] variants = getVariants();
models = new ArrayList<ModelResourceLocation>(variants.length);
for (int i = 0; i < variants.length; ++i) {
models.add(new ModelResourceLocation(variants[i], "inventory"));
}
ModelLoader.registerItemVariants(this, models.toArray(new ModelResourceLocation[0]));
ModelLoader.setCustomMeshDefinition(this, new ItemMeshDefinition() {
@Override
public ModelResourceLocation getModelLocation(ItemStack stack) {
return models.get(0);
}
});
}
@Override
@SideOnly(Side.CLIENT)
public void getSubItems(Item item, CreativeTabs tab, List<ItemStack> list) {
for (int i = 1; i < 4; ++i) {
ItemStack stack = new ItemStack(item);
setLevel(stack, i);
list.add(stack);
}
}
@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.translateToLocalFormatted("tooltip." + getUnlocalizedName().substring(5) + ".desc.1", getLevel(stack)));
Item item = getMode(stack).getArrowItem();
if (item != null) {
list.add(EnumChatFormatting.YELLOW + StatCollector.translateToLocalFormatted("tooltip.zss.mode", new ItemStack(item).getDisplayName()));
}
}
@Override
public void handleFairyUpgrade(EntityItem item, EntityPlayer player, TileEntityDungeonCore core) {
ItemStack stack = item.getEntityItem();
int n = getLevel(stack);
BlockPos pos = core.getPos();
if (n < 3 && core.consumeRupees((n + 1) * Config.getHeroBowUpgradeCost())) {
setLevel(stack, ++n);
core.getWorld().playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.SECRET_MEDLEY, 1.0F, 1.0F);
player.triggerAchievement(ZSSAchievements.fairyBow);
if (n == 3) {
player.triggerAchievement(ZSSAchievements.fairyBowMax);
}
} else {
core.getWorld().playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FAIRY_LAUGH, 1.0F, 1.0F);
PlayerUtils.sendTranslatedChat(player, "chat.zss.fairy.laugh.unworthy");
}
}
@Override
public boolean hasFairyUpgrade(ItemStack stack) {
return getLevel(stack) < 3;
}
@Method(modid="battlegear2")
@Override
public boolean allowOffhand(ItemStack main, ItemStack offhand, EntityPlayer player) {
return offhand == null || offhand.getItem() instanceof ItemQuiver;
}
@Method(modid="battlegear2")
@Override
public boolean sheatheOnBack(ItemStack stack) {
return true;
}
@Method(modid="battlegear2")
@Override
public List<IArrowFireHandler> getFireHandlers(ItemStack arrow, ItemStack bow, EntityPlayer player) {
return fireHandlers;
}
@Method(modid="battlegear2")
@Override
public Result canDrawBow(ItemStack bow, EntityPlayer player) {
ItemStack arrow = BattlegearEvents.getQuiverArrow(bow, player);
return (canShootArrow(player, bow, arrow) ? Result.ALLOW : Result.DENY);
}
/**
* Applies vanilla arrow enchantments and sets critical if applicable
* NOTE: These settings are already added by BattleGear2 when shot from a quiver
* @param charge should be a value between 0.0F and 1.0F, inclusive
*/
public static final void applyArrowSettings(EntityArrow arrow, ItemStack bow, float charge) {
if (charge == 1.0F) {
arrow.setIsCritical(true);
}
int k = EnchantmentHelper.getEnchantmentLevel(Enchantment.power.effectId, bow);
if (k > 0) {
arrow.setDamage(arrow.getDamage() + (double) k * 0.5D + 0.5D);
}
int l = EnchantmentHelper.getEnchantmentLevel(Enchantment.punch.effectId, bow);
if (l > 0) {
arrow.setKnockbackStrength(l);
}
if (EnchantmentHelper.getEnchantmentLevel(Enchantment.flame.effectId, bow) > 0) {
arrow.setFire(100);
}
}
/**
* Applies custom arrow settings
* @param charge A value between 0.0F and 1.0F, inclusive
* @param arrowStack Determines arrow type (fire, ice, etc.) and the item that the arrow entity will drop
*/
private static final void applyCustomArrowSettings(EntityPlayer player, ItemStack bow, ItemStack arrowStack, EntityArrowCustom arrowEntity, float charge) {
Item arrowItem = arrowStack.getItem();
arrowEntity.setArrowItem(arrowItem);
if (player.getCurrentArmor(ArmorIndex.WORN_HELM) != null && player.getCurrentArmor(ArmorIndex.WORN_HELM).getItem() == ZSSItems.maskHawkeye) {
EntityLivingBase target = TargetUtils.acquireLookTarget(player, 64, 1.0F);
if (target != null) {
arrowEntity.setHomingArrow(true);
arrowEntity.setTarget(target);
}
}
if (arrowEntity instanceof EntityArrowBomb && bombArrowMap.containsKey(arrowItem)) {
((EntityArrowBomb) arrowEntity).setType(bombArrowMap.get(arrowItem));
arrowEntity.setDamage(0.0F);
} else if (arrowEntity instanceof EntityArrowElemental && elementalArrowMap.containsKey(arrowItem)) {
((EntityArrowElemental) arrowEntity).setType(elementalArrowMap.get(arrowItem));
}
}
/**
* Returns the entity arrow appropriate for the id given, using the
* shooter and charge provided during construction
*/
@SuppressWarnings("finally")
public static final EntityArrowCustom getArrowEntity(ItemStack arrowStack, World world, EntityLivingBase shooter, float charge) {
Item arrowItem = (arrowStack == null ? null : arrowStack.getItem());
if (arrowMap.containsKey(arrowItem)) {
EntityArrowCustom arrow = null;
try {
try {
arrow = arrowMap.get(arrowItem).getConstructor(World.class, EntityLivingBase.class, float.class).newInstance(world, shooter, charge);
} catch (InstantiationException e) {
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
} catch (InvocationTargetException e) {
e.printStackTrace();
return null;
}
} finally {
return arrow;
}
} else {
return null;
}
}
/**
* Adds all custom arrow items to maps - should be done in post-init.
*/
public static void initializeArrows() {
arrowMap.put(Items.arrow, EntityArrowCustom.class);
arrowMap.put(ZSSItems.arrowBomb, EntityArrowBomb.class);
arrowMap.put(ZSSItems.arrowBombFire, EntityArrowBomb.class);
arrowMap.put(ZSSItems.arrowBombWater, EntityArrowBomb.class);
arrowMap.put(ZSSItems.arrowFire, EntityArrowElemental.class);
arrowMap.put(ZSSItems.arrowIce, EntityArrowElemental.class);
arrowMap.put(ZSSItems.arrowLight, EntityArrowElemental.class);
ImmutableBiMap.Builder<Item, BombType> builder = ImmutableBiMap.builder();
builder.put(ZSSItems.arrowBomb, BombType.BOMB_STANDARD);
builder.put(ZSSItems.arrowBombFire, BombType.BOMB_FIRE);
builder.put(ZSSItems.arrowBombWater, BombType.BOMB_WATER);
bombArrowMap = builder.build();
elementalArrowMap.put(ZSSItems.arrowFire, ElementType.FIRE);
elementalArrowMap.put(ZSSItems.arrowIce, ElementType.ICE);
elementalArrowMap.put(ZSSItems.arrowLight, ElementType.LIGHT);
}
/**
* Registers {@link HeroBowFireHandler} and all arrows required for use with
* Battlegear2's quiver system
*/
@Method(modid="battlegear2")
public static void registerBG2() {
fireHandlers.add(new HeroBowFireHandler());
fireHandlers.add(new DefaultArrowFire());
QuiverArrowRegistry.addArrowFireHandler(new HeroBowFireHandler());
// registering as null prevents default fire handler from handling these arrows:
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowBomb, null);
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowBombFire, null);
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowBombWater, null);
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowFire, null);
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowIce, null);
QuiverArrowRegistry.addArrowToRegistry(ZSSItems.arrowLight, null);
}
/**
* Handler for building ZSS arrows fired from a Battlegear2 quiver, including
* customized versions of the vanilla arrow.
*
* ZSS arrows require an appropriately-leveled ItemHeroBow, or creative mode.
*/
@Optional.Interface(iface="mods.battlegear2.api.quiver.IArrowFireHandler", modid="battlegear2", striprefs=true)
public static class HeroBowFireHandler implements IArrowFireHandler {
@Method(modid="battlegear2")
@Override
public boolean canFireArrow(ItemStack arrow, World world, EntityPlayer player, float charge) {
ItemStack bow = player.getHeldItem();
if (bow != null) { // allow creative players to shoot custom Zelda arrows using any bow
return (bow.getItem() instanceof ItemHeroBow ? ((ItemHeroBow) bow.getItem()).canShootArrow(player, bow, arrow) : player.capabilities.isCreativeMode);
}
return false;
}
@Method(modid="battlegear2")
@Override
public EntityArrow getFiredArrow(ItemStack arrow, World world, EntityPlayer player, float charge) {
ItemStack bow = player.getHeldItem();
if (bow != null && (bow.getItem() instanceof ItemHeroBow || player.capabilities.isCreativeMode)) {
EntityArrowCustom arrowEntity = ItemHeroBow.getArrowEntity(arrow, world, player, charge);
if (arrowEntity != null && (!(bow.getItem() instanceof ItemHeroBow) || ((ItemHeroBow) bow.getItem()).confirmArrowShot(arrow, player))) {
// vanilla arrow settings will be applied by BG2's arrow loose event
ItemHeroBow.applyCustomArrowSettings(player, bow, arrow, arrowEntity, charge);
}
return arrowEntity;
}
return null;
}
}
}