/** Copyright (C) <2015> <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.entity.ai; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.util.MathHelper; /** * * AI for mobs with ranged magical attacks which require charging up for some * number of ticks before they can be released. * * Caster must first be within range and, after enough time passes since the last attack, * will begin charging up the spell. While charging, the entity will not move until the * spell is cast, calling {@link IMagicUser#castRangedSpell}. * * If the spell needs to be interrupted, for example if the casting entity is damaged, * call {@link #interruptCasting}. * * Uses MutexBit 8 + 4 + 1. * */ public class EntityAIRangedMagic extends EntityAIBase { /** Same entity as caster, but as an instance of EntityLiving */ private final EntityLiving entity; /** Same as entity, but as an instance of IMagicUser */ private final IMagicUser caster; /** Timer for casting time */ private int castingTimer; /** Minimum time between casting attempts */ private final int minCastInterval; /** Maximum time between casting attempts */ private final int maxCastInterval; /** Time since last attack */ private int attackTimer; /** Distance at which max damage will be inflicted, with damage decreasing moving closer to the target */ private final double minDistance; /** Distance squared at which a target is considered 'close enough' to attack */ private final double minDistanceSq; /** Current attack target */ private EntityLivingBase attackTarget; /** Number of ticks target has been out of sight */ private int unseenTimer; /** Maximum time target can remain out of sight before caster aborts spell */ private static final int MAX_TIME_UNSEEN = 10; /** * @param entity The spell-casting entity * @param minCastInterval Minimum interval between spell-casting attempts * @param maxCastInterval Maximum interval between spell-casting attempts * @param distance Interval between casting attempts reaches max at this distance, * and is also the casting range for attacking targets */ public <T extends EntityLiving & IMagicUser> EntityAIRangedMagic(T entity, int minCastInterval, int maxCastInterval, double distance) { this.entity = entity; this.caster = entity; this.minCastInterval = minCastInterval; this.maxCastInterval = maxCastInterval; this.minDistance = distance; this.minDistanceSq = (distance * distance); this.setMutexBits(15); // incompatible with swimming, teleporting, and bit1 and bit2 tasks } /** * Forcefully interrupts the spell-casting attempt, such as when taking damage */ public void interruptCasting() { resetTask(); } @Override public boolean continueExecuting() { if (attackTarget == null) { return false; } else if (castingTimer < 1) { return false; } else if (!entity.getEntitySenses().canSee(attackTarget) && ++unseenTimer > MAX_TIME_UNSEEN) { unseenTimer = 0; return false; } else if (!caster.canContinueCasting()) { return false; } return entity.getDistanceSq(attackTarget.posX, attackTarget.getEntityBoundingBox().minY, attackTarget.posZ) < minDistanceSq; } @Override public boolean shouldExecute() { EntityLivingBase target = entity.getAttackTarget(); if (target == null) { return false; } attackTarget = target; double d = entity.getDistanceSq(attackTarget.posX, attackTarget.getEntityBoundingBox().minY, attackTarget.posZ); boolean flag = entity.getEntitySenses().canSee(attackTarget); if (!flag) { interruptCasting(); // calls resetTask return false; } // Handle attackTimer here so other tasks such as teleportation have a chance to execute if (castingTimer == 0) { entity.getLookHelper().setLookPositionWithEntity(attackTarget, 30.0F, 30.0F); if (--attackTimer == 0) { if (d > minDistanceSq || !flag) { return false; } // task finally starts executing if spell begins charging castingTimer = caster.beginSpellCasting(attackTarget); return castingTimer > 0; } else if (attackTimer < 0) { float f = (float)(MathHelper.sqrt_double(d) / minDistance); attackTimer = MathHelper.floor_float(f * (float)(maxCastInterval - minCastInterval) + (float) minCastInterval); } } return false; } @Override public void startExecuting() { entity.getNavigator().clearPathEntity(); } @Override public void resetTask() { caster.stopCasting(); attackTarget = null; castingTimer = 0; unseenTimer = 0; } @Override public void updateTask() { if (castingTimer > 0) { --castingTimer; entity.getLookHelper().setLookPositionWithEntity(attackTarget, 30.0F, 30.0F); if (castingTimer == 0) { double d = entity.getDistanceSq(attackTarget.posX, attackTarget.getEntityBoundingBox().minY, attackTarget.posZ); float f = (float)(MathHelper.sqrt_double(d) / minDistance); float f1 = MathHelper.clamp_float(f, 0.1F, 1.0F); caster.castRangedSpell(attackTarget, f1); attackTimer = MathHelper.floor_float(f * (float)(maxCastInterval - minCastInterval) + (float) minCastInterval); } } } }