package electroblob.wizardry.spell; import electroblob.wizardry.Wizardry; import electroblob.wizardry.registry.WizardryItems; import electroblob.wizardry.util.AllyDesignationSystem; import electroblob.wizardry.util.EntityUtils; import electroblob.wizardry.util.SpellModifiers; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import javax.annotation.Nullable; import java.util.Comparator; import java.util.List; /** * Generic superclass for all spells which affect entities in a radius around the caster. This allows all * the relevant code to be centralised. This class differs from most other spell superclasses in that it is abstract * and as such must be subclassed to define what the spell actually does. This is because spells of this kind do a wider * variety of different things, so it does not make sense to define more specific functions in this class since they * would be redundant in the majority of cases. *
* N.B. The abstract methods in this class have a {@link Nullable} caster parameter (the caster is null when the * spell is cast by a dispenser). When implementing this method, be sure to check whether the caster is {@code null} * and deal with it appropriately. * * Properties added by this type of spell: {@link Spell#EFFECT_RADIUS} * * By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastBy(EntityLiving, boolean)} * * By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastBy(TileEntityDispenser)} * * By default, this type of spell requires a packet to be sent. {@link Spell#requiresPacket()} * * @author Electroblob * @since Wizardry 4.3 */ public abstract class SpellAreaEffect extends Spell { /** True if this spell should target allies of the caster instead of hostiles. Positional casting always targets * everything, regardless of this setting. */ protected boolean targetAllies = false; /** True if this spell should succeed even if no entities are affected, false if at least one entity must be affected. */ protected boolean alwaysSucceed = false; /** The average number of particles to spawn per block in this spell's area of effect. */ protected float particleDensity = 0.65f; public SpellAreaEffect(String name, EnumAction action, boolean continuous){ this(Wizardry.MODID, name, action, continuous); } public SpellAreaEffect(String modID, String name, EnumAction action, boolean continuous){ super(modID, name, action, continuous); this.addProperties(EFFECT_RADIUS); this.npcSelector((e, o) -> true); } /** * Sets whether this spell should target allies of the caster instead of hostiles. Positional casting always targets * everything, regardless of this setting. * @param targetAllies True to call {@link SpellAreaEffect#affectEntity(World, Vec3d, EntityLivingBase, EntityLivingBase, int, int, SpellModifiers)} * on allies of the caster, false to call it on entities considered hostile to the caster (not * necessarily collectively-exhaustive; some entities may belong to neither category). * @return The spell instance, allowing this method to be chained onto the constructor. */ public SpellAreaEffect targetAllies(boolean targetAllies) { this.targetAllies = targetAllies; return this; } /** * Sets whether this spell this spell should succeed even if no entities are affected. * @param alwaysSucceed True if this spell should succeed even if no entities are affected, false if * {@link SpellAreaEffect#affectEntity(World, Vec3d, EntityLivingBase, EntityLivingBase, int, int, SpellModifiers)} * must return true at least once for the spell to succeed * @return The spell instance, allowing this method to be chained onto the constructor. */ public SpellAreaEffect alwaysSucceed(boolean alwaysSucceed) { this.alwaysSucceed = alwaysSucceed; return this; } /** * Sets the number of particles to spawn per block for this spell. * @param particleDensity The average number of particles to spawn per block in this spell's area of effect. * @return The spell instance, allowing this method to be chained onto the constructor. */ public SpellAreaEffect particleDensity(float particleDensity) { this.particleDensity = particleDensity; return this; } @Override public boolean canBeCastBy(TileEntityDispenser dispenser){ return true; } @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ boolean result = findAndAffectEntities(world, caster.getPositionVector(), caster, ticksInUse, modifiers); if(result) this.playSound(world, caster, ticksInUse, -1, modifiers); return result; } @Override public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ boolean result = findAndAffectEntities(world, caster.getPositionVector(), caster, ticksInUse, modifiers); if(result) this.playSound(world, caster, ticksInUse, -1, modifiers); return result; } @Override public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ boolean result = findAndAffectEntities(world, new Vec3d(x, y, z), null, ticksInUse, modifiers); if(result) this.playSound(world, x, y, z, ticksInUse, -1, modifiers); return result; } /** Takes care of the shared stuff for the three casting methods. This is mainly for internal use. */ protected boolean findAndAffectEntities(World world, Vec3d origin, @Nullable EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){ double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade); List