package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.entity.living.EntityEvilWizard; import electroblob.wizardry.item.SpellActions; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.registry.WizardryPotions; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.ParticleBuilder; import electroblob.wizardry.util.ParticleBuilder.Type; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.*; import net.minecraft.entity.item.EntityArmorStand; import net.minecraft.entity.passive.EntitySheep; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.SoundEvents; import net.minecraft.item.EnumDyeColor; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.potion.PotionEffect; import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent; import net.minecraftforge.event.entity.living.PotionEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import java.util.Arrays; import java.util.List; @Mod.EventBusSubscriber public class MindControl extends SpellRay { /** The NBT tag name for storing the controlling entity's UUID in the target's tag compound. */ public static final String NBT_KEY = "controllingEntity"; public MindControl(){ super("mind_control", SpellActions.POINT, false); addProperties(EFFECT_DURATION); } @Override public boolean canBeCastBy(EntityLiving npc, boolean override) { return false; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser) { return false; } @Override protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ if(EntityUtils.isLiving(target)){ if(!canControl(target)){ if(!world.isRemote){ if(caster instanceof EntityPlayer){ // Adds a message saying that the player/boss entity/wizard resisted mind control ((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true); } } }else if(target instanceof EntityLiving){ if(!world.isRemote){ if(!MindControl.findMindControlTarget((EntityLiving)target, caster, world)){ // If no valid target was found, this just acts like mind trick. ((EntityLiving)target).setAttackTarget(null); } } if(target instanceof EntitySheep && ((EntitySheep)target).getFleeceColor() == EnumDyeColor.BLUE && EntityUtils.canDamageBlocks(caster, world)){ if(!world.isRemote) ((EntitySheep)target).setFleeceColor(EnumDyeColor.RED); // Wololo! world.playSound(caster.posX, caster.posY, caster.posZ, SoundEvents.EVOCATION_ILLAGER_PREPARE_WOLOLO, WizardrySounds.SPELLS, 1, 1, false); } if(!world.isRemote) startControlling((EntityLiving)target, caster, (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))); } if(world.isRemote){ for(int i=0; i<10; i++){ ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX, target.getEntityBoundingBox().minY + target.getEyeHeight(), target.posZ, 0.25, false) .clr(0.8f, 0.2f, 1.0f).spawn(world); ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX, target.getEntityBoundingBox().minY + target.getEyeHeight(), target.posZ, 0.25, false) .clr(0.2f, 0.04f, 0.25f).spawn(world); } } return true; } return false; } @Override protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){ return false; } @Override protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){ return false; } /** Returns true if the given entity can be mind controlled (i.e. is not a player, npc, evil wizard or boss). */ public static boolean canControl(Entity target){ return target instanceof EntityLiving && target.isNonBoss() && !(target instanceof INpc) && !(target instanceof EntityEvilWizard) && !Arrays.asList(Wizardry.settings.mindControlTargetsBlacklist) .contains(EntityList.getKey(target.getClass())); } public static void startControlling(EntityLiving target, EntityLivingBase controller, int duration){ target.getEntityData().setUniqueId(NBT_KEY, controller.getUniqueID()); target.addPotionEffect(new PotionEffect(WizardryPotions.mind_control, duration, 0)); } /** * Finds the nearest creature to the given target which it is allowed to attack according to the given caster and * sets it as the target's attack target. Handles both new and old AI and takes follow range into account. Defined * here so it can be used both in the spell itself and in the potion effect (event handler). * * @param target The entity being mind controlled * @param caster The entity doing the controlling * @param world The world to look for targets in * @return True if a new target was found and set, false if not. */ public static boolean findMindControlTarget(EntityLiving target, EntityLivingBase caster, World world){ // As of 1.1, this now uses the creature's follow range, like normal targeting. It also // no longer lasts until the creature dies; instead it is a potion effect which continues to // set the target until it wears off. List possibleTargets = EntityUtils.getLivingWithinRadius( target.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(), target.posX, target.posY, target.posZ, world); possibleTargets.remove(target); possibleTargets.remove(target.getRidingEntity()); possibleTargets.removeIf(e -> e instanceof EntityArmorStand); EntityLivingBase newAITarget = null; for(EntityLivingBase possibleTarget : possibleTargets){ if(AllyDesignationSystem.isValidTarget(caster, possibleTarget) && (newAITarget == null || target.getDistance(possibleTarget) < target.getDistance(newAITarget))){ newAITarget = possibleTarget; } } if(newAITarget != null){ target.setAttackTarget(newAITarget); return true; } return false; } /** Retrieves the given entity's controller and decides whether it needs to search for a target. */ private static void processTargeting(World world, EntityLiving entity, EntityLivingBase currentTarget){ if(entity.isPotionActive(WizardryPotions.mind_control) && MindControl.canControl(entity)){ NBTTagCompound entityNBT = entity.getEntityData(); if(entityNBT != null && entityNBT.hasUniqueId(MindControl.NBT_KEY)){ Entity caster = EntityUtils.getEntityByUUID(world, entityNBT.getUniqueId(MindControl.NBT_KEY)); if(caster instanceof EntityLivingBase){ // If the current target is already a valid mind control target, nothing happens. if(AllyDesignationSystem.isValidTarget(caster, currentTarget)) return; if(MindControl.findMindControlTarget(entity, (EntityLivingBase)caster, world)){ // If it worked, skip setting the target to null. return; } } } // If the caster couldn't be found or no valid target was found, this just acts like mind trick. entity.setAttackTarget(null); } } @SubscribeEvent public static void onLivingUpdateEvent(LivingUpdateEvent event){ // Tries to find a new target for mind-controlled creatures that do not currently have one // When the mind-controlled creature does have a target, LivingSetAttackTargetEvent is used instead since it is // more efficient (because it only fires when the entity tries to set a target) // Of course, in survival this code is unlikely to be used much because the entity will always try to target the // player and hence will rarely have no target. if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && event.getEntityLiving() instanceof EntityLiving){ EntityLiving entity = (EntityLiving)event.getEntityLiving(); // Processes targeting if the current target is null or has died if(((EntityLiving)event.getEntityLiving()).getAttackTarget() == null || !((EntityLiving)event.getEntityLiving()).getAttackTarget().isEntityAlive()){ processTargeting(entity.world, entity, null); } } } @SubscribeEvent public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ // The != null check prevents infinite loops with mind trick if(event.getTarget() != null && event.getEntityLiving() instanceof EntityLiving) processTargeting(event.getEntity().world, (EntityLiving)event.getEntityLiving(), event.getTarget()); } @SubscribeEvent public static void onPotionExpiryEvent(PotionEvent.PotionExpiryEvent event){ onEffectEnd(event.getPotionEffect(), event.getEntity()); } @SubscribeEvent public static void onPotionExpiryEvent(PotionEvent.PotionRemoveEvent event){ onEffectEnd(event.getPotionEffect(), event.getEntity()); } private static void onEffectEnd(PotionEffect effect, Entity entity){ if(effect != null && effect.getPotion() == WizardryPotions.mind_control && entity instanceof EntityLiving){ ((EntityLiving)entity).setAttackTarget(null); // End effect ((EntityLiving)entity).setRevengeTarget(null); // End effect } } }