package slimeknights.tconstruct.library.tools.ranged;
import com.google.common.collect.Multimap;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.Items;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.item.IItemPropertyGetter;
import net.minecraft.item.Item;
import net.minecraft.item.ItemArrow;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.stats.StatList;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.world.World;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import slimeknights.tconstruct.common.ClientProxy;
import slimeknights.tconstruct.library.client.BooleanItemPropertyGetter;
import slimeknights.tconstruct.library.events.ProjectileEvent;
import slimeknights.tconstruct.library.events.TinkerToolEvent;
import slimeknights.tconstruct.library.materials.Material;
import slimeknights.tconstruct.library.tinkering.Category;
import slimeknights.tconstruct.library.tinkering.PartMaterialType;
import slimeknights.tconstruct.library.tools.IAmmoUser;
import slimeknights.tconstruct.library.tools.ProjectileLauncherNBT;
import slimeknights.tconstruct.library.utils.AmmoHelper;
import slimeknights.tconstruct.library.utils.TagUtil;
import slimeknights.tconstruct.library.utils.ToolHelper;
import slimeknights.tconstruct.tools.ranged.TinkerRangedWeapons;
public abstract class BowCore extends ProjectileLauncherCore implements IAmmoUser, ILauncher {
protected static final UUID LAUNCHER_BONUS_DAMAGE = UUID.fromString("066b8892-d2ac-4bae-ac22-26f9f91a02ee");
protected static final UUID LAUNCHER_DAMAGE_MODIFIER = UUID.fromString("4f76565a-3845-4a09-ba8f-92a37937a7c3");
protected static final ResourceLocation PROPERTY_PULL_PROGRESS = new ResourceLocation("pull");
protected static final ResourceLocation PROPERTY_IS_PULLING = new ResourceLocation("pulling");
protected final IItemPropertyGetter pullProgressPropertyGetter;
protected final IItemPropertyGetter isPullingPropertyGetter;
public BowCore(PartMaterialType... requiredComponents) {
super(requiredComponents);
addCategory(Category.LAUNCHER);
pullProgressPropertyGetter = new IItemPropertyGetter() {
@Override
@SideOnly(Side.CLIENT)
public float apply(ItemStack stack, @Nullable World worldIn, @Nullable EntityLivingBase entityIn) {
if(entityIn == null) {
return 0.0F;
}
else {
ItemStack itemstack = entityIn.getActiveItemStack();
return getDrawbackProgress(itemstack, entityIn);
}
}
};
isPullingPropertyGetter = new BooleanItemPropertyGetter() {
@Override
@SideOnly(Side.CLIENT)
public boolean applyIf(ItemStack stack, @Nullable World worldIn, @Nullable EntityLivingBase entityIn) {
return entityIn != null && entityIn.isHandActive() && entityIn.getActiveItemStack() == stack;
}
};
}
/* Stuff to override */
protected float baseInaccuracy() {
return 0f;
}
protected float baseProjectileSpeed() {
return 3f;
}
public int getDrawTime() {
return 20;
}
public float getDrawbackProgress(ItemStack itemstack, EntityLivingBase entityIn) {
if(itemstack != null && itemstack.getItem() == BowCore.this) {
int timePassed = itemstack.getMaxItemUseDuration() - entityIn.getItemInUseCount();
return getDrawbackProgress(itemstack, timePassed);
}
else {
return 0f;
}
}
protected float getDrawbackProgress(ItemStack itemStack, int timePassed) {
float drawProgress = ProjectileLauncherNBT.from(itemStack).drawSpeed * (float) timePassed;
return Math.min(1f, drawProgress / (float) getDrawTime());
}
/* Bow usage stuff */
@Nonnull
@Override
public EnumAction getItemUseAction(ItemStack stack) {
return EnumAction.BOW;
}
@Override
public int getMaxItemUseDuration(ItemStack stack) {
return 72000;
}
@Nonnull
@Override
public ActionResult<ItemStack> onItemRightClick(@Nonnull ItemStack itemStackIn, World worldIn, EntityPlayer playerIn, EnumHand hand) {
if(!ToolHelper.isBroken(itemStackIn)) {
boolean hasAmmo = findAmmo(itemStackIn, playerIn) != null;
ActionResult<ItemStack> ret = net.minecraftforge.event.ForgeEventFactory.onArrowNock(itemStackIn, worldIn, playerIn, hand, hasAmmo);
if(ret != null) {
return ret;
}
if(playerIn.capabilities.isCreativeMode || hasAmmo) {
playerIn.setActiveHand(hand);
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, itemStackIn);
}
}
return new ActionResult<ItemStack>(EnumActionResult.FAIL, itemStackIn);
}
@Override
public void onPlayerStoppedUsing(ItemStack stack, World worldIn, EntityLivingBase entityLiving, int timeLeft) {
if(ToolHelper.isBroken(stack) || !(entityLiving instanceof EntityPlayer)) {
return;
}
EntityPlayer player = (EntityPlayer) entityLiving;
ItemStack ammo = findAmmo(stack, entityLiving);
if(ammo == null && !player.capabilities.isCreativeMode) {
return;
}
int useTime = this.getMaxItemUseDuration(stack) - timeLeft;
useTime = ForgeEventFactory.onArrowLoose(stack, worldIn, player, useTime, ammo != null);
if(useTime < 5) {
return;
}
if(ammo == null) {
ammo = getCreativeProjectileStack();
}
shootProjectile(ammo, stack, worldIn, player, useTime);
player.addStat(StatList.getObjectUseStats(this));
// needs to be done manually for the overrides to work out correctly
// since TiC tools don't get updated by default due to their custom equip check
// this interferes with the item properties since it gets the wrong itemstack
// causing animations not to work
TinkerRangedWeapons.proxy.updateEquippedItemForRendering(entityLiving.getActiveHand());
TagUtil.setResetFlag(stack, true);
}
public void shootProjectile(ItemStack ammo, ItemStack bow, World worldIn, EntityPlayer player, int useTime) {
float progress = getDrawbackProgress(bow, useTime);
float power = ItemBow.getArrowVelocity((int)(progress * 20f)) * progress * baseProjectileSpeed();
power *= ProjectileLauncherNBT.from(bow).range;
if(!worldIn.isRemote) {
TinkerToolEvent.OnBowShoot event = TinkerToolEvent.OnBowShoot.fireEvent(bow, ammo, player, useTime, baseInaccuracy());
for(int i = 0; i < event.projectileCount; i++) {
boolean usedAmmo = false;
if(i == 0 || event.consumeAmmoPerProjectile) {
usedAmmo = consumeAmmo(ammo, player);
}
float inaccuracy = event.getBaseInaccuracy();
if(i > 0) {
inaccuracy += event.bonusInaccuracy;
}
EntityArrow projectile = getProjectileEntity(ammo, bow, worldIn, player, power, inaccuracy, progress*progress, usedAmmo);
if(projectile != null && ProjectileEvent.OnLaunch.fireEvent(projectile, bow, player)) {
if(progress >= 1f) {
projectile.setIsCritical(true);
}
if(!player.capabilities.isCreativeMode) {
ToolHelper.damageTool(bow, 1, player);
}
worldIn.spawnEntity(projectile);
}
}
}
playShootSound(power, worldIn, player);
}
public EntityArrow getProjectileEntity(ItemStack ammo, ItemStack bow, World world, EntityPlayer player, float power, float inaccuracy, float progress, boolean usedAmmo) {
if(ammo.getItem() instanceof IAmmo) {
return ((IAmmo) ammo.getItem()).getProjectile(ammo, bow, world, player, power, inaccuracy, progress, usedAmmo);
}
else if(ammo.getItem() instanceof ItemArrow) {
EntityArrow projectile = ((ItemArrow) ammo.getItem()).createArrow(world, ammo, player);
projectile.setAim(player, player.rotationPitch, player.rotationYaw, 0.0F, power, inaccuracy);
if(player.capabilities.isCreativeMode) {
projectile.pickupStatus = EntityArrow.PickupStatus.CREATIVE_ONLY;
}
else if(!usedAmmo) {
projectile.pickupStatus = EntityArrow.PickupStatus.DISALLOWED;
}
return projectile;
}
// shizzle-foo, this fizzles too!
return null;
}
public boolean consumeAmmo(ItemStack ammo, EntityPlayer player) {
// no ammo consumption in creative
if(player.capabilities.isCreativeMode) {
return false;
}
if(ammo.getItem() instanceof IAmmo) {
return ((IAmmo) ammo.getItem()).useAmmo(ammo, player);
}
else {
ammo.stackSize--;
if (ammo.stackSize == 0)
{
player.inventory.deleteStack(ammo);
}
return true;
}
}
protected ItemStack getCreativeProjectileStack() {
return new ItemStack(Items.ARROW);
}
public void playShootSound(float power, World world, EntityPlayer entityPlayer) {
world.playSound(null, entityPlayer.posX, entityPlayer.posY, entityPlayer.posZ, SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F, 1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + power * 0.5F);
}
@Override
public ItemStack findAmmo(ItemStack weapon, EntityLivingBase player) {
return AmmoHelper.findAmmoFromInventory(getAmmoItems(), player);
}
@Override
public ItemStack getAmmoToRender(ItemStack weapon, EntityLivingBase player) {
if(ToolHelper.isBroken(weapon)) {
return null;
}
return findAmmo(weapon, player);
}
public abstract float baseProjectileDamage();
public abstract float projectileDamageModifier();
@Override
public void modifyProjectileAttributes(Multimap<String, AttributeModifier> projectileAttributes, @Nullable ItemStack launcher, ItemStack projectile, float power) {
double dmg = baseProjectileDamage() * power;
dmg += ProjectileLauncherNBT.from(launcher).bonusDamage;
if(dmg != 0) {
projectileAttributes.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(LAUNCHER_BONUS_DAMAGE, "Launcher bonus damage", dmg, 0));
}
if(projectileDamageModifier() != 0f) {
projectileAttributes.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(LAUNCHER_DAMAGE_MODIFIER, "Launcher damage modifier", projectileDamageModifier() - 1f, 1));
}
}
protected abstract List<Item> getAmmoItems();
@Override
@SideOnly(Side.CLIENT)
public Material getMaterialForPartForGuiRendering(int index) {
if(index == getRequiredComponents().size()-1) {
return ClientProxy.RenderMaterialString;
}
switch(index) {
case 0: return ClientProxy.RenderMaterials[0];
case 1: return ClientProxy.RenderMaterials[2];
case 2: return ClientProxy.RenderMaterials[1];
default: return super.getMaterialForPartForGuiRendering(index);
}
}
}