2f91573284
- Unifies all block protection checks into helper methods in BlockUtils which trigger all the necessary forge events and vanilla methods - Implements these checks in all the appropriate places and removes calls to EntityUtils#canDamageBlocks(...) where they are no longer needed - Changes the mine spell to respect the config settings in line with the rest of the spells - Fixes earthquake not respecting the settings at all - Adds a separate config option for dispenser block damage and updates the description for the existing settings to reflect the changes
461 lines
21 KiB
Java
461 lines
21 KiB
Java
package electroblob.wizardry.util;
|
|
|
|
import com.google.common.collect.Streams;
|
|
import electroblob.wizardry.Wizardry;
|
|
import electroblob.wizardry.data.WizardData;
|
|
import electroblob.wizardry.entity.living.ISpellCaster;
|
|
import electroblob.wizardry.item.ISpellCastingItem;
|
|
import electroblob.wizardry.spell.Spell;
|
|
import net.minecraft.entity.Entity;
|
|
import net.minecraft.entity.EntityLivingBase;
|
|
import net.minecraft.entity.effect.EntityLightningBolt;
|
|
import net.minecraft.entity.item.EntityArmorStand;
|
|
import net.minecraft.entity.monster.EntityCreeper;
|
|
import net.minecraft.entity.player.EntityPlayer;
|
|
import net.minecraft.entity.projectile.EntityArrow;
|
|
import net.minecraft.entity.projectile.EntityThrowable;
|
|
import net.minecraft.item.ItemStack;
|
|
import net.minecraft.network.datasync.DataParameter;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.util.DamageSource;
|
|
import net.minecraft.util.EnumFacing;
|
|
import net.minecraft.util.SoundCategory;
|
|
import net.minecraft.util.SoundEvent;
|
|
import net.minecraft.util.math.AxisAlignedBB;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.util.math.MathHelper;
|
|
import net.minecraft.util.math.Vec3d;
|
|
import net.minecraft.world.EnumDifficulty;
|
|
import net.minecraft.world.World;
|
|
import net.minecraftforge.event.ForgeEventFactory;
|
|
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* Contains useful static methods for retrieving and interacting with players, mobs and other entities. These methods
|
|
* used to be part of {@code WizardryUtilities}.
|
|
* @see BlockUtils
|
|
* @see MagicDamage
|
|
* @see AllyDesignationSystem
|
|
* @author Electroblob
|
|
* @since Wizardry 4.3
|
|
*/
|
|
public final class EntityUtils {
|
|
|
|
private EntityUtils(){} // No instances!
|
|
|
|
/** Changed to a constant in wizardry 2.1, since this is a lot more efficient. */
|
|
private static final DataParameter<Boolean> POWERED;
|
|
|
|
static {
|
|
// Null is passed in deliberately since POWERED is a static field.
|
|
POWERED = ObfuscationReflectionHelper.getPrivateValue(EntityCreeper.class, null, "field_184714_b");
|
|
}
|
|
|
|
/** Stores constant values for attribute modifier operations (and javadoc for what they actually do!) */
|
|
// I'm fed up with remembering these...
|
|
public static final class Operations {
|
|
|
|
private Operations(){} // No instances!
|
|
|
|
/** Adds the attribute modifier amount to the base value. */
|
|
public static final int ADD = 0;
|
|
/** Multiplies the base value by 1 plus the attribute modifier amount. Multiple modifiers are processed in
|
|
* parallel, i.e. the calculation is based on the base value and does not depend on previous modifiers. */
|
|
public static final int MULTIPLY_FLAT = 1;
|
|
/** Multiplies the base value by 1 plus the attribute modifier amount. Multiple modifiers are processed in
|
|
* series, i.e. the calculation is based on the value after previous modifiers are applied, in the order added. */
|
|
public static final int MULTIPLY_CUMULATIVE = 2;
|
|
}
|
|
|
|
// Entity retrieval
|
|
// ===============================================================================================================
|
|
|
|
/**
|
|
* Shorthand for {@link EntityUtils#getEntitiesWithinRadius(double, double, double, double, World, Class)}
|
|
* with EntityLivingBase as the entity type. This is by far the most common use for that method.
|
|
*
|
|
* @param radius The search radius
|
|
* @param x The x coordinate to search around
|
|
* @param y The y coordinate to search around
|
|
* @param z The z coordinate to search around
|
|
* @param world The world to search in
|
|
*/
|
|
public static List<EntityLivingBase> getLivingWithinRadius(double radius, double x, double y, double z, World world){
|
|
return getEntitiesWithinRadius(radius, x, y, z, world, EntityLivingBase.class);
|
|
}
|
|
|
|
/**
|
|
* Returns all entities of the specified type within the specified radius of the given coordinates. This is
|
|
* different to using a raw AABB because a raw AABB will search in a cube volume rather than a sphere. Note that
|
|
* this does not exclude any entities; if any specific entities are to be excluded this must be checked when
|
|
* iterating through the list.
|
|
*
|
|
* @see EntityUtils#getLivingWithinRadius(double, double, double, double, World)
|
|
* @param radius The search radius
|
|
* @param x The x coordinate to search around
|
|
* @param y The y coordinate to search around
|
|
* @param z The z coordinate to search around
|
|
* @param world The world to search in
|
|
* @param entityType The class of entity to search for; pass in Entity.class for all entities
|
|
*/
|
|
public static <T extends Entity> List<T> getEntitiesWithinRadius(double radius, double x, double y, double z, World world, Class<T> entityType){
|
|
AxisAlignedBB aabb = new AxisAlignedBB(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius);
|
|
List<T> entityList = world.getEntitiesWithinAABB(entityType, aabb);
|
|
for(int i = 0; i < entityList.size(); i++){
|
|
if(entityList.get(i).getDistance(x, y, z) > radius){
|
|
entityList.remove(i);
|
|
break;
|
|
}
|
|
}
|
|
return entityList;
|
|
}
|
|
|
|
/**
|
|
* Gets an entity from its UUID. If the UUID is known to belong to an {@code EntityPlayer}, use the more efficient
|
|
* {@link World#getPlayerEntityByUUID(UUID)} instead.
|
|
*
|
|
* @param world The world the entity is in
|
|
* @param id The entity's UUID
|
|
* @return The Entity that has the given UUID, or null if no such entity exists in the specified world.
|
|
*/
|
|
@Nullable
|
|
public static Entity getEntityByUUID(World world, @Nullable UUID id){
|
|
|
|
if(id == null) return null; // It would return null eventually but there's no point even looking
|
|
|
|
for(Entity entity : world.loadedEntityList){
|
|
// This is a perfect example of where you need to use .equals() and not ==. For most applications,
|
|
// this was unnoticeable until world reload because the UUID instance or entity instance is stored.
|
|
// Fixed now though.
|
|
if(entity != null && entity.getUniqueID() != null && entity.getUniqueID().equals(id)){
|
|
return entity;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the entity riding the given entity, or null if there is none. Allows for neater code now that entities
|
|
* have a list of passengers, because it is necessary to check that the list is not empty first.
|
|
*/
|
|
@Nullable
|
|
public static Entity getRider(Entity entity){
|
|
return !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) : null;
|
|
}
|
|
|
|
// Motion
|
|
// ===============================================================================================================
|
|
|
|
/**
|
|
* Undoes 1 tick's worth of velocity change due to gravity for the given entity. If the entity has no gravity,
|
|
* this method does nothing. This method is intended to be used in situations where entity gravity needs to be
|
|
* turned on and off and it is not practical to use {@link Entity#setNoGravity(boolean)}, usually if there is no
|
|
* easy way to get a reference to the entity to turn gravity back on.
|
|
*
|
|
* @param entity The entity to undo gravity for.
|
|
*/
|
|
public static void undoGravity(Entity entity){
|
|
if(!entity.hasNoGravity()){
|
|
double gravity = 0.04;
|
|
if(entity instanceof EntityThrowable) gravity = 0.03;
|
|
else if(entity instanceof EntityArrow) gravity = 0.05;
|
|
else if(entity instanceof EntityLivingBase) gravity = 0.08;
|
|
entity.motionY += gravity;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies the standard (non-enchanted) amount of knockback to the given target, using the same calculation and
|
|
* strength value (0.4) as {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}. Use in conjunction with
|
|
* {@link EntityUtils#attackEntityWithoutKnockback(Entity, DamageSource, float)} to change the source of
|
|
* knockback for an attack.
|
|
*
|
|
* @param attacker The entity that caused the knockback; the target will be pushed away from this entity
|
|
* @param target The entity to be knocked back
|
|
*/
|
|
public static void applyStandardKnockback(Entity attacker, EntityLivingBase target){
|
|
applyStandardKnockback(attacker, target, 0.4f);
|
|
}
|
|
|
|
/**
|
|
* Applies the standard knockback calculation to the given target, using the same calculation as
|
|
* {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}.
|
|
*
|
|
* @param attacker The entity that caused the knockback; the target will be pushed away from this entity
|
|
* @param target The entity to be knocked back
|
|
* @param strength The strength of the knockback
|
|
*/
|
|
public static void applyStandardKnockback(Entity attacker, EntityLivingBase target, float strength){
|
|
double dx = attacker.posX - target.posX;
|
|
double dz;
|
|
for(dz = attacker.posZ - target.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random())
|
|
* 0.01D){
|
|
dx = (Math.random() - Math.random()) * 0.01D;
|
|
}
|
|
target.knockBack(attacker, strength, dx, dz);
|
|
}
|
|
|
|
/**
|
|
* Finds the nearest space to the specified position that the given entity can teleport to without being inside one
|
|
* or more solid blocks. The search volume is twice the size of the entity's bounding box (meaning that when
|
|
* teleported to the returned position, the original destination remains within the entity's bounding box).
|
|
* @param entity The entity being teleported
|
|
* @param destination The target position to search around
|
|
* @param accountForPassengers True to take passengers into account when searching for a space, false to ignore them
|
|
* @return The resulting position, or null if no space was found.
|
|
*/
|
|
public static Vec3d findSpaceForTeleport(Entity entity, Vec3d destination, boolean accountForPassengers){
|
|
|
|
World world = entity.world;
|
|
AxisAlignedBB box = entity.getEntityBoundingBox();
|
|
|
|
if(accountForPassengers){
|
|
for(Entity passenger : entity.getPassengers()){
|
|
box = box.union(passenger.getEntityBoundingBox());
|
|
}
|
|
}
|
|
|
|
box = box.offset(destination.subtract(entity.posX, entity.posY, entity.posZ));
|
|
|
|
// All the parameters of this method are INCLUSIVE, so even the max coordinates should be rounded down
|
|
Iterable<BlockPos> cuboid = BlockPos.getAllInBox(MathHelper.floor(box.minX), MathHelper.floor(box.minY),
|
|
MathHelper.floor(box.minZ), MathHelper.floor(box.maxX), MathHelper.floor(box.maxY), MathHelper.floor(box.maxZ));
|
|
|
|
if(Streams.stream(cuboid).noneMatch(b -> world.collidesWithAnyBlock(new AxisAlignedBB(b)))){
|
|
// Nothing in the way
|
|
return destination;
|
|
|
|
}else{
|
|
// Nearby position search
|
|
double dx = box.maxX - box.minX;
|
|
double dy = box.maxY - box.minY;
|
|
double dz = box.maxZ - box.minZ;
|
|
|
|
// Minimum space required is (nx + px) blocks * (ny + py) blocks * (nz + pz) blocks
|
|
int nx = MathHelper.ceil(dx) / 2;
|
|
int px = MathHelper.ceil(dx) - nx;
|
|
int ny = MathHelper.ceil(dy) / 2;
|
|
int py = MathHelper.ceil(dy) - ny;
|
|
int nz = MathHelper.ceil(dz) / 2;
|
|
int pz = MathHelper.ceil(dz) - nz;
|
|
|
|
// Check all the blocks in and around the bounding box...
|
|
List<BlockPos> nearby = Streams.stream(BlockPos.getAllInBox(MathHelper.floor(box.minX) - 1,
|
|
MathHelper.floor(box.minY) - 1, MathHelper.floor(box.minZ) - 1,
|
|
MathHelper.floor(box.maxX) + 1, MathHelper.floor(box.maxY) + 1,
|
|
MathHelper.floor(box.maxZ) + 1)).collect(Collectors.toList());
|
|
|
|
// ... but only return positions actually inside the box
|
|
List<BlockPos> possiblePositions = Streams.stream(cuboid).collect(Collectors.toList());
|
|
|
|
// Rather than iterate over each position and check if the box fits, find all solid blocks and cut out all
|
|
// positions whose corresponding box would include them - this is waaay more efficient!
|
|
while(!nearby.isEmpty()){
|
|
|
|
BlockPos pos = nearby.remove(0);
|
|
|
|
if(world.collidesWithAnyBlock(new AxisAlignedBB(pos))){
|
|
Predicate<BlockPos> nearSolidBlock = b -> b.getX() >= pos.getX() - nx && b.getX() <= pos.getX() + px
|
|
&& b.getY() >= pos.getY() - ny && b.getY() <= pos.getY() + py
|
|
&& b.getZ() >= pos.getZ() - nz && b.getZ() <= pos.getZ() + pz;
|
|
nearby.removeIf(nearSolidBlock);
|
|
possiblePositions.removeIf(nearSolidBlock);
|
|
}
|
|
}
|
|
|
|
if(possiblePositions.isEmpty()) return null; // No space nearby
|
|
|
|
BlockPos nearest = possiblePositions.stream().min(Comparator.comparingDouble(b -> destination.squareDistanceTo(
|
|
b.getX() + 0.5, b.getY() + 0.5, b.getZ() + 0.5))).get(); // The list can't be empty
|
|
|
|
return GeometryUtils.getFaceCentre(nearest, EnumFacing.DOWN);
|
|
}
|
|
}
|
|
|
|
// Damage
|
|
// ===============================================================================================================
|
|
|
|
/**
|
|
* Attacks the given entity with the given damage source and amount, but preserving the entity's original velocity
|
|
* instead of applying knockback, as would happen with
|
|
* {@link EntityLivingBase#attackEntityFrom(DamageSource, float)} <i>(More accurately, calls that method as normal
|
|
* and then resets the entity's velocity to what it was before).</i> Handy for when you need to damage an entity
|
|
* repeatedly in a short space of time.
|
|
*
|
|
* @param entity The entity to attack
|
|
* @param source The source of the damage
|
|
* @param amount The amount of damage to apply
|
|
* @return True if the attack succeeded, false if not.
|
|
*/
|
|
public static boolean attackEntityWithoutKnockback(Entity entity, DamageSource source, float amount){
|
|
double vx = entity.motionX;
|
|
double vy = entity.motionY;
|
|
double vz = entity.motionZ;
|
|
boolean succeeded = entity.attackEntityFrom(source, amount);
|
|
entity.motionX = vx;
|
|
entity.motionY = vy;
|
|
entity.motionZ = vz;
|
|
return succeeded;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the given {@link DamageSource} is melee damage. This method makes a best guess as to whether
|
|
* the damage was from a melee attack; there is no way of testing this properly.
|
|
* @param source The damage source to be tested.
|
|
* @return True if the given damage source is melee damage, false otherwise.
|
|
*/
|
|
public static boolean isMeleeDamage(DamageSource source){
|
|
|
|
// With the exception of minions, melee damage always has the same entity for immediate/true source
|
|
if(!(source instanceof MinionDamage) && source.getImmediateSource() != source.getTrueSource()) return false;
|
|
if(source.isProjectile()) return false; // Projectile damage obviously isn't melee damage
|
|
if(source.isUnblockable()) return false; // Melee damage should always be blockable
|
|
if(!(source instanceof MinionDamage) && source instanceof IElementalDamage) return false;
|
|
if(!(source.getTrueSource() instanceof EntityLivingBase)) return false; // Only living things can melee!
|
|
|
|
if(source.getTrueSource() instanceof EntityPlayer && source.getDamageLocation() != null
|
|
&& source.getDamageLocation().distanceTo(source.getTrueSource().getPositionVector()) > ((EntityLivingBase)source
|
|
.getTrueSource()).getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue()){
|
|
return false; // Out of melee reach for players
|
|
}
|
|
|
|
// If it got through all that, chances are it's melee damage
|
|
return true;
|
|
}
|
|
|
|
// Boolean checks
|
|
// ===============================================================================================================
|
|
|
|
/**
|
|
* Returns true if the given entity is an EntityLivingBase and not an armour stand; makes the code a bit neater.
|
|
* This was added because armour stands are a subclass of EntityLivingBase, but shouldn't necessarily be treated
|
|
* as living entities - this depends on the situation. <i>The given entity can safely be cast to EntityLivingBase
|
|
* if this method returns true.</i>
|
|
*/
|
|
// In my opinion, it's a bad design choice to have armour stands extend EntityLivingBase directly - it would be
|
|
// better to make a parent class which is extended by both armour stands and EntityLivingBase and contains only
|
|
// the code required by both.
|
|
public static boolean isLiving(Entity entity){
|
|
return entity instanceof EntityLivingBase && !(entity instanceof EntityArmorStand);
|
|
}
|
|
|
|
/**
|
|
* Checks if the given player is opped on the given server. If the server is a singleplayer or LAN server, this
|
|
* means they have cheats enabled.
|
|
*/
|
|
public static boolean isPlayerOp(EntityPlayer player, MinecraftServer server){
|
|
return server.getPlayerList().getOppedPlayers().getEntry(player.getGameProfile()) != null;
|
|
}
|
|
|
|
/**
|
|
* Checks that the given entity is allowed to damage blocks in the given world. If the entity is a player or null,
|
|
* this checks the player block damage config setting and (for players only) the {@code PlayerCapabilities} object,
|
|
* otherwise it posts a mob griefing event and returns the result.
|
|
* <p></p>
|
|
* This method only checks whether the entity can damage blocks <i>in general</i>. It can be useful in certain
|
|
* situations where breaking of individual blocks is out of the caller's control (e.g. explosions, which have their
|
|
* own events anyway) or as an optimisation where multiple blocks are affected. In most cases, however, it is
|
|
* advisable to use the location-specific methods in {@link BlockUtils}, as they call this method anyway and allow
|
|
* for better compatibility with other mods, particularly region protection mods.
|
|
*/
|
|
public static boolean canDamageBlocks(@Nullable Entity entity, World world){
|
|
if(entity == null) return Wizardry.settings.dispenserBlockDamage;
|
|
else if(entity instanceof EntityPlayer) return ((EntityPlayer)entity).isAllowEdit() && Wizardry.settings.playerBlockDamage;
|
|
return ForgeEventFactory.getMobGriefingEvent(world, entity);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given caster is currently casting the given spell by any means. This method is intended to
|
|
* eliminate the long and cumbersome wand use checking in event handlers, which often missed out spells cast by
|
|
* means other than wands.
|
|
* @param caster The potential spell caster, which may be a player or an {@link ISpellCaster}. Any other entity will
|
|
* cause this method to always return false.
|
|
* @param spell The spell to check for. The spell must be continuous or this method will always return false.
|
|
* @return True if the caster is currently casting the given spell through any means, false otherwise.
|
|
*/
|
|
// The reason this is a boolean check is that actually returning a spell presents a problem: players can cast two
|
|
// continuous spells at once, one via commands and one via an item, so which do you choose? Since the main point was
|
|
// to check for specific spells, it seems more useful to do it this way.
|
|
public static boolean isCasting(EntityLivingBase caster, Spell spell){
|
|
|
|
if(!spell.isContinuous) return false;
|
|
|
|
if(caster instanceof EntityPlayer){
|
|
|
|
WizardData data = WizardData.get((EntityPlayer)caster);
|
|
|
|
if(data != null && data.currentlyCasting() == spell) return true;
|
|
|
|
if(caster.isHandActive() && caster.getItemInUseMaxCount() >= spell.getChargeup()){
|
|
|
|
ItemStack stack = caster.getHeldItem(caster.getActiveHand());
|
|
|
|
if(stack.getItem() instanceof ISpellCastingItem && ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) == spell){
|
|
// Don't do this, it interferes with stuff! We effectively already tested this with caster.isHandActive() anyway
|
|
// && ((ISpellCastingItem)stack.getItem()).canCast(stack, spell, (EntityPlayer)caster,
|
|
// EnumHand.MAIN_HAND, 0, new SpellModifiers())){
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}else if(caster instanceof ISpellCaster){
|
|
if(((ISpellCaster)caster).getContinuousSpell() == spell) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Misc
|
|
// ===============================================================================================================
|
|
|
|
/** Returns the default aiming arror used by skeletons for the given difficulty. For reference, these are: Easy - 10,
|
|
* Normal - 6, Hard - 2, Peaceful - 10 (rarely used). */
|
|
public static int getDefaultAimingError(EnumDifficulty difficulty){
|
|
switch(difficulty){
|
|
case EASY: return 10;
|
|
case NORMAL: return 6;
|
|
case HARD: return 2;
|
|
default: return 10; // Peaceful counts as easy; the only time this is used is when a player attacks a (good) wizard.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Turns the given creeper into a charged creeper. In 1.10, this requires reflection since the DataManager keys are
|
|
* private. (You <i>could</i> call {@link EntityCreeper#onStruckByLightning(EntityLightningBolt)} and then heal it
|
|
* and extinguish it, but that's a bit awkward, and it'll trigger events and stuff...)
|
|
*/
|
|
// The reflection here only gets done once to initialise the POWERED field, so it's not a performance issue at all.
|
|
public static void chargeCreeper(EntityCreeper creeper){
|
|
creeper.getDataManager().set(POWERED, true);
|
|
}
|
|
|
|
// No point allowing anything other than players for these methods since other entities can use Entity#playSound.
|
|
|
|
/**
|
|
* Shortcut for
|
|
* {@link World#playSound(EntityPlayer, double, double, double, SoundEvent, SoundCategory, float, float)} where the
|
|
* player is null but the x, y and z coordinates are those of the passed in player. Use in preference to
|
|
* {@link EntityPlayer#playSound(SoundEvent, float, float)} if there are client-server discrepancies.
|
|
*/
|
|
public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, SoundCategory category, float volume,
|
|
float pitch){
|
|
player.world.playSound(null, player.posX, player.posY, player.posZ, sound, category, volume, pitch);
|
|
}
|
|
|
|
/**
|
|
* See {@link EntityUtils#playSoundAtPlayer(EntityPlayer, SoundEvent, SoundCategory, float, float)}. Category
|
|
* defaults to {@link SoundCategory#PLAYERS}.
|
|
*/
|
|
public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, float volume, float pitch){
|
|
player.world.playSound(null, player.posX, player.posY, player.posZ, sound, SoundCategory.PLAYERS, volume, pitch);
|
|
}
|
|
|
|
}
|