aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch')
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch657
1 files changed, 657 insertions, 0 deletions
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
new file mode 100644
index 0000000000..754c0d02de
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
@@ -0,0 +1,657 @@
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -163,6 +164,19 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import net.minecraft.world.ticks.LevelTicks;
+ import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.WeatherType;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.generator.CustomWorldChunkManager;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.craftbukkit.util.WorldUUID;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.server.MapInitializeEvent;
++import org.bukkit.event.weather.LightningStrikeEvent;
++import org.bukkit.event.world.GenericGameEvent;
++import org.bukkit.event.world.TimeSkipEvent;
++// CraftBukkit end
+
+ public class ServerLevel extends Level implements WorldGenLevel {
+
+@@ -177,7 +191,7 @@
+ final List<ServerPlayer> players;
+ private final ServerChunkCache chunkSource;
+ private final MinecraftServer server;
+- private final ServerLevelData serverLevelData;
++ public final PrimaryLevelData serverLevelData; // CraftBukkit - type
+ final EntityTickList entityTickList;
+ private final PersistentEntitySectionManager<Entity> entityManager;
+ private final GameEventDispatcher gameEventDispatcher;
+@@ -202,12 +216,30 @@
+ private final boolean tickTime;
+ private final RandomSequences randomSequences;
+
+- public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, ServerLevelData serverleveldata, ResourceKey<Level> resourcekey, LevelStem levelstem, ChunkProgressListener chunkprogresslistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences) {
+- RegistryAccess.Frozen registryaccess_frozen = minecraftserver.registryAccess();
+- Holder holder = levelstem.type();
++ // CraftBukkit start
++ public final LevelStorageSource.LevelStorageAccess convertable;
++ public final UUID uuid;
+
+- Objects.requireNonNull(minecraftserver);
+- super(serverleveldata, resourcekey, registryaccess_frozen, holder, minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates());
++ public LevelChunk getChunkIfLoaded(int x, int z) {
++ return this.chunkSource.getChunk(x, z, false);
++ }
++
++ @Override
++ public ResourceKey<LevelStem> getTypeKey() {
++ return convertable.dimensionType;
++ }
++
++ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
++ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
++ // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
++ // Holder holder = worlddimension.type(); // CraftBukkit - decompile error
++
++ // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env);
++ this.pvpMode = minecraftserver.isPvpAllowed();
++ convertable = convertable_conversionsession;
++ uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
++ // CraftBukkit end
+ this.players = Lists.newArrayList();
+ this.entityTickList = new EntityTickList();
+ this.blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier());
+@@ -219,8 +251,24 @@
+ this.tickTime = flag1;
+ this.server = minecraftserver;
+ this.customSpawners = list;
+- this.serverLevelData = serverleveldata;
+- ChunkGenerator chunkgenerator = levelstem.generator();
++ this.serverLevelData = iworlddataserver;
++ ChunkGenerator chunkgenerator = worlddimension.generator();
++ // CraftBukkit start
++ serverLevelData.setWorld(this);
++
++ if (biomeProvider != null) {
++ BiomeSource worldChunkManager = new CustomWorldChunkManager(getWorld(), biomeProvider, server.registryAccess().registryOrThrow(Registries.BIOME));
++ if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
++ chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
++ } else if (chunkgenerator instanceof FlatLevelSource cpf) {
++ chunkgenerator = new FlatLevelSource(cpf.settings(), worldChunkManager);
++ }
++ }
++
++ if (gen != null) {
++ chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen);
++ }
++ // CraftBukkit end
+ boolean flag2 = minecraftserver.forceSynchronousWrites();
+ DataFixer datafixer = minecraftserver.getFixerUpper();
+ EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, levelstoragesource_levelstorageaccess.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
+@@ -248,9 +296,9 @@
+ long l = minecraftserver.getWorldData().worldGenOptions().seed();
+
+ this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), resourcekey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer);
+- this.structureManager = new StructureManager(this, minecraftserver.getWorldData().worldGenOptions(), this.structureCheck);
+- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
+- this.dragonFight = new EndDragonFight(this, l, minecraftserver.getWorldData().endDragonFightData());
++ this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), structureCheck); // CraftBukkit
++ if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END
++ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit
+ } else {
+ this.dragonFight = null;
+ }
+@@ -260,6 +308,7 @@
+ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
+ return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
+ });
++ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ }
+
+ /** @deprecated */
+@@ -305,12 +353,18 @@
+ long j;
+
+ if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
++ // CraftBukkit start
++ j = this.levelData.getDayTime() + 24000L;
++ TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime());
+ if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ j = this.levelData.getDayTime() + 24000L;
+ this.setDayTime(j - j % 24000L);
+ }
+
+- this.wakeUpAllPlayers();
++ if (!event.isCancelled()) {
++ this.wakeUpAllPlayers();
++ }
++ // CraftBukkit end
+ if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
+ this.resetWeatherCycle();
+ }
+@@ -344,8 +400,8 @@
+ }
+
+ this.handlingTick = false;
+- profilerfiller.pop();
+- boolean flag1 = !this.players.isEmpty() || !this.getForcedChunks().isEmpty();
++ gameprofilerfiller.pop();
++ boolean flag1 = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
+
+ if (flag1) {
+ this.resetEmptyTime();
+@@ -361,7 +417,7 @@
+
+ this.entityTickList.forEach((entity) -> {
+ if (!entity.isRemoved()) {
+- if (this.shouldDiscardEntity(entity)) {
++ if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
+ entity.discard();
+ } else if (!tickratemanager.isEntityFrozen(entity)) {
+ profilerfiller.push("checkDespawn");
+@@ -457,20 +512,20 @@
+ if (flag1) {
+ SkeletonHorse skeletonhorse = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this);
+
+- if (skeletonhorse != null) {
+- skeletonhorse.setTrap(true);
+- skeletonhorse.setAge(0);
+- skeletonhorse.setPos((double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ());
+- this.addFreshEntity(skeletonhorse);
++ if (entityhorseskeleton != null) {
++ entityhorseskeleton.setTrap(true);
++ entityhorseskeleton.setAge(0);
++ entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
++ this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
+ }
+ }
+
+ LightningBolt lightningbolt = (LightningBolt) EntityType.LIGHTNING_BOLT.create(this);
+
+- if (lightningbolt != null) {
+- lightningbolt.moveTo(Vec3.atBottomCenterOf(blockpos));
+- lightningbolt.setVisualOnly(flag1);
+- this.addFreshEntity(lightningbolt);
++ if (entitylightning != null) {
++ entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
++ entitylightning.setVisualOnly(flag1);
++ this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit
+ }
+ }
+ }
+@@ -525,8 +580,8 @@
+ BlockPos blockpos2 = blockpos1.below();
+ Biome biome = (Biome) this.getBiome(blockpos1).value();
+
+- if (biome.shouldFreeze(this, blockpos2)) {
+- this.setBlockAndUpdate(blockpos2, Blocks.ICE.defaultBlockState());
++ if (biomebase.shouldFreeze(this, blockposition2)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ }
+
+ if (this.isRaining()) {
+@@ -541,11 +596,11 @@
+ if (j < Math.min(i, 8)) {
+ BlockState blockstate1 = (BlockState) blockstate.setValue(SnowLayerBlock.LAYERS, j + 1);
+
+- Block.pushEntitiesUp(blockstate, blockstate1, this, blockpos1);
+- this.setBlockAndUpdate(blockpos1, blockstate1);
++ Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit
+ }
+ } else {
+- this.setBlockAndUpdate(blockpos1, Blocks.SNOW.defaultBlockState());
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
+ }
+ }
+
+@@ -707,6 +761,7 @@
+ this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
+ }
+
++ /* CraftBukkit start
+ if (this.oRainLevel != this.rainLevel) {
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension());
+ }
+@@ -726,14 +787,41 @@
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
+ }
+
++ if (flag != this.isRaining()) {
++ // Only send weather packets to those affected
++ for (int idx = 0; idx < this.players.size(); ++idx) {
++ if (((ServerPlayer) this.players.get(idx)).level() == this) {
++ ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false);
++ }
++ }
++ }
++ for (int idx = 0; idx < this.players.size(); ++idx) {
++ if (((ServerPlayer) this.players.get(idx)).level() == this) {
++ ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
++ }
++ }
++ // CraftBukkit end
++
+ }
+
+ @VisibleForTesting
+ public void resetWeatherCycle() {
+- this.serverLevelData.setRainTime(0);
++ // CraftBukkit start
+ this.serverLevelData.setRaining(false);
+- this.serverLevelData.setThunderTime(0);
++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++ // Not that everyone ever manages to get the whole server to sleep at the same time....
++ if (!this.serverLevelData.isRaining()) {
++ this.serverLevelData.setRainTime(0);
++ }
++ // CraftBukkit end
+ this.serverLevelData.setThundering(false);
++ // CraftBukkit start
++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++ // Not that everyone ever manages to get the whole server to sleep at the same time....
++ if (!this.serverLevelData.isThundering()) {
++ this.serverLevelData.setThunderTime(0);
++ }
++ // CraftBukkit end
+ }
+
+ public void resetEmptyTime() {
+@@ -768,6 +856,7 @@
+ });
+ profilerfiller.incrementCounter("tickNonPassenger");
+ entity.tick();
++ entity.postTick(); // CraftBukkit
+ this.getProfiler().pop();
+ Iterator iterator = entity.getPassengers().iterator();
+
+@@ -789,10 +878,11 @@
+ profilerfiller.push(() -> {
+ return BuiltInRegistries.ENTITY_TYPE.getKey(entity1.getType()).toString();
+ });
+- profilerfiller.incrementCounter("tickPassenger");
+- entity1.rideTick();
+- profilerfiller.pop();
+- Iterator iterator = entity1.getPassengers().iterator();
++ gameprofilerfiller.incrementCounter("tickPassenger");
++ passengerEntity.rideTick();
++ passengerEntity.postTick(); // CraftBukkit
++ gameprofilerfiller.pop();
++ Iterator iterator = passengerEntity.getPassengers().iterator();
+
+ while (iterator.hasNext()) {
+ Entity entity2 = (Entity) iterator.next();
+@@ -815,9 +904,10 @@
+ public void save(@Nullable ProgressListener progresslistener, boolean flag, boolean flag1) {
+ ServerChunkCache serverchunkcache = this.getChunkSource();
+
+- if (!flag1) {
+- if (progresslistener != null) {
+- progresslistener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
++ if (!skipSave) {
++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
++ if (progress != null) {
++ progress.progressStartNoAbort(Component.translatable("menu.savingLevel"));
+ }
+
+ this.saveLevelData();
+@@ -833,11 +923,19 @@
+ }
+
+ }
++
++ // CraftBukkit start - moved from MinecraftServer.saveChunks
++ ServerLevel worldserver1 = this;
++
++ serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
++ serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save());
++ convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
++ // CraftBukkit end
+ }
+
+ private void saveLevelData() {
+ if (this.dragonFight != null) {
+- this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData());
++ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
+ }
+
+ this.getChunkSource().getDataStorage().save();
+@@ -903,19 +1000,37 @@
+ @Override
+ @Override
+ public boolean addFreshEntity(Entity entity) {
+- return this.addEntity(entity);
++ // CraftBukkit start
++ return this.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
++ @Override
++ public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ return this.addEntity(entity, reason);
++ // CraftBukkit end
++ }
++
+ public boolean addWithUUID(Entity entity) {
+- return this.addEntity(entity);
++ // CraftBukkit start
++ return this.addWithUUID(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
++ public boolean addWithUUID(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ return this.addEntity(entity, reason);
++ // CraftBukkit end
++ }
++
+ public void addDuringTeleport(Entity entity) {
+- this.addEntity(entity);
++ // CraftBukkit start
++ // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds,
++ // since it is only an implementation detail, that a new entity is created when
++ // they are traveling between worlds.
++ this.addDuringTeleport(entity, null);
+ }
+
+- public void addDuringCommandTeleport(ServerPlayer serverplayer) {
+- this.addPlayer(serverplayer);
++ public void addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ this.addEntity(entity, reason);
++ // CraftBukkit end
+ }
+
+ public void addDuringPortalTeleport(ServerPlayer serverplayer) {
+@@ -942,24 +1061,37 @@
+ this.entityManager.addNewEntity(serverplayer);
+ }
+
+- private boolean addEntity(Entity entity) {
++ // CraftBukkit start
++ private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
+ if (entity.isRemoved()) {
+- ServerLevel.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType()));
++ // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit
+ return false;
+ } else {
++ // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world.
++ if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
++ return false;
++ }
++ // CraftBukkit end
++
+ return this.entityManager.addNewEntity(entity);
+ }
+ }
+
+ public boolean tryAddFreshEntityWithPassengers(Entity entity) {
+- Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID);
++ // CraftBukkit start
++ return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ // CraftBukkit end
++ Stream<UUID> stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error
+ PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
+
+ Objects.requireNonNull(this.entityManager);
+ if (stream.anyMatch(persistententitysectionmanager::isLoaded)) {
+ return false;
+ } else {
+- this.addFreshEntityWithPassengers(entity);
++ this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
+ return true;
+ }
+ }
+@@ -973,11 +1105,33 @@
+ serverplayer.remove(entity_removalreason);
+ }
+
++ // CraftBukkit start
++ public boolean strikeLightning(Entity entitylightning) {
++ return this.strikeLightning(entitylightning, LightningStrikeEvent.Cause.UNKNOWN);
++ }
++
++ public boolean strikeLightning(Entity entitylightning, LightningStrikeEvent.Cause cause) {
++ LightningStrikeEvent lightning = CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause);
++
++ if (lightning.isCancelled()) {
++ return false;
++ }
++
++ return this.addFreshEntity(entitylightning);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void destroyBlockProgress(int i, BlockPos blockpos, int j) {
+ Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
+
++ // CraftBukkit start
++ Player entityhuman = null;
++ Entity entity = this.getEntity(breakerId);
++ if (entity instanceof Player) entityhuman = (Player) entity;
++ // CraftBukkit end
++
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+
+@@ -986,6 +1139,12 @@
+ double d1 = (double) blockpos.getY() - serverplayer.getY();
+ double d2 = (double) blockpos.getZ() - serverplayer.getZ();
+
++ // CraftBukkit start
++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
++ continue;
++ }
++ // CraftBukkit end
++
+ if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
+ serverplayer.connection.send(new ClientboundBlockDestructionPacket(i, blockpos, j));
+ }
+@@ -1051,8 +1204,19 @@
+ Iterator iterator = this.navigatingMobs.iterator();
+
+ while (iterator.hasNext()) {
+- Mob mob = (Mob) iterator.next();
+- PathNavigation pathnavigation = mob.getNavigation();
++ // CraftBukkit start - fix SPIGOT-6362
++ Mob entityinsentient;
++ try {
++ entityinsentient = (Mob) iterator.next();
++ } catch (java.util.ConcurrentModificationException ex) {
++ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
++ // In this case we just run the update again across all the iterators as the chunk will then be loaded
++ // As this is a relative edge case it is much faster than copying navigators (on either read or write)
++ sendBlockUpdated(pos, oldState, newState, flags);
++ return;
++ }
++ // CraftBukkit end
++ PathNavigation navigationabstract = entityinsentient.getNavigation();
+
+ if (pathnavigation.shouldRecomputePath(blockpos)) {
+ list.add(pathnavigation);
+@@ -1118,9 +1275,13 @@
+ }
+
+ @Override
+- @Override
+- public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction level_explosioninteraction, ParticleOptions particleoptions, ParticleOptions particleoptions1, SoundEvent soundevent) {
+- Explosion explosion = this.explode(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, level_explosioninteraction, false, particleoptions, particleoptions1, soundevent);
++ public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.a world_a, ParticleOptions particleparam, ParticleOptions particleparam1, SoundEvent soundeffect) {
++ Explosion explosion = this.explode(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, false, particleparam, particleparam1, soundeffect);
++ // CraftBukkit start
++ if (explosion.wasCanceled) {
++ return explosion;
++ }
++ // CraftBukkit end
+
+ if (!explosion.interactsWithBlocks()) {
+ explosion.clearToBlow();
+@@ -1196,14 +1353,21 @@
+ return this.server.getStructureManager();
+ }
+
+- public <T extends ParticleOptions> int sendParticles(T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) {
+- ClientboundLevelParticlesPacket clientboundlevelparticlespacket = new ClientboundLevelParticlesPacket(t0, false, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i);
++ public <T extends ParticleOptions> int sendParticles(T type, double posX, double d1, double posY, int i, double posZ, double d4, double particleCount, double xOffset) {
++ // CraftBukkit - visibility api support
++ return sendParticles(null, type, posX, d1, posY, i, posZ, d4, particleCount, xOffset, false);
++ }
++
++ public <T extends ParticleOptions> int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) {
++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i);
++ // CraftBukkit end
+ int j = 0;
+
+ for (int k = 0; k < this.players.size(); ++k) {
+- ServerPlayer serverplayer = (ServerPlayer) this.players.get(k);
++ ServerPlayer entityplayer = (ServerPlayer) this.players.get(k);
++ if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
+
+- if (this.sendParticles(serverplayer, false, d0, d1, d2, clientboundlevelparticlespacket)) {
++ if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit
+ ++j;
+ }
+ }
+@@ -1254,8 +1417,8 @@
+ }
+
+ @Nullable
+- public BlockPos findNearestMapStructure(TagKey<Structure> tagkey, BlockPos blockpos, int i, boolean flag) {
+- if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
++ public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipExistingChunks) {
++ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
+ return null;
+ } else {
+ Optional<HolderSet.Named<Structure>> optional = this.registryAccess().registryOrThrow(Registries.STRUCTURE).getTag(tagkey);
+@@ -1299,15 +1459,24 @@
+
+ @Nullable
+ @Override
+- @Override
+- public MapItemSavedData getMapData(String s) {
+- return (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), s);
++ public MapItemSavedData getMapData(String mapName) {
++ // CraftBukkit start
++ MapItemSavedData worldmap = (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), mapName);
++ if (worldmap != null) {
++ worldmap.id = mapName;
++ }
++ return worldmap;
++ // CraftBukkit end
+ }
+
+ @Override
+- @Override
+- public void setMapData(String s, MapItemSavedData mapitemsaveddata) {
+- this.getServer().overworld().getDataStorage().set(s, mapitemsaveddata);
++ public void setMapData(String mapName, MapItemSavedData data) {
++ // CraftBukkit start
++ data.id = mapName;
++ MapInitializeEvent event = new MapInitializeEvent(data.mapView);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
++ this.getServer().overworld().getDataStorage().set(mapName, data);
+ }
+
+ @Override
+@@ -1608,7 +1773,12 @@
+ @Override
+ public void blockUpdated(BlockPos blockpos, Block block) {
+ if (!this.isDebug()) {
+- this.updateNeighborsAt(blockpos, block);
++ // CraftBukkit start
++ if (populating) {
++ return;
++ }
++ // CraftBukkit end
++ this.updateNeighborsAt(pos, block);
+ }
+
+ }
+@@ -1629,13 +1797,13 @@
+ }
+
+ public boolean isFlat() {
+- return this.server.getWorldData().isFlatWorld();
++ return this.serverLevelData.isFlatWorld(); // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public long getSeed() {
+- return this.server.getWorldData().worldGenOptions().seed();
++ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit
+ }
+
+ @Nullable
+@@ -1678,18 +1844,34 @@
+ }
+ }
+
+- public static void makeObsidianPlatform(ServerLevel serverlevel) {
+- BlockPos blockpos = ServerLevel.END_SPAWN_POINT;
+- int i = blockpos.getX();
+- int j = blockpos.getY() - 2;
+- int k = blockpos.getZ();
++ public static void makeObsidianPlatform(ServerLevel serverLevel) {
++ // CraftBukkit start
++ ServerLevel.makeObsidianPlatform(serverLevel, null);
++ }
+
+- BlockPos.betweenClosed(i - 2, j + 1, k - 2, i + 2, j + 3, k + 2).forEach((blockpos1) -> {
+- serverlevel.setBlockAndUpdate(blockpos1, Blocks.AIR.defaultBlockState());
++ public static void makeObsidianPlatform(ServerLevel worldserver, Entity entity) {
++ // CraftBukkit end
++ BlockPos blockposition = ServerLevel.END_SPAWN_POINT;
++ int i = blockposition.getX();
++ int j = blockposition.getY() - 2;
++ int k = blockposition.getZ();
++
++ // CraftBukkit start
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(worldserver);
++ BlockPos.betweenClosed(i - 2, j + 1, k - 2, i + 2, j + 3, k + 2).forEach((blockposition1) -> {
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
+ });
+ BlockPos.betweenClosed(i - 2, j, k - 2, i + 2, j, k + 2).forEach((blockpos1) -> {
+ serverlevel.setBlockAndUpdate(blockpos1, Blocks.OBSIDIAN.defaultBlockState());
+ });
++ org.bukkit.World bworld = worldserver.getWorld();
++ org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((List<org.bukkit.block.BlockState>) (List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
++
++ worldserver.getCraftServer().getPluginManager().callEvent(portalEvent);
++ if (!portalEvent.isCancelled()) {
++ blockList.updateList();
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -1833,6 +2005,8 @@
+ }
+
+ entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
++ entity.inWorld = true; // CraftBukkit - Mark entity as in world
++ entity.valid = true; // CraftBukkit
+ }
+
+ @Override
+@@ -1870,6 +2043,14 @@
+ }
+
+ entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
++ // CraftBukkit start
++ entity.valid = false;
++ if (!(entity instanceof ServerPlayer)) {
++ for (ServerPlayer player : players) {
++ player.getBukkitEntity().onEntityRemove(entity);
++ }
++ }
++ // CraftBukkit end
+ }
+
+ @Override