package choonster.testmod3.item;
import choonster.testmod3.TestMod3;
import choonster.testmod3.util.CapabilityUtils;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.Enchantments;
import net.minecraft.init.Items;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemArrow;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.stats.StatList;
import net.minecraft.util.*;
import net.minecraft.world.World;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.PlayerOffhandInvWrapper;
import net.minecraftforge.items.wrapper.RangedWrapper;
import javax.annotation.Nullable;
import java.util.function.Predicate;
/**
* A bow that uses custom models identical to the vanilla ones and shoots custom arrows.
* <p>
* Test for this thread:
* http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/modification-development/2576588-custom-bow-wont-load-model
*
* @author Choonster
*/
public class ItemModBow extends ItemBow {
public ItemModBow(String itemName) {
ItemTestMod3.setItemName(this, itemName);
setCreativeTab(TestMod3.creativeTab);
// ItemBow's "pull" getter only works for Items.bow, so register a custom getter that works for any instance of this class.
addPropertyOverride(new ResourceLocation(TestMod3.MODID, "pull"),
IItemPropertyGetterFix.create((stack, worldIn, entityIn) -> {
if (entityIn == null) return 0.0f;
final ItemStack activeItemStack = entityIn.getActiveItemStack();
if (!activeItemStack.isEmpty() && activeItemStack.getItem() instanceof ItemModBow) {
return (stack.getMaxItemUseDuration() - entityIn.getItemInUseCount()) / 20.0f;
}
return 0.0f;
})
);
}
/**
* Get an {@link IItemHandler} wrapper of the first slot in the player's inventory containing an {@link ItemStack} of ammunition.
* <p>
* This {@link IItemHandler} will always have a single slot containing the ammunition {@link ItemStack}.
* <p>
* Adapted from {@link ItemBow#findAmmo(EntityPlayer)}.
*
* @param player The player
* @param isAmmo A function that detects whether a given ItemStack is valid ammunition
* @return The ammunition slot's IItemHandler, or null if there isn't any ammunition
*/
@Nullable
public static IItemHandler findAmmoSlot(EntityPlayer player, Predicate<ItemStack> isAmmo) {
if (isAmmo.test(player.getHeldItemOffhand())) {
return new PlayerOffhandInvWrapper(player.inventory);
}
// Vertical facing = main inventory
final EnumFacing mainInventoryFacing = EnumFacing.UP;
final IItemHandler mainInventory = CapabilityUtils.getCapability(player, CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, mainInventoryFacing);
if (mainInventory != null) {
if (isAmmo.test(player.getHeldItemMainhand())) {
final int currentItem = player.inventory.currentItem;
return new RangedWrapper((IItemHandlerModifiable) mainInventory, currentItem, currentItem + 1);
}
for (int slot = 0; slot < mainInventory.getSlots(); ++slot) {
final ItemStack itemStack = mainInventory.getStackInSlot(slot);
if (isAmmo.test(itemStack)) {
return new RangedWrapper((IItemHandlerModifiable) mainInventory, slot, slot + 1);
}
}
}
return null;
}
/**
* Is ammunition required to fire this bow?
*
* @param bow The bow
* @param shooter The shooter
* @return Is ammunition required?
*/
protected boolean isAmmoRequired(ItemStack bow, EntityPlayer shooter) {
return !shooter.capabilities.isCreativeMode && EnchantmentHelper.getEnchantmentLevel(Enchantments.INFINITY, bow) == 0;
}
/**
* Nock an arrow.
*
* @param bow The bow ItemStack
* @param shooter The player shooting the bow
* @param world The World
* @param hand The hand holding the bow
* @return The result
*/
protected ActionResult<ItemStack> nockArrow(ItemStack bow, World world, EntityPlayer shooter, EnumHand hand) {
boolean hasAmmo = findAmmoSlot(shooter, this::isArrow) != null;
final ActionResult<ItemStack> ret = ForgeEventFactory.onArrowNock(bow, world, shooter, hand, hasAmmo);
if (ret != null) return ret;
if (isAmmoRequired(bow, shooter) && !hasAmmo) {
return new ActionResult<>(EnumActionResult.FAIL, bow);
} else {
shooter.setActiveHand(hand);
return new ActionResult<>(EnumActionResult.SUCCESS, bow);
}
}
/**
* Fire an arrow with the specified charge.
*
* @param bow The bow ItemStack
* @param world The firing player's World
* @param shooter The player firing the bow
* @param charge The charge of the arrow
*/
protected void fireArrow(ItemStack bow, World world, EntityLivingBase shooter, int charge) {
if (!(shooter instanceof EntityPlayer)) return;
final EntityPlayer player = (EntityPlayer) shooter;
final boolean ammoRequired = isAmmoRequired(bow, player);
IItemHandler ammoSlot = findAmmoSlot(player, this::isArrow);
charge = ForgeEventFactory.onArrowLoose(bow, world, player, charge, ammoSlot != null || !ammoRequired);
if (charge < 0) return;
if (ammoSlot != null || !ammoRequired) {
if (ammoSlot == null) {
ammoSlot = new ItemStackHandler(NonNullList.withSize(1, new ItemStack(Items.ARROW)));
}
final ItemStack ammo = ammoSlot.getStackInSlot(0);
final float arrowVelocity = getArrowVelocity(charge);
if (arrowVelocity >= 0.1) {
final boolean isInfinite = player.capabilities.isCreativeMode || ammo.getItem() instanceof ItemArrow && ((ItemArrow) ammo.getItem()).isInfinite(ammo, bow, player);
if (!world.isRemote) {
final ItemArrow itemArrow = (ItemArrow) (ammo.getItem() instanceof ItemArrow ? ammo.getItem() : Items.ARROW);
final EntityArrow entityArrow = itemArrow.createArrow(world, ammo, player);
entityArrow.setAim(player, player.rotationPitch, player.rotationYaw, 0.0F, arrowVelocity * 3.0F, 1.0F);
if (arrowVelocity == 1.0f) {
entityArrow.setIsCritical(true);
}
final int powerLevel = EnchantmentHelper.getEnchantmentLevel(Enchantments.POWER, bow);
if (powerLevel > 0) {
entityArrow.setDamage(entityArrow.getDamage() + (double) powerLevel * 0.5D + 0.5D);
}
final int punchLevel = EnchantmentHelper.getEnchantmentLevel(Enchantments.PUNCH, bow);
if (punchLevel > 0) {
entityArrow.setKnockbackStrength(punchLevel);
}
if (EnchantmentHelper.getEnchantmentLevel(Enchantments.FLAME, bow) > 0) {
entityArrow.setFire(100);
}
bow.damageItem(1, player);
if (isInfinite) {
entityArrow.pickupStatus = EntityArrow.PickupStatus.CREATIVE_ONLY;
}
world.spawnEntity(entityArrow);
}
world.playSound(null, player.posX, player.posY, player.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + arrowVelocity * 0.5F);
if (!isInfinite && !ammoSlot.extractItem(0, 1, true).isEmpty()) {
ammoSlot.extractItem(0, 1, false);
}
player.addStat(StatList.getObjectUseStats(this));
}
}
}
@Override
public void onPlayerStoppedUsing(ItemStack stack, World worldIn, EntityLivingBase entityLiving, int timeLeft) {
final int charge = this.getMaxItemUseDuration(stack) - timeLeft;
fireArrow(stack, worldIn, entityLiving, charge);
}
@Override
public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand hand) {
return nockArrow(playerIn.getHeldItem(hand), worldIn, playerIn, hand);
}
}