예제 #1
0
@NonnullByDefault
@Mixin(net.minecraft.world.World.class)
public abstract class MixinWorld implements World, IMixinWorld {

  private static final Vector3i BLOCK_MIN = new Vector3i(-30000000, 0, -30000000);
  private static final Vector3i BLOCK_MAX = new Vector3i(30000000, 256, 30000000).sub(1, 1, 1);
  private static final Vector3i BLOCK_SIZE = BLOCK_MAX.sub(BLOCK_MIN).add(1, 1, 1);
  private static final Vector2i BIOME_MIN = BLOCK_MIN.toVector2(true);
  private static final Vector2i BIOME_MAX = BLOCK_MAX.toVector2(true);
  private static final Vector2i BIOME_SIZE = BIOME_MAX.sub(BIOME_MIN).add(1, 1);
  private static final String CHECK_NO_ENTITY_COLLISION =
      "checkNoEntityCollision(Lnet/minecraft/util/AxisAlignedBB;Lnet/minecraft/entity/Entity;)Z";
  private static final String GET_ENTITIES_WITHIN_AABB =
      "Lnet/minecraft/world/World;getEntitiesWithinAABBExcludingEntity(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/AxisAlignedBB;)Ljava/util/List;";
  public SpongeBlockSnapshotBuilder builder = new SpongeBlockSnapshotBuilder();
  private boolean keepSpawnLoaded;
  private Context worldContext;
  private SpongeChunkProvider spongegen;
  protected boolean processingExplosion = false;
  private SpongeConfig<?> activeConfig;

  // @formatter:off
  @Shadow @Final public boolean isRemote;
  @Shadow @Final public WorldProvider provider;
  @Shadow @Final public Random rand;
  @Shadow @Final public List<net.minecraft.entity.Entity> loadedEntityList;
  @Shadow @Final public List<net.minecraft.tileentity.TileEntity> loadedTileEntityList;
  @Shadow @Final private net.minecraft.world.border.WorldBorder worldBorder;
  @Shadow @Final public List<EntityPlayer> playerEntities;
  @Shadow protected boolean scheduledUpdatesAreImmediate;
  @Shadow protected WorldInfo worldInfo;

  @Shadow
  public abstract net.minecraft.world.border.WorldBorder shadow$getWorldBorder();

  @Shadow
  public abstract EnumDifficulty shadow$getDifficulty();

  @Shadow
  public net.minecraft.world.World init() {
    // Should never be overwritten because this is @Shadow'ed
    throw new RuntimeException("Bad things have happened");
  }

  @Shadow
  public abstract void onEntityAdded(net.minecraft.entity.Entity entityIn);

  @Shadow
  public abstract void updateEntity(net.minecraft.entity.Entity ent);

  @Shadow
  public abstract net.minecraft.world.chunk.Chunk getChunkFromBlockCoords(BlockPos pos);

  @Shadow
  public abstract boolean isBlockLoaded(BlockPos pos);

  @Shadow
  public abstract boolean addWeatherEffect(net.minecraft.entity.Entity entityIn);

  @Shadow
  public abstract BiomeGenBase getBiomeGenForCoords(BlockPos pos);

  @Shadow
  public abstract IChunkProvider getChunkProvider();

  @Shadow
  public abstract WorldChunkManager getWorldChunkManager();

  @Shadow
  @Nullable
  public abstract net.minecraft.tileentity.TileEntity getTileEntity(BlockPos pos);

  @Shadow
  public abstract boolean isBlockPowered(BlockPos pos);

  @Shadow
  public abstract IBlockState getBlockState(BlockPos pos);

  @Shadow
  public abstract net.minecraft.world.chunk.Chunk getChunkFromChunkCoords(int chunkX, int chunkZ);

  @Shadow
  public abstract boolean isChunkLoaded(int x, int z, boolean allowEmpty);

  @Shadow
  public abstract net.minecraft.world.Explosion newExplosion(
      net.minecraft.entity.Entity entityIn,
      double x,
      double y,
      double z,
      float strength,
      boolean isFlaming,
      boolean isSmoking);

  @Shadow
  public abstract List<net.minecraft.entity.Entity> getEntities(
      Class<net.minecraft.entity.Entity> entityType,
      com.google.common.base.Predicate<net.minecraft.entity.Entity> filter);

  @Shadow
  public abstract List<net.minecraft.entity.Entity> getEntitiesWithinAABBExcludingEntity(
      net.minecraft.entity.Entity entityIn, AxisAlignedBB bb);

