package minefantasy.mf2.api.helpers;
import java.util.Random;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import minefantasy.mf2.api.MineFantasyAPI;
import minefantasy.mf2.api.armour.CogworkArmour;
import minefantasy.mf2.api.armour.IArmouredEntity;
import minefantasy.mf2.api.armour.ISpecialArmourMF;
import minefantasy.mf2.api.armour.IElementalResistance;
import minefantasy.mf2.api.knowledge.ResearchLogic;
import minefantasy.mf2.api.stamina.StaminaBar;
import minefantasy.mf2.api.weapon.IParryable;
import minefantasy.mf2.entity.EntityArrowMF;
import minefantasy.mf2.mechanics.CombatMechanics;
import minefantasy.mf2.util.MFLogUtil;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.EnumCreatureAttribute;
import net.minecraft.entity.monster.EntitySpider;
import net.minecraft.entity.monster.EntityWitch;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.potion.Potion;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSourceIndirect;
import net.minecraft.util.MathHelper;
/**
* This calculates different tactical contexts for combat like flanking and blocking
*/
public class TacticalManager
{
private static Random rand = new Random();
public static boolean shouldSlow = true;
public static float minWeightSpeed = 10F;
public static float arrowDeflectChance = 1.0F;
/**
* Determines if you cant block with no stamina
*/
public static boolean shouldStaminaBlock = false;
/**
* Determines if the defender is hit in the front (180degree arc)
* @param attacker the attacker
* @param defender the defender
* @return true if the hit is on the front
*/
public static boolean canBlock(Entity attacker, EntityLivingBase defender)
{
return canBlock(attacker, defender, 180);
}
/**
* Determines if the defender is hit in the front with a custom arc
* @param attacker the attacker
* @param defender the defender
* @param arc the arc that can be blocked
* @return true if the hit is on the front
*/
public static boolean canBlock(Entity attacker, EntityLivingBase defender, float blockAngle)
{
if(attacker == null || defender == null)return false;
float yaw = calculateHitAngle(attacker, defender);
return yaw < blockAngle && yaw > -blockAngle;
}
/**
* Determines if an entity is flanking another
* @param source the source of the attack
* @param attacker the attacking entity
* @param defender the entity on view is questioned
* @param angle the angle the defender is flanked by
* @return true if the attacker hit within the back of defender between the angle
*/
public static boolean isFlankedBy(Entity attacker, EntityLivingBase defender, float angle)
{
float yaw = calculateHitAngle(attacker, defender);
float blockAngle = (360-angle)/2;
return !(yaw < blockAngle && yaw > -blockAngle);//Flanking is like reverse block
}
public static boolean canParry(DamageSource source, EntityLivingBase user, Entity entityHitting, ItemStack weapon)
{
boolean autoParry = false;
if(shouldStaminaBlock && StaminaBar.isSystemActive && StaminaBar.doesAffectEntity(user) && !StaminaBar.isAnyStamina(user, false))
{
return false;
}
if(user.getHeldItem() != null && !canWeaponBlock(user.getHeldItem()))
{
return false;
}
if(user instanceof EntityPlayer)
{
EntityPlayer player = (EntityPlayer)user;
autoParry = ResearchLogic.hasInfoUnlocked(player, "autoparry") && !player.isBlocking();
if(!player.isBlocking() && !autoParry)
{
return false;
}
}
else
{
if(!isMobBlocking(user))
{
return false;
}
}
if(!CombatMechanics.isParryAvailable(user))
{
return false;
}
int confusion = 0;
if(user.getActivePotionEffect(Potion.confusion) != null)
{
confusion = user.getActivePotionEffect(Potion.confusion).getAmplifier() +1;
}
float arc = source.isProjectile() ? 10 : 20;//DEFAULT
arc *= getHighgroundModifier(user, entityHitting, 1.5F);
arc = ArmourCalculator.adjustACForDamage(source, arc, 1.0F, 1.0F, 0.5F);//Harder to block piercing
if(autoParry)
{
arc *= 0.5F;
}
if(weapon != null && weapon.getItem() instanceof IParryable)
{
IParryable parry = (IParryable)weapon.getItem();
if(!parry.canUserParry(user))
{
return false;
}
arc = parry.getParryAngle(source, user, weapon);
if(!parry.canParry(source, user, weapon))
{
return false;
}
}
if(confusion > 0 && rand.nextInt(confusion+1) != 0)
{
return false;
}
return arc > 0 && canBlock(entityHitting, user, arc);
}
private static boolean canWeaponBlock(ItemStack item)
{
return item.getItem() instanceof ItemSword || item.getItem() instanceof IParryable;
}
private static boolean isMobBlocking(EntityLivingBase user)
{
if(!user.isImmuneToFire() && user.isBurning())
{
return false;//If burning and can't take the heat.. can't block!
}
if(user.getHeldItem() != null)
{
return user.getHeldItem().getItem().getItemUseAction(user.getHeldItem()) == EnumAction.block;
}
return false;
}
public static float getHighgroundModifier(Entity target, Entity hitter, float value)
{
if(target == null || hitter == null)
{
return 1.0F;
}
float gap = 0.5F;
if(target.posY > hitter.posY + gap)//Blocker on high ground
{
return 1.0F * value;
}
if(target.posY < hitter.posY - gap)//Attacker on high ground
{
return 1.0F / value;
}
return 1.0F;
}
/**
* Knocks the target back from the source
* @param target the enemy to be knocked back
* @param source the source of the knockback
* @param power the power a - pulls
* @param height the height they jump
*/
public static void knockbackEntity(Entity target, Entity source, float power, float height)
{
target.addVelocity(-MathHelper.sin(source.rotationYaw * (float) Math.PI / 180.0F) * power * 0.5F, height, MathHelper.cos(source.rotationYaw * (float) Math.PI / 180.0F) * power * 0.5F);
}
/**
* Causes the attacker to fly towards the target
* @param attacker the entity that moves
* @param target the target attacker aims for
* @param power the forward momentum
* @param height the height of the jump
*/
public static void lungeEntity(Entity attacker, Entity target, float power, float height)
{
attacker.addVelocity(-MathHelper.sin(attacker.rotationYaw * (float) Math.PI / 180.0F) * power * 0.5F, height, MathHelper.cos(attacker.rotationYaw * (float) Math.PI / 180.0F) * power * 0.5F);
}
public static boolean isRanged(DamageSource source)
{
if(source == null)
{
return false;
}
if(source.isProjectile() || source instanceof EntityDamageSourceIndirect)
{
return true;
}
if(source.getEntity() != null && source.getSourceOfDamage() != null)
{
return source.getEntity() != source.getSourceOfDamage();
}
return false;
}
/**
* This gets the angle that "attacker" hit "defender" relating 0 to front
* @param attacker the entity that hit defender
* @param defender the hit entity in question
* @return the angle defender was hit (0 = front)
*/
private static float calculateHitAngle(Entity attacker, EntityLivingBase defender)
{
if(attacker == null)
{
return 0F;
}
double xGap = attacker.posX - defender.posX;
double zGap;
for (zGap = attacker.posZ - defender.posZ; xGap * xGap + zGap * zGap < 1.0E-4D; zGap = (Math.random() - Math.random()) * 0.01D)
{
xGap = (Math.random() - Math.random()) * 0.01D; //makes the zgap
}
float yaw = (float)(Math.atan2(zGap, xGap) * 180.0D / Math.PI) - defender.rotationYaw;
yaw = yaw - 90;
//CONVERTS THE ANGLES
while(yaw < -180)
{
yaw += 360;
}
while(yaw >= 180)
{
yaw -= 360;
}
return yaw;
}
/**
* Modifies the movement of an entity based on armour
*/
public static void applyArmourWeight(EntityLivingBase entityLiving)
{
if(entityLiving instanceof EntityPlayer && ((EntityPlayer)entityLiving).capabilities.isCreativeMode)
{
return;
}
//Default speed is 100%
float totalSpeed = 100F;
if(CogworkArmour.isWearingAnyCogwork(entityLiving))
{
totalSpeed = 80F;//Cogwork regardless always slows speed by 20%
}
else if(shouldSlow && !isImmuneToWeight(entityLiving))
{
totalSpeed += ArmourCalculator.getSpeedModForWeight(entityLiving);
//Limit the slowest speed to 1%
if(totalSpeed <= minWeightSpeed)
{
totalSpeed = minWeightSpeed;
}
}
//apply speed mod
if(totalSpeed != 100F && entityLiving.onGround)
{
entityLiving.motionX *= (totalSpeed/100F);
entityLiving.motionZ *= (totalSpeed/100F);
}
}
/**
* Gets the modifer for magic resistance 1.0=no effect
*/
public static float resistMagic(EntityLivingBase user, DamageSource source)
{
float resistance = 100F;
for(int a = 0; a < 4; a ++)
{
ItemStack armour = user.getEquipmentInSlot(a+1);
if(armour != null && armour.getItem() instanceof IElementalResistance)
{
float modifier = ((IElementalResistance)armour.getItem()).getMagicResistance(armour, source);
modifier *= ArmourCalculator.sizes[3-a];
resistance -= modifier;
}
}
return resistance/100F;
}
/**
* Gets the modifer for fire resistance 1.0=no effect
*/
public static float resistFire(EntityLivingBase user, DamageSource source)
{
float resistance = 100F;
for(int a = 0; a < 4; a ++)
{
ItemStack armour = user.getEquipmentInSlot(a+1);
if(armour != null && armour.getItem() instanceof IElementalResistance)
{
float modifier = ((IElementalResistance)armour.getItem()).getFireResistance(armour, source);
modifier *= ArmourCalculator.sizes[3-a];
resistance -= modifier;
}
}
return resistance/100F;
}
public static boolean resistArrow(EntityLivingBase user, DamageSource source, float dam)
{
Entity hitter = source.getSourceOfDamage();
if(hitter == null || !isArrow(hitter))
{
return false;
}
float threshold = 0.25F;
float resistance = 1.0F;
for(int a = 0; a < 4; a ++)
{
ItemStack armour = user.getEquipmentInSlot(4-a);
if(armour != null && armour.getItem() instanceof IElementalResistance)
{
float modifier = ((IElementalResistance)armour.getItem()).getArrowDeflection(armour, source);
modifier *= ArmourCalculator.sizes[a];
resistance += modifier;
}
}
threshold *= resistance;
if(!user.worldObj.isRemote)
MineFantasyAPI.debugMsg("Arrow Damage: " + dam + " Projectile Threshold: " + threshold);
return dam <= threshold && dam > 0;
}
/**
* This tries to see if the projectile is an arrow, just so certain projectiles dont do the armour bouncy thing
*/
public static boolean isArrow(Entity hitter)
{
return hitter instanceof EntityArrow || hitter instanceof EntityArrowMF;
}
/**
* Gets the modifer for base resistance(non magic/fire)
*/
public static float resistBase(EntityLivingBase user, DamageSource source)
{
float resistance = 100F;
for(int a = 0; a < 4; a ++)
{
ItemStack armour = user.getEquipmentInSlot(a+1);
if(armour != null && armour.getItem() instanceof IElementalResistance)
{
float modifier = ((IElementalResistance)armour.getItem()).getBaseResistance(armour, source);
modifier *= ArmourCalculator.sizes[3-a];
resistance -= modifier;
}
}
return resistance/100F;
}
public static float getResistance(EntityLivingBase user, DamageSource source)
{
if(source.isFireDamage())
{
return resistFire(user, source);
}
if(source.isMagicDamage() || source == DamageSource.wither)
{
return resistMagic(user, source);
}
return resistBase(user, source);
}
/**
* Returns if a target should not be set
* @param entity the attacker
* @param target the target chosen
*/
public static boolean shouldNotAttack(Entity attacker, EntityLivingBase target)
{
//TODO aggro
return false;
}
public static void throwPlayerOffBalance(EntityPlayer entityPlayer, float balance, boolean throwDown)
{
float amplify = 30.0F;
float offsetX = -balance/2;
float offsetY = balance;
float yawBalance = offsetX*amplify;
float pitchBalance = offsetY*amplify;
entityPlayer.moveStrafing += offsetX;
if(offsetY > 0)
{
entityPlayer.moveForward += offsetY;
}
if(newBalanceSystem)
{
entityPlayer.getEntityData().setFloat("MF_Balance_Pitch", pitchBalance);
entityPlayer.getEntityData().setFloat("MF_Balance_Yaw", yawBalance);
}
else
{
entityPlayer.rotationPitch += pitchBalance;
entityPlayer.rotationYaw += yawBalance;
}
}
public static boolean newBalanceSystem = false;
public static boolean isEntityMoving(EntityLivingBase player)
{
return player.moveForward != 0 || player.moveStrafing != 0;
}
public static boolean isMelee(DamageSource source)
{
if(source.getEntity() != null && source.getSourceOfDamage() != null)
{
return source.getEntity() == source.getSourceOfDamage() && !source.isProjectile();
}
return false;
}
public static boolean isUnholyCreature(EntityLivingBase entityHit)
{
if(((EntityLivingBase)entityHit).isEntityUndead())
{
return true;
}
if(entityHit instanceof EntityWitch)
{
return true;
}
if(entityHit.getClass().getName().contains("Wraith"))
{
return true;
}
return false;
}
public static boolean isDragon(EntityLivingBase entityHit)
{
if(entityHit instanceof net.minecraft.entity.boss.EntityDragon)
{
return true;
}
if(entityHit instanceof minefantasy.mf2.entity.mob.EntityDragon)
{
return true;
}
return false;
}
/**
* Simply drops an item
*/
public static boolean tryDisarm(EntityLivingBase target)
{
return tryDisarm(null, target, false);
}
/**
* Try to disarm the target
* @param attacker Who is attacking (can be null)
* @param target Who is wielding
* @param steal If the attacker should wield the target's weapon
*/
public static boolean tryDisarm(EntityLivingBase attacker, EntityLivingBase target, boolean steal)
{
if(target.getHeldItem() == null)
{
return false;
}
if(attacker != null && steal && attacker.getHeldItem() == null)
{
attacker.setCurrentItemOrArmor(0, target.getHeldItem().copy());
}
else
{
target.entityDropItem(target.getHeldItem(), 1.0F);
}
target.setCurrentItemOrArmor(0, null);
return true;
}
/**
* Checks if Armour Debuffs are Ignored
*/
public static boolean isImmuneToWeight(EntityLivingBase entityLiving)
{
return CogworkArmour.hasPoweredSuit(entityLiving);
}
}