--- a/net/minecraft/world/World.java
+++ b/net/minecraft/world/World.java
@@ -63,7 +63,7 @@
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-public abstract class World implements IWorld, AutoCloseable {
+public abstract class World extends net.minecraftforge.common.capabilities.CapabilityProvider<World> implements IWorld, AutoCloseable, net.minecraftforge.common.extensions.IForgeWorld {
    protected static final Logger field_195596_d = LogManager.getLogger();
    public static final Codec<RegistryKey<World>> field_234917_f_ = ResourceLocation.field_240908_a_.xmap(RegistryKey.func_240902_a_(Registry.field_239699_ae_), RegistryKey::func_240901_a_);
    public static final RegistryKey<World> field_234918_g_ = RegistryKey.func_240903_a_(Registry.field_239699_ae_, new ResourceLocation("overworld"));
@@ -73,7 +73,7 @@
    public final List<TileEntity> field_147482_g = Lists.newArrayList();
    public final List<TileEntity> field_175730_i = Lists.newArrayList();
    protected final List<TileEntity> field_147484_a = Lists.newArrayList();
-   protected final List<TileEntity> field_147483_b = Lists.newArrayList();
+   protected final java.util.Set<TileEntity> field_147483_b = java.util.Collections.newSetFromMap(new java.util.IdentityHashMap<>()); // Forge: faster "contains" makes removal much more efficient
    private final Thread field_217407_c;
    private final boolean field_234916_c_;
    private int field_73008_k;
@@ -92,8 +92,12 @@
    private final WorldBorder field_175728_M;
    private final BiomeManager field_226689_w_;
    private final RegistryKey<World> field_73011_w;
+   public boolean restoringBlockSnapshots = false;
+   public boolean captureBlockSnapshots = false;
+   public java.util.ArrayList<net.minecraftforge.common.util.BlockSnapshot> capturedBlockSnapshots = new java.util.ArrayList<>();
 
    protected World(ISpawnWorldInfo p_i241925_1_, RegistryKey<World> p_i241925_2_, final DimensionType p_i241925_3_, Supplier<IProfiler> p_i241925_4_, boolean p_i241925_5_, boolean p_i241925_6_, long p_i241925_7_) {
+      super(World.class);
       this.field_72984_F = p_i241925_4_;
       this.field_72986_A = p_i241925_1_;
       this.field_234921_x_ = p_i241925_3_;
@@ -180,17 +184,45 @@
       } else {
          Chunk chunk = this.func_175726_f(p_241211_1_);
          Block block = p_241211_2_.func_177230_c();
+
+         p_241211_1_ = p_241211_1_.func_185334_h(); // Forge - prevent mutable BlockPos leaks
+         net.minecraftforge.common.util.BlockSnapshot blockSnapshot = null;
+         if (this.captureBlockSnapshots && !this.field_72995_K) {
+             blockSnapshot = net.minecraftforge.common.util.BlockSnapshot.create(this.field_73011_w, this, p_241211_1_, p_241211_3_);
+             this.capturedBlockSnapshots.add(blockSnapshot);
+         }
+
+         BlockState old = func_180495_p(p_241211_1_);
+         int oldLight = old.getLightValue(this, p_241211_1_);
+         int oldOpacity = old.func_200016_a(this, p_241211_1_);
+
          BlockState blockstate = chunk.func_177436_a(p_241211_1_, p_241211_2_, (p_241211_3_ & 64) != 0);
          if (blockstate == null) {
+            if (blockSnapshot != null) this.capturedBlockSnapshots.remove(blockSnapshot);
             return false;
          } else {
             BlockState blockstate1 = this.func_180495_p(p_241211_1_);
-            if ((p_241211_3_ & 128) == 0 && blockstate1 != blockstate && (blockstate1.func_200016_a(this, p_241211_1_) != blockstate.func_200016_a(this, p_241211_1_) || blockstate1.func_185906_d() != blockstate.func_185906_d() || blockstate1.func_215691_g() || blockstate.func_215691_g())) {
+            if ((p_241211_3_ & 128) == 0 && blockstate1 != blockstate && (blockstate1.func_200016_a(this, p_241211_1_) != oldOpacity || blockstate1.getLightValue(this, p_241211_1_) != oldLight || blockstate1.func_215691_g() || blockstate.func_215691_g())) {
                this.func_217381_Z().func_76320_a("queueCheckLight");
                this.func_72863_F().func_212863_j_().func_215568_a(p_241211_1_);
                this.func_217381_Z().func_76319_b();
             }
 
+            if (blockSnapshot == null) { // Don't notify clients or update physics while capturing blockstates
+               this.markAndNotifyBlock(p_241211_1_, chunk, blockstate, p_241211_2_, p_241211_3_, p_241211_4_);
+            }
+            return true;
+         }
+      }
+   }
+
+   // Split off from original setBlockState(BlockPos, BlockState, int, int) method in order to directly send client and physic updates
+   public void markAndNotifyBlock(BlockPos p_241211_1_, @Nullable Chunk chunk, BlockState blockstate, BlockState p_241211_2_, int p_241211_3_, int p_241211_4_)
+   {
+       Block block = p_241211_2_.func_177230_c();
+       BlockState blockstate1 = func_180495_p(p_241211_1_);
+       {
+           {
             if (blockstate1 == p_241211_2_) {
                if (blockstate != blockstate1) {
                   this.func_225319_b(p_241211_1_, blockstate, blockstate1);
@@ -216,8 +248,6 @@
 
                this.func_217393_a(p_241211_1_, blockstate, blockstate1);
             }
-
-            return true;
          }
       }
    }
@@ -232,7 +262,7 @@
 
    public boolean func_241212_a_(BlockPos p_241212_1_, boolean p_241212_2_, @Nullable Entity p_241212_3_, int p_241212_4_) {
       BlockState blockstate = this.func_180495_p(p_241212_1_);
-      if (blockstate.func_196958_f()) {
+      if (blockstate.isAir(this, p_241212_1_)) {
          return false;
       } else {
          FluidState fluidstate = this.func_204610_c(p_241212_1_);
@@ -241,7 +271,7 @@
          }
 
          if (p_241212_2_) {
-            TileEntity tileentity = blockstate.func_177230_c().func_235695_q_() ? this.func_175625_s(p_241212_1_) : null;
+            TileEntity tileentity = blockstate.hasTileEntity() ? this.func_175625_s(p_241212_1_) : null;
             Block.func_220054_a(blockstate, this, p_241212_1_, tileentity, p_241212_3_, ItemStack.field_190927_a);
          }
 
@@ -259,6 +289,8 @@
    }
 
    public void func_195593_d(BlockPos p_195593_1_, Block p_195593_2_) {
+      if (net.minecraftforge.event.ForgeEventFactory.onNeighborNotify(this, p_195593_1_, this.func_180495_p(p_195593_1_), java.util.EnumSet.allOf(Direction.class), false).isCanceled())
+         return;
       this.func_190524_a(p_195593_1_.func_177976_e(), p_195593_2_, p_195593_1_);
       this.func_190524_a(p_195593_1_.func_177974_f(), p_195593_2_, p_195593_1_);
       this.func_190524_a(p_195593_1_.func_177977_b(), p_195593_2_, p_195593_1_);
@@ -268,6 +300,11 @@
    }
 
    public void func_175695_a(BlockPos p_175695_1_, Block p_175695_2_, Direction p_175695_3_) {
+      java.util.EnumSet<Direction> directions = java.util.EnumSet.allOf(Direction.class);
+      directions.remove(p_175695_3_);
+      if (net.minecraftforge.event.ForgeEventFactory.onNeighborNotify(this, p_175695_1_, this.func_180495_p(p_175695_1_), directions, false).isCanceled())
+         return;
+
       if (p_175695_3_ != Direction.WEST) {
          this.func_190524_a(p_175695_1_.func_177976_e(), p_175695_2_, p_175695_1_);
       }
@@ -305,9 +342,9 @@
             CrashReportCategory crashreportcategory = crashreport.func_85058_a("Block being updated");
             crashreportcategory.func_189529_a("Source block type", () -> {
                try {
-                  return String.format("ID #%s (%s // %s)", Registry.field_212618_g.func_177774_c(p_190524_2_), p_190524_2_.func_149739_a(), p_190524_2_.getClass().getCanonicalName());
+                  return String.format("ID #%s (%s // %s)", p_190524_2_.getRegistryName(), p_190524_2_.func_149739_a(), p_190524_2_.getClass().getCanonicalName());
                } catch (Throwable throwable1) {
-                  return "ID #" + Registry.field_212618_g.func_177774_c(p_190524_2_);
+                  return "ID #" + p_190524_2_.getRegistryName();
                }
             });
             CrashReportCategory.func_175750_a(crashreportcategory, p_190524_1_, blockstate);
@@ -391,10 +428,12 @@
    }
 
    public boolean func_175700_a(TileEntity p_175700_1_) {
+      if (p_175700_1_.func_145831_w() != this) p_175700_1_.func_226984_a_(this, p_175700_1_.func_174877_v()); // Forge - set the world early as vanilla doesn't set it until next tick
       if (this.field_147481_N) {
          field_195596_d.error("Adding block entity while ticking: {} @ {}", () -> {
             return Registry.field_212626_o.func_177774_c(p_175700_1_.func_200662_C());
          }, p_175700_1_::func_174877_v);
+         return field_147484_a.add(p_175700_1_); // Forge: wait to add new TE if we're currently processing existing ones
       }
 
       boolean flag = this.field_147482_g.add(p_175700_1_);
@@ -402,6 +441,8 @@
          this.field_175730_i.add(p_175700_1_);
       }
 
+      p_175700_1_.onLoad();
+
       if (this.field_72995_K) {
          BlockPos blockpos = p_175700_1_.func_174877_v();
          BlockState blockstate = this.func_180495_p(blockpos);
@@ -413,6 +454,7 @@
 
    public void func_147448_a(Collection<TileEntity> p_147448_1_) {
       if (this.field_147481_N) {
+         p_147448_1_.stream().filter(te -> te.func_145831_w() != this).forEach(te -> te.func_226984_a_(this, te.func_174877_v())); // Forge - set the world early as vanilla doesn't set it until next tick
          this.field_147484_a.addAll(p_147448_1_);
       } else {
          for(TileEntity tileentity : p_147448_1_) {
@@ -425,13 +467,14 @@
    public void func_217391_K() {
       IProfiler iprofiler = this.func_217381_Z();
       iprofiler.func_76320_a("blockEntities");
+      this.field_147481_N = true;// Forge: Move above remove to prevent CMEs
       if (!this.field_147483_b.isEmpty()) {
+         this.field_147483_b.forEach(e -> e.onChunkUnloaded());
          this.field_175730_i.removeAll(this.field_147483_b);
          this.field_147482_g.removeAll(this.field_147483_b);
          this.field_147483_b.clear();
       }
 
-      this.field_147481_N = true;
       Iterator<TileEntity> iterator = this.field_175730_i.iterator();
 
       while(iterator.hasNext()) {
@@ -440,8 +483,9 @@
             BlockPos blockpos = tileentity.func_174877_v();
             if (this.func_72863_F().func_222866_a(blockpos) && this.func_175723_af().func_177746_a(blockpos)) {
                try {
+                  net.minecraftforge.server.timings.TimeTracker.TILE_ENTITY_UPDATE.trackStart(tileentity);
                   iprofiler.func_194340_a(() -> {
-                     return String.valueOf((Object)TileEntityType.func_200969_a(tileentity.func_200662_C()));
+                     return String.valueOf(tileentity.func_200662_C().getRegistryName());
                   });
                   if (tileentity.func_200662_C().func_223045_a(this.func_180495_p(blockpos).func_177230_c())) {
                      ((ITickableTileEntity)tileentity).func_73660_a();
@@ -454,8 +498,16 @@
                   CrashReport crashreport = CrashReport.func_85055_a(throwable, "Ticking block entity");
                   CrashReportCategory crashreportcategory = crashreport.func_85058_a("Block entity being ticked");
                   tileentity.func_145828_a(crashreportcategory);
+                  if (net.minecraftforge.common.ForgeConfig.SERVER.removeErroringTileEntities.get()) {
+                     LogManager.getLogger().fatal("{}", crashreport.func_71502_e());
+                     tileentity.func_145843_s();
+                     this.func_175713_t(tileentity.func_174877_v());
+                  } else
                   throw new ReportedException(crashreport);
                }
+               finally {
+                  net.minecraftforge.server.timings.TimeTracker.TILE_ENTITY_UPDATE.trackEnd(tileentity);
+               }
             }
          }
 
@@ -463,7 +515,10 @@
             iterator.remove();
             this.field_147482_g.remove(tileentity);
             if (this.func_175667_e(tileentity.func_174877_v())) {
-               this.func_175726_f(tileentity.func_174877_v()).func_177425_e(tileentity.func_174877_v());
+               //Forge: Bugfix: If we set the tile entity it immediately sets it in the chunk, so we could be desyned
+               Chunk chunk = this.func_175726_f(tileentity.func_174877_v());
+               if (chunk.func_177424_a(tileentity.func_174877_v(), Chunk.CreateEntityType.CHECK) == tileentity)
+                  chunk.func_177425_e(tileentity.func_174877_v());
             }
          }
       }
@@ -495,12 +550,15 @@
 
    public void func_217390_a(Consumer<Entity> p_217390_1_, Entity p_217390_2_) {
       try {
+         net.minecraftforge.server.timings.TimeTracker.ENTITY_UPDATE.trackStart(p_217390_2_);
          p_217390_1_.accept(p_217390_2_);
       } catch (Throwable throwable) {
          CrashReport crashreport = CrashReport.func_85055_a(throwable, "Ticking entity");
          CrashReportCategory crashreportcategory = crashreport.func_85058_a("Entity being ticked");
          p_217390_2_.func_85029_a(crashreportcategory);
          throw new ReportedException(crashreport);
+      } finally {
+         net.minecraftforge.server.timings.TimeTracker.ENTITY_UPDATE.trackEnd(p_217390_2_);
       }
    }
 
@@ -514,6 +572,7 @@
 
    public Explosion func_230546_a_(@Nullable Entity p_230546_1_, @Nullable DamageSource p_230546_2_, @Nullable ExplosionContext p_230546_3_, double p_230546_4_, double p_230546_6_, double p_230546_8_, float p_230546_10_, boolean p_230546_11_, Explosion.Mode p_230546_12_) {
       Explosion explosion = new Explosion(this, p_230546_1_, p_230546_2_, p_230546_3_, p_230546_4_, p_230546_6_, p_230546_8_, p_230546_10_, p_230546_11_, p_230546_12_);
+      if (net.minecraftforge.event.ForgeEventFactory.onExplosionStart(this, explosion)) return explosion;
       explosion.func_77278_a();
       explosion.func_77279_a(true);
       return explosion;
@@ -561,6 +620,7 @@
 
    public void func_175690_a(BlockPos p_175690_1_, @Nullable TileEntity p_175690_2_) {
       if (!func_189509_E(p_175690_1_)) {
+         p_175690_1_ = p_175690_1_.func_185334_h(); // Forge - prevent mutable BlockPos leaks
          if (p_175690_2_ != null && !p_175690_2_.func_145837_r()) {
             if (this.field_147481_N) {
                p_175690_2_.func_226984_a_(this, p_175690_1_);
@@ -576,7 +636,8 @@
 
                this.field_147484_a.add(p_175690_2_);
             } else {
-               this.func_175726_f(p_175690_1_).func_177426_a(p_175690_1_, p_175690_2_);
+               Chunk chunk = this.func_175726_f(p_175690_1_);
+               if (chunk != null) chunk.func_177426_a(p_175690_1_, p_175690_2_);
                this.func_175700_a(p_175690_2_);
             }
          }
@@ -589,6 +650,8 @@
       if (tileentity != null && this.field_147481_N) {
          tileentity.func_145843_s();
          this.field_147484_a.remove(tileentity);
+         if (!(tileentity instanceof ITickableTileEntity)) //Forge: If they are not tickable they wont be removed in the update loop.
+            this.field_147482_g.remove(tileentity);
       } else {
          if (tileentity != null) {
             this.field_147484_a.remove(tileentity);
@@ -598,7 +661,7 @@
 
          this.func_175726_f(p_175713_1_).func_177425_e(p_175713_1_);
       }
-
+      this.func_175666_e(p_175713_1_, func_180495_p(p_175713_1_).func_177230_c()); //Notify neighbors of changes
    }
 
    public boolean func_195588_v(BlockPos p_195588_1_) {
@@ -651,10 +714,10 @@
    public List<Entity> func_175674_a(@Nullable Entity p_175674_1_, AxisAlignedBB p_175674_2_, @Nullable Predicate<? super Entity> p_175674_3_) {
       this.func_217381_Z().func_230035_c_("getEntities");
       List<Entity> list = Lists.newArrayList();
-      int i = MathHelper.func_76128_c((p_175674_2_.field_72340_a - 2.0D) / 16.0D);
-      int j = MathHelper.func_76128_c((p_175674_2_.field_72336_d + 2.0D) / 16.0D);
-      int k = MathHelper.func_76128_c((p_175674_2_.field_72339_c - 2.0D) / 16.0D);
-      int l = MathHelper.func_76128_c((p_175674_2_.field_72334_f + 2.0D) / 16.0D);
+      int i = MathHelper.func_76128_c((p_175674_2_.field_72340_a - getMaxEntityRadius()) / 16.0D);
+      int j = MathHelper.func_76128_c((p_175674_2_.field_72336_d + getMaxEntityRadius()) / 16.0D);
+      int k = MathHelper.func_76128_c((p_175674_2_.field_72339_c - getMaxEntityRadius()) / 16.0D);
+      int l = MathHelper.func_76128_c((p_175674_2_.field_72334_f + getMaxEntityRadius()) / 16.0D);
       AbstractChunkProvider abstractchunkprovider = this.func_72863_F();
 
       for(int i1 = i; i1 <= j; ++i1) {
@@ -671,10 +734,10 @@
 
    public <T extends Entity> List<T> func_217394_a(@Nullable EntityType<T> p_217394_1_, AxisAlignedBB p_217394_2_, Predicate<? super T> p_217394_3_) {
       this.func_217381_Z().func_230035_c_("getEntities");
-      int i = MathHelper.func_76128_c((p_217394_2_.field_72340_a - 2.0D) / 16.0D);
-      int j = MathHelper.func_76143_f((p_217394_2_.field_72336_d + 2.0D) / 16.0D);
-      int k = MathHelper.func_76128_c((p_217394_2_.field_72339_c - 2.0D) / 16.0D);
-      int l = MathHelper.func_76143_f((p_217394_2_.field_72334_f + 2.0D) / 16.0D);
+      int i = MathHelper.func_76128_c((p_217394_2_.field_72340_a - getMaxEntityRadius()) / 16.0D);
+      int j = MathHelper.func_76143_f((p_217394_2_.field_72336_d + getMaxEntityRadius()) / 16.0D);
+      int k = MathHelper.func_76128_c((p_217394_2_.field_72339_c - getMaxEntityRadius()) / 16.0D);
+      int l = MathHelper.func_76143_f((p_217394_2_.field_72334_f + getMaxEntityRadius()) / 16.0D);
       List<T> list = Lists.newArrayList();
 
       for(int i1 = i; i1 < j; ++i1) {
@@ -691,10 +754,10 @@
 
    public <T extends Entity> List<T> func_175647_a(Class<? extends T> p_175647_1_, AxisAlignedBB p_175647_2_, @Nullable Predicate<? super T> p_175647_3_) {
       this.func_217381_Z().func_230035_c_("getEntities");
-      int i = MathHelper.func_76128_c((p_175647_2_.field_72340_a - 2.0D) / 16.0D);
-      int j = MathHelper.func_76143_f((p_175647_2_.field_72336_d + 2.0D) / 16.0D);
-      int k = MathHelper.func_76128_c((p_175647_2_.field_72339_c - 2.0D) / 16.0D);
-      int l = MathHelper.func_76143_f((p_175647_2_.field_72334_f + 2.0D) / 16.0D);
+      int i = MathHelper.func_76128_c((p_175647_2_.field_72340_a - getMaxEntityRadius()) / 16.0D);
+      int j = MathHelper.func_76143_f((p_175647_2_.field_72336_d + getMaxEntityRadius()) / 16.0D);
+      int k = MathHelper.func_76128_c((p_175647_2_.field_72339_c - getMaxEntityRadius()) / 16.0D);
+      int l = MathHelper.func_76143_f((p_175647_2_.field_72334_f + getMaxEntityRadius()) / 16.0D);
       List<T> list = Lists.newArrayList();
       AbstractChunkProvider abstractchunkprovider = this.func_72863_F();
 
@@ -712,10 +775,10 @@
 
    public <T extends Entity> List<T> func_225316_b(Class<? extends T> p_225316_1_, AxisAlignedBB p_225316_2_, @Nullable Predicate<? super T> p_225316_3_) {
       this.func_217381_Z().func_230035_c_("getLoadedEntities");
-      int i = MathHelper.func_76128_c((p_225316_2_.field_72340_a - 2.0D) / 16.0D);
-      int j = MathHelper.func_76143_f((p_225316_2_.field_72336_d + 2.0D) / 16.0D);
-      int k = MathHelper.func_76128_c((p_225316_2_.field_72339_c - 2.0D) / 16.0D);
-      int l = MathHelper.func_76143_f((p_225316_2_.field_72334_f + 2.0D) / 16.0D);
+      int i = MathHelper.func_76128_c((p_225316_2_.field_72340_a - getMaxEntityRadius()) / 16.0D);
+      int j = MathHelper.func_76143_f((p_225316_2_.field_72336_d + getMaxEntityRadius()) / 16.0D);
+      int k = MathHelper.func_76128_c((p_225316_2_.field_72339_c - getMaxEntityRadius()) / 16.0D);
+      int l = MathHelper.func_76143_f((p_225316_2_.field_72334_f + getMaxEntityRadius()) / 16.0D);
       List<T> list = Lists.newArrayList();
       AbstractChunkProvider abstractchunkprovider = this.func_72863_F();
 
@@ -739,6 +802,7 @@
          this.func_175726_f(p_175646_1_).func_76630_e();
       }
 
+      this.func_175666_e(p_175646_1_, func_180495_p(p_175646_1_).func_177230_c()); //Notify neighbors of changes
    }
 
    public int func_181545_F() {
@@ -783,7 +847,7 @@
    public int func_175651_c(BlockPos p_175651_1_, Direction p_175651_2_) {
       BlockState blockstate = this.func_180495_p(p_175651_1_);
       int i = blockstate.func_185911_a(this, p_175651_1_, p_175651_2_);
-      return blockstate.func_215686_e(this, p_175651_1_) ? Math.max(i, this.func_175676_y(p_175651_1_)) : i;
+      return blockstate.shouldCheckWeakPower(this, p_175651_1_, p_175651_2_) ? Math.max(i, this.func_175676_y(p_175651_1_)) : i;
    }
 
    public boolean func_175640_z(BlockPos p_175640_1_) {
@@ -938,16 +1002,15 @@
    public abstract Scoreboard func_96441_U();
 
    public void func_175666_e(BlockPos p_175666_1_, Block p_175666_2_) {
-      for(Direction direction : Direction.Plane.HORIZONTAL) {
+      for(Direction direction : Direction.values()) {
          BlockPos blockpos = p_175666_1_.func_177972_a(direction);
          if (this.func_175667_e(blockpos)) {
             BlockState blockstate = this.func_180495_p(blockpos);
-            if (blockstate.func_203425_a(Blocks.field_196762_fd)) {
-               blockstate.func_215697_a(this, blockpos, p_175666_2_, p_175666_1_, false);
-            } else if (blockstate.func_215686_e(this, blockpos)) {
+            blockstate.onNeighborChange(this, blockpos, p_175666_1_);
+            if (blockstate.func_215686_e(this, blockpos)) {
                blockpos = blockpos.func_177972_a(direction);
                blockstate = this.func_180495_p(blockpos);
-               if (blockstate.func_203425_a(Blocks.field_196762_fd)) {
+               if (blockstate.getWeakChanges(this, blockpos)) {
                   blockstate.func_215697_a(this, blockpos, p_175666_2_, p_175666_1_, false);
                }
             }
@@ -1024,6 +1087,18 @@
       return this.field_226689_w_;
    }
 
+   private double maxEntityRadius = 2.0D;
+   @Override
+   public double getMaxEntityRadius() {
+      return maxEntityRadius;
+   }
+   @Override
+   public double increaseMaxEntityRadius(double value) {
+      if (value > maxEntityRadius)
+         maxEntityRadius = value;
+      return maxEntityRadius;
+   }
+
    public final boolean func_234925_Z_() {
       return this.field_234916_c_;
    }