  // @formatter:on
  @Inject(method = "<init>", at = @At("RETURN"))
  public void onConstructed(
      ISaveHandler saveHandlerIn,
      WorldInfo info,
      WorldProvider providerIn,
      Profiler profilerIn,
      boolean client,
      CallbackInfo ci) {
    if (info == null) {
      SpongeImpl.getLogger()
          .warn(
              "World constructed without a WorldInfo! This will likely cause problems. Subsituting dummy info.",
              new RuntimeException("Stack trace:"));
      this.worldInfo =
          new WorldInfo(
              new WorldSettings(0, WorldSettings.GameType.NOT_SET, false, false, WorldType.DEFAULT),
              "sponge$dummy_world");
    }
    this.worldContext = new Context(Context.WORLD_KEY, this.worldInfo.getWorldName());
    if (SpongeImpl.getGame().getPlatform().getType() == Platform.Type.SERVER) {
      this.worldBorder.addListener(new PlayerBorderListener(providerIn.getDimensionId()));
    }
    this.activeConfig = SpongeHooks.getActiveConfig((net.minecraft.world.World) (Object) this);
  }

  @Override
  public SpongeBlockSnapshot createSpongeBlockSnapshot(
      IBlockState state, IBlockState extended, BlockPos pos, int updateFlag) {
    this.builder.reset();
    Location<World> location = new Location<>((World) this, VecHelper.toVector(pos));
    this.builder
        .blockState((BlockState) state)
        .extendedState((BlockState) extended)
        .worldId(location.getExtent().getUniqueId())
        .position(location.getBlockPosition());
    Optional<UUID> creator = getCreator(pos.getX(), pos.getY(), pos.getZ());
    Optional<UUID> notifier = getNotifier(pos.getX(), pos.getY(), pos.getZ());
    if (creator.isPresent()) {
      this.builder.creator(creator.get());
    }
    if (notifier.isPresent()) {
      this.builder.notifier(notifier.get());
    }
    if (state.getBlock() instanceof ITileEntityProvider) {
      net.minecraft.tileentity.TileEntity te = getTileEntity(pos);
      if (te != null) {
        TileEntity tile = (TileEntity) te;
        for (DataManipulator<?, ?> manipulator : tile.getContainers()) {
          this.builder.add(manipulator);
        }
        NBTTagCompound nbt = new NBTTagCompound();
        te.writeToNBT(nbt);
        this.builder.unsafeNbt(nbt);
      }
    }
    return new SpongeBlockSnapshot(this.builder, updateFlag);
  }

  @SuppressWarnings("rawtypes")
  @Inject(
      method =
          "getCollidingBoundingBoxes(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/AxisAlignedBB;)Ljava/util/List;",
      at = @At("HEAD"),
      cancellable = true)
  public void onGetCollidingBoundingBoxes(
      net.minecraft.entity.Entity entity, AxisAlignedBB axis, CallbackInfoReturnable<List> cir) {
    if (!entity.worldObj.isRemote && SpongeHooks.checkBoundingBoxSize(entity, axis)) {
      // Removing misbehaved living entities
      cir.setReturnValue(new ArrayList());
    }
  }

  @Override
  public UUID getUniqueId() {
    return ((WorldProperties) this.worldInfo).getUniqueId();
  }

  @Override
  public String getName() {
    return this.worldInfo.getWorldName();
  }

  @Override
  public Optional<Chunk> getChunk(int x, int y, int z) {
    if (!SpongeChunkLayout.instance.isValidChunk(x, y, z)) {
      return Optional.empty();
    }
    WorldServer worldserver = (WorldServer) (Object) this;
    net.minecraft.world.chunk.Chunk chunk = null;
    if (worldserver.theChunkProviderServer.chunkExists(x, z)) {
      chunk = worldserver.theChunkProviderServer.provideChunk(x, z);
    }
    return Optional.ofNullable((Chunk) chunk);
  }

  @Override
  public Optional<Chunk> loadChunk(Vector3i position, boolean shouldGenerate) {
    return loadChunk(position.getX(), position.getY(), position.getZ(), shouldGenerate);
  }

  @Override
  public Optional<Chunk> loadChunk(int x, int y, int z, boolean shouldGenerate) {
    if (!SpongeChunkLayout.instance.isValidChunk(x, y, z)) {
      return Optional.empty();
    }
    WorldServer worldserver = (WorldServer) (Object) this;
    net.minecraft.world.chunk.Chunk chunk = null;
    if (worldserver.theChunkProviderServer.chunkExists(x, z) || shouldGenerate) {
      chunk = worldserver.theChunkProviderServer.loadChunk(x, z);
    }
    return Optional.ofNullable((Chunk) chunk);
  }

