package mhfc.net.common.ai.general; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import com.google.common.base.Preconditions; import mhfc.net.common.entity.type.EntityMHFCBase; import mhfc.net.common.helper.DamageHelper; import mhfc.net.common.util.world.WorldHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.DamageSource; import net.minecraft.util.Vec3; public class AIUtils { /** * A constant to convert from radiant to degrees.<br> * <code>rad*RAD2DEG == deg</code> */ public static final double RAD2DEG = 180d / Math.PI; /** * A constant to convert from degrees to radiant.<br> * <code>rad*DEG2RAD == deg</code> */ public static final double DEG2RAD = Math.PI / 180d; public static interface IDamageCalculator { /** * Given an entity e calculates the damage done to it. * * @param e * @return */ public float accept(Entity e); } public static abstract class DecisiveDamageCalculator implements IDamageCalculator { /** * Decides whether the given Entity should be damaged */ public abstract boolean shouldDamage(Entity e); /** * Returns the damage that should be dealt to the entity e. */ public abstract float damage(Entity e); @Override public float accept(Entity e) { if (shouldDamage(e)) { return damage(e); } return 0f; } } /** * This special damage calculator remembers which entities were damaged by it and only damages entities once until * it is reset. */ public static class MemoryDamageCalculator extends DecisiveDamageCalculator { private final Set<Entity> damagedEntities = new HashSet<>(); private IDamageCalculator forward; public MemoryDamageCalculator(IDamageCalculator otherCalculator) { forward = Objects.requireNonNull(otherCalculator); } @Override public boolean shouldDamage(Entity e) { return !damagedEntities.contains(e); } @Override public float damage(Entity e) { damagedEntities.add(e); return forward.accept(e); } public void reset() { damagedEntities.clear(); } } /** * A default implementation for {@link IDamageCalculator}. It decides * * @author WorldSEnder * */ private static class DefDamageCalculator implements IDamageCalculator { private float player; private float wyvern; private float rest; public DefDamageCalculator(float player, float wyvern, float rest) { this.player = player; this.wyvern = wyvern; this.rest = rest; } @Override public float accept(Entity trgt) { if (trgt instanceof EntityPlayer) { return player; } else if (trgt instanceof EntityMHFCBase) { return wyvern; } return rest; } } public static class DamageCalculatorHelper { private enum Type { MEMORY, DEFAULT, NONE, UNKNOWN; } private IDamageCalculator calculator; private Type type; public DamageCalculatorHelper() { type = Type.NONE; } public void setDamageCalculator(IDamageCalculator dmg) { calculator = dmg; if (calculator == null) { type = Type.NONE; } else { type = Type.UNKNOWN; } } public void setDefaultDamageCalculator(float player, float wyvern, float rest) { calculator = AIUtils.defaultDamageCalc(player, wyvern, rest); type = Type.DEFAULT; } /** * Create a new MemoryDamageCalculator using the provided calculator */ public void setMemoryDamageCalculator(IDamageCalculator dmg) { calculator = new MemoryDamageCalculator(dmg); type = Type.MEMORY; } /** * Create a new MemoryDamageCalculator using a default calculator with the provided damage values. */ public void setMemoryDamageCalculator(float player, float wyvern, float rest) { calculator = new MemoryDamageCalculator(AIUtils.defaultDamageCalc(player, wyvern, rest)); type = Type.MEMORY; } /** * Resets the damage calculator if it is of memory type. Does nothing if not. */ public void reset() { if (calculator == null) { return; } if (type == Type.MEMORY) { ((MemoryDamageCalculator) calculator).reset(); } if (type == Type.UNKNOWN) { if (calculator instanceof MemoryDamageCalculator) { ((MemoryDamageCalculator) calculator).reset(); } } } public IDamageCalculator getCalculator() { return calculator; } } /** * Damages all entities who collide with the given Entity * * @param ai * the entity to check collision against */ public static void damageCollidingEntities(EntityLivingBase ai, final float damage) { damageCollidingEntities(ai, e -> damage); } /** * The same as {@link #damageCollidingEntities(EntityLivingBase, float)} but with an {@link IDamageCalculator} that * returns the damage that is inflicted for every entity. * * @param ai * the entity to collide with * @param damageCalc * a damage calculator * @see IDamageCalculator */ public static void damageCollidingEntities(EntityLivingBase ai, IDamageCalculator damageCalc) { List<Entity> collidingEntities = WorldHelper.collidingEntities(ai); for (Entity trgt : collidingEntities) { float damage = damageCalc.accept(trgt); if(trgt instanceof EntityPlayer || trgt instanceof EntityMHFCBase){ trgt.attackEntityFrom(DamageSource.causeMobDamage(ai), damage); } else{ trgt.attackEntityFrom(DamageHelper.anti , damage); } } } public static IDamageCalculator defaultDamageCalc(final float player, final float wyvern, final float rest) { return new DefDamageCalculator(player, wyvern, rest); } /** * Gives the yaw of a vector, or throws an exception of no yaw exists * * @param vec * @return * @see #lookVecToYawUnsafe(Vec3) */ public static float lookVecToYaw(Vec3 vec) { float unsafeResult = lookVecToYawUnsafe(vec); if (Float.isNaN(unsafeResult)) { throw new IllegalArgumentException("The vector may not have zero length"); } return unsafeResult; } /** * Gives the yaw of a vector, or NaN if the no yaw exists. * * @see #lookVecToYaw(Vec3) */ public static float lookVecToYawUnsafe(Vec3 vec) { Objects.requireNonNull(vec); if (vec.lengthVector() == 0) { return Float.NaN; } vec = vec.normalize(); if (vec.xCoord == 0 && vec.zCoord == 0) { return Float.NaN; } double pitch_rad = Math.asin(vec.yCoord); double cos_pitch = Math.cos(pitch_rad); double adjusted_z = vec.zCoord / cos_pitch; // Corrects rounding issues adjusted_z = Math.max(-1.0, Math.min(1.0, adjusted_z)); double yaw_rad = Math.acos(adjusted_z); yaw_rad *= Math.signum(-vec.xCoord); return normalizeAngle((float) Math.toDegrees(yaw_rad)); } /** * Returns the yaw that gives the direction of the target but with a maximum absolute value of max * * @param look * A normalized look vector * @param target * A normalized vector, the target for the look * @param maxAbsoluteChange * The maximum allowed change of the look in degrees. Must be greater than zero * @return Float.NaN if look doesn't represent a vector with yaw, the current yaw if target can't be turned to * (maybe right above), else the new yaw. */ public static float modifyYaw(Vec3 look, Vec3 target, float maxAbsoluteChange) { Preconditions.checkArgument(maxAbsoluteChange >= 0, "max change must be greater than 0"); float yaw = lookVecToYaw(look); float tarYaw = lookVecToYaw(target); if (Float.isNaN(yaw)) { return Float.NaN; } else if (Float.isNaN(tarYaw)) { return yaw; } float diff = tarYaw - yaw; diff = normalizeAngle(diff); diff = Math.signum(diff) * Math.min(Math.abs(diff), maxAbsoluteChange); return normalizeAngle(yaw + diff); } /** * Transforms an angle into the Minecraft specific angle between -180 and 180 degrees. */ public static float normalizeAngle(float yaw) { yaw = yaw % 360; if (yaw > 180) { return yaw - 360; } else if (yaw < -180) { return yaw + 360; } else { return yaw; } } /** * Determines if the length of the direction vector lies between the arguments. Both sides of the range are * inclusive. */ public static boolean isInDistance(Vec3 direction, double minDistance, double maxDistance) { double distance = direction.lengthVector(); return distance >= minDistance && distance <= maxDistance; } /** * Returns the yaw under which the given target is viewed by the actor. Negative values represent the left side of * the actor, positive ones the right. */ public static float getViewingAngle(EntityLiving actor, Entity target) { Vec3 targetVector = WorldHelper.getVectorToTarget(actor, target); return getViewing(actor, targetVector); } public static float getViewingAngle(EntityLiving actor, Vec3 point) { Vec3 pos = WorldHelper.getEntityPositionVector(actor); Vec3 targetVector = pos.subtract(point); return getViewing(actor, targetVector); } /** * @param actor * @param toTarget * @return the yaw the target is viewed at, or NaN if no such yaw exists. */ private static float getViewing(EntityLiving actor, Vec3 toTarget) { Vec3 lookVector = actor.getLookVec(); float yaw = lookVecToYawUnsafe(lookVector); if (Float.isNaN(yaw)) { return yaw; } float tarYaw = lookVecToYawUnsafe(toTarget.normalize()); if (Float.isNaN(tarYaw)) { return tarYaw; } return normalizeAngle(tarYaw - yaw); } public static List<AxisAlignedBB> gatherOverlappingBounds(AxisAlignedBB bounds, Entity entity) { int minX = (int) Math.floor(bounds.minX), // maxX = (int) Math.ceil(bounds.maxX); int minY = (int) Math.floor(bounds.minY), // maxY = (int) Math.ceil(bounds.maxY); int minZ = (int) Math.floor(bounds.minZ), // maxZ = (int) Math.ceil(bounds.maxZ); List<AxisAlignedBB> list = new ArrayList<>(); for (int xC = minX; xC <= maxX; xC++) { for (int yC = minY; yC <= maxY; yC++) { for (int zC = minZ; zC <= maxZ; zC++) { entity.worldObj.getBlock(xC, yC, zC) .addCollisionBoxesToList(entity.worldObj, xC, yC, zC, bounds, list, entity); } } } return list; } }