From 47f350c4da626128719ad3bfbdfaf29b277c0a60 Mon Sep 17 00:00:00 2001 From: ryanbennitt Date: Fri, 1 Jan 2016 14:03:17 +0000 Subject: [PATCH] Fixed issues with Array.fill upper bound in setRegion diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java index 438bed8..01264a7 100644 --- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java @@ -97,7 +97,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { if (xMin >= xMax || yMin >= yMax || zMin >= zMax) { return; } - char typeChar = (char) (blockId << 4 | data); + char typeChar = (char) ((blockId << 4) | data); if (xMin == 0 && xMax == 0x10) { if (zMin == 0 && zMax == 0x10) { for (int y = yMin & 0xf0; y < yMax; y += 0x10) { @@ -110,7 +110,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { // First chunk section Arrays.fill(section, (yMin & 0xf) << 8, 0x1000, typeChar); } - } else if (y + 0x10 >= yMax) { + } else if (y + 0x10 > yMax) { // Last chunk section Arrays.fill(section, 0, (yMax & 0xf) << 8, typeChar); } else { @@ -123,7 +123,8 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { char[] section = getChunkSection(y, true); int offsetBase = (y & 0xf) << 8; int min = offsetBase | (zMin << 4); - int max = offsetBase | (zMax << 4); + // Need to add zMax as it can be 16, which overlaps the y coordinate bits + int max = offsetBase + (zMax << 4); Arrays.fill(section, min, max, typeChar); } } @@ -132,8 +133,9 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { char[] section = getChunkSection(y, true); int offsetBase = (y & 0xf) << 8; for (int z = zMin; z < zMax; z++) { - int offset = offsetBase | z << 4; - Arrays.fill(section, offset | xMin, offset | xMax, typeChar); + int offset = offsetBase | (z << 4); + // Need to add xMax as it can be 16, which overlaps the z coordinate bits + Arrays.fill(section, offset | xMin, offset + xMax, typeChar); } } } diff --git a/src/test/java/org/bukkit/craftbukkit/generator/CraftChunkDataTest.java b/src/test/java/org/bukkit/craftbukkit/generator/CraftChunkDataTest.java new file mode 100644 index 0000000..0b09238 --- /dev/null +++ b/src/test/java/org/bukkit/craftbukkit/generator/CraftChunkDataTest.java @@ -0,0 +1,414 @@ +package org.bukkit.craftbukkit.generator; + +import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.BlockChangeDelegate; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Difficulty; +import org.bukkit.DyeColor; +import org.bukkit.Effect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.TreeSpecies; +import org.bukkit.TreeType; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.WorldType; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.CreatureType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Item; +import org.bukkit.entity.LightningStrike; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.material.Tree; +import org.bukkit.material.Wool; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.support.AbstractTestingBase; +import org.bukkit.util.Vector; +import org.junit.Test; + +@SuppressWarnings("deprecation") +public class CraftChunkDataTest extends AbstractTestingBase { + private Random rand = new Random(0l); + @Test + public void testFillChunk() { + CraftChunkData chunk = new CraftChunkData(createMockWorld(256)); + Tree tree = new Tree(Material.LOG_2, TreeSpecies.DARK_OAK); + chunk.setRegion(0, 0, 0, 16, 256, 16, tree); + // check min and max extents have been set plus somewhere in the middle + assertThat("Block at <0,0,0> is Dark Oak Wood",tree,equalTo(chunk.getTypeAndData(0, 0, 0))); + assertThat("Block at <7,127,7> is Dark Oak Wood",tree,equalTo(chunk.getTypeAndData(7, 127, 7))); + assertThat("Block at <15,255,15> is Dark Oak Wood",tree,equalTo(chunk.getTypeAndData(15, 255, 15))); + } + + @Test + public void testFillChunkLayers() { + CraftChunkData chunk = new CraftChunkData(createMockWorld(256)); + + // Create some different values for each layer + TreeSpecies[] species = new TreeSpecies[] {TreeSpecies.GENERIC, TreeSpecies.REDWOOD, TreeSpecies.BIRCH, TreeSpecies.JUNGLE}; + BlockFace[] faces = new BlockFace[] {BlockFace.UP, BlockFace.WEST, BlockFace.NORTH, BlockFace.SELF}; + + // Fill some layers with different values + for(int y = 0; y < 16; y++) + { + // check min and max extents have been set plus somewhere in the middle + + Tree tree = new Tree(Material.LOG, species[y&0x03], faces[y>>2]); + testSetChunkRegion(chunk, 0, y, 0, 16, y+1, 16, tree); + + int yMiddle = y+112; + testSetChunkRegion(chunk, 0, yMiddle, 0, 16, yMiddle+1, 16, tree); + + int yMax = y+240; + testSetChunkRegion(chunk, 0, yMax, 0, 16, yMax+1, 16, tree); + } + } + + @Test + public void testFillChunkBlocks() { + CraftChunkData chunk = new CraftChunkData(createMockWorld(256)); + + // Create some different values for each layer + DyeColor[] dyes = DyeColor.values(); + Wool[] wools = new Wool[dyes.length]; + for(int w = 0; w < dyes.length; w++) + { + wools[w] = new Wool(); + wools[w].setColor(dyes[w]); + } + + // Fill some layers with different values + for(int y = 0; y < 256; y+=120) + { + for(int split = 1; split <= 7; split++) + { + // check corners, sides have been set plus somewhere in the middle + // Bottom corners + testSetChunkRegion(chunk, 0, y, 0, split, y+split, split, wools[split]); + testSetChunkRegion(chunk, 0, y, 16-split, split, y+split, 16, wools[split+1]); + testSetChunkRegion(chunk, 16-split, y, 0, 16, y+split, split, wools[split+2]); + testSetChunkRegion(chunk, 16-split, y, 16-split, 16, y+split, 16, wools[split+3]); + // Bottom edges + testSetChunkRegion(chunk, split, y, 0, 16-split, y+split, split, wools[split+4]); + testSetChunkRegion(chunk, 0, y, split, split, y+split, 16-split, wools[split+5]); + testSetChunkRegion(chunk, split, y, 15, 16-split, y+split, 16, wools[split+6]); + testSetChunkRegion(chunk, 15, y, split, 16, y+split, 16-split, wools[split+7]); + // Bottom centre + testSetChunkRegion(chunk, split, y, split, 16-split, y+16-split, 16-split, wools[split+8]); + // Middle corners + testSetChunkRegion(chunk, 0, y+split, 0, split, y+16-split, split, wools[split]); + testSetChunkRegion(chunk, 0, y+split, 16-split, split, y+16-split, 16, wools[split+1]); + testSetChunkRegion(chunk, 16-split, y+split, 0, 16, y+16-split, split, wools[split+2]); + testSetChunkRegion(chunk, 16-split, y+split, 16-split, 16, y+16-split, 16, wools[split+3]); + // Middle edges + testSetChunkRegion(chunk, split, y+split, 0, 16-split, y+16-split, split, wools[split+4]); + testSetChunkRegion(chunk, 0, y+split, split, split, y+16-split, 16-split, wools[split+5]); + testSetChunkRegion(chunk, split, y+split, 15, 16-split, y+16-split, 16, wools[split+6]); + testSetChunkRegion(chunk, 15, y+split, split, 16, y+16-split, 16-split, wools[split+7]); + // Middle centre + testSetChunkRegion(chunk, split, y+split, split, 16-split, y+16-split, 16-split, wools[split+8]); + // Top corners + testSetChunkRegion(chunk, 0, y+16-split, 0, split, y+16, split, wools[split]); + testSetChunkRegion(chunk, 0, y+16-split, 16-split, split, y+16, 16, wools[split+1]); + testSetChunkRegion(chunk, 16-split, y+16-split, 0, 16, y+16, split, wools[split+2]); + testSetChunkRegion(chunk, 16-split, y+16-split, 16-split, 16, y+16, 16, wools[split+3]); + // Top edges + testSetChunkRegion(chunk, split, y+16-split, 0, 16-split, y+16, split, wools[split+4]); + testSetChunkRegion(chunk, 0, y+16-split, split, split, y+16, 16-split, wools[split+5]); + testSetChunkRegion(chunk, split, y+16-split, 15, 16-split, y+16, 16, wools[split+6]); + testSetChunkRegion(chunk, 15, y+16-split, split, 16, y+16, 16-split, wools[split+7]); + // Top centre + testSetChunkRegion(chunk, split, y+16-split, split, 16-split, y+16, 16-split, wools[split+8]); + } + } + } + + private void testSetChunkRegion(CraftChunkData chunk, int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData mat) + { + // Top centre + chunk.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, mat); + int xTest = xMin + rand.nextInt(xMax-xMin); + int yTest = yMin + rand.nextInt(yMax-yMin); + int zTest = zMin + rand.nextInt(zMax-zMin); + assertThat( + String.format("Block at <%d, %d, %d> is Material (%s, %d)",xTest, yTest, zTest, mat.getItemType().name(), mat.getData()), + mat, equalTo(chunk.getTypeAndData(xTest, yTest, zTest))); + } + + private World createMockWorld( + final int maxWorldHeight) + { + return new World() { + + @Override + public int getMaxHeight() { + return maxWorldHeight; + } + + @Override + public Set getListeningPluginChannels() { return null; } + @Override + public void sendPluginMessage(Plugin arg0, String arg1, byte[] arg2) {} + @Override + public List getMetadata(String arg0) { return null; } + @Override + public boolean hasMetadata(String arg0) { return false; } + @Override + public void removeMetadata(String arg0, Plugin arg1) {} + @Override + public void setMetadata(String arg0, MetadataValue arg1) {} + @Override + public boolean canGenerateStructures() { return false; } + @Override + public boolean createExplosion(Location arg0, float arg1) { return false; } + @Override + public boolean createExplosion(Location arg0, float arg1, boolean arg2) { return false; } + @Override + public boolean createExplosion(double arg0, double arg1, double arg2, float arg3) { return false; } + @Override + public boolean createExplosion(double arg0, double arg1, double arg2, float arg3, boolean arg4) { return false; } + @Override + public boolean createExplosion(double arg0, double arg1, double arg2, float arg3, boolean arg4, boolean arg5) { return false; } + @Override + public Item dropItem(Location arg0, ItemStack arg1) { return null; } + @Override + public Item dropItemNaturally(Location arg0, ItemStack arg1) { return null; } + @Override + public boolean generateTree(Location arg0, TreeType arg1) { return false; } + @Override + public boolean generateTree(Location arg0, TreeType arg1, BlockChangeDelegate arg2) { return false; } + @Override + public boolean getAllowAnimals() { return false; } + @Override + public boolean getAllowMonsters() { return false; } + @Override + public int getAmbientSpawnLimit() { return 0; } + @Override + public int getAnimalSpawnLimit() { return 0; } + @Override + public Biome getBiome(int arg0, int arg1) { return null; } + @Override + public Block getBlockAt(Location arg0) { return null; } + @Override + public Block getBlockAt(int arg0, int arg1, int arg2) { return null; } + @Override + public int getBlockTypeIdAt(Location arg0) { return 0; } + @Override + public int getBlockTypeIdAt(int arg0, int arg1, int arg2) { return 0; } + @Override + public Chunk getChunkAt(Location arg0) { return null; } + @Override + public Chunk getChunkAt(Block arg0) { return null; } + @Override + public Chunk getChunkAt(int arg0, int arg1) { return null; } + @Override + public Difficulty getDifficulty() { return null; } + @Override + public ChunkSnapshot getEmptyChunkSnapshot(int arg0, int arg1, boolean arg2, boolean arg3) { return null; } + @Override + public List getEntities() { return null; } + @Override + public Collection getEntitiesByClass(Class... arg0) { return null; } + @Override + public Collection getEntitiesByClass(Class arg0) { return null; } + @Override + public Collection getEntitiesByClasses(Class... arg0) { return null; } + @Override + public Environment getEnvironment() { return null; } + @Override + public long getFullTime() { return 0; } + @Override + public String getGameRuleValue(String arg0) { return null; } + @Override + public String[] getGameRules() { return null; } + @Override + public ChunkGenerator getGenerator() { return null; } + @Override + public Block getHighestBlockAt(Location arg0) { return null; } + @Override + public Block getHighestBlockAt(int arg0, int arg1) { return null; } + @Override + public int getHighestBlockYAt(Location arg0) { return 0; } + @Override + public int getHighestBlockYAt(int arg0, int arg1) { return 0; } + @Override + public double getHumidity(int arg0, int arg1) { return 0; } + @Override + public boolean getKeepSpawnInMemory() { return false; } + @Override + public List getLivingEntities() { return null; } + @Override + public Chunk[] getLoadedChunks() { return null; } + @Override + public int getMonsterSpawnLimit() { return 0; } + @Override + public String getName() { return null; } + @Override + public Collection getNearbyEntities(Location arg0, double arg1, double arg2, double arg3) { return null; } + @Override + public boolean getPVP() { return false; } + @Override + public List getPlayers() { return null; } + @Override + public List getPopulators() { return null; } + @Override + public int getSeaLevel() { return 0; } + @Override + public long getSeed() { return 0; } + @Override + public Location getSpawnLocation() { return null; } + @Override + public double getTemperature(int arg0, int arg1) { return 0; } + @Override + public int getThunderDuration() { return 0; } + @Override + public long getTicksPerAnimalSpawns() { return 0; } + @Override + public long getTicksPerMonsterSpawns() { return 0; } + @Override + public long getTime() { return 0; } + @Override + public UUID getUID() { return null; } + @Override + public int getWaterAnimalSpawnLimit() { return 0; } + @Override + public int getWeatherDuration() { return 0; } + @Override + public WorldBorder getWorldBorder() { return null; } + @Override + public File getWorldFolder() { return null; } + @Override + public WorldType getWorldType() { return null; } + @Override + public boolean hasStorm() { return false; } + @Override + public boolean isAutoSave() { return false; } + @Override + public boolean isChunkInUse(int arg0, int arg1) { return false; } + @Override + public boolean isChunkLoaded(Chunk arg0) { return false; } + @Override + public boolean isChunkLoaded(int arg0, int arg1) { return false; } + @Override + public boolean isGameRule(String arg0) { return false; } + @Override + public boolean isThundering() { return false; } + @Override + public void loadChunk(Chunk arg0) {} + @Override + public void loadChunk(int arg0, int arg1) {} + @Override + public boolean loadChunk(int arg0, int arg1, boolean arg2) { return false; } + @Override + public void playEffect(Location arg0, Effect arg1, int arg2) {} + @Override + public void playEffect(Location arg0, Effect arg1, T arg2) {} + @Override + public void playEffect(Location arg0, Effect arg1, int arg2, int arg3) {} + @Override + public void playEffect(Location arg0, Effect arg1, T arg2, int arg3) {} + @Override + public void playSound(Location arg0, Sound arg1, float arg2, float arg3) {} + @Override + public boolean refreshChunk(int arg0, int arg1) { return false; } + @Override + public boolean regenerateChunk(int arg0, int arg1) { return false; } + @Override + public void save() {} + @Override + public void setAmbientSpawnLimit(int arg0) {} + @Override + public void setAnimalSpawnLimit(int arg0) {} + @Override + public void setAutoSave(boolean arg0) {} + @Override + public void setBiome(int arg0, int arg1, Biome arg2) {} + @Override + public void setDifficulty(Difficulty arg0) {} + @Override + public void setFullTime(long arg0) {} + @Override + public boolean setGameRuleValue(String arg0, String arg1) { return false; } + @Override + public void setKeepSpawnInMemory(boolean arg0) {} + @Override + public void setMonsterSpawnLimit(int arg0) {} + @Override + public void setPVP(boolean arg0) {} + @Override + public void setSpawnFlags(boolean arg0, boolean arg1) {} + @Override + public boolean setSpawnLocation(int arg0, int arg1, int arg2) { return false; } + @Override + public void setStorm(boolean arg0) {} + @Override + public void setThunderDuration(int arg0) {} + @Override + public void setThundering(boolean arg0) {} + @Override + public void setTicksPerAnimalSpawns(int arg0) {} + @Override + public void setTicksPerMonsterSpawns(int arg0) {} + @Override + public void setTime(long arg0) {} + @Override + public void setWaterAnimalSpawnLimit(int arg0) {} + @Override + public void setWeatherDuration(int arg0) {} + @Override + public T spawn(Location arg0, Class arg1) throws IllegalArgumentException { return null; } + @Override + public Arrow spawnArrow(Location arg0, Vector arg1, float arg2, float arg3) { return null; } + @Override + public LivingEntity spawnCreature(Location arg0, EntityType arg1) { return null; } + @Override + public LivingEntity spawnCreature(Location arg0, CreatureType arg1) { return null; } + @Override + public Entity spawnEntity(Location arg0, EntityType arg1) { return null; } + @Override + public FallingBlock spawnFallingBlock(Location arg0, Material arg1, byte arg2) throws IllegalArgumentException { return null; } + @Override + public FallingBlock spawnFallingBlock(Location arg0, int arg1, byte arg2) throws IllegalArgumentException { return null; } + @Override + public LightningStrike strikeLightning(Location arg0) { return null; } + @Override + public LightningStrike strikeLightningEffect(Location arg0) { return null; } + @Override + public boolean unloadChunk(Chunk arg0) { return false; } + @Override + public boolean unloadChunk(int arg0, int arg1) { return false; } + @Override + public boolean unloadChunk(int arg0, int arg1, boolean arg2) { return false; } + @Override + public boolean unloadChunk(int arg0, int arg1, boolean arg2, boolean arg3) { return false; } + @Override + public boolean unloadChunkRequest(int arg0, int arg1) { return false; } + @Override + public boolean unloadChunkRequest(int arg0, int arg1, boolean arg2) { return false; } + @Override + public Spigot spigot() { return null; } + }; + } +} -- 1.9.5.msysgit.1