From 90d42b8b9f22cbf32ce939e7c8d2c216d6fffecf Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Thu, 9 Jun 2022 20:49:43 +0100 Subject: [PATCH] Implement tile entities to generation using Populators - Fixes #3051 --- .../bukkit/generator/BlockStatePopulator.java | 58 ++-- .../generator/BlockStatePopulator116.java | 78 +++++ .../bukkit/generator/BukkitPlotGenerator.java | 13 +- .../queue/LimitedRegionWrapperQueue.java | 106 +++++++ .../bukkit/schematic/StateWrapper.java | 23 ++ .../plotsquared/core/generator/HybridGen.java | 268 +++++++++++++++--- .../core/generator/HybridPlotWorld.java | 118 ++++++-- .../generator/IndependentPlotGenerator.java | 13 +- .../plotsquared/core/location/Location.java | 4 +- .../core/location/UncheckedWorldLocation.java | 68 +++++ 10 files changed, 651 insertions(+), 98 deletions(-) create mode 100644 Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator116.java create mode 100644 Bukkit/src/main/java/com/plotsquared/bukkit/queue/LimitedRegionWrapperQueue.java create mode 100644 Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator.java index 9574a2fc8..70ad50db4 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator.java @@ -25,17 +25,18 @@ */ package com.plotsquared.bukkit.generator; -import com.plotsquared.core.PlotSquared; +import com.plotsquared.bukkit.queue.LimitedRegionWrapperQueue; +import com.plotsquared.core.generator.HybridPlotWorld; import com.plotsquared.core.generator.IndependentPlotGenerator; -import com.plotsquared.core.location.ChunkWrapper; +import com.plotsquared.core.location.Location; +import com.plotsquared.core.location.UncheckedWorldLocation; import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.PlotAreaManager; -import com.plotsquared.core.queue.QueueCoordinator; +import com.plotsquared.core.plot.world.SinglePlotArea; import com.plotsquared.core.queue.ScopedQueueCoordinator; -import com.sk89q.worldedit.bukkit.BukkitWorld; -import org.bukkit.Chunk; -import org.bukkit.World; import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.LimitedRegion; +import org.bukkit.generator.WorldInfo; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Random; @@ -43,35 +44,48 @@ import java.util.Random; final class BlockStatePopulator extends BlockPopulator { private final IndependentPlotGenerator plotGenerator; - private final PlotAreaManager plotAreaManager; - private QueueCoordinator queue; + public BlockStatePopulator( + final @NonNull IndependentPlotGenerator plotGenerator + ) { + this.plotGenerator = plotGenerator; + } + /** + * @deprecated Use {@link BlockStatePopulator#BlockStatePopulator(IndependentPlotGenerator)} as plotAreManager is unused + */ + @Deprecated(forRemoval = true, since = "TODO") public BlockStatePopulator( final @NonNull IndependentPlotGenerator plotGenerator, final @NonNull PlotAreaManager plotAreaManager ) { this.plotGenerator = plotGenerator; - this.plotAreaManager = plotAreaManager; } @Override - public void populate(final @NonNull World world, final @NonNull Random random, final @NonNull Chunk source) { - if (this.queue == null) { - this.queue = PlotSquared.platform().globalBlockQueue().getNewQueue(new BukkitWorld(world)); - } - final PlotArea area = this.plotAreaManager.getPlotArea(world.getName(), null); - if (area == null) { + public void populate( + @NonNull final WorldInfo worldInfo, + @NonNull final Random random, + final int chunkX, + final int chunkZ, + @NonNull final LimitedRegion limitedRegion + ) { + PlotArea area = UncheckedWorldLocation.at(worldInfo.getName(), chunkX << 4, 0, chunkZ << 4).getPlotArea(); + if (area == null || (area instanceof HybridPlotWorld hpw && !hpw.populationNeeded()) || area instanceof SinglePlotArea) { return; } - final ChunkWrapper wrap = new ChunkWrapper(area.getWorldName(), source.getX(), source.getZ()); - final ScopedQueueCoordinator chunk = this.queue.getForChunk(wrap.x, wrap.z, - com.plotsquared.bukkit.util.BukkitWorld.getMinWorldHeight(world), - com.plotsquared.bukkit.util.BukkitWorld.getMaxWorldHeight(world) - 1 + LimitedRegionWrapperQueue wrapped = new LimitedRegionWrapperQueue(limitedRegion); + // It is possible for the region to be larger than the chunk, but there is not reason for P2 to need to populate + // outside of the actual chunk area. + Location min = UncheckedWorldLocation.at(worldInfo.getName(), chunkX << 4, worldInfo.getMinHeight(), chunkZ << 4); + Location max = UncheckedWorldLocation.at( + worldInfo.getName(), + (chunkX << 4) + 15, + worldInfo.getMaxHeight(), + (chunkZ << 4) + 15 ); - if (this.plotGenerator.populateChunk(chunk, area)) { - this.queue.enqueue(); - } + ScopedQueueCoordinator offsetChunkQueue = new ScopedQueueCoordinator(wrapped, min, max); + this.plotGenerator.populateChunk(offsetChunkQueue, area); } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator116.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator116.java new file mode 100644 index 000000000..8b9cc51a4 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BlockStatePopulator116.java @@ -0,0 +1,78 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2014 - 2022 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.bukkit.generator; + +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.generator.HybridPlotWorld; +import com.plotsquared.core.generator.IndependentPlotGenerator; +import com.plotsquared.core.location.Location; +import com.plotsquared.core.location.UncheckedWorldLocation; +import com.plotsquared.core.plot.PlotArea; +import com.plotsquared.core.plot.world.SinglePlotArea; +import com.plotsquared.core.queue.QueueCoordinator; +import com.plotsquared.core.queue.ScopedQueueCoordinator; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.util.SideEffectSet; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.generator.BlockPopulator; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.util.Random; + +final class BlockStatePopulator116 extends BlockPopulator { + + private final IndependentPlotGenerator plotGenerator; + + public BlockStatePopulator116( + final @NonNull IndependentPlotGenerator plotGenerator + ) { + this.plotGenerator = plotGenerator; + } + + @Override + public void populate(@NotNull final World world, @NotNull final Random random, @NotNull final Chunk source) { + int chunkMinX = source.getX() << 4; + int chunkMinZ = source.getZ() << 4; + PlotArea area = Location.at(world.getName(), chunkMinX, 0, chunkMinZ).getPlotArea(); + if (area == null || (area instanceof HybridPlotWorld hpw && !hpw.populationNeeded()) || area instanceof SinglePlotArea) { + return; + } + + QueueCoordinator queue = PlotSquared.platform().globalBlockQueue().getNewQueue(new BukkitWorld(world)); + queue.setForceSync(true); + queue.setSideEffectSet(SideEffectSet.none()); + queue.setBiomesEnabled(false); + queue.setChunkObject(source); + Location min = UncheckedWorldLocation.at(world.getName(), chunkMinX, world.getMinHeight(), chunkMinZ); + Location max = UncheckedWorldLocation.at(world.getName(), chunkMinX + 15, world.getMaxHeight(), chunkMinZ + 15); + ScopedQueueCoordinator offsetChunkQueue = new ScopedQueueCoordinator(queue, min, max); + this.plotGenerator.populateChunk(offsetChunkQueue, area); + queue.enqueue(); + } + +} diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java index baaf95dfc..a537ee615 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java @@ -37,6 +37,8 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.queue.ScopedQueueCoordinator; import com.plotsquared.core.util.ChunkManager; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.math.BlockVector2; import org.bukkit.World; import org.bukkit.block.Biome; @@ -49,8 +51,7 @@ import java.util.List; import java.util.Random; import java.util.Set; -public class BukkitPlotGenerator extends ChunkGenerator - implements GeneratorWrapper { +public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrapper { @SuppressWarnings("unused") public final boolean PAPER_ASYNC_SAFE = true; @@ -73,7 +74,12 @@ public class BukkitPlotGenerator extends ChunkGenerator this.plotGenerator = generator; this.platformGenerator = this; this.populators = new ArrayList<>(); - this.populators.add(new BlockStatePopulator(this.plotGenerator, this.plotAreaManager)); + int minecraftMinorVersion = PlotSquared.platform().serverVersion()[1]; + if (minecraftMinorVersion >= 17) { + this.populators.add(new BlockStatePopulator(this.plotGenerator)); + } else { + this.populators.add(new BlockStatePopulator116(this.plotGenerator)); + } this.full = true; } @@ -159,7 +165,6 @@ public class BukkitPlotGenerator extends ChunkGenerator @NonNull World world, @NonNull Random random, int x, int z, @NonNull BiomeGrid biome ) { - int minY = BukkitWorld.getMinWorldHeight(world); int maxY = BukkitWorld.getMaxWorldHeight(world); GenChunk result = new GenChunk(minY, maxY); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/queue/LimitedRegionWrapperQueue.java b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/LimitedRegionWrapperQueue.java new file mode 100644 index 000000000..75141de28 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/queue/LimitedRegionWrapperQueue.java @@ -0,0 +1,106 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2014 - 2022 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.bukkit.queue; + +import com.plotsquared.bukkit.schematic.StateWrapper; +import com.plotsquared.core.queue.DelegateQueueCoordinator; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.generator.LimitedRegion; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator { + + private final LimitedRegion limitedRegion; + + public LimitedRegionWrapperQueue(LimitedRegion limitedRegion) { + super(null); + this.limitedRegion = limitedRegion; + } + + @Override + public boolean setBlock(final int x, final int y, final int z, @NonNull final Pattern pattern) { + return setBlock(x, y, z, pattern.applyBlock(BlockVector3.at(x, y, z))); + } + + @Override + public boolean setBlock(final int x, final int y, final int z, @NonNull final BaseBlock id) { + boolean result = setBlock(x, y, z, id.toImmutableState()); + if (result && id.hasNbtData()) { + CompoundTag tag = id.getNbtData(); + StateWrapper sw = new StateWrapper(tag); + sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock()); + } + return result; + } + + @Override + public boolean setBlock(final int x, final int y, final int z, @NonNull final BlockState id) { + try { + limitedRegion.setType(x, y, z, BukkitAdapter.adapt(id.getBlockType())); + limitedRegion.setBlockData(x, y, z, BukkitAdapter.adapt(id)); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + public boolean setEntity(@NonNull final Entity entity) { + try { + EntityType type = BukkitAdapter.adapt(entity.getState().getType()); + double x = entity.getLocation().getX(); + double y = entity.getLocation().getY(); + double z = entity.getLocation().getZ(); + Location location = new Location(limitedRegion.getWorld(), x, y, z); + limitedRegion.spawnEntity(location, type); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + public boolean setTile(final int x, final int y, final int z, @NonNull final CompoundTag tag) { + StateWrapper sw = new StateWrapper(tag); + return sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock()); + } + + @Override + public boolean isSettingTiles() { + return true; + } + +} diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java b/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java index c320ded27..6957ae0bc 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/schematic/StateWrapper.java @@ -35,11 +35,13 @@ import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.world.item.ItemType; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.Container; import org.bukkit.block.Sign; +import org.bukkit.block.Skull; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; @@ -57,6 +59,10 @@ public class StateWrapper { public org.bukkit.block.BlockState state = null; public CompoundTag tag = null; + /** + * @deprecated in favour of using WE methods for obtaining NBT + */ + @Deprecated(forRemoval = true, since = "TODO") public StateWrapper(org.bukkit.block.BlockState state) { this.state = state; } @@ -230,10 +236,27 @@ public class StateWrapper { } return false; } + case "skull" -> { + if (state instanceof Skull skull) { + CompoundTag skullOwner = ((CompoundTag) this.tag.getValue().get("SkullOwner")); + if (skullOwner == null) { + return true; + } + String player = skullOwner.getString("Name"); + skull.setOwningPlayer(Bukkit.getOfflinePlayer(player)); + skull.update(true); + return true; + } + return false; + } } return false; } + /** + * @deprecated in favour of using WE methods for obtaining NBT + */ + @Deprecated(forRemoval = true, since = "TODO") public CompoundTag getTag() { if (this.tag != null) { return this.tag; diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java index c9242a400..74ab035f2 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java @@ -35,13 +35,23 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.queue.ScopedQueueCoordinator; import com.plotsquared.core.util.MathMan; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.RegionOperationException; +import com.sk89q.worldedit.world.NullWorld; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockTypes; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class HybridGen extends IndependentPlotGenerator { + private static final CuboidRegion CHUNK = new CuboidRegion(BlockVector3.ZERO, BlockVector3.at(15, 396, 15)); private final HybridPlotWorldFactory hybridPlotWorldFactory; @Inject @@ -55,12 +65,17 @@ public class HybridGen extends IndependentPlotGenerator { } private void placeSchem( - HybridPlotWorld world, ScopedQueueCoordinator result, short relativeX, - short relativeZ, int x, int z, boolean isRoad + HybridPlotWorld world, + ScopedQueueCoordinator result, + short relativeX, + short relativeZ, + int x, + int z, + boolean isRoad, + boolean isPopulating ) { int minY; // Math.min(world.PLOT_HEIGHT, world.ROAD_HEIGHT); - if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad - && Settings.Schematics.PASTE_ON_TOP)) { + if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) { minY = world.SCHEM_Y; } else { minY = world.getMinBuildHeight(); @@ -69,7 +84,9 @@ public class HybridGen extends IndependentPlotGenerator { if (blocks != null) { for (int y = 0; y < blocks.length; y++) { if (blocks[y] != null) { - result.setBlock(x, minY + y, z, blocks[y]); + if (!isPopulating || blocks[y].hasNbtData()) { + result.setBlock(x, minY + y, z, blocks[y]); + } } } } @@ -128,19 +145,17 @@ public class HybridGen extends IndependentPlotGenerator { } relativeX[i] = v; if (hybridPlotWorld.ROAD_WIDTH != 0) { - insideRoadX[i] = - v < hybridPlotWorld.PATH_WIDTH_LOWER || v > hybridPlotWorld.PATH_WIDTH_UPPER; - insideWallX[i] = - v == hybridPlotWorld.PATH_WIDTH_LOWER || v == hybridPlotWorld.PATH_WIDTH_UPPER; + insideRoadX[i] = v < hybridPlotWorld.PATH_WIDTH_LOWER || v > hybridPlotWorld.PATH_WIDTH_UPPER; + insideWallX[i] = v == hybridPlotWorld.PATH_WIDTH_LOWER || v == hybridPlotWorld.PATH_WIDTH_UPPER; } } // The Z-coordinate of a given Z coordinate, relative to the // plot (Counting from the corner with the least positive // coordinates) short[] relativeZ = new short[16]; - // Whether or not the given Z coordinate belongs to the road + // Whether the given Z coordinate belongs to the road boolean[] insideRoadZ = new boolean[16]; - // Whether or not the given Z coordinate belongs to the wall + // Whether the given Z coordinate belongs to the wall boolean[] insideWallZ = new boolean[16]; for (short i = 0; i < 16; i++) { short v = (short) (relativeOffsetZ + i); @@ -149,14 +164,12 @@ public class HybridGen extends IndependentPlotGenerator { } relativeZ[i] = v; if (hybridPlotWorld.ROAD_WIDTH != 0) { - insideRoadZ[i] = - v < hybridPlotWorld.PATH_WIDTH_LOWER || v > hybridPlotWorld.PATH_WIDTH_UPPER; - insideWallZ[i] = - v == hybridPlotWorld.PATH_WIDTH_LOWER || v == hybridPlotWorld.PATH_WIDTH_UPPER; + insideRoadZ[i] = v < hybridPlotWorld.PATH_WIDTH_LOWER || v > hybridPlotWorld.PATH_WIDTH_UPPER; + insideWallZ[i] = v == hybridPlotWorld.PATH_WIDTH_LOWER || v == hybridPlotWorld.PATH_WIDTH_UPPER; } } // generation - int startY = hybridPlotWorld.getMinGenHeight() + (hybridPlotWorld.PLOT_BEDROCK ? 1: 0); + int startY = hybridPlotWorld.getMinGenHeight() + (hybridPlotWorld.PLOT_BEDROCK ? 1 : 0); for (short x = 0; x < 16; x++) { if (insideRoadX[x]) { for (short z = 0; z < 16; z++) { @@ -165,7 +178,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); } } } else if (insideWallX[x]) { @@ -176,9 +189,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, - true - ); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); } } else { // wall @@ -187,14 +198,10 @@ public class HybridGen extends IndependentPlotGenerator { } if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { if (hybridPlotWorld.PLACE_TOP_BLOCK) { - result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, - hybridPlotWorld.WALL_BLOCK.toPattern() - ); + result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern()); } } else { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, - true - ); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); } } } @@ -206,9 +213,7 @@ public class HybridGen extends IndependentPlotGenerator { result.setBlock(x, y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); } if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, - true - ); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); } } else if (insideWallZ[z]) { // wall @@ -217,27 +222,19 @@ public class HybridGen extends IndependentPlotGenerator { } if (!hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { if (hybridPlotWorld.PLACE_TOP_BLOCK) { - result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, - hybridPlotWorld.WALL_BLOCK.toPattern() - ); + result.setBlock(x, hybridPlotWorld.WALL_HEIGHT + 1, z, hybridPlotWorld.WALL_BLOCK.toPattern()); } } else { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, - true - ); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, false); } } else { // plot for (int y = startY; y < hybridPlotWorld.PLOT_HEIGHT; y++) { result.setBlock(x, y, z, hybridPlotWorld.MAIN_BLOCK.toPattern()); } - result.setBlock(x, hybridPlotWorld.PLOT_HEIGHT, z, - hybridPlotWorld.TOP_BLOCK.toPattern() - ); + result.setBlock(x, hybridPlotWorld.PLOT_HEIGHT, z, hybridPlotWorld.TOP_BLOCK.toPattern()); if (hybridPlotWorld.PLOT_SCHEMATIC) { - placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, - false - ); + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, false); } } } @@ -245,6 +242,150 @@ public class HybridGen extends IndependentPlotGenerator { } } + @Override + public boolean populateChunk(final ScopedQueueCoordinator result, final PlotArea settings) { + HybridPlotWorld hybridPlotWorld = (HybridPlotWorld) settings; + if (!hybridPlotWorld.populationNeeded()) { + return false; + } + // Coords + Location min = result.getMin(); + int bx = min.getX() - hybridPlotWorld.ROAD_OFFSET_X; + int bz = min.getZ() - hybridPlotWorld.ROAD_OFFSET_Z; + // The relative X-coordinate (within the plot) of the minimum X coordinate + // contained in the scoped queue + short relativeOffsetX; + if (bx < 0) { + relativeOffsetX = (short) (hybridPlotWorld.SIZE + (bx % hybridPlotWorld.SIZE)); + } else { + relativeOffsetX = (short) (bx % hybridPlotWorld.SIZE); + } + // The relative Z-coordinate (within the plot) of the minimum Z coordinate + // contained in the scoped queue + short relativeOffsetZ; + if (bz < 0) { + relativeOffsetZ = (short) (hybridPlotWorld.SIZE + (bz % hybridPlotWorld.SIZE)); + } else { + relativeOffsetZ = (short) (bz % hybridPlotWorld.SIZE); + } + boolean allRoad = true; + boolean overlap = false; + + // The X-coordinate of a given X coordinate, relative to the + // plot (Counting from the corner with the least positive + // coordinates) + short[] relativeX = new short[16]; + boolean[] insideRoadX = new boolean[16]; + boolean[] insideWallX = new boolean[16]; + short offsetX = relativeOffsetX; + while (offsetX >= hybridPlotWorld.SIZE) { + offsetX -= hybridPlotWorld.SIZE; + } + for (short i = 0; i < 16; i++) { + if (offsetX >= hybridPlotWorld.SIZE) { + offsetX -= hybridPlotWorld.SIZE; + overlap = true; + } + relativeX[i] = offsetX; + if (hybridPlotWorld.ROAD_WIDTH != 0) { + boolean insideRoad = offsetX < hybridPlotWorld.PATH_WIDTH_LOWER || offsetX > hybridPlotWorld.PATH_WIDTH_UPPER; + boolean insideWall = offsetX == hybridPlotWorld.PATH_WIDTH_LOWER || offsetX == hybridPlotWorld.PATH_WIDTH_UPPER; + insideRoadX[i] = insideRoad; + insideWallX[i] = insideWall; + allRoad &= insideRoad && insideWall; + } + offsetX++; + } + + // The Z-coordinate of a given Z coordinate, relative to the + // plot (Counting from the corner with the least positive + // coordinates) + short[] relativeZ = new short[16]; + boolean[] insideRoadZ = new boolean[16]; + boolean[] insideWallZ = new boolean[16]; + short offsetZ = relativeOffsetZ; + while (offsetZ >= hybridPlotWorld.SIZE) { + offsetZ -= hybridPlotWorld.SIZE; + overlap = true; + } + for (short i = 0; i < 16; i++) { + if (offsetZ >= hybridPlotWorld.SIZE) { + offsetZ -= hybridPlotWorld.SIZE; + } + relativeZ[i] = offsetZ; + if (hybridPlotWorld.ROAD_WIDTH != 0) { + boolean insideRoad = offsetZ < hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ > hybridPlotWorld.PATH_WIDTH_UPPER; + boolean insideWall = offsetZ == hybridPlotWorld.PATH_WIDTH_LOWER || offsetZ == hybridPlotWorld.PATH_WIDTH_UPPER; + insideRoadZ[i] = insideRoad; + insideWallZ[i] = insideWall; + allRoad &= insideRoad && insideWall; + } + offsetZ++; + } + for (short x = 0; x < 16; x++) { + if (insideRoadX[x]) { + if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { + for (short z = 0; z < 16; z++) { + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true); + } + } + } else if (insideWallX[x]) { + if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { + for (short z = 0; z < 16; z++) { + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true); + } + } + } else { + for (short z = 0; z < 16; z++) { + if (insideRoadZ[z] || insideWallZ[z]) { + if (hybridPlotWorld.ROAD_SCHEMATIC_ENABLED) { + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, true, true); + } + } else if (hybridPlotWorld.PLOT_SCHEMATIC) { + placeSchem(hybridPlotWorld, result, relativeX[x], relativeZ[z], x, z, false, true); + } + } + } + } + if (!allRoad && hybridPlotWorld.getPlotSchematicEntities() != null && !hybridPlotWorld + .getPlotSchematicEntities() + .isEmpty()) { + CuboidRegion region = CHUNK.clone(); + try { + region.shift(hybridPlotWorld + .getPlotSchematicMinPoint() + .add(relativeOffsetX, 0, relativeOffsetZ) + .subtract(hybridPlotWorld.PATH_WIDTH_LOWER + 1, 0, hybridPlotWorld.PATH_WIDTH_LOWER + 1)); + for (Entity entity : hybridPlotWorld.getPlotSchematicEntities()) { + if (region.contains(entity.getLocation().toVector().toBlockPoint())) { + Vector3 pos = (entity.getLocation().toVector() + .subtract(region.getMinimumPoint().withY(hybridPlotWorld.getPlotSchematicMinPoint().getY()).toVector3())) + .add(min.getBlockVector3().withY(hybridPlotWorld.SCHEM_Y).toVector3()); + result.setEntity(new PopulatingEntity( + entity, + new com.sk89q.worldedit.util.Location(NullWorld.getInstance(), pos) + )); + } + } + } catch (RegionOperationException e) { + throw new RuntimeException(e); + } + if (overlap) { + try { + region.shift(BlockVector3.at(-hybridPlotWorld.SIZE, 0, -hybridPlotWorld.SIZE)); + for (Entity entity : hybridPlotWorld.getPlotSchematicEntities()) { + if (region.contains(entity.getLocation().toVector().toBlockPoint())) { + result.setEntity(entity); + } + } + } catch (RegionOperationException e) { + throw new RuntimeException(e); + } + } + } + return true; + } + @Override public PlotArea getNewPlotArea(String world, String id, PlotId min, PlotId max) { return this.hybridPlotWorldFactory.create(world, id, this, min, max); @@ -255,4 +396,49 @@ public class HybridGen extends IndependentPlotGenerator { // All initialization is done in the PlotArea class } + private static final class PopulatingEntity implements Entity { + + private final Entity parent; + private com.sk89q.worldedit.util.Location location; + + private PopulatingEntity(Entity parent, com.sk89q.worldedit.util.Location location) { + this.parent = parent; + this.location = location; + } + + @Nullable + @Override + public BaseEntity getState() { + return parent.getState(); + } + + @Override + public boolean remove() { + return parent.remove(); + } + + @Override + public com.sk89q.worldedit.util.Location getLocation() { + return location; + } + + @Override + public boolean setLocation(final com.sk89q.worldedit.util.Location location) { + this.location = location; + return true; + } + + @Override + public Extent getExtent() { + return parent.getExtent(); + } + + @Nullable + @Override + public T getFacet(final Class cls) { + return parent.getFacet(cls); + } + + } + } diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java index 815972ee0..cab012611 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java @@ -40,11 +40,13 @@ import com.plotsquared.core.plot.PlotId; import com.plotsquared.core.plot.PlotManager; import com.plotsquared.core.plot.schematic.Schematic; import com.plotsquared.core.queue.GlobalBlockQueue; +import com.plotsquared.core.util.AnnotationHelper; import com.plotsquared.core.util.FileUtils; import com.plotsquared.core.util.MathMan; import com.plotsquared.core.util.SchematicHandler; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTagBuilder; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.internal.helper.MCDirections; @@ -58,11 +60,13 @@ import com.sk89q.worldedit.world.block.BaseBlock; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; -import javax.annotation.Nullable; import java.io.File; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; public class HybridPlotWorld extends ClassicPlotWorld { @@ -71,6 +75,7 @@ public class HybridPlotWorld extends ClassicPlotWorld { private static final AffineTransform transform = new AffineTransform().rotateY(90); public boolean ROAD_SCHEMATIC_ENABLED; public boolean PLOT_SCHEMATIC = false; + @Deprecated(forRemoval = true, since = "TODO") public int PLOT_SCHEMATIC_HEIGHT = -1; public short PATH_WIDTH_LOWER; public short PATH_WIDTH_UPPER; @@ -80,6 +85,11 @@ public class HybridPlotWorld extends ClassicPlotWorld { private Location SIGN_LOCATION; private File root = null; private int lastOverlayHeightError = Integer.MIN_VALUE; + private List schem3Entities = null; + private BlockVector3 schem3MinPoint = null; + private boolean schem1PopulationNeeded = false; + private boolean schem2PopulationNeeded = false; + private boolean schem3PopulationNeeded = false; @Inject private SchematicHandler schematicHandler; @@ -98,6 +108,7 @@ public class HybridPlotWorld extends ClassicPlotWorld { PlotSquared.platform().injector().injectMembers(this); } + @Deprecated(forRemoval = true, since = "TODO") public static byte wrap(byte data, int start) { if ((data >= start) && (data < (start + 4))) { data = (byte) ((((data - start) + 2) & 3) + start); @@ -105,6 +116,7 @@ public class HybridPlotWorld extends ClassicPlotWorld { return data; } + @Deprecated(forRemoval = true, since = "TODO") public static byte wrap2(byte data, int start) { if ((data >= start) && (data < (start + 2))) { data = (byte) ((((data - start) + 1) & 1) + start); @@ -112,8 +124,6 @@ public class HybridPlotWorld extends ClassicPlotWorld { return data; } - // FIXME depends on block ids - // Possibly make abstract? public static BaseBlock rotate(BaseBlock id) { CompoundTag tag = id.getNbtData(); @@ -251,6 +261,14 @@ public class HybridPlotWorld extends ClassicPlotWorld { Schematic schematic1 = this.schematicHandler.getSchematic(schematic1File); Schematic schematic2 = this.schematicHandler.getSchematic(schematic2File); Schematic schematic3 = this.schematicHandler.getSchematic(schematic3File); + + // If the plot schematic contains entities, then they need to be populated upon generation. + if (schematic3 != null && !schematic3.getClipboard().getEntities().isEmpty()) { + this.schem3Entities = new ArrayList<>(schematic3.getClipboard().getEntities()); + this.schem3MinPoint = schematic3.getClipboard().getMinimumPoint(); + this.schem3PopulationNeeded = true; + } + int shift = this.ROAD_WIDTH / 2; int oddshift = (this.ROAD_WIDTH & 1); @@ -314,24 +332,34 @@ public class HybridPlotWorld extends ClassicPlotWorld { for (short x = 0; x < w3; x++) { for (short z = 0; z < l3; z++) { for (short y = 0; y < h3; y++) { - BaseBlock id = - blockArrayClipboard3.getFullBlock(BlockVector3.at( - x + min.getBlockX(), - y + min.getBlockY(), - z + min.getBlockZ() - )); + BaseBlock id = blockArrayClipboard3.getFullBlock(BlockVector3.at( + x + min.getBlockX(), + y + min.getBlockY(), + z + min.getBlockZ() + )); if (!id.getBlockType().getMaterial().isAir()) { - addOverlayBlock((short) (x + shift + oddshift + centerShiftX), (short) (y + plotY), - (short) (z + shift + oddshift + centerShiftZ), id, false, h3 + schem3PopulationNeeded |= id.hasNbtData(); + addOverlayBlock( + (short) (x + shift + oddshift + centerShiftX), + (short) (y + plotY), + (short) (z + shift + oddshift + centerShiftZ), + id, + false, + h3 ); } } - BiomeType biome = blockArrayClipboard3.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); - addOverlayBiome( - (short) (x + shift + oddshift + centerShiftX), - (short) (z + shift + oddshift + centerShiftZ), - biome - ); + if (blockArrayClipboard3.hasBiomes()) { + BiomeType biome = blockArrayClipboard3.getBiome(BlockVector2.at( + x + min.getBlockX(), + z + min.getBlockZ() + )); + addOverlayBiome( + (short) (x + shift + oddshift + centerShiftX), + (short) (z + shift + oddshift + centerShiftZ), + biome + ); + } } } @@ -339,7 +367,7 @@ public class HybridPlotWorld extends ClassicPlotWorld { LOGGER.info("- plot schematic: {}", schematic3File.getPath()); } } - if ((schematic1 == null&& schematic2 == null) || this.ROAD_WIDTH == 0) { + if ((schematic1 == null && schematic2 == null) || this.ROAD_WIDTH == 0) { if (Settings.DEBUG) { LOGGER.info("- schematic: false"); } @@ -370,6 +398,7 @@ public class HybridPlotWorld extends ClassicPlotWorld { z + min.getBlockZ() )); if (!id.getBlockType().getMaterial().isAir()) { + schem1PopulationNeeded |= id.hasNbtData(); addOverlayBlock((short) (x - shift), (short) (y + roadY), (short) (z + shift + oddshift), id, false, h1); addOverlayBlock( (short) (z + shift + oddshift), @@ -381,9 +410,11 @@ public class HybridPlotWorld extends ClassicPlotWorld { ); } } - BiomeType biome = blockArrayClipboard1.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); - addOverlayBiome((short) (x - shift), (short) (z + shift + oddshift), biome); - addOverlayBiome((short) (z + shift + oddshift), (short) (shift - x + (oddshift - 1)), biome); + if (blockArrayClipboard1.hasBiomes()) { + BiomeType biome = blockArrayClipboard1.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); + addOverlayBiome((short) (x - shift), (short) (z + shift + oddshift), biome); + addOverlayBiome((short) (z + shift + oddshift), (short) (shift - x + (oddshift - 1)), biome); + } } } @@ -406,11 +437,14 @@ public class HybridPlotWorld extends ClassicPlotWorld { z + min.getBlockZ() )); if (!id.getBlockType().getMaterial().isAir()) { + schem2PopulationNeeded |= id.hasNbtData(); addOverlayBlock((short) (x - shift), (short) (y + roadY), (short) (z - shift), id, false, h2); } } - BiomeType biome = blockArrayClipboard2.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); - addOverlayBiome((short) (x - shift), (short) (z - shift), biome); + if (blockArrayClipboard2.hasBiomes()) { + BiomeType biome = blockArrayClipboard2.getBiome(BlockVector2.at(x + min.getBlockX(), z + min.getBlockZ())); + addOverlayBiome((short) (x - shift), (short) (z - shift), biome); + } } } } @@ -456,8 +490,46 @@ public class HybridPlotWorld extends ClassicPlotWorld { this.G_SCH_B.put(pair, id); } + /** + * Get the entities contained within the plot schematic for generation. Intended for internal use only. + */ + @AnnotationHelper.ApiDescription(info = "Internal use only. Subject to changes at any time.") + public @Nullable List getPlotSchematicEntities() { + return schem3Entities; + } + + /** + * Get the minimum point of the plot schematic for generation. Intended for internal use only. + */ + @AnnotationHelper.ApiDescription(info = "Internal use only. Subject to changes at any time.") + public @Nullable BlockVector3 getPlotSchematicMinPoint() { + return schem3MinPoint; + } + + /** + * Get if post-generation population of chunks with tiles/entities is needed for this world. Not for public API use. + */ + @AnnotationHelper.ApiDescription(info = "Internal use only. Subject to changes at any time.") + public boolean populationNeeded() { + return schem1PopulationNeeded || schem2PopulationNeeded || schem3PopulationNeeded; + } + + /** + * @deprecated in favour of {@link HybridPlotWorld#getSchematicRoot()} + */ + @Deprecated(forRemoval = true, since = "TODO") public File getRoot() { return this.root; } + /** + * Get the root folder for this world's generation schematics. May be null if schematics not initialised via + * {@link HybridPlotWorld#setupSchematics()} + * + * @since TODO + */ + public @Nullable File getSchematicRoot() { + return this.root; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java b/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java index ea207177d..a917e52a6 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java +++ b/Core/src/main/java/com/plotsquared/core/generator/IndependentPlotGenerator.java @@ -47,9 +47,7 @@ public abstract class IndependentPlotGenerator { public abstract String getName(); /** - * Use the setBlock or setBiome method of the PlotChunk result parameter to make changes. - * The PlotArea settings is the same one this was initialized with. - * The PseudoRandom random is a fast random object. + * Generate chunk block data * * @param result queue * @param settings PlotArea (settings) @@ -59,10 +57,13 @@ public abstract class IndependentPlotGenerator { public abstract void generateChunk(ScopedQueueCoordinator result, PlotArea settings); /** - * @deprecated {@link ScopedQueueCoordinator} will be renamed in v7. + * Populates the queue representing a chunk area with tile entities and entities + * + * @param result Queue to write to + * @param settings PlotArea (settings) + * @return True if any population occured */ - @Deprecated(forRemoval = true, since = "TODO") - public boolean populateChunk(ScopedQueueCoordinator result, PlotArea setting) { + public boolean populateChunk(ScopedQueueCoordinator result, PlotArea settings) { return false; } diff --git a/Core/src/main/java/com/plotsquared/core/location/Location.java b/Core/src/main/java/com/plotsquared/core/location/Location.java index cb37d41af..7737e806c 100644 --- a/Core/src/main/java/com/plotsquared/core/location/Location.java +++ b/Core/src/main/java/com/plotsquared/core/location/Location.java @@ -41,14 +41,14 @@ import org.khelekore.prtree.SimpleMBR; * An unmodifiable 6-tuple (world,x,y,z,yaw,pitch) */ @SuppressWarnings("unused") -public final class Location extends BlockLoc implements Comparable { +public class Location extends BlockLoc implements Comparable { private final float yaw; private final float pitch; private final BlockVector3 blockVector3; private final World world; - private Location( + protected Location( final @NonNull World world, final @NonNull BlockVector3 blockVector3, final float yaw, final float pitch ) { diff --git a/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java b/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java new file mode 100644 index 000000000..e193c34a0 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java @@ -0,0 +1,68 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2014 - 2022 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.location; + +import com.plotsquared.core.util.AnnotationHelper; +import com.sk89q.worldedit.math.BlockVector3; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Used internally for generation to reference locations in worlds that "don't exist yet". There is no guarantee that the world + * name provided by {@link UncheckedWorldLocation#getWorldName()} exists on the server. + */ +@AnnotationHelper.ApiDescription(info = "Internal use only. Subject to changes at any time.") +public class UncheckedWorldLocation extends Location { + + private final String worldName; + + private UncheckedWorldLocation( + final @NonNull String worldName, final int x, final int y, final int z + ) { + super(World.nullWorld(), BlockVector3.at(x, y, z), 0f, 0f); + this.worldName = worldName; + } + + /** + * Construct a new location with yaw and pitch equal to 0 + * + * @param world World + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return New location + */ + public static @NonNull UncheckedWorldLocation at( + final @NonNull String world, final int x, final int y, final int z + ) { + return new UncheckedWorldLocation(world, x, y, z); + } + + @Override + public @NonNull String getWorldName() { + return this.worldName; + } + +}