  @Override
  public BlockState getBlock(int x, int y, int z) {
    checkBlockBounds(x, y, z);
    return (BlockState) getBlockState(new BlockPos(x, y, z));
  }

  @Override
  public BlockType getBlockType(int x, int y, int z) {
    checkBlockBounds(x, y, z);
    // avoid intermediate object creation from using BlockState
    return (BlockType) getChunkFromChunkCoords(x >> 4, z >> 4).getBlock(x, y, z);
  }

  @Override
  public void setBlock(int x, int y, int z, BlockState block) {
    setBlock(x, y, z, block, true);
  }

  @Override
  public BiomeType getBiome(int x, int z) {
    checkBiomeBounds(x, z);
    return (BiomeType) this.getBiomeGenForCoords(new BlockPos(x, 0, z));
  }

  @Override
  public void setBiome(int x, int z, BiomeType biome) {
    checkBiomeBounds(x, z);
    ((Chunk) getChunkFromChunkCoords(x >> 4, z >> 4)).setBiome(x, z, biome);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Collection<Entity> getEntities() {
    return Lists.newArrayList((Collection<Entity>) (Object) this.loadedEntityList);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Collection<Entity> getEntities(Predicate<Entity> filter) {
    // This already returns a new copy
    return (Collection<Entity>)
        (Object)
            this.getEntities(
                net.minecraft.entity.Entity.class,
                Functional.java8ToGuava((Predicate<net.minecraft.entity.Entity>) (Object) filter));
  }

  @Override
  public Optional<Entity> createEntity(EntityType type, Vector3d position) {
    checkNotNull(type, "The entity type cannot be null!");
    checkNotNull(position, "The position cannot be null!");

    Entity entity = null;

    Class<? extends Entity> entityClass = type.getEntityClass();
    double x = position.getX();
    double y = position.getY();
    double z = position.getZ();

    if (entityClass.isAssignableFrom(EntityPlayerMP.class)
        || entityClass.isAssignableFrom(EntityDragonPart.class)) {
      // Unable to construct these
      return Optional.empty();
    }

    net.minecraft.world.World world = (net.minecraft.world.World) (Object) this;

    // Not all entities have a single World parameter as their constructor
    if (entityClass.isAssignableFrom(EntityLightningBolt.class)) {
      entity = (Entity) new EntityLightningBolt(world, x, y, z);
    } else if (entityClass.isAssignableFrom(EntityEnderPearl.class)) {
      EntityArmorStand tempEntity = new EntityArmorStand(world, x, y, z);
      tempEntity.posY -= tempEntity.getEyeHeight();
      entity = (Entity) new EntityEnderPearl(world, tempEntity);
      ((EnderPearl) entity).setShooter(ProjectileSource.UNKNOWN);
    }

    // Some entities need to have non-null fields (and the easiest way to
    // set them is to use the more specialised constructor).
    if (entityClass.isAssignableFrom(EntityFallingBlock.class)) {
      entity = (Entity) new EntityFallingBlock(world, x, y, z, Blocks.sand.getDefaultState());
    } else if (entityClass.isAssignableFrom(EntityItem.class)) {
      entity = (Entity) new EntityItem(world, x, y, z, new ItemStack(Blocks.stone));
    }

    if (entity == null) {
      try {
        entity = ConstructorUtils.invokeConstructor(entityClass, this);
        ((net.minecraft.entity.Entity) entity).setPosition(x, y, z);
      } catch (Exception e) {
        SpongeImpl.getLogger().error(ExceptionUtils.getStackTrace(e));
      }
    }

    // TODO - replace this with an actual check
    /*
    if (entity instanceof EntityHanging) {
        if (((EntityHanging) entity).facingDirection == null) {
            // TODO Some sort of detection of a valid direction?
            // i.e scan immediate blocks for something to attach onto.
            ((EntityHanging) entity).facingDirection = EnumFacing.NORTH;
        }
        if (!((EntityHanging) entity).onValidSurface()) {
            return Optional.empty();
        }
    }*/

    // Last chance to fix null fields
    if (entity instanceof EntityPotion) {
      // make sure EntityPotion.potionDamage is not null
      ((EntityPotion) entity).getPotionDamage();
    } else if (entity instanceof EntityPainting) {
      // This is default when art is null when reading from NBT, could
      // choose a random art instead?
      ((EntityPainting) entity).art = EnumArt.KEBAB;
    }

    return Optional.ofNullable(entity);
  }

  @Override
  public Optional<Entity> createEntity(DataContainer entityContainer) {
    // TODO once entity containers are implemented
    return Optional.empty();
  }

  @Override
  public Optional<Entity> createEntity(DataContainer entityContainer, Vector3d position) {
    // TODO once entity containers are implemented
    return Optional.empty();
  }

  @Override
  public Optional<Entity> restoreSnapshot(EntitySnapshot snapshot, Vector3d position) {
    EntitySnapshot entitySnapshot = snapshot.withLocation(new Location<>(this, position));
    return entitySnapshot.restore();
  }

  @Override
  public WorldBorder getWorldBorder() {
    return (WorldBorder) shadow$getWorldBorder();
  }

  @Override
  public WorldBorder.ChunkPreGenerate newChunkPreGenerate(Vector3d center, double diameter) {
    return new SpongeChunkPreGenerate(this, center, diameter);
  }

  @Override
  public Dimension getDimension() {
    return (Dimension) this.provider;
  }

  @Override
  public boolean doesKeepSpawnLoaded() {
    return this.keepSpawnLoaded;
  }

  @Override
  public void setKeepSpawnLoaded(boolean keepLoaded) {
    this.keepSpawnLoaded = keepLoaded;
  }

  @Override
  public SpongeConfig<SpongeConfig.WorldConfig> getWorldConfig() {
    return ((IMixinWorldInfo) this.worldInfo).getWorldConfig();
  }

  @Override
  public Optional<Entity> getEntity(UUID uuid) {
    // Note that MixinWorldServer is properly overriding this to use it's own mapping.
    for (net.minecraft.entity.Entity entity : this.loadedEntityList) {
      if (entity.getUniqueID().equals(uuid)) {
        return Optional.of((Entity) entity);
      }
    }
    return Optional.empty();
  }

  @SuppressWarnings("unchecked")
  @Override
  public Iterable<Chunk> getLoadedChunks() {
    return (List<Chunk>) (List<?>) ((ChunkProviderServer) this.getChunkProvider()).loadedChunks;
  }

  @Override
  public boolean unloadChunk(Chunk chunk) {
    checkArgument(chunk != null, "Chunk cannot be null!");
    return chunk.unloadChunk();
  }

  @Override
  public WorldCreationSettings getCreationSettings() {
    WorldProperties properties = this.getProperties();

    // Create based on WorldProperties
    WorldSettings settings = new WorldSettings(this.worldInfo);
    IMixinWorldSettings mixin = (IMixinWorldSettings) (Object) settings;
    mixin.setDimensionType(properties.getDimensionType());
    mixin.setGeneratorSettings(properties.getGeneratorSettings());
    mixin.setGeneratorModifiers(properties.getGeneratorModifiers());
    mixin.setEnabled(true);
    mixin.setKeepSpawnLoaded(this.keepSpawnLoaded);
    mixin.setLoadOnStartup(properties.loadOnStartup());

    return (WorldCreationSettings) (Object) settings;
  }

  @Override
  public void updateWorldGenerator() {

    IMixinWorldType worldType = (IMixinWorldType) this.getProperties().getGeneratorType();
    // Get the default generator for the world type
    DataContainer generatorSettings = this.getProperties().getGeneratorSettings();

    SpongeWorldGenerator newGenerator = worldType.createGenerator(this, generatorSettings);
    // If the base generator is an IChunkProvider which implements
    // IPopulatorProvider we request that it add its populators not covered
    // by the base generation populator
    if (newGenerator.getBaseGenerationPopulator() instanceof IChunkProvider) {
      // We check here to ensure that the IPopulatorProvider is one of our mixed in ones and not
      // from a mod chunk provider extending a provider that we mixed into
      if (WorldGenConstants.isValid(
          (IChunkProvider) newGenerator.getBaseGenerationPopulator(), IPopulatorProvider.class)) {
        ((IPopulatorProvider) newGenerator.getBaseGenerationPopulator())
            .addPopulators(newGenerator);
      }
    } else if (newGenerator.getBaseGenerationPopulator() instanceof IPopulatorProvider) {
      // If its not a chunk provider but is a populator provider then we call it as well
      ((IPopulatorProvider) newGenerator.getBaseGenerationPopulator()).addPopulators(newGenerator);
    }

    // Re-apply all world generator modifiers
    WorldCreationSettings creationSettings = this.getCreationSettings();

    for (WorldGeneratorModifier modifier : this.getProperties().getGeneratorModifiers()) {
      modifier.modifyWorldGenerator(creationSettings, generatorSettings, newGenerator);
    }

    this.spongegen = createChunkProvider(newGenerator);
    this.spongegen.setGenerationPopulators(newGenerator.getGenerationPopulators());
    this.spongegen.setPopulators(newGenerator.getPopulators());
    this.spongegen.setBiomeOverrides(newGenerator.getBiomeSettings());

    ChunkProviderServer chunkProviderServer = (ChunkProviderServer) this.getChunkProvider();
    chunkProviderServer.serverChunkGenerator = this.spongegen;
  }

  @Override
  public SpongeChunkProvider createChunkProvider(SpongeWorldGenerator newGenerator) {
    return new SpongeChunkProvider(
        (net.minecraft.world.World) (Object) this,
        newGenerator.getBaseGenerationPopulator(),
        newGenerator.getBiomeGenerator());
  }

  @Override
  public void onSpongeEntityAdded(net.minecraft.entity.Entity entity) {
    this.onEntityAdded(entity);
  }

  @Override
  public WorldGenerator getWorldGenerator() {
    return this.spongegen;
  }

  @Override
  public WorldProperties getProperties() {
    return (WorldProperties) this.worldInfo;
  }

  @Override
  public Location<World> getSpawnLocation() {
    return new Location<>(
        this, this.worldInfo.getSpawnX(), this.worldInfo.getSpawnY(), this.worldInfo.getSpawnZ());
  }

  @Override
  public Context getContext() {
    return this.worldContext;
  }

  @Override
  public Optional<TileEntity> getTileEntity(int x, int y, int z) {
    net.minecraft.tileentity.TileEntity tileEntity = getTileEntity(new BlockPos(x, y, z));
    if (tileEntity == null) {
      return Optional.empty();
    } else {
      return Optional.of((TileEntity) tileEntity);
    }
  }

  @Override
  public Vector2i getBiomeMin() {
    return BIOME_MIN;
  }

  @Override
  public Vector2i getBiomeMax() {
    return BIOME_MAX;
  }

  @Override
  public Vector2i getBiomeSize() {
    return BIOME_SIZE;
  }

  @Override
  public Vector3i getBlockMin() {
    return BLOCK_MIN;
  }

  @Override
  public Vector3i getBlockMax() {
    return BLOCK_MAX;
  }

  @Override
  public Vector3i getBlockSize() {
    return BLOCK_SIZE;
  }

  @Override
  public boolean containsBiome(int x, int z) {
    return VecHelper.inBounds(x, z, BIOME_MIN, BIOME_MAX);
  }

  @Override
  public boolean containsBlock(int x, int y, int z) {
    return VecHelper.inBounds(x, y, z, BLOCK_MIN, BLOCK_MAX);
  }

  private void checkBiomeBounds(int x, int z) {
    if (!containsBiome(x, z)) {
      throw new PositionOutOfBoundsException(new Vector2i(x, z), BIOME_MIN, BIOME_MAX);
    }
  }

  private void checkBlockBounds(int x, int y, int z) {
    if (!containsBlock(x, y, z)) {
      throw new PositionOutOfBoundsException(new Vector3i(x, y, z), BLOCK_MIN, BLOCK_MAX);
    }
  }

  @Override
  public Difficulty getDifficulty() {
    return (Difficulty) (Object) this.shadow$getDifficulty();
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private List<Player> getPlayers() {
    return (List)
        ((net.minecraft.world.World) (Object) this)
            .getPlayers(EntityPlayerMP.class, Predicates.alwaysTrue());
  }

  @Override
  public void sendMessage(ChatType type, Text message) {
    checkNotNull(type, "type");
    checkNotNull(message, "message");

    for (Player player : this.getPlayers()) {
      player.sendMessage(type, message);
    }
  }

  @Override
  public void sendTitle(Title title) {
    checkNotNull(title, "title");

    for (Player player : getPlayers()) {
      player.sendTitle(title);
    }
  }

  @Override
  public void resetTitle() {
    getPlayers().forEach(Player::resetTitle);
  }

  @Override
  public void clearTitle() {
    getPlayers().forEach(Player::clearTitle);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Collection<TileEntity> getTileEntities() {
    return Lists.newArrayList((List<TileEntity>) (Object) this.loadedTileEntityList);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Collection<TileEntity> getTileEntities(Predicate<TileEntity> filter) {
    return ((List<TileEntity>) (Object) this.loadedTileEntityList)
        .stream()
        .filter(filter)
        .collect(Collectors.toList());
  }

  @Override
  public boolean isLoaded() {
    return DimensionManager.getWorldFromDimId(this.provider.getDimensionId()) != null;
  }

  @Override
  public Optional<String> getGameRule(String gameRule) {
    return this.getProperties().getGameRule(gameRule);
  }

  @Override
  public Map<String, String> getGameRules() {
    return this.getProperties().getGameRules();
  }

  @Override
  public void triggerExplosion(Explosion explosion) {
    checkNotNull(explosion, "explosion");
    checkNotNull(explosion.getOrigin(), "origin");

    newExplosion(
        (net.minecraft.entity.Entity) explosion.getSourceExplosive().orElse(null),
        explosion.getOrigin().getX(),
        explosion.getOrigin().getY(),
        explosion.getOrigin().getZ(),
        explosion.getRadius(),
        explosion.canCauseFire(),
        explosion.shouldBreakBlocks());
  }

  @Override
  public Extent getExtentView(Vector3i newMin, Vector3i newMax) {
    checkBlockBounds(newMin.getX(), newMin.getY(), newMin.getZ());
    checkBlockBounds(newMax.getX(), newMax.getY(), newMax.getZ());
    return new ExtentViewDownsize(this, newMin, newMax);
  }

  @Override
  public Extent getExtentView(DiscreteTransform3 transform) {
    return new ExtentViewTransform(this, transform);
  }

  @Override
  public MutableBiomeAreaWorker<? extends World> getBiomeWorker() {
    return new SpongeMutableBiomeAreaWorker<>(this);
  }

  @Override
  public MutableBlockVolumeWorker<? extends World> getBlockWorker() {
    return new SpongeMutableBlockVolumeWorker<>(this);
  }

  @Override
  public BlockSnapshot createSnapshot(int x, int y, int z) {
    World world = this;
    BlockState state = world.getBlock(x, y, z);
    Optional<TileEntity> te = world.getTileEntity(x, y, z);
    SpongeBlockSnapshotBuilder builder =
        new SpongeBlockSnapshotBuilder()
            .blockState(state)
            .worldId(world.getUniqueId())
            .position(new Vector3i(x, y, z));
    Optional<UUID> creator = getCreator(x, y, z);
    Optional<UUID> notifier = getNotifier(x, y, z);
    if (creator.isPresent()) {
      builder.creator(creator.get());
    }
    if (notifier.isPresent()) {
      builder.notifier(notifier.get());
    }
    if (te.isPresent()) {
      final TileEntity tileEntity = te.get();
      for (DataManipulator<?, ?> manipulator : tileEntity.getContainers()) {
        builder.add(manipulator);
      }
      final NBTTagCompound compound = new NBTTagCompound();
      ((net.minecraft.tileentity.TileEntity) tileEntity).writeToNBT(compound);
      builder.unsafeNbt(compound);
    }
    return builder.build();
  }

  @Override
  public boolean restoreSnapshot(BlockSnapshot snapshot, boolean force, boolean notifyNeighbors) {
    return snapshot.restore(force, notifyNeighbors);
  }

  @Override
  public boolean restoreSnapshot(
      int x, int y, int z, BlockSnapshot snapshot, boolean force, boolean notifyNeighbors) {
    snapshot = snapshot.withLocation(new Location<>(this, new Vector3i(x, y, z)));
    return snapshot.restore(force, notifyNeighbors);
  }

  @Override
  public Optional<UUID> getCreator(int x, int y, int z) {
    BlockPos pos = new BlockPos(x, y, z);
    IMixinChunk spongeChunk = (IMixinChunk) getChunkFromBlockCoords(pos);
    Optional<User> user = spongeChunk.getBlockOwner(pos);
    if (user.isPresent()) {
      return Optional.of(user.get().getUniqueId());
    } else {
      return Optional.empty();
    }
  }

  @Override
  public Optional<UUID> getNotifier(int x, int y, int z) {
    BlockPos pos = new BlockPos(x, y, z);
    IMixinChunk spongeChunk = (IMixinChunk) getChunkFromBlockCoords(pos);
    Optional<User> user = spongeChunk.getBlockNotifier(pos);
    if (user.isPresent()) {
      return Optional.of(user.get().getUniqueId());
    } else {
      return Optional.empty();
    }
  }

  @Override
  public void setCreator(int x, int y, int z, @Nullable UUID uuid) {
    BlockPos pos = new BlockPos(x, y, z);
    IMixinChunk spongeChunk = (IMixinChunk) getChunkFromBlockCoords(pos);
    spongeChunk.setBlockCreator(pos, uuid);
  }

  @Override
  public void setNotifier(int x, int y, int z, @Nullable UUID uuid) {
    BlockPos pos = new BlockPos(x, y, z);
    IMixinChunk spongeChunk = (IMixinChunk) getChunkFromBlockCoords(pos);
    spongeChunk.setBlockNotifier(pos, uuid);
  }

  @Nullable
  @Override
  public EntityPlayer getClosestPlayerToEntityWhoAffectsSpawning(
      net.minecraft.entity.Entity entity, double distance) {
    return this.getClosestPlayerWhoAffectsSpawning(entity.posX, entity.posY, entity.posZ, distance);
  }

  @Nullable
  @Override
  public EntityPlayer getClosestPlayerWhoAffectsSpawning(
      double x, double y, double z, double distance) {
    double bestDistance = -1.0D;
    EntityPlayer result = null;

    for (Object entity : this.playerEntities) {
      EntityPlayer player = (EntityPlayer) entity;
      if (player == null || player.isDead || !((IMixinEntityPlayer) player).affectsSpawning()) {
        continue;
      }

      double playerDistance = player.getDistanceSq(x, y, z);

      if ((distance < 0.0D || playerDistance < distance * distance)
          && (bestDistance == -1.0D || playerDistance < bestDistance)) {
        bestDistance = playerDistance;
        result = player;
      }
    }

    return result;
  }

  @Redirect(
      method = "isAnyPlayerWithinRangeAt",
      at =
          @At(
              value = "INVOKE",
              target = "Lcom/google/common/base/Predicate;apply(Ljava/lang/Object;)Z",
              remap = false))
  public boolean onIsAnyPlayerWithinRangePredicate(
      com.google.common.base.Predicate<EntityPlayer> predicate, Object object) {
    EntityPlayer player = (EntityPlayer) object;
    return !(player.isDead || !((IMixinEntityPlayer) player).affectsSpawning())
        && predicate.apply(player);
  }

  // For invisibility
  @Redirect(
      method = CHECK_NO_ENTITY_COLLISION,
      at = @At(value = "INVOKE", target = GET_ENTITIES_WITHIN_AABB))
  public List<net.minecraft.entity.Entity> filterInvisibile(
      net.minecraft.world.World world,
      net.minecraft.entity.Entity entityIn,
      AxisAlignedBB axisAlignedBB) {
    List<net.minecraft.entity.Entity> entities =
        world.getEntitiesWithinAABBExcludingEntity(entityIn, axisAlignedBB);
    Iterator<net.minecraft.entity.Entity> iterator = entities.iterator();
    while (iterator.hasNext()) {
      net.minecraft.entity.Entity entity = iterator.next();
      if (((IMixinEntity) entity).isVanished() && ((IMixinEntity) entity).ignoresCollision()) {
        iterator.remove();
      }
    }
    return entities;
  }

  @Redirect(
      method = "getClosestPlayer",
      at =
          @At(
              value = "INVOKE",
              target = "Lcom/google/common/base/Predicate;apply(Ljava/lang/Object;)Z",
              remap = false))
  private boolean onGetClosestPlayerCheck(
      com.google.common.base.Predicate<net.minecraft.entity.Entity> predicate,
      Object entityPlayer) {
    EntityPlayer player = (EntityPlayer) entityPlayer;
    IMixinEntity mixinEntity = (IMixinEntity) player;
    return predicate.apply(player) && !mixinEntity.isVanished();
  }

  /**
   * @author gabizou - February 7th, 2016
   *     <p>This will short circuit all other patches such that we control the entities being loaded
   *     by chunkloading and can throw our bulk entity event. This will bypass Forge's hook for
   *     individual entity events, but the SpongeModEventManager will still successfully throw the
   *     appropriate event and cancel the entities otherwise contained.
   * @param entities The entities being loaded
   * @param callbackInfo The callback info
   */
  @Final
  @Inject(method = "loadEntities", at = @At("HEAD"), cancellable = true)
  private void spongeLoadEntities(
      Collection<net.minecraft.entity.Entity> entities, CallbackInfo callbackInfo) {
    if (entities.isEmpty()) {
      // just return, no entities to load!
      callbackInfo.cancel();
      return;
    }
    List<Entity> entityList = new ArrayList<>();
    ImmutableList.Builder<EntitySnapshot> snapshotBuilder = ImmutableList.builder();
    for (net.minecraft.entity.Entity entity : entities) {
      entityList.add((Entity) entity);
      snapshotBuilder.add(((Entity) entity).createSnapshot());
    }
    SpawnCause cause = SpawnCause.builder().type(InternalSpawnTypes.CHUNK_LOAD).build();
    List<NamedCause> causes = new ArrayList<>();
    causes.add(NamedCause.source(cause));
    causes.add(NamedCause.of("World", this));
    SpawnEntityEvent.ChunkLoad chunkLoad =
        SpongeEventFactory.createSpawnEntityEventChunkLoad(
            Cause.of(causes), entityList, snapshotBuilder.build(), this);
    SpongeImpl.postEvent(chunkLoad);
    if (!chunkLoad.isCancelled()) {
      for (Entity successful : chunkLoad.getEntities()) {
        this.loadedEntityList.add((net.minecraft.entity.Entity) successful);
        this.onEntityAdded((net.minecraft.entity.Entity) successful);
      }
    }
    callbackInfo.cancel();
  }

  @Inject(method = "playSoundAtEntity", at = @At("HEAD"), cancellable = true)
  private void spongePlaySoundAtEntity(
      net.minecraft.entity.Entity entity,
      String name,
      float volume,
      float pitch,
      CallbackInfo callbackInfo) {
    if (((IMixinEntity) entity).isVanished()) {
      callbackInfo.cancel();
    }
  }

  @Override
  public void sendBlockChange(int x, int y, int z, BlockState state) {
    checkNotNull(state, "state");
    S23PacketBlockChange packet = new S23PacketBlockChange();
    packet.blockPosition = new BlockPos(x, y, z);
    packet.blockState = (IBlockState) state;

    for (EntityPlayer player : this.playerEntities) {
      if (player instanceof EntityPlayerMP) {
        ((EntityPlayerMP) player).playerNetServerHandler.sendPacket(packet);
      }
    }
  }

  @Override
  public void resetBlockChange(int x, int y, int z) {
    S23PacketBlockChange packet =
        new S23PacketBlockChange((net.minecraft.world.World) (Object) this, new BlockPos(x, y, z));

    for (EntityPlayer player : this.playerEntities) {
      if (player instanceof EntityPlayerMP) {
        ((EntityPlayerMP) player).playerNetServerHandler.sendPacket(packet);
      }
    }
  }

  /**
   * @author amaranth - April 25th, 2016
   * @reason Avoid 25 chunk map lookups per entity per tick by using neighbor pointers
   * @param xStart X block start coordinate
   * @param yStart Y block start coordinate
   * @param zStart Z block start coordinate
   * @param xEnd X block end coordinate
   * @param yEnd Y block end coordinate
   * @param zEnd Z block end coordinate
   * @param allowEmpty Whether empty chunks should be accepted
   * @return If the chunks for the area are loaded
   */
  @Overwrite
  public boolean isAreaLoaded(
      int xStart, int yStart, int zStart, int xEnd, int yEnd, int zEnd, boolean allowEmpty) {
    if (yEnd < 0 || yStart > 255) {
      return false;
    }

    xStart = xStart >> 4;
    zStart = zStart >> 4;
    xEnd = xEnd >> 4;
    zEnd = zEnd >> 4;

    Chunk base =
        (Chunk)
            ((IMixinChunkProviderServer) this.getChunkProvider()).getChunkIfLoaded(xStart, zStart);
    if (base == null) {
      return false;
    }

    for (int i = xStart; i <= xEnd; i++) {
      Optional<Chunk> column = base.getNeighbor(Direction.EAST);
      if (!column.isPresent()) {
        return false;
      }

      Chunk unwrapped = column.get();
      for (int j = zStart; j <= zEnd; j++) {
        Optional<Chunk> row = unwrapped.getNeighbor(Direction.SOUTH);
        if (!row.isPresent()) {
          return false;
        }

        if (!allowEmpty && ((net.minecraft.world.chunk.Chunk) row.get()).isEmpty()) {
          return false;
        }
      }
    }

    return true;
  }

  @Override
  public boolean isProcessingExplosion() {
    return this.processingExplosion;
  }

  @Override
  public SpongeConfig<?> getActiveConfig() {
    return this.activeConfig;
  }

  @Override
  public void setActiveConfig(SpongeConfig<?> config) {
    this.activeConfig = config;
  }
}