diff --git a/src/main/java/com/sk89q/worldedit/EditSession.java b/src/main/java/com/sk89q/worldedit/EditSession.java index db294e957..8a22c90af 100644 --- a/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/src/main/java/com/sk89q/worldedit/EditSession.java @@ -46,6 +46,7 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionOperationException; +import com.sk89q.worldedit.shape.ArbitraryBiomeShape; import com.sk89q.worldedit.shape.ArbitraryShape; import com.sk89q.worldedit.shape.RegionShape; import com.sk89q.worldedit.shape.WorldEditExpressionEnvironment; @@ -3019,4 +3020,38 @@ private void recurseHollow(Region region, BlockVector origin, Set o } // while } + public int makeBiomeShape(final Region region, final Vector zero, final Vector unit, final BiomeType biomeType, final String expressionString, final boolean hollow) throws ExpressionException, MaxChangedBlocksException { + final Vector2D zero2D = zero.toVector2D(); + final Vector2D unit2D = unit.toVector2D(); + + final Expression expression = Expression.compile(expressionString, "x", "z"); + expression.optimize(); + + final EditSession editSession = this; + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(editSession, unit, zero); + expression.setEnvironment(environment); + + final ArbitraryBiomeShape shape = new ArbitraryBiomeShape(region) { + @Override + protected BiomeType getBiome(int x, int z, BiomeType defaultBiomeType) { + final Vector2D current = new Vector2D(x, z); + environment.setCurrentBlock(current.toVector(0)); + final Vector2D scaled = current.subtract(zero2D).divide(unit2D); + + try { + if (expression.evaluate(scaled.getX(), scaled.getZ()) <= 0) { + return null; + } + + // TODO: Allow biome setting via a script variable (needs BiomeType<->int mapping) + return defaultBiomeType; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + }; + + return shape.generate(this, biomeType, hollow); + } } diff --git a/src/main/java/com/sk89q/worldedit/commands/GenerationCommands.java b/src/main/java/com/sk89q/worldedit/commands/GenerationCommands.java index 6c9ec388c..d904e5f1b 100644 --- a/src/main/java/com/sk89q/worldedit/commands/GenerationCommands.java +++ b/src/main/java/com/sk89q/worldedit/commands/GenerationCommands.java @@ -27,10 +27,12 @@ import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.Logging; +import com.sk89q.worldedit.BiomeType; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.expression.ExpressionException; @@ -415,4 +417,71 @@ public void generate(CommandContext args, LocalSession session, LocalPlayer play player.printError(e.getMessage()); } } + + @Command( + aliases = { "/generatebiome", "/genbiome", "/gb" }, + usage = " ", + desc = "Sets biome according to a formula.", + help = + "Generates a shape according to a formula that is expected to\n" + + "return positive numbers (true) if the point is inside the shape\n" + + "Optionally set type/data to the desired block.\n" + + "Flags:\n" + + " -h to generate a hollow shape\n" + + " -r to use raw minecraft coordinates\n" + + " -o is like -r, except offset from placement.\n" + + " -c is like -r, except offset selection center.\n" + + "If neither -r nor -o is given, the selection is mapped to -1..1\n" + + "See also tinyurl.com/wesyntax.", + flags = "hroc", + min = 2, + max = -1 + ) + @CommandPermissions({"worldedit.generation.shape", "worldedit.biome.set"}) + @Logging(ALL) + public void generateBiome(CommandContext args, LocalSession session, LocalPlayer player, + EditSession editSession) throws WorldEditException { + + final BiomeType target = we.getServer().getBiomes().get(args.getString(0)); + final Region region = session.getSelection(player.getWorld()); + + final boolean hollow = args.hasFlag('h'); + + final String expression = args.getJoinedStrings(1); + + final Vector zero; + Vector unit; + + if (args.hasFlag('r')) { + zero = Vector.ZERO; + unit = Vector.ONE; + } else if (args.hasFlag('o')) { + zero = session.getPlacementPosition(player); + unit = Vector.ONE; + } else if (args.hasFlag('c')) { + final Vector min = region.getMinimumPoint(); + final Vector max = region.getMaximumPoint(); + + zero = max.add(min).multiply(0.5); + unit = Vector.ONE; + } else { + final Vector min = region.getMinimumPoint(); + final Vector max = region.getMaximumPoint(); + + zero = max.add(min).multiply(0.5); + unit = max.subtract(zero); + + if (unit.getX() == 0) unit = unit.setX(1.0); + if (unit.getY() == 0) unit = unit.setY(1.0); + if (unit.getZ() == 0) unit = unit.setZ(1.0); + } + + try { + final int affected = editSession.makeBiomeShape(region, zero, unit, target, expression, hollow); + player.findFreePosition(); + player.print("Biome changed to " + target.getName() + ". " + affected + " columns affected."); + } catch (ExpressionException e) { + player.printError(e.getMessage()); + } + } } diff --git a/src/main/java/com/sk89q/worldedit/shape/ArbitraryBiomeShape.java b/src/main/java/com/sk89q/worldedit/shape/ArbitraryBiomeShape.java new file mode 100644 index 000000000..9b2c3c273 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/shape/ArbitraryBiomeShape.java @@ -0,0 +1,189 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q and contributors + * + * 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.sk89q.worldedit.shape; + +import com.sk89q.worldedit.BiomeType; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.FlatRegion; +import com.sk89q.worldedit.regions.Region; + +/** + * Generates solid and hollow shapes according to materials returned by the + * {@link #getBiome} method. + * + * @author TomyLobo + */ +public abstract class ArbitraryBiomeShape { + private final FlatRegion extent; + private int cacheOffsetX; + private int cacheOffsetZ; + @SuppressWarnings("FieldCanBeLocal") + private int cacheSizeX; + private int cacheSizeZ; + + public ArbitraryBiomeShape(Region extent) { + if (extent instanceof FlatRegion) { + this.extent = (FlatRegion) extent; + } + else { + // TODO: polygonize + this.extent = new CuboidRegion(extent.getWorld(), extent.getMinimumPoint(), extent.getMaximumPoint()); + } + + Vector2D min = extent.getMinimumPoint().toVector2D(); + Vector2D max = extent.getMaximumPoint().toVector2D(); + + cacheOffsetX = min.getBlockX() - 1; + cacheOffsetZ = min.getBlockZ() - 1; + + cacheSizeX = (int) (max.getX() - cacheOffsetX + 2); + cacheSizeZ = (int) (max.getZ() - cacheOffsetZ + 2); + + cache = new BiomeType[cacheSizeX * cacheSizeZ]; + } + + protected Iterable getExtent() { + return extent.asFlatRegion(); + } + + + /** + * Cache entries: + * null = unknown + * OUTSIDE = outside + * else = inside + */ + private final BiomeType[] cache; + + /** + * Override this function to specify the shape to generate. + * + * @param x X coordinate to be queried + * @param z Z coordinate to be queried + * @param defaultBiomeType The default biome for the current column. + * @return material to place or null to not place anything. + */ + protected abstract BiomeType getBiome(int x, int z, BiomeType defaultBiomeType); + + private BiomeType getBiomeCached(int x, int z, BiomeType biomeType) { + final int index = (z - cacheOffsetZ) + (x - cacheOffsetX) * cacheSizeZ; + + final BiomeType cacheEntry = cache[index]; + if (cacheEntry == null) {// unknown, fetch material + final BiomeType material = getBiome(x, z, biomeType); + if (material == null) { + // outside + cache[index] = OUTSIDE; + return null; + } + + cache[index] = material; + return material; + } + + if (cacheEntry == OUTSIDE) { + // outside + return null; + } + + return cacheEntry; + } + + private boolean isInsideCached(int x, int z, BiomeType biomeType) { + final int index = (z - cacheOffsetZ) + (x - cacheOffsetX) * cacheSizeZ; + + final BiomeType cacheEntry = cache[index]; + if (cacheEntry == null) { + // unknown block, meaning they must be outside the extent at this stage, but might still be inside the shape + return getBiomeCached(x, z, biomeType) != null; + } + + return cacheEntry != OUTSIDE; + } + + /** + * Generates the shape. + * + * @param editSession The EditSession to use. + * @param biomeType The default biome type. + * @param hollow Specifies whether to generate a hollow shape. + * @return number of affected blocks. + */ + public int generate(EditSession editSession, BiomeType biomeType, boolean hollow) { + int affected = 0; + + for (Vector2D position : getExtent()) { + int x = position.getBlockX(); + int z = position.getBlockZ(); + + if (!hollow) { + final BiomeType material = getBiome(x, z, biomeType); + if (material != OUTSIDE) { + editSession.getWorld().setBiome(position, material); + ++affected; + } + + continue; + } + + final BiomeType material = getBiomeCached(x, z, biomeType); + if (material == null) { + continue; + } + + boolean draw = false; + do { + if (!isInsideCached(x + 1, z, biomeType)) { + draw = true; + break; + } + if (!isInsideCached(x - 1, z, biomeType)) { + draw = true; + break; + } + if (!isInsideCached(x, z + 1, biomeType)) { + draw = true; + break; + } + if (!isInsideCached(x, z - 1, biomeType)) { + draw = true; + break; + } + } while (false); + + if (!draw) { + continue; + } + + editSession.getWorld().setBiome(position, material); + ++affected; + } + + return affected; + } + + private static final BiomeType OUTSIDE = new BiomeType() { + public String getName() { + throw new UnsupportedOperationException(); + } + }; +}