package com.bergerkiller.bukkit.common.controller; import java.util.List; import org.bukkit.Sound; import org.bukkit.block.BlockFace; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Vehicle; import org.bukkit.event.entity.EntityCombustEvent; import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; import net.minecraft.server.AxisAlignedBB; import net.minecraft.server.Block; import net.minecraft.server.Blocks; import net.minecraft.server.DamageSource; import net.minecraft.server.Entity; import com.bergerkiller.bukkit.common.entity.CommonEntity; import com.bergerkiller.bukkit.common.entity.CommonEntityController; import com.bergerkiller.bukkit.common.entity.nms.NMSEntityHook; import com.bergerkiller.bukkit.common.internal.CommonNMS; import com.bergerkiller.bukkit.common.reflection.classes.EntityRef; import com.bergerkiller.bukkit.common.utils.CommonUtil; import com.bergerkiller.bukkit.common.utils.MathUtil; public class EntityController<T extends CommonEntity<?>> extends CommonEntityController<T> { /** * Binds this Entity Controller to an Entity. * This is called from elsewhere, and should be ignored entirely. * * @param entity to bind with */ @SuppressWarnings("unchecked") public final void bind(CommonEntity<?> entity) { if (this.entity != null) { this.onDetached(); } this.entity = (T) entity; if (this.entity != null) { final Object handle = this.entity.getHandle(); if (handle instanceof NMSEntityHook) { ((NMSEntityHook) handle).setController(this); } if (entity.isSpawned()) { this.onAttached(); } } } /** * Called when this Entity dies (could be called more than one time) */ public void onDie() { entity.getHandle(NMSEntityHook.class).super_die(); } /** * Called every tick to update the entity */ public void onTick() { entity.getHandle(NMSEntityHook.class).super_h(); } /** * Called when the entity is interacted by something * * @param interacter that interacted * @return True if interaction occurred, False if not */ public boolean onInteractBy(HumanEntity interacter) { return entity.getHandle(NMSEntityHook.class).super_c(CommonNMS.getNative(interacter)); } /** * Called when the entity is damaged by something * * @param damageSource of the damage * @param damage amount */ public void onDamage(com.bergerkiller.bukkit.common.wrappers.DamageSource damageSource, double damage) { entity.getHandle(NMSEntityHook.class).super_damageEntity((DamageSource) damageSource.getHandle(), (float) damage); } /** * Handles the collision of this minecart with another Entity * * @param e entity with which is collided * @return True if collision is allowed, False if it is ignored */ public boolean onEntityCollision(org.bukkit.entity.Entity e) { return true; } /** * Handles the collision of this minecart with a Block * * @param block with which this minecart collided * @param hitFace of the block that the minecart hit * @return True if collision is allowed, False if it is ignored */ public boolean onBlockCollision(org.bukkit.block.Block block, BlockFace hitFace) { return true; } /** * Fired when the entity is getting burned by something * * @param damage dealt */ public void onBurnDamage(double damage) { entity.getHandle(NMSEntityHook.class).super_burn((float) damage); } /** * Gets whether this Entity Controller allows players to take this Entity with them. * With this enabled, players take the vehicle with them. * To disable this default behavior, override this method. * * @return True if players can take the entity with them, False if not */ public boolean isPlayerTakable() { return true; } /** * Gets the localized name of this Entity. Override this method to change the name. * * @return Localized name */ public String getLocalizedName() { return entity.getHandle(NMSEntityHook.class).super_getName(); } public void onPush(double dx, double dy, double dz) { entity.getHandle(NMSEntityHook.class).super_g(dx, dy, dz); } /** * Performs Entity movement logic, to move the Entity and handle collisions * * @param dx offset to move * @param dy offset to move * @param dz offset to move */ public void onMove(double dx, double dy, double dz) { final Entity handle = entity.getHandle(Entity.class); if (handle.X) { handle.boundingBox.d(dx, dy, dz); handle.locX = CommonNMS.getMiddleX(handle.boundingBox); handle.locY = (handle.boundingBox.b + (double) handle.height) - (double) handle.W; handle.locZ = CommonNMS.getMiddleZ(handle.boundingBox); } else { handle.W *= 0.4f; final double oldLocX = handle.locX; final double oldLocY = handle.locY; final double oldLocZ = handle.locZ; if (EntityRef.justLanded.get(handle)) { EntityRef.justLanded.set(handle, false); dx *= 0.25; dy *= 0.05; dz *= 0.25; entity.vel.setZero(); } // Don't perform any movement updates when not moving if (dx == 0 && dy == 0 && dz == 0 && !entity.hasPassenger() && !entity.isInsideVehicle()) { onPostMove(0.0, 0.0, 0.0); return; } // Perform movement updates final double oldDx = dx; final double oldDy = dy; final double oldDz = dz; AxisAlignedBB axisalignedbb = handle.boundingBox.clone(); List<AxisAlignedBB> list = EntityControllerCollisionHelper.getCollisions(this, handle.boundingBox.a(dx, dy, dz)); // Collision testing using Y for (AxisAlignedBB aabb : list) { dy = aabb.b(handle.boundingBox, dy); } handle.boundingBox.d(0.0, dy, 0.0); if (!handle.J && oldDy != dy) { dx = dy = dz = 0.0; } boolean isOnGround = handle.onGround || oldDy != dy && oldDy < 0.0; // Collision testing using X for (AxisAlignedBB aabb : list) { dx = aabb.a(handle.boundingBox, dx); } handle.boundingBox.d(dx, 0.0, 0.0); if (!handle.J && oldDx != dx) { dx = dy = dz = 0.0; } // Collision testing using Z for (AxisAlignedBB aabb : list) { dz = aabb.c(handle.boundingBox, dz); } handle.boundingBox.d(0.0, 0.0, dz); if (!handle.J && oldDz != dz) { dx = dy = dz = 0.0; } double moveDx; double moveDy; double moveDz; if (handle.Y > 0.0f && handle.Y < 0.05f && isOnGround && (oldDx != dx || oldDz != dz)) { moveDx = dx; moveDy = dy; moveDz = dz; dx = oldDx; dy = (double) handle.Y; dz = oldDz; AxisAlignedBB axisalignedbb1 = handle.boundingBox.clone(); handle.boundingBox.d(axisalignedbb); list = EntityControllerCollisionHelper.getCollisions(this, handle.boundingBox.a(oldDx, dy, oldDz)); // Collision testing using Y for (AxisAlignedBB aabb : list) { dy = aabb.b(handle.boundingBox, dy); } handle.boundingBox.d(0.0, dy, 0.0); if (!handle.J && oldDy != dy) { dx = dy = dz = 0.0; } // Collision testing using X for (AxisAlignedBB aabb : list) { dx = aabb.a(handle.boundingBox, dx); } handle.boundingBox.d(dx, 0.0, 0.0); if (!handle.J && oldDx != dx) { dx = dy = dz = 0.0; } // Collision testing using Z for (AxisAlignedBB aabb : list) { dz = aabb.c(handle.boundingBox, dz); } handle.boundingBox.d(0.0, 0.0, dz); if (!handle.J && oldDz != dz) { dx = dy = dz = 0.0; } if (!handle.J && oldDy != dy) { dx = dy = dz = 0.0; } else { dy = (double) -handle.Y; for (int k = 0; k < list.size(); k++) { dy = list.get(k).b(handle.boundingBox, dy); } handle.boundingBox.d(0.0, dy, 0.0); } if (MathUtil.lengthSquared(moveDx, moveDz) >= MathUtil.lengthSquared(dx, dz)) { dx = moveDx; dy = moveDy; dz = moveDz; handle.boundingBox.d(axisalignedbb1); } else { double subY = handle.boundingBox.b - (int) handle.boundingBox.b; if (subY > 0.0) { handle.Y += subY + 0.01; } } } handle.locX = CommonNMS.getMiddleX(handle.boundingBox); handle.locY = handle.boundingBox.b + (double) handle.height - (double) handle.Y; handle.locZ = CommonNMS.getMiddleZ(handle.boundingBox); entity.setMovementImpaired(oldDx != dx || oldDz != dz); handle.onGround = oldDy != dy && oldDy < 0.0; handle.G = oldDy != dy; handle.F = entity.isMovementImpaired() || handle.G; EntityRef.updateFalling(handle, dy, handle.onGround); // ================ Collision slowdown caused by ============== if (oldDy != dy) { handle.motY = 0.0; } if (oldDx != dx && entity.vel.x.abs() > entity.vel.z.abs()) { handle.motX = 0.0; } if (oldDz != dz && entity.vel.z.abs() > entity.vel.x.abs()) { handle.motZ = 0.0; } // ============================================================= moveDx = handle.locX - oldLocX; moveDy = handle.locY - oldLocY; moveDz = handle.locZ - oldLocZ; if (entity.getEntity() instanceof Vehicle && entity.isMovementImpaired()) { Vehicle vehicle = (Vehicle) entity.getEntity(); org.bukkit.block.Block block = entity.getWorld().getBlockAt(entity.loc.x.block(), MathUtil.floor(handle.locY - (double) handle.height), entity.loc.z.block()); if (oldDx > dx) { block = block.getRelative(BlockFace.EAST); } else if (oldDx < dx) { block = block.getRelative(BlockFace.WEST); } else if (oldDz > dz) { block = block.getRelative(BlockFace.SOUTH); } else if (oldDz < dz) { block = block.getRelative(BlockFace.NORTH); } CommonUtil.callEvent(new VehicleBlockCollisionEvent(vehicle, block)); } // Perform post movement logic onPostMove(moveDx, moveDy, moveDz); } } private void onPostMove(double moveDx, double moveDy, double moveDz) { Entity handle = this.entity.getHandle(Entity.class); // Update entity movement sounds if (EntityRef.hasMovementSound(handle) && handle.vehicle == null) { int bX = entity.loc.x.block(); int bY = MathUtil.floor(handle.locY - 0.2 - (double) handle.height); int bZ = entity.loc.z.block(); Block block = handle.world.getType(bX, bY, bZ); int j1 = handle.world.getType(bX, bY - 1, bZ).b(); // Magic values! *gasp* Bad, BAD Minecraft! Go sit in a corner! if (j1 == 11 || j1 == 32 || j1 == 21) { block = handle.world.getType(bX, bY - 1, bZ); } if (block != Blocks.LADDER) { moveDy = 0.0; } handle.Q += MathUtil.length(moveDx, moveDz) * 0.6; handle.O += MathUtil.length(moveDx, moveDy, moveDz) * 0.6; if (handle.O > EntityRef.stepCounter.get(entity.getHandle()) && block != Blocks.AIR) { EntityRef.stepCounter.set(entity.getHandle(), (int) handle.O + 1); if (entity.isInWater(true)) { float f = (float) Math.sqrt(entity.vel.y.squared() + 0.2 * entity.vel.xz.lengthSquared()) * 0.35f; if (f > 1.0f) { f = 1.0f; } entity.makeRandomSound(EntityRef.getSwimSound.invoke(handle), f, 1.0f); } entity.makeStepSound(bX, bY, bZ, CommonNMS.getMaterial(block)); block.b(handle.world, bX, bY, bZ, handle); } } EntityRef.updateBlockCollision(handle); // Fire tick calculation (check using block collision) final boolean isInWater = handle.L(); // In water or raining if (handle.world.e(handle.boundingBox.shrink(0.001, 0.001, 0.001))) { onBurnDamage(1); if (!isInWater) { handle.fireTicks++; if (handle.fireTicks <= 0) { EntityCombustEvent event = new EntityCombustEvent(entity.getEntity(), 8); if (!CommonUtil.callEvent(event).isCancelled()) { handle.setOnFire(event.getDuration()); } } else { handle.setOnFire(8); } } } else if (handle.fireTicks <= 0) { handle.fireTicks = -handle.maxFireTicks; } if (isInWater && handle.fireTicks > 0) { entity.makeRandomSound(Sound.FIZZ, 0.7f, 1.6f); handle.fireTicks = -handle.maxFireTicks; } } }