package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import net.minecraft.core.BlockPosition; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.IRegistry; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.network.protocol.game.PacketPlayOutEntitySound; import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect; import net.minecraft.network.protocol.game.PacketPlayOutUpdateTime; import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent; import net.minecraft.resources.MinecraftKey; import net.minecraft.server.level.ChunkMapDistance; import net.minecraft.server.level.EntityPlayer; import net.minecraft.server.level.PlayerChunk; import net.minecraft.server.level.PlayerChunkMap; import net.minecraft.server.level.Ticket; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.WorldServer; import net.minecraft.sounds.SoundCategory; import net.minecraft.sounds.SoundEffect; import net.minecraft.util.ArraySetSorted; import net.minecraft.util.Unit; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.entity.EntityLightning; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.item.EntityFallingBlock; import net.minecraft.world.entity.item.EntityItem; import net.minecraft.world.entity.player.EntityHuman; import net.minecraft.world.entity.projectile.EntityArrow; import net.minecraft.world.entity.raid.PersistentRaid; import net.minecraft.world.level.ChunkCoordIntPair; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.RayTrace; import net.minecraft.world.level.biome.BiomeBase; import net.minecraft.world.level.biome.Climate; import net.minecraft.world.level.chunk.IChunkAccess; import net.minecraft.world.level.chunk.ProtoChunkExtension; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.storage.SavedFile; import net.minecraft.world.phys.AxisAlignedBB; import net.minecraft.world.phys.MovingObjectPosition; import net.minecraft.world.phys.Vec3D; import net.minecraft.world.phys.shapes.VoxelShapeCollision; import org.bukkit.BlockChangeDelegate; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Difficulty; import org.bukkit.Effect; import org.bukkit.FeatureFlag; import org.bukkit.FluidCollisionMode; import org.bukkit.GameRule; import org.bukkit.Instrument; import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.Note; import org.bukkit.Particle; import org.bukkit.Raid; import org.bukkit.Registry; import org.bukkit.Sound; import org.bukkit.TreeType; import org.bukkit.World; import org.bukkit.WorldBorder; import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.boss.DragonBattle; import org.bukkit.craftbukkit.block.CraftBiome; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.boss.CraftDragonBattle; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.generator.structure.CraftGeneratedStructure; import org.bukkit.craftbukkit.generator.structure.CraftStructure; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.metadata.BlockMetadataStore; import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; import org.bukkit.craftbukkit.util.CraftBiomeSearchResult; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.CraftRayTraceResult; import org.bukkit.craftbukkit.util.CraftSpawnCategory; import org.bukkit.craftbukkit.util.CraftStructureSearchResult; import org.bukkit.entity.AbstractArrow; import org.bukkit.entity.Arrow; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.LightningStrike; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.SpawnCategory; import org.bukkit.entity.SpectralArrow; import org.bukkit.entity.TippedArrow; import org.bukkit.entity.Trident; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.world.SpawnChangeEvent; import org.bukkit.event.world.TimeSkipEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.structure.GeneratedStructure; import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.StructureType; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; import org.bukkit.metadata.MetadataValue; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.StandardMessenger; import org.bukkit.potion.PotionType; import org.bukkit.util.BiomeSearchResult; import org.bukkit.util.BoundingBox; import org.bukkit.util.NumberConversions; import org.bukkit.util.RayTraceResult; import org.bukkit.util.StructureSearchResult; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); private final WorldServer world; private WorldBorder worldBorder; private Environment environment; private final CraftServer server = (CraftServer) Bukkit.getServer(); private final ChunkGenerator generator; private final BiomeProvider biomeProvider; private final List populators = new ArrayList(); private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this); private final Object2IntOpenHashMap spawnCategoryLimit = new Object2IntOpenHashMap<>(); private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); private static final Random rand = new Random(); public CraftWorld(WorldServer world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) { this.world = world; this.generator = gen; this.biomeProvider = biomeProvider; environment = env; } @Override public Block getBlockAt(int x, int y, int z) { return CraftBlock.at(world, new BlockPosition(x, y, z)); } @Override public Location getSpawnLocation() { BlockPosition spawn = world.getSharedSpawnPos(); float yaw = world.getSharedSpawnAngle(); return CraftLocation.toBukkit(spawn, this, yaw, 0); } @Override public boolean setSpawnLocation(Location location) { Preconditions.checkArgument(location != null, "location"); return equals(location.getWorld()) ? setSpawnLocation(location.getBlockX(), location.getBlockY(), location.getBlockZ(), location.getYaw()) : false; } @Override public boolean setSpawnLocation(int x, int y, int z, float angle) { try { Location previousLocation = getSpawnLocation(); world.levelData.setSpawn(new BlockPosition(x, y, z), angle); // Notify anyone who's listening. SpawnChangeEvent event = new SpawnChangeEvent(this, previousLocation); server.getPluginManager().callEvent(event); return true; } catch (Exception e) { return false; } } @Override public boolean setSpawnLocation(int x, int y, int z) { return setSpawnLocation(x, y, z, 0.0F); } @Override public Chunk getChunkAt(int x, int z) { net.minecraft.world.level.chunk.Chunk chunk = (net.minecraft.world.level.chunk.Chunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); return new CraftChunk(chunk); } @NotNull @Override public Chunk getChunkAt(int x, int z, boolean generate) { if (generate) { return getChunkAt(x, z); } return new CraftChunk(getHandle(), x, z); } @Override public Chunk getChunkAt(Block block) { Preconditions.checkArgument(block != null, "null block"); return getChunkAt(block.getX() >> 4, block.getZ() >> 4); } @Override public boolean isChunkLoaded(int x, int z) { return world.getChunkSource().isChunkLoaded(x, z); } @Override public boolean isChunkGenerated(int x, int z) { try { return isChunkLoaded(x, z) || world.getChunkSource().chunkMap.read(new ChunkCoordIntPair(x, z)).get().isPresent(); } catch (InterruptedException | ExecutionException ex) { throw new RuntimeException(ex); } } @Override public Chunk[] getLoadedChunks() { Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; return chunks.values().stream().map(PlayerChunk::getFullChunkNow).filter(Objects::nonNull).map(CraftChunk::new).toArray(Chunk[]::new); } @Override public void loadChunk(int x, int z) { loadChunk(x, z, true); } @Override public boolean unloadChunk(Chunk chunk) { return unloadChunk(chunk.getX(), chunk.getZ()); } @Override public boolean unloadChunk(int x, int z) { return unloadChunk(x, z, true); } @Override public boolean unloadChunk(int x, int z, boolean save) { return unloadChunk0(x, z, save); } @Override public boolean unloadChunkRequest(int x, int z) { if (isChunkLoaded(x, z)) { world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); } return true; } private boolean unloadChunk0(int x, int z, boolean save) { if (!isChunkLoaded(x, z)) { return true; } net.minecraft.world.level.chunk.Chunk chunk = world.getChunk(x, z); chunk.setUnsaved(!save); // Use method call to account for persistentDataContainer unloadChunkRequest(x, z); world.getChunkSource().purgeUnload(); return !isChunkLoaded(x, z); } @Override public boolean regenerateChunk(int x, int z) { throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); /* if (!unloadChunk0(x, z, false)) { return false; } final long chunkKey = ChunkCoordIntPair.pair(x, z); world.getChunkProvider().unloadQueue.remove(chunkKey); net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); if (playerChunk != null) { playerChunk.chunk = chunk; } if (chunk != null) { refreshChunk(x, z); } return chunk != null; */ } @Override public boolean refreshChunk(int x, int z) { PlayerChunk playerChunk = world.getChunkSource().chunkMap.visibleChunkMap.get(ChunkCoordIntPair.asLong(x, z)); if (playerChunk == null) return false; playerChunk.getTickingChunkFuture().thenAccept(either -> { either.ifSuccess(chunk -> { List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); if (playersInRange.isEmpty()) return; ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null); for (EntityPlayer player : playersInRange) { if (player.connection == null) continue; player.connection.send(refreshPacket); } }); }); return true; } @Override public Collection getPlayersSeeingChunk(Chunk chunk) { Preconditions.checkArgument(chunk != null, "chunk cannot be null"); return getPlayersSeeingChunk(chunk.getX(), chunk.getZ()); } @Override public Collection getPlayersSeeingChunk(int x, int z) { if (!isChunkLoaded(x, z)) { return Collections.emptySet(); } List players = world.getChunkSource().chunkMap.getPlayers(new ChunkCoordIntPair(x, z), false); if (players.isEmpty()) { return Collections.emptySet(); } return players.stream() .filter(Objects::nonNull) .map(EntityPlayer::getBukkitEntity) .collect(Collectors.toUnmodifiableSet()); } @Override public boolean isChunkInUse(int x, int z) { return isChunkLoaded(x, z); } @Override public boolean loadChunk(int x, int z, boolean generate) { IChunkAccess chunk = world.getChunkSource().getChunk(x, z, generate ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // If generate = false, but the chunk already exists, we will get this back. if (chunk instanceof ProtoChunkExtension) { // We then cycle through again to get the full chunk immediately, rather than after the ticket addition chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); } if (chunk instanceof net.minecraft.world.level.chunk.Chunk) { world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); return true; } return false; } @Override public boolean isChunkLoaded(Chunk chunk) { Preconditions.checkArgument(chunk != null, "null chunk"); return isChunkLoaded(chunk.getX(), chunk.getZ()); } @Override public void loadChunk(Chunk chunk) { Preconditions.checkArgument(chunk != null, "null chunk"); loadChunk(chunk.getX(), chunk.getZ()); } @Override public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { Preconditions.checkArgument(plugin != null, "null plugin"); Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); ChunkMapDistance chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; if (chunkDistanceManager.addRegionTicketAtDistance(TicketType.PLUGIN_TICKET, new ChunkCoordIntPair(x, z), 2, plugin)) { // keep in-line with force loading, add at level 31 this.getChunkAt(x, z); // ensure loaded return true; } return false; } @Override public boolean removePluginChunkTicket(int x, int z, Plugin plugin) { Preconditions.checkNotNull(plugin, "null plugin"); ChunkMapDistance chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; return chunkDistanceManager.removeRegionTicketAtDistance(TicketType.PLUGIN_TICKET, new ChunkCoordIntPair(x, z), 2, plugin); // keep in-line with force loading, remove at level 31 } @Override public void removePluginChunkTickets(Plugin plugin) { Preconditions.checkNotNull(plugin, "null plugin"); ChunkMapDistance chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; chunkDistanceManager.removeAllTicketsFor(TicketType.PLUGIN_TICKET, 31, plugin); // keep in-line with force loading, remove at level 31 } @Override public Collection getPluginChunkTickets(int x, int z) { ChunkMapDistance chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; ArraySetSorted> tickets = chunkDistanceManager.tickets.get(ChunkCoordIntPair.asLong(x, z)); if (tickets == null) { return Collections.emptyList(); } ImmutableList.Builder ret = ImmutableList.builder(); for (Ticket ticket : tickets) { if (ticket.getType() == TicketType.PLUGIN_TICKET) { ret.add((Plugin) ticket.key); } } return ret.build(); } @Override public Map> getPluginChunkTickets() { Map> ret = new HashMap<>(); ChunkMapDistance chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; for (Long2ObjectMap.Entry>> chunkTickets : chunkDistanceManager.tickets.long2ObjectEntrySet()) { long chunkKey = chunkTickets.getLongKey(); ArraySetSorted> tickets = chunkTickets.getValue(); Chunk chunk = null; for (Ticket ticket : tickets) { if (ticket.getType() != TicketType.PLUGIN_TICKET) { continue; } if (chunk == null) { chunk = this.getChunkAt(ChunkCoordIntPair.getX(chunkKey), ChunkCoordIntPair.getZ(chunkKey)); } ret.computeIfAbsent((Plugin) ticket.key, (key) -> ImmutableList.builder()).add(chunk); } } return ret.entrySet().stream().collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, (entry) -> entry.getValue().build())); } @NotNull @Override public Collection getIntersectingChunks(@NotNull BoundingBox boundingBox) { List chunks = new ArrayList<>(); int minX = NumberConversions.floor(boundingBox.getMinX()) >> 4; int maxX = NumberConversions.floor(boundingBox.getMaxX()) >> 4; int minZ = NumberConversions.floor(boundingBox.getMinZ()) >> 4; int maxZ = NumberConversions.floor(boundingBox.getMaxZ()) >> 4; for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { chunks.add(getChunkAt(x, z, false)); } } return chunks; } @Override public boolean isChunkForceLoaded(int x, int z) { return getHandle().getForcedChunks().contains(ChunkCoordIntPair.asLong(x, z)); } @Override public void setChunkForceLoaded(int x, int z, boolean forced) { getHandle().setChunkForced(x, z, forced); } @Override public Collection getForceLoadedChunks() { Set chunks = new HashSet<>(); for (long coord : getHandle().getForcedChunks()) { chunks.add(getChunkAt(ChunkCoordIntPair.getX(coord), ChunkCoordIntPair.getZ(coord))); } return Collections.unmodifiableCollection(chunks); } public WorldServer getHandle() { return world; } @Override public org.bukkit.entity.Item dropItem(Location loc, ItemStack item) { return dropItem(loc, item, null); } @Override public org.bukkit.entity.Item dropItem(Location loc, ItemStack item, Consumer function) { Preconditions.checkArgument(loc != null, "Location cannot be null"); Preconditions.checkArgument(item != null, "ItemStack cannot be null"); EntityItem entity = new EntityItem(world, loc.getX(), loc.getY(), loc.getZ(), CraftItemStack.asNMSCopy(item)); org.bukkit.entity.Item itemEntity = (org.bukkit.entity.Item) entity.getBukkitEntity(); entity.pickupDelay = 10; if (function != null) { function.accept(itemEntity); } world.addFreshEntity(entity, SpawnReason.CUSTOM); return itemEntity; } @Override public org.bukkit.entity.Item dropItemNaturally(Location loc, ItemStack item) { return dropItemNaturally(loc, item, null); } @Override public org.bukkit.entity.Item dropItemNaturally(Location loc, ItemStack item, Consumer function) { Preconditions.checkArgument(loc != null, "Location cannot be null"); Preconditions.checkArgument(item != null, "ItemStack cannot be null"); double xs = (world.random.nextFloat() * 0.5F) + 0.25D; double ys = (world.random.nextFloat() * 0.5F) + 0.25D; double zs = (world.random.nextFloat() * 0.5F) + 0.25D; loc = loc.clone().add(xs, ys, zs); return dropItem(loc, item, function); } @Override public Arrow spawnArrow(Location loc, Vector velocity, float speed, float spread) { return spawnArrow(loc, velocity, speed, spread, Arrow.class); } @Override public T spawnArrow(Location loc, Vector velocity, float speed, float spread, Class clazz) { Preconditions.checkArgument(loc != null, "Location cannot be null"); Preconditions.checkArgument(velocity != null, "Vector cannot be null"); Preconditions.checkArgument(clazz != null, "clazz Entity for the arrow cannot be null"); EntityArrow arrow; if (TippedArrow.class.isAssignableFrom(clazz)) { arrow = EntityTypes.ARROW.create(world); ((Arrow) arrow.getBukkitEntity()).setBasePotionType(PotionType.WATER); } else if (SpectralArrow.class.isAssignableFrom(clazz)) { arrow = EntityTypes.SPECTRAL_ARROW.create(world); } else if (Trident.class.isAssignableFrom(clazz)) { arrow = EntityTypes.TRIDENT.create(world); } else { arrow = EntityTypes.ARROW.create(world); } arrow.moveTo(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); arrow.shoot(velocity.getX(), velocity.getY(), velocity.getZ(), speed, spread); world.addFreshEntity(arrow); return (T) arrow.getBukkitEntity(); } @Override public LightningStrike strikeLightning(Location loc) { return strikeLightning0(loc, false); } @Override public LightningStrike strikeLightningEffect(Location loc) { return strikeLightning0(loc, true); } private LightningStrike strikeLightning0(Location loc, boolean isVisual) { Preconditions.checkArgument(loc != null, "Location cannot be null"); EntityLightning lightning = EntityTypes.LIGHTNING_BOLT.create(world); lightning.moveTo(loc.getX(), loc.getY(), loc.getZ()); lightning.setVisualOnly(isVisual); world.strikeLightning(lightning, LightningStrikeEvent.Cause.CUSTOM); return (LightningStrike) lightning.getBukkitEntity(); } @Override public boolean generateTree(Location loc, TreeType type) { return generateTree(loc, rand, type); } @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { world.captureTreeGeneration = true; world.captureBlockStates = true; boolean grownTree = generateTree(loc, type); world.captureBlockStates = false; world.captureTreeGeneration = false; if (grownTree) { // Copy block data to delegate for (BlockState blockstate : world.capturedBlockStates.values()) { BlockPosition position = ((CraftBlockState) blockstate).getPosition(); net.minecraft.world.level.block.state.IBlockData oldBlock = world.getBlockState(position); int flag = ((CraftBlockState) blockstate).getFlag(); delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData()); net.minecraft.world.level.block.state.IBlockData newBlock = world.getBlockState(position); world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flag, 512); } world.capturedBlockStates.clear(); return true; } else { world.capturedBlockStates.clear(); return false; } } @Override public String getName() { return world.serverLevelData.getLevelName(); } @Override public UUID getUID() { return world.uuid; } @Override public NamespacedKey getKey() { return CraftNamespacedKey.fromMinecraft(world.dimension().location()); } @Override public String toString() { return "CraftWorld{name=" + getName() + '}'; } @Override public long getTime() { long time = getFullTime() % 24000; if (time < 0) time += 24000; return time; } @Override public void setTime(long time) { long margin = (time - getFullTime()) % 24000; if (margin < 0) margin += 24000; setFullTime(getFullTime() + margin); } @Override public long getFullTime() { return world.getDayTime(); } @Override public void setFullTime(long time) { // Notify anyone who's listening TimeSkipEvent event = new TimeSkipEvent(this, TimeSkipEvent.SkipReason.CUSTOM, time - world.getDayTime()); server.getPluginManager().callEvent(event); if (event.isCancelled()) { return; } world.setDayTime(world.getDayTime() + event.getSkipAmount()); // Forces the client to update to the new time immediately for (Player p : getPlayers()) { CraftPlayer cp = (CraftPlayer) p; if (cp.getHandle().connection == null) continue; cp.getHandle().connection.send(new PacketPlayOutUpdateTime(cp.getHandle().level().getGameTime(), cp.getHandle().getPlayerTime(), cp.getHandle().level().getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); } } @Override public long getGameTime() { return world.levelData.getGameTime(); } @Override public boolean createExplosion(double x, double y, double z, float power) { return createExplosion(x, y, z, power, false, true); } @Override public boolean createExplosion(double x, double y, double z, float power, boolean setFire) { return createExplosion(x, y, z, power, setFire, true); } @Override public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks) { return createExplosion(x, y, z, power, setFire, breakBlocks, null); } @Override public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { net.minecraft.world.level.World.a explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.World.a.NONE; // Don't break blocks } else if (source == null) { explosionType = net.minecraft.world.level.World.a.STANDARD; // Break blocks, don't decay drops } else { explosionType = net.minecraft.world.level.World.a.MOB; // Respect mobGriefing gamerule } return !world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, explosionType).wasCanceled; } @Override public boolean createExplosion(Location loc, float power) { return createExplosion(loc, power, false); } @Override public boolean createExplosion(Location loc, float power, boolean setFire) { return createExplosion(loc, power, setFire, true); } @Override public boolean createExplosion(Location loc, float power, boolean setFire, boolean breakBlocks) { return createExplosion(loc, power, setFire, breakBlocks, null); } @Override public boolean createExplosion(Location loc, float power, boolean setFire, boolean breakBlocks, Entity source) { Preconditions.checkArgument(loc != null, "Location is null"); Preconditions.checkArgument(this.equals(loc.getWorld()), "Location not in world"); return createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks, source); } @Override public Environment getEnvironment() { return environment; } @Override public Block getBlockAt(Location location) { return getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); } @Override public Chunk getChunkAt(Location location) { return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4); } @Override public ChunkGenerator getGenerator() { return generator; } @Override public BiomeProvider getBiomeProvider() { return biomeProvider; } @Override public List getPopulators() { return populators; } @NotNull @Override public T spawn(@NotNull Location location, @NotNull Class clazz, @NotNull SpawnReason spawnReason, boolean randomizeData, @Nullable Consumer function) throws IllegalArgumentException { Preconditions.checkArgument(spawnReason != null, "Spawn reason cannot be null"); return spawn(location, clazz, function, spawnReason, randomizeData); } @Override public Block getHighestBlockAt(int x, int z) { return getBlockAt(x, getHighestBlockYAt(x, z), z); } @Override public Block getHighestBlockAt(Location location) { return getHighestBlockAt(location.getBlockX(), location.getBlockZ()); } @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { // Transient load for this tick return world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); } @Override public Block getHighestBlockAt(int x, int z, org.bukkit.HeightMap heightMap) { return getBlockAt(x, getHighestBlockYAt(x, z, heightMap), z); } @Override public Block getHighestBlockAt(Location location, org.bukkit.HeightMap heightMap) { return getHighestBlockAt(location.getBlockX(), location.getBlockZ(), heightMap); } @Override public Biome getBiome(int x, int z) { return getBiome(x, 0, z); } @Override public void setBiome(int x, int z, Biome bio) { for (int y = getMinHeight(); y < getMaxHeight(); y++) { setBiome(x, y, z, bio); } } @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPosition pos = new BlockPosition(x, 0, z); if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.Chunk chunk = this.world.getChunkAt(pos); if (chunk != null) { chunk.setBiome(x >> 2, y >> 2, z >> 2, bb); chunk.setUnsaved(true); // SPIGOT-2890 } } } @Override public double getTemperature(int x, int z) { return getTemperature(x, 0, z); } @Override public double getTemperature(int x, int y, int z) { BlockPosition pos = new BlockPosition(x, y, z); return this.world.getNoiseBiome(x >> 2, y >> 2, z >> 2).value().getTemperature(pos); } @Override public double getHumidity(int x, int z) { return getHumidity(x, 0, z); } @Override public double getHumidity(int x, int y, int z) { return this.world.getNoiseBiome(x >> 2, y >> 2, z >> 2).value().climateSettings.downfall(); } @Override @SuppressWarnings("unchecked") @Deprecated public Collection getEntitiesByClass(Class... classes) { return (Collection) getEntitiesByClasses(classes); } @Override public Iterable getNMSEntities() { return getHandle().getEntities().getAll(); } @Override public void addEntityToWorld(net.minecraft.world.entity.Entity entity, SpawnReason reason) { getHandle().addFreshEntity(entity, reason); } @Override public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, SpawnReason reason) { getHandle().tryAddFreshEntityWithPassengers(entity, reason); } @Override public Collection getNearbyEntities(Location location, double x, double y, double z) { return this.getNearbyEntities(location, x, y, z, null); } @Override public Collection getNearbyEntities(Location location, double x, double y, double z, Predicate filter) { Preconditions.checkArgument(location != null, "Location cannot be null"); Preconditions.checkArgument(this.equals(location.getWorld()), "Location cannot be in a different world"); BoundingBox aabb = BoundingBox.of(location, x, y, z); return this.getNearbyEntities(aabb, filter); } @Override public Collection getNearbyEntities(BoundingBox boundingBox) { return this.getNearbyEntities(boundingBox, null); } @Override public Collection getNearbyEntities(BoundingBox boundingBox, Predicate filter) { Preconditions.checkArgument(boundingBox != null, "BoundingBox cannot be null"); AxisAlignedBB bb = new AxisAlignedBB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ()); List entityList = getHandle().getEntities((net.minecraft.world.entity.Entity) null, bb, Predicates.alwaysTrue()); List bukkitEntityList = new ArrayList(entityList.size()); for (net.minecraft.world.entity.Entity entity : entityList) { Entity bukkitEntity = entity.getBukkitEntity(); if (filter == null || filter.test(bukkitEntity)) { bukkitEntityList.add(bukkitEntity); } } return bukkitEntityList; } @Override public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance) { return this.rayTraceEntities(start, direction, maxDistance, null); } @Override public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize) { return this.rayTraceEntities(start, direction, maxDistance, raySize, null); } @Override public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, Predicate filter) { return this.rayTraceEntities(start, direction, maxDistance, 0.0D, filter); } @Override public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter) { Preconditions.checkArgument(start != null, "Location start cannot be null"); Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); start.checkFinite(); Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); direction.checkFinite(); Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) need to be greater than 0", direction.lengthSquared()); if (maxDistance < 0.0D) { return null; } Vector startPos = start.toVector(); Vector dir = direction.clone().normalize().multiply(maxDistance); BoundingBox aabb = BoundingBox.of(startPos, startPos).expandDirectional(dir).expand(raySize); Collection entities = this.getNearbyEntities(aabb, filter); Entity nearestHitEntity = null; RayTraceResult nearestHitResult = null; double nearestDistanceSq = Double.MAX_VALUE; for (Entity entity : entities) { BoundingBox boundingBox = entity.getBoundingBox().expand(raySize); RayTraceResult hitResult = boundingBox.rayTrace(startPos, direction, maxDistance); if (hitResult != null) { double distanceSq = startPos.distanceSquared(hitResult.getHitPosition()); if (distanceSq < nearestDistanceSq) { nearestHitEntity = entity; nearestHitResult = hitResult; nearestDistanceSq = distanceSq; } } } return (nearestHitEntity == null) ? null : new RayTraceResult(nearestHitResult.getHitPosition(), nearestHitEntity, nearestHitResult.getHitBlockFace()); } @Override public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance) { return this.rayTraceBlocks(start, direction, maxDistance, FluidCollisionMode.NEVER, false); } @Override public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { return this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, false); } @Override public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks) { Preconditions.checkArgument(start != null, "Location start cannot be null"); Preconditions.checkArgument(this.equals(start.getWorld()), "Location start cannot be in a different world"); start.checkFinite(); Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); direction.checkFinite(); Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) need to be greater than 0", direction.lengthSquared()); Preconditions.checkArgument(fluidCollisionMode != null, "FluidCollisionMode cannot be null"); if (maxDistance < 0.0D) { return null; } Vector dir = direction.clone().normalize().multiply(maxDistance); Vec3D startPos = CraftLocation.toVec3D(start); Vec3D endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); MovingObjectPosition nmsHitResult = this.getHandle().clip(new RayTrace(startPos, endPos, ignorePassableBlocks ? RayTrace.BlockCollisionOption.COLLIDER : RayTrace.BlockCollisionOption.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), VoxelShapeCollision.empty())); return CraftRayTraceResult.fromNMS(this, nmsHitResult); } @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter) { RayTraceResult blockHit = this.rayTraceBlocks(start, direction, maxDistance, fluidCollisionMode, ignorePassableBlocks); Vector startVec = null; double blockHitDistance = maxDistance; // limiting the entity search range if we found a block hit: if (blockHit != null) { startVec = start.toVector(); blockHitDistance = startVec.distance(blockHit.getHitPosition()); } RayTraceResult entityHit = this.rayTraceEntities(start, direction, blockHitDistance, raySize, filter); if (blockHit == null) { return entityHit; } if (entityHit == null) { return blockHit; } // Cannot be null as blockHit == null returns above double entityHitDistanceSquared = startVec.distanceSquared(entityHit.getHitPosition()); if (entityHitDistanceSquared < (blockHitDistance * blockHitDistance)) { return entityHit; } return blockHit; } @Override public List getPlayers() { List list = new ArrayList(world.players().size()); for (EntityHuman human : world.players()) { HumanEntity bukkitEntity = human.getBukkitEntity(); if ((bukkitEntity != null) && (bukkitEntity instanceof Player)) { list.add((Player) bukkitEntity); } } return list; } @Override public void save() { this.server.checkSaveState(); boolean oldSave = world.noSave; world.noSave = false; world.save(null, false, false); world.noSave = oldSave; } @Override public boolean isAutoSave() { return !world.noSave; } @Override public void setAutoSave(boolean value) { world.noSave = !value; } @Override public void setDifficulty(Difficulty difficulty) { this.getHandle().serverLevelData.setDifficulty(EnumDifficulty.byId(difficulty.getValue())); } @Override public Difficulty getDifficulty() { return Difficulty.getByValue(this.getHandle().getDifficulty().ordinal()); } @Override public int getViewDistance() { return world.getChunkSource().chunkMap.serverViewDistance; } @Override public int getSimulationDistance() { return world.getChunkSource().chunkMap.getDistanceManager().simulationDistance; } public BlockMetadataStore getBlockMetadata() { return blockMetadata; } @Override public boolean hasStorm() { return world.levelData.isRaining(); } @Override public void setStorm(boolean hasStorm) { world.levelData.setRaining(hasStorm); setWeatherDuration(0); // Reset weather duration (legacy behaviour) setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) } @Override public int getWeatherDuration() { return world.serverLevelData.getRainTime(); } @Override public void setWeatherDuration(int duration) { world.serverLevelData.setRainTime(duration); } @Override public boolean isThundering() { return world.levelData.isThundering(); } @Override public void setThundering(boolean thundering) { world.serverLevelData.setThundering(thundering); setThunderDuration(0); // Reset weather duration (legacy behaviour) setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) } @Override public int getThunderDuration() { return world.serverLevelData.getThunderTime(); } @Override public void setThunderDuration(int duration) { world.serverLevelData.setThunderTime(duration); } @Override public boolean isClearWeather() { return !this.hasStorm() && !this.isThundering(); } @Override public void setClearWeatherDuration(int duration) { world.serverLevelData.setClearWeatherTime(duration); } @Override public int getClearWeatherDuration() { return world.serverLevelData.getClearWeatherTime(); } @Override public long getSeed() { return world.getSeed(); } @Override public boolean getPVP() { return world.pvpMode; } @Override public void setPVP(boolean pvp) { world.pvpMode = pvp; } public void playEffect(Player player, Effect effect, int data) { playEffect(player.getLocation(), effect, data, 0); } @Override public void playEffect(Location location, Effect effect, int data) { playEffect(location, effect, data, 64); } @Override public void playEffect(Location loc, Effect effect, T data) { playEffect(loc, effect, data, 64); } @Override public void playEffect(Location loc, Effect effect, T data, int radius) { if (data != null) { Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); } else { // Special case: the axis is optional for ELECTRIC_SPARK Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); } int datavalue = CraftEffect.getDataValue(effect, data); playEffect(loc, effect, datavalue, radius); } @Override public void playEffect(Location location, Effect effect, int data, int radius) { Preconditions.checkArgument(effect != null, "Effect cannot be null"); Preconditions.checkArgument(location != null, "Location cannot be null"); Preconditions.checkArgument(location.getWorld() != null, "World of Location cannot be null"); int packetData = effect.getId(); PacketPlayOutWorldEvent packet = new PacketPlayOutWorldEvent(packetData, CraftLocation.toBlockPosition(location), data, false); int distance; radius *= radius; for (Player player : getPlayers()) { if (((CraftPlayer) player).getHandle().connection == null) continue; if (!location.getWorld().equals(player.getWorld())) continue; distance = (int) player.getLocation().distanceSquared(location); if (distance <= radius) { ((CraftPlayer) player).getHandle().connection.send(packet); } } } @Override public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException { Preconditions.checkArgument(data != null, "MaterialData cannot be null"); return spawnFallingBlock(location, data.getItemType(), data.getData()); } @Override public FallingBlock spawnFallingBlock(Location location, org.bukkit.Material material, byte data) throws IllegalArgumentException { Preconditions.checkArgument(location != null, "Location cannot be null"); Preconditions.checkArgument(material != null, "Material cannot be null"); Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material); EntityFallingBlock entity = EntityFallingBlock.fall(world, BlockPosition.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM); return (FallingBlock) entity.getBukkitEntity(); } @Override public FallingBlock spawnFallingBlock(Location location, BlockData data) throws IllegalArgumentException { Preconditions.checkArgument(location != null, "Location cannot be null"); Preconditions.checkArgument(data != null, "BlockData cannot be null"); EntityFallingBlock entity = EntityFallingBlock.fall(world, BlockPosition.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); return (FallingBlock) entity.getBukkitEntity(); } @Override public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTempRain) { return CraftChunk.getEmptyChunkSnapshot(x, z, this, includeBiome, includeBiomeTempRain); } @Override public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) { world.setSpawnSettings(allowMonsters, allowAnimals); } @Override public boolean getAllowAnimals() { return world.getChunkSource().spawnFriendlies; } @Override public boolean getAllowMonsters() { return world.getChunkSource().spawnEnemies; } @Override public int getMinHeight() { return world.getMinBuildHeight(); } @Override public int getMaxHeight() { return world.getMaxBuildHeight(); } @Override public int getLogicalHeight() { return world.dimensionType().logicalHeight(); } @Override public boolean isNatural() { return world.dimensionType().natural(); } @Override public boolean isBedWorks() { return world.dimensionType().bedWorks(); } @Override public boolean hasSkyLight() { return world.dimensionType().hasSkyLight(); } @Override public boolean hasCeiling() { return world.dimensionType().hasCeiling(); } @Override public boolean isPiglinSafe() { return world.dimensionType().piglinSafe(); } @Override public boolean isRespawnAnchorWorks() { return world.dimensionType().respawnAnchorWorks(); } @Override public boolean hasRaids() { return world.dimensionType().hasRaids(); } @Override public boolean isUltraWarm() { return world.dimensionType().ultraWarm(); } @Override public int getSeaLevel() { return world.getSeaLevel(); } @Override public boolean getKeepSpawnInMemory() { return getGameRuleValue(GameRule.SPAWN_RADIUS) > 0; } @Override public void setKeepSpawnInMemory(boolean keepLoaded) { if (keepLoaded) { setGameRule(GameRule.SPAWN_CHUNK_RADIUS, getGameRuleDefault(GameRule.SPAWN_CHUNK_RADIUS)); } else { setGameRule(GameRule.SPAWN_CHUNK_RADIUS, 0); } } @Override public int hashCode() { return getUID().hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final CraftWorld other = (CraftWorld) obj; return this.getUID() == other.getUID(); } @Override public File getWorldFolder() { return world.convertable.getLevelPath(SavedFile.ROOT).toFile().getParentFile(); } @Override public void sendPluginMessage(Plugin source, String channel, byte[] message) { StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message); for (Player player : getPlayers()) { player.sendPluginMessage(source, channel, message); } } @Override public Set getListeningPluginChannels() { Set result = new HashSet(); for (Player player : getPlayers()) { result.addAll(player.getListeningPluginChannels()); } return result; } @Override public org.bukkit.WorldType getWorldType() { return world.isFlat() ? org.bukkit.WorldType.FLAT : org.bukkit.WorldType.NORMAL; } @Override public boolean canGenerateStructures() { return world.serverLevelData.worldGenOptions().generateStructures(); } @Override public boolean isHardcore() { return world.getLevelData().isHardcore(); } @Override public void setHardcore(boolean hardcore) { world.serverLevelData.settings.hardcore = hardcore; } @Override @Deprecated public long getTicksPerAnimalSpawns() { return getTicksPerSpawns(SpawnCategory.ANIMAL); } @Override @Deprecated public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { setTicksPerSpawns(SpawnCategory.ANIMAL, ticksPerAnimalSpawns); } @Override @Deprecated public long getTicksPerMonsterSpawns() { return getTicksPerSpawns(SpawnCategory.MONSTER); } @Override @Deprecated public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { setTicksPerSpawns(SpawnCategory.MONSTER, ticksPerMonsterSpawns); } @Override @Deprecated public long getTicksPerWaterSpawns() { return getTicksPerSpawns(SpawnCategory.WATER_ANIMAL); } @Override @Deprecated public void setTicksPerWaterSpawns(int ticksPerWaterSpawns) { setTicksPerSpawns(SpawnCategory.WATER_ANIMAL, ticksPerWaterSpawns); } @Override @Deprecated public long getTicksPerWaterAmbientSpawns() { return getTicksPerSpawns(SpawnCategory.WATER_AMBIENT); } @Override @Deprecated public void setTicksPerWaterAmbientSpawns(int ticksPerWaterAmbientSpawns) { setTicksPerSpawns(SpawnCategory.WATER_AMBIENT, ticksPerWaterAmbientSpawns); } @Override @Deprecated public long getTicksPerWaterUndergroundCreatureSpawns() { return getTicksPerSpawns(SpawnCategory.WATER_UNDERGROUND_CREATURE); } @Override @Deprecated public void setTicksPerWaterUndergroundCreatureSpawns(int ticksPerWaterUndergroundCreatureSpawns) { setTicksPerSpawns(SpawnCategory.WATER_UNDERGROUND_CREATURE, ticksPerWaterUndergroundCreatureSpawns); } @Override @Deprecated public long getTicksPerAmbientSpawns() { return getTicksPerSpawns(SpawnCategory.AMBIENT); } @Override @Deprecated public void setTicksPerAmbientSpawns(int ticksPerAmbientSpawns) { setTicksPerSpawns(SpawnCategory.AMBIENT, ticksPerAmbientSpawns); } @Override public void setTicksPerSpawns(SpawnCategory spawnCategory, int ticksPerCategorySpawn) { Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); world.ticksPerSpawnCategory.put(spawnCategory, (long) ticksPerCategorySpawn); } @Override public long getTicksPerSpawns(SpawnCategory spawnCategory) { Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); return world.ticksPerSpawnCategory.getLong(spawnCategory); } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { server.getWorldMetadata().setMetadata(this, metadataKey, newMetadataValue); } @Override public List getMetadata(String metadataKey) { return server.getWorldMetadata().getMetadata(this, metadataKey); } @Override public boolean hasMetadata(String metadataKey) { return server.getWorldMetadata().hasMetadata(this, metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { server.getWorldMetadata().removeMetadata(this, metadataKey, owningPlugin); } @Override @Deprecated public int getMonsterSpawnLimit() { return getSpawnLimit(SpawnCategory.MONSTER); } @Override @Deprecated public void setMonsterSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.MONSTER, limit); } @Override @Deprecated public int getAnimalSpawnLimit() { return getSpawnLimit(SpawnCategory.ANIMAL); } @Override @Deprecated public void setAnimalSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.ANIMAL, limit); } @Override @Deprecated public int getWaterAnimalSpawnLimit() { return getSpawnLimit(SpawnCategory.WATER_ANIMAL); } @Override @Deprecated public void setWaterAnimalSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.WATER_ANIMAL, limit); } @Override @Deprecated public int getWaterAmbientSpawnLimit() { return getSpawnLimit(SpawnCategory.WATER_AMBIENT); } @Override @Deprecated public void setWaterAmbientSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.WATER_AMBIENT, limit); } @Override @Deprecated public int getWaterUndergroundCreatureSpawnLimit() { return getSpawnLimit(SpawnCategory.WATER_UNDERGROUND_CREATURE); } @Override @Deprecated public void setWaterUndergroundCreatureSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.WATER_UNDERGROUND_CREATURE, limit); } @Override @Deprecated public int getAmbientSpawnLimit() { return getSpawnLimit(SpawnCategory.AMBIENT); } @Override @Deprecated public void setAmbientSpawnLimit(int limit) { setSpawnLimit(SpawnCategory.AMBIENT, limit); } @Override public int getSpawnLimit(SpawnCategory spawnCategory) { Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); int limit = spawnCategoryLimit.getOrDefault(spawnCategory, -1); if (limit < 0) { limit = server.getSpawnLimit(spawnCategory); } return limit; } @Override public void setSpawnLimit(SpawnCategory spawnCategory, int limit) { Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory); spawnCategoryLimit.put(spawnCategory, limit); } @Override public void playNote(@NotNull Location loc, @NotNull Instrument instrument, @NotNull Note note) { playSound(loc, instrument.getSound(), org.bukkit.SoundCategory.RECORDS, 3f, note.getPitch()); } @Override public void playSound(Location loc, Sound sound, float volume, float pitch) { playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); } @Override public void playSound(Location loc, String sound, float volume, float pitch) { playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); } @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch) { playSound(loc, sound, category, volume, pitch, getHandle().random.nextLong());; } @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch) { playSound(loc, sound, category, volume, pitch, getHandle().random.nextLong()); } @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (loc == null || sound == null || category == null) return; double x = loc.getX(); double y = loc.getY(); double z = loc.getZ(); getHandle().playSeededSound(null, x, y, z, CraftSound.bukkitToMinecraft(sound), SoundCategory.valueOf(category.name()), volume, pitch, seed); } @Override public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (loc == null || sound == null || category == null) return; double x = loc.getX(); double y = loc.getY(); double z = loc.getZ(); PacketPlayOutNamedSoundEffect packet = new PacketPlayOutNamedSoundEffect(Holder.direct(SoundEffect.createVariableRangeEvent(MinecraftKey.parse(sound))), SoundCategory.valueOf(category.name()), x, y, z, volume, pitch, seed); world.getServer().getPlayerList().broadcast(null, x, y, z, volume > 1.0F ? 16.0F * volume : 16.0D, this.world.dimension(), packet); } @Override public void playSound(Entity entity, Sound sound, float volume, float pitch) { playSound(entity, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); } @Override public void playSound(Entity entity, String sound, float volume, float pitch) { playSound(entity, sound, org.bukkit.SoundCategory.MASTER, volume, pitch); } @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch) { playSound(entity, sound, category, volume, pitch, getHandle().random.nextLong()); } @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch) { playSound(entity, sound, category, volume, pitch, getHandle().random.nextLong()); } @Override public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; PacketPlayOutEntitySound packet = new PacketPlayOutEntitySound(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundCategory.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); PlayerChunkMap.EntityTracker entityTracker = getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); if (entityTracker != null) { entityTracker.broadcastAndSend(packet); } } @Override public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; PacketPlayOutEntitySound packet = new PacketPlayOutEntitySound(Holder.direct(SoundEffect.createVariableRangeEvent(MinecraftKey.parse(sound))), net.minecraft.sounds.SoundCategory.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed); PlayerChunkMap.EntityTracker entityTracker = getHandle().getChunkSource().chunkMap.entityMap.get(entity.getEntityId()); if (entityTracker != null) { entityTracker.broadcastAndSend(packet); } } private static Map> gamerules; public static synchronized Map> getGameRulesNMS() { if (gamerules != null) { return gamerules; } Map> gamerules = new HashMap<>(); GameRules.visitGameRuleTypes(new GameRules.GameRuleVisitor() { @Override public > void visit(GameRules.GameRuleKey gamerules_gamerulekey, GameRules.GameRuleDefinition gamerules_gameruledefinition) { gamerules.put(gamerules_gamerulekey.getId(), gamerules_gamerulekey); } }); return CraftWorld.gamerules = gamerules; } private static Map> gameruleDefinitions; public static synchronized Map> getGameRuleDefinitions() { if (gameruleDefinitions != null) { return gameruleDefinitions; } Map> gameruleDefinitions = new HashMap<>(); GameRules.visitGameRuleTypes(new GameRules.GameRuleVisitor() { @Override public > void visit(GameRules.GameRuleKey gamerules_gamerulekey, GameRules.GameRuleDefinition gamerules_gameruledefinition) { gameruleDefinitions.put(gamerules_gamerulekey.getId(), gamerules_gameruledefinition); } }); return CraftWorld.gameruleDefinitions = gameruleDefinitions; } @Override public String getGameRuleValue(String rule) { // In method contract for some reason if (rule == null) { return null; } GameRules.GameRuleValue value = getHandle().getGameRules().getRule(getGameRulesNMS().get(rule)); return value != null ? value.toString() : ""; } @Override public boolean setGameRuleValue(String rule, String value) { // No null values allowed if (rule == null || value == null) return false; if (!isGameRule(rule)) return false; GameRules.GameRuleValue handle = getHandle().getGameRules().getRule(getGameRulesNMS().get(rule)); handle.deserialize(value); handle.onChanged(getHandle()); return true; } @Override public String[] getGameRules() { return getGameRulesNMS().keySet().toArray(new String[getGameRulesNMS().size()]); } @Override public boolean isGameRule(String rule) { Preconditions.checkArgument(rule != null, "String rule cannot be null"); Preconditions.checkArgument(!rule.isEmpty(), "String rule cannot be empty"); return getGameRulesNMS().containsKey(rule); } @Override public T getGameRuleValue(GameRule rule) { Preconditions.checkArgument(rule != null, "GameRule cannot be null"); return convert(rule, getHandle().getGameRules().getRule(getGameRulesNMS().get(rule.getName()))); } @Override public T getGameRuleDefault(GameRule rule) { Preconditions.checkArgument(rule != null, "GameRule cannot be null"); return convert(rule, getGameRuleDefinitions().get(rule.getName()).createRule()); } @Override public boolean setGameRule(GameRule rule, T newValue) { Preconditions.checkArgument(rule != null, "GameRule cannot be null"); Preconditions.checkArgument(newValue != null, "GameRule value cannot be null"); if (!isGameRule(rule.getName())) return false; GameRules.GameRuleValue handle = getHandle().getGameRules().getRule(getGameRulesNMS().get(rule.getName())); handle.deserialize(newValue.toString()); handle.onChanged(getHandle()); return true; } private T convert(GameRule rule, GameRules.GameRuleValue value) { if (value == null) { return null; } if (value instanceof GameRules.GameRuleBoolean) { return rule.getType().cast(((GameRules.GameRuleBoolean) value).get()); } else if (value instanceof GameRules.GameRuleInt) { return rule.getType().cast(value.getCommandResult()); } else { throw new IllegalArgumentException("Invalid GameRule type (" + value + ") for GameRule " + rule.getName()); } } @Override public WorldBorder getWorldBorder() { if (this.worldBorder == null) { this.worldBorder = new CraftWorldBorder(this); } return this.worldBorder; } @Override public void spawnParticle(Particle particle, Location location, int count) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count) { spawnParticle(particle, x, y, z, count, null); } @Override public void spawnParticle(Particle particle, Location location, int count, T data) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, data); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { spawnParticle(particle, x, y, z, count, 0, 0, 0, data); } @Override public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ) { spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, null); } @Override public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data) { spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, 1, data); } @Override public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra) { spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); } @Override public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, false); } @Override public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data, force); } @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { getHandle().sendParticles( null, // Sender CraftParticle.createParticleParam(particle, data), // Particle x, y, z, // Position count, // Count offsetX, offsetY, offsetZ, // Random offset extra, // Speed? force ); } @Deprecated @Override public Location locateNearestStructure(Location origin, org.bukkit.StructureType structureType, int radius, boolean findUnexplored) { StructureSearchResult result = null; // Manually map the mess of the old StructureType to the new StructureType and normal Structure if (org.bukkit.StructureType.MINESHAFT == structureType) { result = locateNearestStructure(origin, StructureType.MINESHAFT, radius, findUnexplored); } else if (org.bukkit.StructureType.VILLAGE == structureType) { result = locateNearestStructure(origin, List.of(Structure.VILLAGE_DESERT, Structure.VILLAGE_PLAINS, Structure.VILLAGE_SAVANNA, Structure.VILLAGE_SNOWY, Structure.VILLAGE_TAIGA), radius, findUnexplored); } else if (org.bukkit.StructureType.NETHER_FORTRESS == structureType) { result = locateNearestStructure(origin, StructureType.FORTRESS, radius, findUnexplored); } else if (org.bukkit.StructureType.STRONGHOLD == structureType) { result = locateNearestStructure(origin, StructureType.STRONGHOLD, radius, findUnexplored); } else if (org.bukkit.StructureType.JUNGLE_PYRAMID == structureType) { result = locateNearestStructure(origin, StructureType.JUNGLE_TEMPLE, radius, findUnexplored); } else if (org.bukkit.StructureType.OCEAN_RUIN == structureType) { result = locateNearestStructure(origin, StructureType.OCEAN_RUIN, radius, findUnexplored); } else if (org.bukkit.StructureType.DESERT_PYRAMID == structureType) { result = locateNearestStructure(origin, StructureType.DESERT_PYRAMID, radius, findUnexplored); } else if (org.bukkit.StructureType.IGLOO == structureType) { result = locateNearestStructure(origin, StructureType.IGLOO, radius, findUnexplored); } else if (org.bukkit.StructureType.SWAMP_HUT == structureType) { result = locateNearestStructure(origin, StructureType.SWAMP_HUT, radius, findUnexplored); } else if (org.bukkit.StructureType.OCEAN_MONUMENT == structureType) { result = locateNearestStructure(origin, StructureType.OCEAN_MONUMENT, radius, findUnexplored); } else if (org.bukkit.StructureType.END_CITY == structureType) { result = locateNearestStructure(origin, StructureType.END_CITY, radius, findUnexplored); } else if (org.bukkit.StructureType.WOODLAND_MANSION == structureType) { result = locateNearestStructure(origin, StructureType.WOODLAND_MANSION, radius, findUnexplored); } else if (org.bukkit.StructureType.BURIED_TREASURE == structureType) { result = locateNearestStructure(origin, StructureType.BURIED_TREASURE, radius, findUnexplored); } else if (org.bukkit.StructureType.SHIPWRECK == structureType) { result = locateNearestStructure(origin, StructureType.SHIPWRECK, radius, findUnexplored); } else if (org.bukkit.StructureType.PILLAGER_OUTPOST == structureType) { result = locateNearestStructure(origin, Structure.PILLAGER_OUTPOST, radius, findUnexplored); } else if (org.bukkit.StructureType.NETHER_FOSSIL == structureType) { result = locateNearestStructure(origin, StructureType.NETHER_FOSSIL, radius, findUnexplored); } else if (org.bukkit.StructureType.RUINED_PORTAL == structureType) { result = locateNearestStructure(origin, StructureType.RUINED_PORTAL, radius, findUnexplored); } else if (org.bukkit.StructureType.BASTION_REMNANT == structureType) { result = locateNearestStructure(origin, Structure.BASTION_REMNANT, radius, findUnexplored); } return (result == null) ? null : result.getLocation(); } @Override public StructureSearchResult locateNearestStructure(Location origin, StructureType structureType, int radius, boolean findUnexplored) { List structures = new ArrayList<>(); for (Structure structure : Registry.STRUCTURE) { if (structure.getStructureType() == structureType) { structures.add(structure); } } return locateNearestStructure(origin, structures, radius, findUnexplored); } @Override public StructureSearchResult locateNearestStructure(Location origin, Structure structure, int radius, boolean findUnexplored) { return locateNearestStructure(origin, List.of(structure), radius, findUnexplored); } public StructureSearchResult locateNearestStructure(Location origin, List structures, int radius, boolean findUnexplored) { BlockPosition originPos = BlockPosition.containing(origin.getX(), origin.getY(), origin.getZ()); List> holders = new ArrayList<>(); for (Structure structure : structures) { holders.add(Holder.direct(CraftStructure.bukkitToMinecraft(structure))); } Pair> found = getHandle().getChunkSource().getGenerator().findNearestMapStructure(getHandle(), HolderSet.direct(holders), originPos, radius, findUnexplored); if (found == null) { return null; } return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this)); } @Override public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) { return locateNearestBiome(origin, radius, 32, 64, biomes); } @Override public BiomeSearchResult locateNearestBiome(Location origin, int radius, int horizontalInterval, int verticalInterval, Biome... biomes) { BlockPosition originPos = BlockPosition.containing(origin.getX(), origin.getY(), origin.getZ()); Set> holders = new HashSet<>(); for (Biome biome : biomes) { holders.add(CraftBiome.bukkitToMinecraftHolder(biome)); } Climate.Sampler sampler = getHandle().getChunkSource().randomState().sampler(); // The given predicate is evaluated once at the start of the search, so performance isn't a large concern. Pair> found = getHandle().getChunkSource().getGenerator().getBiomeSource().findClosestBiome3d(originPos, radius, horizontalInterval, verticalInterval, holders::contains, sampler, getHandle()); if (found == null) { return null; } return new CraftBiomeSearchResult(CraftBiome.minecraftHolderToBukkit(found.getSecond()), new Location(this, found.getFirst().getX(), found.getFirst().getY(), found.getFirst().getZ())); } @Override public Raid locateNearestRaid(Location location, int radius) { Preconditions.checkArgument(location != null, "Location cannot be null"); Preconditions.checkArgument(radius >= 0, "Radius value (%s) cannot be negative", radius); PersistentRaid persistentRaid = world.getRaids(); net.minecraft.world.entity.raid.Raid raid = persistentRaid.getNearbyRaid(CraftLocation.toBlockPosition(location), radius * radius); return (raid == null) ? null : new CraftRaid(raid); } @Override public List getRaids() { PersistentRaid persistentRaid = world.getRaids(); return persistentRaid.raidMap.values().stream().map(CraftRaid::new).collect(Collectors.toList()); } @Override public DragonBattle getEnderDragonBattle() { return (getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(getHandle().getDragonFight()); } @Override public Collection getStructures(int x, int z) { return getStructures(x, z, struct -> true); } @Override public Collection getStructures(int x, int z, Structure structure) { Preconditions.checkArgument(structure != null, "Structure cannot be null"); IRegistry registry = CraftRegistry.getMinecraftRegistry(Registries.STRUCTURE); MinecraftKey key = registry.getKey(CraftStructure.bukkitToMinecraft(structure)); return getStructures(x, z, struct -> registry.getKey(struct).equals(key)); } private List getStructures(int x, int z, Predicate predicate) { List structures = new ArrayList<>(); for (StructureStart start : getHandle().structureManager().startsForStructure(new ChunkCoordIntPair(x, z), predicate)) { structures.add(new CraftGeneratedStructure(start)); } return structures; } @Override public PersistentDataContainer getPersistentDataContainer() { return persistentDataContainer; } @Override public Set getFeatureFlags() { return CraftFeatureFlag.getFromNMS(this.getHandle().enabledFeatures()).stream().map(FeatureFlag.class::cast).collect(Collectors.toUnmodifiableSet()); } public void storeBukkitValues(NBTTagCompound c) { if (!this.persistentDataContainer.isEmpty()) { c.put("BukkitValues", this.persistentDataContainer.toTagCompound()); } } public void readBukkitValues(NBTBase c) { if (c instanceof NBTTagCompound) { this.persistentDataContainer.putAll((NBTTagCompound) c); } } }