From c16a924bb974286f3a3a9b1c00beff320cf7db23 Mon Sep 17 00:00:00 2001 From: Christian Knaapen Date: Mon, 23 Dec 2019 12:39:08 +0100 Subject: [PATCH] Added circle, cylinder and sphere buildmodes. Refactored buildmodes to use hierarchy classes, much less code duplication. Added support for multiple options per build mode. Added alternative placement key (ctrl) that toggles between first two actions of first option. Fixed crash when placing ladders. --- build.gradle | 2 +- .../EffortlessBuilding.java | 2 +- .../buildmode/BaseBuildMode.java | 40 ++++ .../buildmode/BuildModes.java | 48 +++- .../effortlessbuilding/buildmode/Cube.java | 208 ---------------- .../buildmode/DiagonalWall.java | 138 ----------- .../effortlessbuilding/buildmode/Floor.java | 183 -------------- .../buildmode/ModeOptions.java | 52 +++- .../buildmode/SlopeFloor.java | 178 -------------- ...nalLine.java => ThreeClicksBuildMode.java} | 143 +++++------ .../buildmode/TwoClicksBuildMode.java | 86 +++++++ .../effortlessbuilding/buildmode/Wall.java | 226 ------------------ .../buildmode/buildmodes/Circle.java | 88 +++++++ .../buildmode/buildmodes/Cube.java | 104 ++++++++ .../buildmode/buildmodes/Cylinder.java | 50 ++++ .../buildmode/buildmodes/DiagonalLine.java | 54 +++++ .../buildmode/buildmodes/DiagonalWall.java | 52 ++++ .../buildmode/buildmodes/Floor.java | 93 +++++++ .../buildmode/{ => buildmodes}/Line.java | 124 ++-------- .../buildmode/{ => buildmodes}/Normal.java | 3 +- .../{ => buildmodes}/NormalPlus.java | 3 +- .../buildmode/buildmodes/SlopeFloor.java | 95 ++++++++ .../buildmode/buildmodes/Sphere.java | 110 +++++++++ .../buildmode/buildmodes/Wall.java | 134 +++++++++++ .../buildmodifier/UndoRedo.java | 1 + .../gui/buildmode/RadialMenu.java | 88 ++++--- .../effortlessbuilding/proxy/ClientProxy.java | 25 +- .../render/BlockPreviewRenderer.java | 28 ++- .../render/RenderHandler.java | 2 + .../assets/effortlessbuilding/lang/en_us.lang | 9 +- .../textures/icons/circle.png | Bin 0 -> 273 bytes .../textures/icons/circle_start_center.png | Bin 0 -> 232 bytes .../textures/icons/circle_start_corner.png | Bin 0 -> 222 bytes .../textures/icons/cylinder.png | Bin 0 -> 292 bytes .../textures/icons/sphere.png | Bin 0 -> 361 bytes 35 files changed, 1173 insertions(+), 1196 deletions(-) create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/BaseBuildMode.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/Cube.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalWall.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/Floor.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/SlopeFloor.java rename src/main/java/nl/requios/effortlessbuilding/buildmode/{DiagonalLine.java => ThreeClicksBuildMode.java} (61%) create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java delete mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/Wall.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Circle.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cube.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cylinder.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalLine.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalWall.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Floor.java rename src/main/java/nl/requios/effortlessbuilding/buildmode/{ => buildmodes}/Line.java (52%) rename src/main/java/nl/requios/effortlessbuilding/buildmode/{ => buildmodes}/Normal.java (89%) rename src/main/java/nl/requios/effortlessbuilding/buildmode/{ => buildmodes}/NormalPlus.java (89%) create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/SlopeFloor.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Sphere.java create mode 100644 src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Wall.java create mode 100644 src/main/resources/assets/effortlessbuilding/textures/icons/circle.png create mode 100644 src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_center.png create mode 100644 src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_corner.png create mode 100644 src/main/resources/assets/effortlessbuilding/textures/icons/cylinder.png create mode 100644 src/main/resources/assets/effortlessbuilding/textures/icons/sphere.png diff --git a/build.gradle b/build.gradle index 62b15e0..7b92238 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ apply plugin: 'net.minecraftforge.gradle.forge' //Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. -version = "1.12.2-2.15" +version = "1.12.2-2.16" group = "nl.requios.effortlessbuilding" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "effortlessbuilding" diff --git a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java index f2dc819..11a7182 100644 --- a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java +++ b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java @@ -39,7 +39,7 @@ public class EffortlessBuilding { public static final String MODID = "effortlessbuilding"; public static final String NAME = "Effortless Building"; - public static final String VERSION = "1.12.2-2.15"; + public static final String VERSION = "1.12.2-2.16"; @Mod.Instance(EffortlessBuilding.MODID) public static EffortlessBuilding instance; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/BaseBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/BaseBuildMode.java new file mode 100644 index 0000000..ac4da0d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/BaseBuildMode.java @@ -0,0 +1,40 @@ +package nl.requios.effortlessbuilding.buildmode; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.UUID; + +public abstract class BaseBuildMode implements IBuildMode { + //In singleplayer client and server variables are shared + //Split everything that needs separate values and may not be called twice in one click + protected Dictionary rightClickClientTable = new Hashtable<>(); + protected Dictionary rightClickServerTable = new Hashtable<>(); + protected Dictionary firstPosTable = new Hashtable<>(); + protected Dictionary sideHitTable = new Hashtable<>(); + protected Dictionary hitVecTable = new Hashtable<>(); + + @Override + public void initialize(EntityPlayer player) { + rightClickClientTable.put(player.getUniqueID(), 0); + rightClickServerTable.put(player.getUniqueID(), 0); + firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); + sideHitTable.put(player.getUniqueID(), EnumFacing.UP); + hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); + } + + @Override + public EnumFacing getSideHit(EntityPlayer player) { + return sideHitTable.get(player.getUniqueID()); + } + + @Override + public Vec3d getHitVec(EntityPlayer player) { + return hitVecTable.get(player.getUniqueID()); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java index 2c557de..ba85003 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java @@ -4,11 +4,14 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmode.ModeOptions.ActionEnum; +import nl.requios.effortlessbuilding.buildmode.buildmodes.*; +import nl.requios.effortlessbuilding.buildmode.buildmodes.Circle; +import nl.requios.effortlessbuilding.buildmode.buildmodes.Cylinder; +import nl.requios.effortlessbuilding.buildmode.buildmodes.Sphere; import nl.requios.effortlessbuilding.buildmodifier.*; -import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.helper.ReachHelper; import nl.requios.effortlessbuilding.helper.SurvivalHelper; import nl.requios.effortlessbuilding.network.BlockBrokenMessage; @@ -19,6 +22,8 @@ import java.util.Dictionary; import java.util.Hashtable; import java.util.List; +import static nl.requios.effortlessbuilding.buildmode.ModeOptions.*; + public class BuildModes { //Static variables are shared between client and server in singleplayer @@ -27,21 +32,24 @@ public class BuildModes { public static Dictionary currentlyBreakingServer = new Hashtable<>(); public enum BuildModeEnum { - NORMAL("effortlessbuilding.mode.normal", new Normal(), new ActionEnum[]{}), - NORMAL_PLUS("effortlessbuilding.mode.normal_plus", new NormalPlus(), new ActionEnum[]{ActionEnum.NORMAL_SPEED, ActionEnum.FAST_SPEED}), - LINE("effortlessbuilding.mode.line", new Line(), new ActionEnum[]{/*ActionEnum.THICKNESS_1, ActionEnum.THICKNESS_3, ActionEnum.THICKNESS_5*/}), - WALL("effortlessbuilding.mode.wall", new Wall(), new ActionEnum[]{ActionEnum.FULL, ActionEnum.HOLLOW}), - FLOOR("effortlessbuilding.mode.floor", new Floor(), new ActionEnum[]{ActionEnum.FULL, ActionEnum.HOLLOW}), - DIAGONAL_LINE("effortlessbuilding.mode.diagonal_line", new DiagonalLine(), new ActionEnum[]{/*ActionEnum.THICKNESS_1, ActionEnum.THICKNESS_3, ActionEnum.THICKNESS_5*/}), - DIAGONAL_WALL("effortlessbuilding.mode.diagonal_wall", new DiagonalWall(), new ActionEnum[]{/*ActionEnum.FULL, ActionEnum.HOLLOW*/}), - SLOPE_FLOOR("effortlessbuilding.mode.slope_floor", new SlopeFloor(), new ActionEnum[]{ActionEnum.SHORT_EDGE, ActionEnum.LONG_EDGE}), - CUBE("effortlessbuilding.mode.cube", new Cube(), new ActionEnum[]{ActionEnum.CUBE_FULL, ActionEnum.CUBE_HOLLOW, ActionEnum.CUBE_SKELETON}); + NORMAL("effortlessbuilding.mode.normal", new Normal()), + NORMAL_PLUS("effortlessbuilding.mode.normal_plus", new NormalPlus(), OptionEnum.BUILD_SPEED), + LINE("effortlessbuilding.mode.line", new Line() /*, OptionEnum.THICKNESS*/), + WALL("effortlessbuilding.mode.wall", new Wall(), OptionEnum.FILL), + FLOOR("effortlessbuilding.mode.floor", new Floor(), OptionEnum.FILL), + DIAGONAL_LINE("effortlessbuilding.mode.diagonal_line", new DiagonalLine() /*, OptionEnum.THICKNESS*/), + DIAGONAL_WALL("effortlessbuilding.mode.diagonal_wall", new DiagonalWall() /*, OptionEnum.FILL*/), + SLOPE_FLOOR("effortlessbuilding.mode.slope_floor", new SlopeFloor(), OptionEnum.RAISED_EDGE), + CIRCLE("effortlessbuilding.mode.circle", new Circle(), OptionEnum.CIRCLE_START, OptionEnum.FILL), + CYLINDER("effortlessbuilding.mode.cylinder", new Cylinder(), OptionEnum.CIRCLE_START, OptionEnum.FILL), + SPHERE("effortlessbuilding.mode.sphere", new Sphere(), OptionEnum.CIRCLE_START, OptionEnum.FILL), + CUBE("effortlessbuilding.mode.cube", new Cube(), OptionEnum.CUBE_FILL); public String name; public IBuildMode instance; - public ActionEnum[] options; + public OptionEnum[] options; - BuildModeEnum(String name, IBuildMode instance, ActionEnum[] options) { + BuildModeEnum(String name, IBuildMode instance, OptionEnum... options) { this.name = name; this.instance = instance; this.options = options; @@ -220,4 +228,18 @@ public class BuildModes { return new Vec3d(x, y, z); } + public static boolean isCriteriaValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace, Vec3d lineBound, Vec3d planeBound, double distToPlayerSq) { + boolean intersects = false; + if (!skipRaytrace) { + //collision within a 1 block radius to selected is fine + RayTraceResult rayTraceResult = player.world.rayTraceBlocks(start, lineBound, false, true, false); + intersects = rayTraceResult != null && rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK && + planeBound.subtract(rayTraceResult.hitVec).lengthSquared() > 4; + } + + return planeBound.subtract(start).dotProduct(look) > 0 && + distToPlayerSq > 2 && distToPlayerSq < reach * reach && + !intersects; + } + } diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/Cube.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/Cube.java deleted file mode 100644 index 12258bd..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/Cube.java +++ /dev/null @@ -1,208 +0,0 @@ -package nl.requios.effortlessbuilding.buildmode; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import nl.requios.effortlessbuilding.helper.ReachHelper; - -import java.util.*; - -public class Cube implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary secondPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); - - @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else if (rightClickNr == 2) { - //Second click, find other floor point - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = Floor.findFloor(player, firstPos, skipRaytrace); - - if (secondPos == null) { - rightClickTable.put(player.getUniqueID(), 1); - return list; - } - - secondPosTable.put(player.getUniqueID(), secondPos); - - } else { - //Third click, place cube with height - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else if (rightClickNr == 1) { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - BlockPos secondPos = Floor.findFloor(player, firstPos, skipRaytrace); - if (secondPos == null) return list; - - //Add whole floor - //Limit amount of blocks you can place per row - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - int x1 = firstPos.getX(), x2 = secondPos.getX(); - int y = firstPos.getY(); - int z1 = firstPos.getZ(), z2 = secondPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - - if (ModeOptions.getCubeFill() == ModeOptions.ActionEnum.CUBE_SKELETON) { - //Hollow floor - Line.addXLineBlocks(list, x1, x2, y, z1); - Line.addXLineBlocks(list, x1, x2, y, z2); - Line.addZLineBlocks(list, z1, z2, x1, y); - Line.addZLineBlocks(list, z1, z2, x2, y); - } else { - //Filled floor - for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { - - for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { - - list.add(new BlockPos(l, y, n)); - } - } - } - - } else { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = secondPosTable.get(player.getUniqueID()); - - BlockPos thirdPos = DiagonalLine.findHeight(player, secondPos, skipRaytrace); - if (thirdPos == null) return list; - - //Add whole cube - //Limit amount of blocks you can place per row - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - int x1 = firstPos.getX(), x2 = thirdPos.getX(); - int y1 = firstPos.getY(), y2 = thirdPos.getY(); - int z1 = firstPos.getZ(), z2 = thirdPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; - if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - - switch (ModeOptions.getCubeFill()) { - case CUBE_FULL: - addCubeBlocks(list, x1, x2, y1, y2, z1, z2); - break; - case CUBE_HOLLOW: - addHollowCubeBlocks(list, x1, x2, y1, y2, z1, z2); - break; - case CUBE_SKELETON: - addSkeletonCubeBlocks(list, x1, x2, y1, y2, z1, z2); - break; - } - - } - - return list; - } - - public static void addCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { - for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { - - for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { - - for (int m = y1; y1 < y2 ? m <= y2 : m >= y2; m += y1 < y2 ? 1 : -1) { - list.add(new BlockPos(l, m, n)); - } - } - } - } - - public static void addHollowCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { - Wall.addXWallBlocks(list, x1, y1, y2, z1, z2); - Wall.addXWallBlocks(list, x2, y1, y2, z1, z2); - - Wall.addZWallBlocks(list, x1, x2, y1, y2, z1); - Wall.addZWallBlocks(list, x1, x2, y1, y2, z2); - - Floor.addFloorBlocks(list, x1, x2, y1, z1, z2); - Floor.addFloorBlocks(list, x1, x2, y2, z1, z2); - } - - public static void addSkeletonCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { - Line.addXLineBlocks(list, x1, x2, y1, z1); - Line.addXLineBlocks(list, x1, x2, y1, z2); - Line.addXLineBlocks(list, x1, x2, y2, z1); - Line.addXLineBlocks(list, x1, x2, y2, z2); - - Line.addYLineBlocks(list, y1, y2, x1, z1); - Line.addYLineBlocks(list, y1, y2, x1, z2); - Line.addYLineBlocks(list, y1, y2, x2, z1); - Line.addYLineBlocks(list, y1, y2, x2, z2); - - Line.addZLineBlocks(list, z1, z2, x1, y1); - Line.addZLineBlocks(list, z1, z2, x1, y2); - Line.addZLineBlocks(list, z1, z2, x2, y1); - Line.addZLineBlocks(list, z1, z2, x2, y2); - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } - -} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalWall.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalWall.java deleted file mode 100644 index 7ba351c..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalWall.java +++ /dev/null @@ -1,138 +0,0 @@ -package nl.requios.effortlessbuilding.buildmode; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import nl.requios.effortlessbuilding.helper.ReachHelper; - -import java.util.*; - -public class DiagonalWall implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary secondPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); - - @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else if (rightClickNr == 2) { - //Second click, find other floor point - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = Floor.findFloor(player, firstPos, true); - - if (secondPos == null) { - rightClickTable.put(player.getUniqueID(), 1); - return list; - } - - secondPosTable.put(player.getUniqueID(), secondPos); - - } else { - //Third click, place diagonal wall with height - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else if (rightClickNr == 1) { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - BlockPos secondPos = Floor.findFloor(player, firstPos, true); - if (secondPos == null) return list; - - //Add diagonal line - list.addAll(DiagonalLine.getDiagonalLineBlocks(player, firstPos, secondPos, 1)); - - } else { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = secondPosTable.get(player.getUniqueID()); - - BlockPos thirdPos = DiagonalLine.findHeight(player, secondPos, skipRaytrace); - if (thirdPos == null) return list; - - //Add diagonal wall - list.addAll(getDiagonalWallBlocks(player, firstPos, secondPos, thirdPos)); - } - - return list; - } - - //Add diagonal wall from first to second - public static List getDiagonalWallBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, BlockPos thirdPos) { - List list = new ArrayList<>(); - - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - //Get diagonal line blocks - List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, firstPos, secondPos, 1); - - //Limit amount of blocks we can place - int lowest = Math.min(firstPos.getY(), thirdPos.getY()); - int highest = Math.max(firstPos.getY(), thirdPos.getY()); - - if (highest - lowest >= axisLimit) highest = lowest + axisLimit - 1; - - //Copy diagonal line on y axis - for (int y = lowest; y <= highest; y++) { - for (BlockPos blockPos : diagonalLineBlocks) { - list.add(new BlockPos(blockPos.getX(), y, blockPos.getZ())); - } - } - - return list; - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } -} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/Floor.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/Floor.java deleted file mode 100644 index 92aabbb..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/Floor.java +++ /dev/null @@ -1,183 +0,0 @@ -package nl.requios.effortlessbuilding.buildmode; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; -import nl.requios.effortlessbuilding.helper.ReachHelper; - -import java.util.*; - -public class Floor implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); - - static class Criteria { - Vec3d planeBound; - double distToPlayerSq; - - Criteria(Vec3d planeBound, Vec3d start) { - this.planeBound = planeBound; - this.distToPlayerSq = this.planeBound.subtract(start).lengthSquared(); - } - - //check if its not behind the player and its not too close and not too far - //also check if raytrace from player to block does not intersect blocks - public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { - - boolean intersects = false; - if (!skipRaytrace) { - //collision within a 1 block radius to selected is fine - RayTraceResult rayTraceResult = player.world.rayTraceBlocks(start, planeBound, false, true, false); - intersects = rayTraceResult != null && rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK && - planeBound.subtract(rayTraceResult.hitVec).lengthSquared() > 4; - } - - return planeBound.subtract(start).dotProduct(look) > 0 && - distToPlayerSq > 2 && distToPlayerSq < reach * reach && - !intersects; - } - } - - @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else { - //Second click, place wall - - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else { - BlockPos secondPos = findFloor(player, firstPos, skipRaytrace); - if (secondPos == null) return list; - - //Add whole floor - list.addAll(getFloorBlocks(player, firstPos, secondPos)); - } - - return list; - } - - public static BlockPos findFloor(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { - Vec3d look = player.getLookVec(); - Vec3d start = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ); - - List criteriaList = new ArrayList<>(3); - - //Y - Vec3d yBound = BuildModes.findYBound(firstPos.getY(), start, look); - criteriaList.add(new Criteria(yBound, start)); - - //Remove invalid criteria - int reach = ReachHelper.getPlacementReach(player) * 4; //4 times as much as normal placement reach - criteriaList.removeIf(criteria -> !criteria.isValid(start, look, reach, player, skipRaytrace)); - - //If none are valid, return empty list of blocks - if (criteriaList.isEmpty()) return null; - - //Then only 1 can be valid, return that one - Criteria selected = criteriaList.get(0); - - return new BlockPos(selected.planeBound); - } - - public static List getFloorBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos) { - List list = new ArrayList<>(); - - //Limit amount of blocks you can place per row - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - int x1 = firstPos.getX(), x2 = secondPos.getX(); - int y = firstPos.getY(); - int z1 = firstPos.getZ(), z2 = secondPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - - if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) - addFloorBlocks(list, x1, x2, y, z1, z2); - else - addHollowFloorBlocks(list, x1, x2, y, z1, z2); - - return list; - } - - public static void addFloorBlocks(List list, int x1, int x2, int y, int z1, int z2) { - - for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { - - for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { - - list.add(new BlockPos(l, y, n)); - } - } - } - - public static void addHollowFloorBlocks(List list, int x1, int x2, int y, int z1, int z2) { - Line.addXLineBlocks(list, x1, x2, y, z1); - Line.addXLineBlocks(list, x1, x2, y, z2); - Line.addZLineBlocks(list, z1, z2, x1, y); - Line.addZLineBlocks(list, z1, z2, x2, y); - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java index e82ea70..81dec27 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java @@ -30,7 +30,10 @@ public class ModeOptions { THICKNESS_1("effortlessbuilding.action.thickness_1"), THICKNESS_3("effortlessbuilding.action.thickness_3"), - THICKNESS_5("effortlessbuilding.action.thickness_5"); + THICKNESS_5("effortlessbuilding.action.thickness_5"), + + CIRCLE_START_CORNER("effortlessbuilding.action.start_corner"), + CIRCLE_START_CENTER("effortlessbuilding.action.start_center"); public String name; @@ -39,11 +42,48 @@ public class ModeOptions { } } + public enum OptionEnum { + BUILD_SPEED("effortlessbuilding.action.build_speed", ActionEnum.NORMAL_SPEED, ActionEnum.FAST_SPEED), + FILL("effortlessbuilding.action.filling", ActionEnum.FULL, ActionEnum.HOLLOW), + CUBE_FILL("effortlessbuilding.action.filling", ActionEnum.CUBE_FULL, ActionEnum.CUBE_HOLLOW, ActionEnum.CUBE_SKELETON), + RAISED_EDGE("effortlessbuilding.action.raised_edge", ActionEnum.SHORT_EDGE, ActionEnum.LONG_EDGE), + LINE_THICKNESS("effortlessbuilding.action.thickness", ActionEnum.THICKNESS_1, ActionEnum.THICKNESS_3, ActionEnum.THICKNESS_5), + CIRCLE_START("effortlessbuilding.action.circle_start", ActionEnum.CIRCLE_START_CORNER, ActionEnum.CIRCLE_START_CENTER); + + public String name; + public ActionEnum[] actions; + + OptionEnum(String name, ActionEnum... actions){ + this.name = name; + this.actions = actions; + } + } + private static ActionEnum buildSpeed = ActionEnum.NORMAL_SPEED; private static ActionEnum fill = ActionEnum.FULL; private static ActionEnum cubeFill = ActionEnum.CUBE_FULL; private static ActionEnum raisedEdge = ActionEnum.SHORT_EDGE; private static ActionEnum lineThickness = ActionEnum.THICKNESS_1; + private static ActionEnum circleStart = ActionEnum.CIRCLE_START_CORNER; + + public static ActionEnum getOptionSetting(OptionEnum option) { + switch (option) { + case BUILD_SPEED: + return getBuildSpeed(); + case FILL: + return getFill(); + case CUBE_FILL: + return getCubeFill(); + case RAISED_EDGE: + return getRaisedEdge(); + case LINE_THICKNESS: + return getLineThickness(); + case CIRCLE_START: + return getCircleStart(); + default: + return null; + } + } public static ActionEnum getBuildSpeed() { return buildSpeed; @@ -65,6 +105,10 @@ public class ModeOptions { return lineThickness; } + public static ActionEnum getCircleStart() { + return circleStart; + } + //Called on both client and server public static void performAction(EntityPlayer player, ActionEnum action) { if (action == null) return; @@ -123,6 +167,12 @@ public class ModeOptions { case THICKNESS_5: lineThickness = ActionEnum.THICKNESS_5; break; + case CIRCLE_START_CENTER: + circleStart = ActionEnum.CIRCLE_START_CENTER; + break; + case CIRCLE_START_CORNER: + circleStart = ActionEnum.CIRCLE_START_CORNER; + break; } if (player.world.isRemote && action != ActionEnum.REPLACE && action != ActionEnum.OPEN_MODIFIER_SETTINGS) { diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/SlopeFloor.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/SlopeFloor.java deleted file mode 100644 index 35fa01a..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/SlopeFloor.java +++ /dev/null @@ -1,178 +0,0 @@ -package nl.requios.effortlessbuilding.buildmode; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import nl.requios.effortlessbuilding.helper.ReachHelper; - -import java.util.*; - -public class SlopeFloor implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary secondPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); - - @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else if (rightClickNr == 2) { - //Second click, find other floor point - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = Floor.findFloor(player, firstPos, true); - - if (secondPos == null) { - rightClickTable.put(player.getUniqueID(), 1); - return list; - } - - secondPosTable.put(player.getUniqueID(), secondPos); - - } else { - //Third click, place slope floor with height - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else if (rightClickNr == 1) { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - BlockPos secondPos = Floor.findFloor(player, firstPos, true); - if (secondPos == null) return list; - - //Add whole floor - list.addAll(Floor.getFloorBlocks(player, firstPos, secondPos)); - - } else { - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = secondPosTable.get(player.getUniqueID()); - - BlockPos thirdPos = DiagonalLine.findHeight(player, secondPos, skipRaytrace); - if (thirdPos == null) return list; - - //Add slope floor blocks - list.addAll(getSlopeFloorBlocks(player, firstPos, secondPos, thirdPos)); - } - - return list; - } - - //Add slope floor from first to second - public static List getSlopeFloorBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, BlockPos thirdPos) { - List list = new ArrayList<>(); - - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - //Determine whether to use x or z axis to slope up - boolean onXAxis = true; - - int xLength = Math.abs(secondPos.getX() - firstPos.getX()); - int zLength = Math.abs(secondPos.getZ() - firstPos.getZ()); - - if (ModeOptions.getRaisedEdge() == ModeOptions.ActionEnum.SHORT_EDGE) { - //Slope along short edge - if (zLength > xLength) onXAxis = false; - } else { - //Slope along long edge - if (zLength <= xLength) onXAxis = false; - } - - if (onXAxis) { - //Along X goes up - - //Get diagonal line blocks - BlockPos linePoint = new BlockPos(secondPos.getX(), thirdPos.getY(), firstPos.getZ()); - List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, firstPos, linePoint, 1f); - - //Limit amount of blocks we can place - int lowest = Math.min(firstPos.getZ(), secondPos.getZ()); - int highest = Math.max(firstPos.getZ(), secondPos.getZ()); - - if (highest - lowest >= axisLimit) highest = lowest + axisLimit - 1; - - //Copy diagonal line on x axis - for (int z = lowest; z <= highest; z++) { - for (BlockPos blockPos : diagonalLineBlocks) { - list.add(new BlockPos(blockPos.getX(), blockPos.getY(), z)); - } - } - - } else { - //Along Z goes up - - //Get diagonal line blocks - BlockPos linePoint = new BlockPos(firstPos.getX(), thirdPos.getY(), secondPos.getZ()); - List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, firstPos, linePoint, 1f); - - //Limit amount of blocks we can place - int lowest = Math.min(firstPos.getX(), secondPos.getX()); - int highest = Math.max(firstPos.getX(), secondPos.getX()); - - if (highest - lowest >= axisLimit) highest = lowest + axisLimit - 1; - - //Copy diagonal line on x axis - for (int x = lowest; x <= highest; x++) { - for (BlockPos blockPos : diagonalLineBlocks) { - list.add(new BlockPos(x, blockPos.getY(), blockPos.getZ())); - } - } - } - - - return list; - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } -} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalLine.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java similarity index 61% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalLine.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java index 734d251..a8f9ba5 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/DiagonalLine.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ThreeClicksBuildMode.java @@ -3,21 +3,15 @@ package nl.requios.effortlessbuilding.buildmode; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.buildmodes.DiagonalLine; +import nl.requios.effortlessbuilding.buildmode.buildmodes.Floor; import nl.requios.effortlessbuilding.helper.ReachHelper; import java.util.*; -public class DiagonalLine implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary secondPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); +public abstract class ThreeClicksBuildMode extends BaseBuildMode { + protected Dictionary secondPosTable = new Hashtable<>(); static class HeightCriteria { Vec3d planeBound; @@ -42,27 +36,14 @@ public class DiagonalLine implements IBuildMode { //also check if raytrace from player to block does not intersect blocks public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { - boolean intersects = false; - if (!skipRaytrace) { - //collision within a 1 block radius to selected is fine - RayTraceResult rayTraceResult = player.world.rayTraceBlocks(start, lineBound, false, true, false); - intersects = rayTraceResult != null && rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK && - planeBound.subtract(rayTraceResult.hitVec).lengthSquared() > 4; - } - - return planeBound.subtract(start).dotProduct(look) > 0 && - distToPlayerSq > 2 && distToPlayerSq < reach * reach && - !intersects; + return BuildModes.isCriteriaValid(start, look, reach, player, skipRaytrace, lineBound, planeBound, distToPlayerSq); } } - + @Override public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); + super.initialize(player); + secondPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); } @Override @@ -89,7 +70,7 @@ public class DiagonalLine implements IBuildMode { } else if (rightClickNr == 2) { //Second click, find other floor point BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = Floor.findFloor(player, firstPos, true); + BlockPos secondPos = findSecondPos(player, firstPos, true); if (secondPos == null) { rightClickTable.put(player.getUniqueID(), 1); @@ -99,7 +80,7 @@ public class DiagonalLine implements IBuildMode { secondPosTable.put(player.getUniqueID(), secondPos); } else { - //Third click, place diagonal line with height + //Third click, place diagonal wall with height list = findCoordinates(player, blockPos, skipRaytrace); rightClickTable.put(player.getUniqueID(), 0); } @@ -119,26 +100,75 @@ public class DiagonalLine implements IBuildMode { } else if (rightClickNr == 1) { BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - BlockPos secondPos = Floor.findFloor(player, firstPos, true); + BlockPos secondPos = findSecondPos(player, firstPos, true); if (secondPos == null) return list; + //Limit amount of blocks you can place per row + int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); + + int x1 = firstPos.getX(), x2 = secondPos.getX(); + int y1 = firstPos.getY(), y2 = secondPos.getY(); + int z1 = firstPos.getZ(), z2 = secondPos.getZ(); + + //limit axis + if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; + if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; + if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; + if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; + if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; + if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; + //Add diagonal line from first to second - list.addAll(getDiagonalLineBlocks(player , firstPos, secondPos, 10)); + list.addAll(getIntermediateBlocks(player, x1, y1, z1, x2, y2, z2)); } else { BlockPos firstPos = firstPosTable.get(player.getUniqueID()); BlockPos secondPos = secondPosTable.get(player.getUniqueID()); - BlockPos thirdPos = findHeight(player, secondPos, skipRaytrace); + BlockPos thirdPos = findThirdPos(player, firstPos, secondPos, skipRaytrace); if (thirdPos == null) return list; + //Limit amount of blocks you can place per row + int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); + + int x1 = firstPos.getX(), x2 = secondPos.getX(), x3 = thirdPos.getX(); + int y1 = firstPos.getY(), y2 = secondPos.getY(), y3 = thirdPos.getY(); + int z1 = firstPos.getZ(), z2 = secondPos.getZ(), z3 = thirdPos.getZ(); + + //limit axis + if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; + if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; + if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; + if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; + if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; + if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; + + if (x3 - x1 >= axisLimit) x3 = x1 + axisLimit - 1; + if (x1 - x3 >= axisLimit) x3 = x1 - axisLimit + 1; + if (y3 - y1 >= axisLimit) y3 = y1 + axisLimit - 1; + if (y1 - y3 >= axisLimit) y3 = y1 - axisLimit + 1; + if (z3 - z1 >= axisLimit) z3 = z1 + axisLimit - 1; + if (z1 - z3 >= axisLimit) z3 = z1 - axisLimit + 1; + //Add diagonal line from first to third - list.addAll(getDiagonalLineBlocks(player , firstPos, thirdPos, 10)); + list.addAll(getFinalBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3)); } return list; } - + + //Finds the place of the second block pos + protected abstract BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace); + + //Finds the place of the third block pos + protected abstract BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace); + + //After first and second pos are known, we want to visualize the blocks in a way (like floor for cube) + protected abstract List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2); + + //After first, second and third pos are known, we want all the blocks + protected abstract List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3); + //Finds height after floor has been chosen in buildmodes with 3 clicks public static BlockPos findHeight(EntityPlayer player, BlockPos secondPos, boolean skipRaytrace) { Vec3d look = player.getLookVec(); @@ -182,47 +212,4 @@ public class DiagonalLine implements IBuildMode { } return new BlockPos(selected.lineBound); } - - //Add diagonal line from first to second - public static List getDiagonalLineBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, float sampleMultiplier) { - List list = new ArrayList<>(); - - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - int x1 = firstPos.getX(), x2 = secondPos.getX(); - int y1 = firstPos.getY(), y2 = secondPos.getY(); - int z1 = firstPos.getZ(), z2 = secondPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; - if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - - Vec3d first = new Vec3d(x1, y1, z1).add(0.5, 0.5, 0.5); - Vec3d second = new Vec3d(x2, y2, z2).add(0.5, 0.5, 0.5); - - int iterations = (int) Math.ceil(first.distanceTo(second) * sampleMultiplier); - for (double t = 0; t <= 1.0; t += 1.0/iterations) { - Vec3d lerp = first.add(second.subtract(first).scale(t)); - BlockPos candidate = new BlockPos(lerp); - //Only add if not equal to the last in the list - if (list.isEmpty() || !list.get(list.size() - 1).equals(candidate)) - list.add(candidate); - } - - return list; - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } -} \ No newline at end of file +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java new file mode 100644 index 0000000..265989f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/TwoClicksBuildMode.java @@ -0,0 +1,86 @@ +package nl.requios.effortlessbuilding.buildmode; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.List; +import java.util.UUID; + +public abstract class TwoClicksBuildMode extends BaseBuildMode { + + @Override + public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { + List list = new ArrayList<>(); + + Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; + int rightClickNr = rightClickTable.get(player.getUniqueID()); + rightClickNr++; + rightClickTable.put(player.getUniqueID(), rightClickNr); + + if (rightClickNr == 1) { + //If clicking in air, reset and try again + if (blockPos == null) { + rightClickTable.put(player.getUniqueID(), 0); + return list; + } + + //First click, remember starting position + firstPosTable.put(player.getUniqueID(), blockPos); + sideHitTable.put(player.getUniqueID(), sideHit); + hitVecTable.put(player.getUniqueID(), hitVec); + //Keep list empty, dont place any blocks yet + } else { + //Second click, place blocks + list = findCoordinates(player, blockPos, skipRaytrace); + rightClickTable.put(player.getUniqueID(), 0); + } + + return list; + } + + @Override + public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { + List list = new ArrayList<>(); + Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; + int rightClickNr = rightClickTable.get(player.getUniqueID()); + BlockPos firstPos = firstPosTable.get(player.getUniqueID()); + + if (rightClickNr == 0) { + if (blockPos != null) + list.add(blockPos); + } else { + BlockPos secondPos = findSecondPos(player, firstPos, skipRaytrace); + if (secondPos == null) return list; + + //Limit amount of blocks we can place per row + int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); + + int x1 = firstPos.getX(), x2 = secondPos.getX(); + int y1 = firstPos.getY(), y2 = secondPos.getY(); + int z1 = firstPos.getZ(), z2 = secondPos.getZ(); + + //limit axis + if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; + if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; + if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; + if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; + if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; + if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; + + list.addAll(getAllBlocks(player, x1, y1, z1, x2, y2, z2)); + } + + return list; + } + + //Finds the place of the second block pos based on criteria (floor must be on same height as first click, wall on same plane etc) + protected abstract BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace); + + //After first and second pos are known, we want all the blocks + protected abstract List getAllBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/Wall.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/Wall.java deleted file mode 100644 index efe6665..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/Wall.java +++ /dev/null @@ -1,226 +0,0 @@ -package nl.requios.effortlessbuilding.buildmode; - -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; -import net.minecraft.util.math.Vec3d; -import nl.requios.effortlessbuilding.helper.ReachHelper; - -import java.util.*; - -public class Wall implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); - - static class Criteria { - Vec3d planeBound; - double distToPlayerSq; - double angle; - - Criteria(Vec3d planeBound, BlockPos firstPos, Vec3d start, Vec3d look) { - this.planeBound = planeBound; - this.distToPlayerSq = this.planeBound.subtract(start).lengthSquared(); - Vec3d wall = this.planeBound.subtract(new Vec3d(firstPos)); - this.angle = wall.x * look.x + wall.z * look.z; //dot product ignoring y (looking up/down should not affect this angle) - } - - //check if its not behind the player and its not too close and not too far - //also check if raytrace from player to block does not intersect blocks - public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { - - boolean intersects = false; - if (!skipRaytrace) { - //collision within a 1 block radius to selected is fine - RayTraceResult rayTraceResult = player.world.rayTraceBlocks(start, planeBound, false, true, false); - intersects = rayTraceResult != null && rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK && - planeBound.subtract(rayTraceResult.hitVec).lengthSquared() > 4; - } - - return planeBound.subtract(start).dotProduct(look) > 0 && - distToPlayerSq > 2 && distToPlayerSq < reach * reach && - !intersects; - } - } - - @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else { - //Second click, place wall - - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else { - BlockPos secondPos = findWall(player, firstPos, skipRaytrace); - if (secondPos == null) return list; - - //Add whole wall - list.addAll(getWallBlocks(player, firstPos, secondPos)); - } - - return list; - } - - public static BlockPos findWall(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { - Vec3d look = player.getLookVec(); - Vec3d start = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ); - - List criteriaList = new ArrayList<>(3); - - //X - Vec3d xBound = BuildModes.findXBound(firstPos.getX(), start, look); - criteriaList.add(new Criteria(xBound, firstPos, start, look)); - - //Z - Vec3d zBound = BuildModes.findZBound(firstPos.getZ(), start, look); - criteriaList.add(new Criteria(zBound, firstPos, start, look)); - - //Remove invalid criteria - int reach = ReachHelper.getPlacementReach(player) * 4; //4 times as much as normal placement reach - criteriaList.removeIf(criteria -> !criteria.isValid(start, look, reach, player, skipRaytrace)); - - //If none are valid, return empty list of blocks - if (criteriaList.isEmpty()) return null; - - //If only 1 is valid, choose that one - Criteria selected = criteriaList.get(0); - - //If multiple are valid, choose based on criteria - if (criteriaList.size() > 1) { - //Select the one that is closest - //Limit the angle to not be too extreme - for (int i = 1; i < criteriaList.size(); i++) { - Criteria criteria = criteriaList.get(i); - if (criteria.distToPlayerSq < selected.distToPlayerSq && Math.abs(criteria.angle) - Math.abs(selected.angle) < 3) - selected = criteria; - } - } - - return new BlockPos(selected.planeBound); - } - - public static List getWallBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos) { - List list = new ArrayList<>(); - - //Limit amount of blocks we can place per row - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - int x1 = firstPos.getX(), x2 = secondPos.getX(); - int y1 = firstPos.getY(), y2 = secondPos.getY(); - int z1 = firstPos.getZ(), z2 = secondPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; - if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - - if (x1 == x2) { - if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) - addXWallBlocks(list, x1, y1, y2, z1, z2); - else - addXHollowWallBlocks(list, x1, y1, y2, z1, z2); - } else { - if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) - addZWallBlocks(list, x1, x2, y1, y2, z1); - else - addZHollowWallBlocks(list, x1, x2, y1, y2, z1); - } - - return list; - } - - public static void addXWallBlocks(List list, int x, int y1, int y2, int z1, int z2) { - - for (int z = z1; z1 < z2 ? z <= z2 : z >= z2; z += z1 < z2 ? 1 : -1) { - - for (int y = y1; y1 < y2 ? y <= y2 : y >= y2; y += y1 < y2 ? 1 : -1) { - list.add(new BlockPos(x, y, z)); - } - } - } - - public static void addZWallBlocks(List list, int x1, int x2, int y1, int y2, int z) { - - for (int x = x1; x1 < x2 ? x <= x2 : x >= x2; x += x1 < x2 ? 1 : -1) { - - for (int y = y1; y1 < y2 ? y <= y2 : y >= y2; y += y1 < y2 ? 1 : -1) { - list.add(new BlockPos(x, y, z)); - } - } - } - - public static void addXHollowWallBlocks(List list, int x, int y1, int y2, int z1, int z2) { - Line.addZLineBlocks(list, z1, z2, x, y1); - Line.addZLineBlocks(list, z1, z2, x, y2); - Line.addYLineBlocks(list, y1, y2, x, z1); - Line.addYLineBlocks(list, y1, y2, x, z2); - } - - public static void addZHollowWallBlocks(List list, int x1, int x2, int y1, int y2, int z) { - Line.addXLineBlocks(list, x1, x2, y1, z); - Line.addXLineBlocks(list, x1, x2, y2, z); - Line.addYLineBlocks(list, y1, y2, x1, z); - Line.addYLineBlocks(list, y1, y2, x2, z); - } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Circle.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Circle.java new file mode 100644 index 0000000..7df5a88 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Circle.java @@ -0,0 +1,88 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import nl.requios.effortlessbuilding.buildmode.TwoClicksBuildMode; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; + +import java.util.*; + +public class Circle extends TwoClicksBuildMode { + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + protected List getAllBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return getCircleBlocks(player, x1, y1, z1, x2, y2, z2); + } + + public static List getCircleBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + List list = new ArrayList<>(); + + float centerX = x1; + float centerZ = z1; + + //Adjust for CIRCLE_START + if (ModeOptions.getCircleStart() == ModeOptions.ActionEnum.CIRCLE_START_CORNER) { + centerX = x1 + (x2 - x1) / 2f; + centerZ = z1 + (z2 - z1) / 2f; + } else { + x1 = (int) (centerX - (x2 - centerX)); + z1 = (int) (centerZ - (z2 - centerZ)); + } + + float radiusX = MathHelper.abs(x2 - centerX); + float radiusZ = MathHelper.abs(z2 - centerZ); + + if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) + addCircleBlocks(list, x1, y1, z1, x2, y2, z2, centerX, centerZ, radiusX, radiusZ); + else + addHollowCircleBlocks(list, x1, y1, z1, x2, y2, z2, centerX, centerZ, radiusX, radiusZ); + + return list; + } + + public static void addCircleBlocks(List list, int x1, int y1, int z1, int x2, int y2, int z2, float centerX, float centerZ, float radiusX, float radiusZ) { + + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + float distance = distance(l, n, centerX, centerZ); + float radius = calculateEllipseRadius(centerX, centerZ, radiusX, radiusZ, l, n); + if (distance < radius + 0.4f) + list.add(new BlockPos(l, y1, n)); + } + } + } + + public static void addHollowCircleBlocks(List list, int x1, int y1, int z1, int x2, int y2, int z2, float centerX, float centerZ, float radiusX, float radiusZ) { + + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + float distance = distance(l, n, centerX, centerZ); + float radius = calculateEllipseRadius(centerX, centerZ, radiusX, radiusZ, l, n); + if (distance < radius + 0.4f && distance > radius - 0.6f) + list.add(new BlockPos(l, y1, n)); + } + } + } + + private static float distance(float x1, float z1, float x2, float z2) { + return MathHelper.sqrt((x2 - x1) * (x2 - x1) + (z2 - z1) * (z2 - z1)); + } + + public static float calculateEllipseRadius(float centerX, float centerZ, float radiusX, float radiusZ, int x, int z) { + //https://math.stackexchange.com/questions/432902/how-to-get-the-radius-of-an-ellipse-at-a-specific-angle-by-knowing-its-semi-majo + float theta = (float) MathHelper.atan2(z - centerZ, x - centerX); + float part1 = radiusX * radiusX * MathHelper.sin(theta) * MathHelper.sin(theta); + float part2 = radiusZ * radiusZ * MathHelper.cos(theta) * MathHelper.cos(theta); + return radiusX * radiusZ / MathHelper.sqrt(part1 + part2); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cube.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cube.java new file mode 100644 index 0000000..95914b2 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cube.java @@ -0,0 +1,104 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.IBuildMode; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.*; + +public class Cube extends ThreeClicksBuildMode { + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + protected BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + protected List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return getFloorBlocksUsingCubeFill(player, x1, y1, z1, x2, y2, z2); + } + + @Override + protected List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + return getCubeBlocks(player, x1, y1, z1, x3, y3, z3); + } + + public static List getFloorBlocksUsingCubeFill(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + List list = new ArrayList<>(); + + if (ModeOptions.getCubeFill() == ModeOptions.ActionEnum.CUBE_SKELETON) + Floor.addHollowFloorBlocks(list, x1, x2, y1, z1, z2); + else + Floor.addFloorBlocks(list, x1, x2, y1, z1, z2); + + return list; + } + + public static List getCubeBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + List list = new ArrayList<>(); + + switch (ModeOptions.getCubeFill()) { + case CUBE_FULL: + addCubeBlocks(list, x1, x2, y1, y2, z1, z2); + break; + case CUBE_HOLLOW: + addHollowCubeBlocks(list, x1, x2, y1, y2, z1, z2); + break; + case CUBE_SKELETON: + addSkeletonCubeBlocks(list, x1, x2, y1, y2, z1, z2); + break; + } + + return list; + } + + public static void addCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + for (int m = y1; y1 < y2 ? m <= y2 : m >= y2; m += y1 < y2 ? 1 : -1) { + list.add(new BlockPos(l, m, n)); + } + } + } + } + + public static void addHollowCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { + Wall.addXWallBlocks(list, x1, y1, y2, z1, z2); + Wall.addXWallBlocks(list, x2, y1, y2, z1, z2); + + Wall.addZWallBlocks(list, x1, x2, y1, y2, z1); + Wall.addZWallBlocks(list, x1, x2, y1, y2, z2); + + Floor.addFloorBlocks(list, x1, x2, y1, z1, z2); + Floor.addFloorBlocks(list, x1, x2, y2, z1, z2); + } + + public static void addSkeletonCubeBlocks(List list, int x1, int x2, int y1, int y2, int z1, int z2) { + Line.addXLineBlocks(list, x1, x2, y1, z1); + Line.addXLineBlocks(list, x1, x2, y1, z2); + Line.addXLineBlocks(list, x1, x2, y2, z1); + Line.addXLineBlocks(list, x1, x2, y2, z2); + + Line.addYLineBlocks(list, y1, y2, x1, z1); + Line.addYLineBlocks(list, y1, y2, x1, z2); + Line.addYLineBlocks(list, y1, y2, x2, z1); + Line.addYLineBlocks(list, y1, y2, x2, z2); + + Line.addZLineBlocks(list, z1, z2, x1, y1); + Line.addZLineBlocks(list, z1, z2, x1, y2); + Line.addZLineBlocks(list, z1, z2, x2, y1); + Line.addZLineBlocks(list, z1, z2, x2, y2); + } +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cylinder.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cylinder.java new file mode 100644 index 0000000..c57b62f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Cylinder.java @@ -0,0 +1,50 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; + +import java.util.ArrayList; +import java.util.List; + +public class Cylinder extends ThreeClicksBuildMode { + + @Override + public BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + public BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + public List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return Circle.getCircleBlocks(player, x1, y1, z1, x2, y2, z2); + } + + @Override + public List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + return getCylinderBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3); + } + + public static List getCylinderBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + List list = new ArrayList<>(); + + //Get circle blocks (using CIRCLE_START and FILL options built-in) + List circleBlocks = Circle.getCircleBlocks(player, x1, y1, z1, x2, y2, z2); + + int lowest = Math.min(y1, y3); + int highest = Math.max(y1, y3); + + //Copy circle on y axis + for (int y = lowest; y <= highest; y++) { + for (BlockPos blockPos : circleBlocks) { + list.add(new BlockPos(blockPos.getX(), y, blockPos.getZ())); + } + } + + return list; + } +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalLine.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalLine.java new file mode 100644 index 0000000..e95fa38 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalLine.java @@ -0,0 +1,54 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.ArrayList; +import java.util.List; + +public class DiagonalLine extends ThreeClicksBuildMode { + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + protected BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + protected List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + //Add diagonal line from first to second + return getDiagonalLineBlocks(player, x1, y1, z1, x2, y2, z2, 10); + } + + @Override + protected List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + //Add diagonal line from first to third + return getDiagonalLineBlocks(player, x1, y1, z1, x3, y3, z3, 10); + } + + //Add diagonal line from first to second + public static List getDiagonalLineBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, float sampleMultiplier) { + List list = new ArrayList<>(); + + Vec3d first = new Vec3d(x1, y1, z1).add(0.5, 0.5, 0.5); + Vec3d second = new Vec3d(x2, y2, z2).add(0.5, 0.5, 0.5); + + int iterations = (int) Math.ceil(first.distanceTo(second) * sampleMultiplier); + for (double t = 0; t <= 1.0; t += 1.0/iterations) { + Vec3d lerp = first.add(second.subtract(first).scale(t)); + BlockPos candidate = new BlockPos(lerp); + //Only add if not equal to the last in the list + if (list.isEmpty() || !list.get(list.size() - 1).equals(candidate)) + list.add(candidate); + } + + return list; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalWall.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalWall.java new file mode 100644 index 0000000..6a4cebb --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/DiagonalWall.java @@ -0,0 +1,52 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.ArrayList; +import java.util.List; + +public class DiagonalWall extends ThreeClicksBuildMode { + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + protected BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + protected List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return DiagonalLine.getDiagonalLineBlocks(player, x1, y1, z1, x2, y2, z2, 1); + } + + @Override + protected List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + return getDiagonalWallBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3); + } + + //Add diagonal wall from first to second + public static List getDiagonalWallBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + List list = new ArrayList<>(); + + //Get diagonal line blocks + List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, x1, y1, z1, x2, y2, z2, 1); + + int lowest = Math.min(y1, y3); + int highest = Math.max(y1, y3); + + //Copy diagonal line on y axis + for (int y = lowest; y <= highest; y++) { + for (BlockPos blockPos : diagonalLineBlocks) { + list.add(new BlockPos(blockPos.getX(), y, blockPos.getZ())); + } + } + + return list; + } +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Floor.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Floor.java new file mode 100644 index 0000000..89f4d04 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Floor.java @@ -0,0 +1,93 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.BuildModes; +import nl.requios.effortlessbuilding.buildmode.TwoClicksBuildMode; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.*; + +public class Floor extends TwoClicksBuildMode { + + static class Criteria { + Vec3d planeBound; + double distToPlayerSq; + + Criteria(Vec3d planeBound, Vec3d start) { + this.planeBound = planeBound; + this.distToPlayerSq = this.planeBound.subtract(start).lengthSquared(); + } + + //check if its not behind the player and its not too close and not too far + //also check if raytrace from player to block does not intersect blocks + public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { + + return BuildModes.isCriteriaValid(start, look, reach, player, skipRaytrace, planeBound, planeBound, distToPlayerSq); + } + } + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return findFloor(player, firstPos, skipRaytrace); + } + + public static BlockPos findFloor(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + Vec3d look = player.getLookVec(); + Vec3d start = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ); + + List criteriaList = new ArrayList<>(3); + + //Y + Vec3d yBound = BuildModes.findYBound(firstPos.getY(), start, look); + criteriaList.add(new Criteria(yBound, start)); + + //Remove invalid criteria + int reach = ReachHelper.getPlacementReach(player) * 4; //4 times as much as normal placement reach + criteriaList.removeIf(criteria -> !criteria.isValid(start, look, reach, player, skipRaytrace)); + + //If none are valid, return empty list of blocks + if (criteriaList.isEmpty()) return null; + + //Then only 1 can be valid, return that one + Criteria selected = criteriaList.get(0); + + return new BlockPos(selected.planeBound); + } + + @Override + protected List getAllBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return getFloorBlocks(player, x1, y1, z1, x2, y2, z2); + } + + public static List getFloorBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + List list = new ArrayList<>(); + + if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) + addFloorBlocks(list, x1, x2, y1, z1, z2); + else + addHollowFloorBlocks(list, x1, x2, y1, z1, z2); + + return list; + } + + public static void addFloorBlocks(List list, int x1, int x2, int y, int z1, int z2) { + + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + list.add(new BlockPos(l, y, n)); + } + } + } + + public static void addHollowFloorBlocks(List list, int x1, int x2, int y, int z1, int z2) { + Line.addXLineBlocks(list, x1, x2, y, z1); + Line.addXLineBlocks(list, x1, x2, y, z2); + Line.addZLineBlocks(list, z1, z2, x1, y); + Line.addZLineBlocks(list, z1, z2, x2, y); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/Line.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Line.java similarity index 52% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/Line.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Line.java index 2da9bc1..5d0e3ad 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/Line.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Line.java @@ -1,22 +1,16 @@ -package nl.requios.effortlessbuilding.buildmode; +package nl.requios.effortlessbuilding.buildmode.buildmodes; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.RayTraceResult; import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.BuildModes; +import nl.requios.effortlessbuilding.buildmode.TwoClicksBuildMode; import nl.requios.effortlessbuilding.helper.ReachHelper; -import java.util.*; +import java.util.ArrayList; +import java.util.List; -public class Line implements IBuildMode { - //In singleplayer client and server variables are shared - //Split everything that needs separate values and may not be called twice in one click - private Dictionary rightClickClientTable = new Hashtable<>(); - private Dictionary rightClickServerTable = new Hashtable<>(); - private Dictionary firstPosTable = new Hashtable<>(); - private Dictionary sideHitTable = new Hashtable<>(); - private Dictionary hitVecTable = new Hashtable<>(); +public class Line extends TwoClicksBuildMode { static class Criteria { Vec3d planeBound; @@ -55,78 +49,14 @@ public class Line implements IBuildMode { //also check if raytrace from player to block does not intersect blocks public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { - boolean intersects = false; - if (!skipRaytrace) { - //collision within a 1 block radius to selected is fine - RayTraceResult rayTraceResult = player.world.rayTraceBlocks(start, lineBound, false, true, false); - intersects = rayTraceResult != null && rayTraceResult.typeOfHit == RayTraceResult.Type.BLOCK && - planeBound.subtract(rayTraceResult.hitVec).lengthSquared() > 4; - } - - return planeBound.subtract(start).dotProduct(look) > 0 && - distToPlayerSq > 2 && distToPlayerSq < reach * reach && - !intersects; + return BuildModes.isCriteriaValid(start, look, reach, player, skipRaytrace, lineBound, planeBound, distToPlayerSq); } + } @Override - public void initialize(EntityPlayer player) { - rightClickClientTable.put(player.getUniqueID(), 0); - rightClickServerTable.put(player.getUniqueID(), 0); - firstPosTable.put(player.getUniqueID(), BlockPos.ORIGIN); - sideHitTable.put(player.getUniqueID(), EnumFacing.UP); - hitVecTable.put(player.getUniqueID(), Vec3d.ZERO); - } - - @Override - public List onRightClick(EntityPlayer player, BlockPos blockPos, EnumFacing sideHit, Vec3d hitVec, boolean skipRaytrace) { - List list = new ArrayList<>(); - - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - rightClickNr++; - rightClickTable.put(player.getUniqueID(), rightClickNr); - - if (rightClickNr == 1) { - //If clicking in air, reset and try again - if (blockPos == null) { - rightClickTable.put(player.getUniqueID(), 0); - return list; - } - - //First click, remember starting position - firstPosTable.put(player.getUniqueID(), blockPos); - sideHitTable.put(player.getUniqueID(), sideHit); - hitVecTable.put(player.getUniqueID(), hitVec); - //Keep list empty, dont place any blocks yet - } else { - //Second click, place wall - - list = findCoordinates(player, blockPos, skipRaytrace); - rightClickTable.put(player.getUniqueID(), 0); - } - - return list; - } - - @Override - public List findCoordinates(EntityPlayer player, BlockPos blockPos, boolean skipRaytrace) { - List list = new ArrayList<>(); - Dictionary rightClickTable = player.world.isRemote ? rightClickClientTable : rightClickServerTable; - int rightClickNr = rightClickTable.get(player.getUniqueID()); - BlockPos firstPos = firstPosTable.get(player.getUniqueID()); - - if (rightClickNr == 0) { - if (blockPos != null) - list.add(blockPos); - } else { - BlockPos secondPos = findLine(player, firstPos, skipRaytrace); - if (secondPos == null) return list; - - list.addAll(getLineBlocks(player, firstPos, secondPos)); - } - - return list; + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return findLine(player, firstPos, skipRaytrace); } public static BlockPos findLine(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { @@ -178,25 +108,14 @@ public class Line implements IBuildMode { return new BlockPos(selected.lineBound); } - public static List getLineBlocks(EntityPlayer player, BlockPos firstPos, BlockPos secondPos) { + @Override + protected List getAllBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return getLineBlocks(player, x1, y1, z1, x2, y2, z2); + } + + public static List getLineBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { List list = new ArrayList<>(); - //Limit amount of blocks we can place - int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); - - //Add whole line - int x1 = firstPos.getX(), x2 = secondPos.getX(); - int y1 = firstPos.getY(), y2 = secondPos.getY(); - int z1 = firstPos.getZ(), z2 = secondPos.getZ(); - - //limit axis - if (x2 - x1 >= axisLimit) x2 = x1 + axisLimit - 1; - if (x1 - x2 >= axisLimit) x2 = x1 - axisLimit + 1; - if (y2 - y1 >= axisLimit) y2 = y1 + axisLimit - 1; - if (y1 - y2 >= axisLimit) y2 = y1 - axisLimit + 1; - if (z2 - z1 >= axisLimit) z2 = z1 + axisLimit - 1; - if (z1 - z2 >= axisLimit) z2 = z1 - axisLimit + 1; - if (x1 != x2) { addXLineBlocks(list, x1, x2, y1, z1); } else if (y1 != y2) { @@ -225,15 +144,4 @@ public class Line implements IBuildMode { list.add(new BlockPos(x, y, z)); } } - - @Override - public EnumFacing getSideHit(EntityPlayer player) { - return sideHitTable.get(player.getUniqueID()); - } - - @Override - public Vec3d getHitVec(EntityPlayer player) { - return hitVecTable.get(player.getUniqueID()); - } - } diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/Normal.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java similarity index 89% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/Normal.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java index 430cb1b..f917a32 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/Normal.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java @@ -1,9 +1,10 @@ -package nl.requios.effortlessbuilding.buildmode; +package nl.requios.effortlessbuilding.buildmode.buildmodes; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.IBuildMode; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/NormalPlus.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java similarity index 89% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/NormalPlus.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java index a503efa..63ec7e5 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/NormalPlus.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java @@ -1,9 +1,10 @@ -package nl.requios.effortlessbuilding.buildmode; +package nl.requios.effortlessbuilding.buildmode.buildmodes; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.IBuildMode; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/SlopeFloor.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/SlopeFloor.java new file mode 100644 index 0000000..df7c78e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/SlopeFloor.java @@ -0,0 +1,95 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.ArrayList; +import java.util.List; + +public class SlopeFloor extends ThreeClicksBuildMode { + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + protected BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + protected List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return Floor.getFloorBlocks(player, x1, y1, z1, x2, y2, z2); + } + + @Override + protected List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + return getSlopeFloorBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3); + } + + //Add slope floor from first to second + public static List getSlopeFloorBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + List list = new ArrayList<>(); + + int axisLimit = ReachHelper.getMaxBlocksPerAxis(player); + + //Determine whether to use x or z axis to slope up + boolean onXAxis = true; + + int xLength = Math.abs(x2 - x1); + int zLength = Math.abs(z2 - z1); + + if (ModeOptions.getRaisedEdge() == ModeOptions.ActionEnum.SHORT_EDGE) { + //Slope along short edge + if (zLength > xLength) onXAxis = false; + } else { + //Slope along long edge + if (zLength <= xLength) onXAxis = false; + } + + if (onXAxis) { + //Along X goes up + + //Get diagonal line blocks + List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, x1, y1, z1, x2, y3, z1, 1f); + + //Limit amount of blocks we can place + int lowest = Math.min(z1, z2); + int highest = Math.max(z1, z2); + + if (highest - lowest >= axisLimit) highest = lowest + axisLimit - 1; + + //Copy diagonal line on x axis + for (int z = lowest; z <= highest; z++) { + for (BlockPos blockPos : diagonalLineBlocks) { + list.add(new BlockPos(blockPos.getX(), blockPos.getY(), z)); + } + } + + } else { + //Along Z goes up + + //Get diagonal line blocks + List diagonalLineBlocks = DiagonalLine.getDiagonalLineBlocks(player, x1, y1, z1, x1, y3, z2, 1f); + + //Limit amount of blocks we can place + int lowest = Math.min(x1, x2); + int highest = Math.max(x1, x2); + + if (highest - lowest >= axisLimit) highest = lowest + axisLimit - 1; + + //Copy diagonal line on x axis + for (int x = lowest; x <= highest; x++) { + for (BlockPos blockPos : diagonalLineBlocks) { + list.add(new BlockPos(x, blockPos.getY(), blockPos.getZ())); + } + } + } + + return list; + } +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Sphere.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Sphere.java new file mode 100644 index 0000000..4d3c24f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Sphere.java @@ -0,0 +1,110 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.buildmode.ThreeClicksBuildMode; + +import java.util.ArrayList; +import java.util.List; + +public class Sphere extends ThreeClicksBuildMode { + + @Override + public BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return Floor.findFloor(player, firstPos, skipRaytrace); + } + + @Override + public BlockPos findThirdPos(EntityPlayer player, BlockPos firstPos, BlockPos secondPos, boolean skipRaytrace) { + return findHeight(player, secondPos, skipRaytrace); + } + + @Override + public List getIntermediateBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return Circle.getCircleBlocks(player, x1, y1, z1, x2, y2, z2); + } + + @Override + public List getFinalBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + return getSphereBlocks(player, x1, y1, z1, x2, y2, z2, x3, y3, z3); + } + + public static List getSphereBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) { + List list = new ArrayList<>(); + + float centerX = x1; + float centerY = y1; + float centerZ = z1; + + //Adjust for CIRCLE_START + if (ModeOptions.getCircleStart() == ModeOptions.ActionEnum.CIRCLE_START_CORNER) { + centerX = x1 + (x2 - x1) / 2f; + centerY = y1 + (y3 - y1) / 2f; + centerZ = z1 + (z2 - z1) / 2f; + } else { + x1 = (int) (centerX - (x2 - centerX)); + y1 = (int) (centerY - (y3 - centerY)); + z1 = (int) (centerZ - (z2 - centerZ)); + } + + float radiusX = MathHelper.abs(x2 - centerX); + float radiusY = MathHelper.abs(y3 - centerY); + float radiusZ = MathHelper.abs(z2 - centerZ); + + if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) + addSphereBlocks(list, x1, y1, z1, x3, y3, z3, centerX, centerY, centerZ, radiusX, radiusY, radiusZ); + else + addHollowSphereBlocks(list, x1, y1, z1, x3, y3, z3, centerX, centerY, centerZ, radiusX, radiusY, radiusZ); + + return list; + } + + public static void addSphereBlocks(List list, int x1, int y1, int z1, int x2, int y2, int z2, + float centerX, float centerY, float centerZ, float radiusX, float radiusY, float radiusZ) { + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + for (int m = y1; y1 < y2 ? m <= y2 : m >= y2; m += y1 < y2 ? 1 : -1) { + + float distance = distance(l, m, n, centerX, centerY, centerZ); + float radius = calculateSpheroidRadius(centerX, centerY, centerZ, radiusX, radiusY, radiusZ, l, m, n); + if (distance < radius + 0.4f) + list.add(new BlockPos(l, m, n)); + } + } + } + } + + public static void addHollowSphereBlocks(List list, int x1, int y1, int z1, int x2, int y2, int z2, + float centerX, float centerY, float centerZ, float radiusX, float radiusY, float radiusZ) { + for (int l = x1; x1 < x2 ? l <= x2 : l >= x2; l += x1 < x2 ? 1 : -1) { + + for (int n = z1; z1 < z2 ? n <= z2 : n >= z2; n += z1 < z2 ? 1 : -1) { + + for (int m = y1; y1 < y2 ? m <= y2 : m >= y2; m += y1 < y2 ? 1 : -1) { + + float distance = distance(l, m, n, centerX, centerY, centerZ); + float radius = calculateSpheroidRadius(centerX, centerY, centerZ, radiusX, radiusY, radiusZ, l, m, n); + if (distance < radius + 0.4f && distance > radius - 0.6f) + list.add(new BlockPos(l, m, n)); + } + } + } + } + + private static float distance(float x1, float y1, float z1, float x2, float y2, float z2) { + return MathHelper.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1)); + } + + public static float calculateSpheroidRadius(float centerX, float centerY, float centerZ, float radiusX, float radiusY, float radiusZ, int x, int y, int z) { + //Twice ellipse radius + float radiusXZ = Circle.calculateEllipseRadius(centerX, centerZ, radiusX, radiusZ, x, z); + + //TODO project x to plane + + return Circle.calculateEllipseRadius(centerX, centerY, radiusXZ, radiusY, x, y); + } +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Wall.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Wall.java new file mode 100644 index 0000000..60da58b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Wall.java @@ -0,0 +1,134 @@ +package nl.requios.effortlessbuilding.buildmode.buildmodes; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import nl.requios.effortlessbuilding.buildmode.BuildModes; +import nl.requios.effortlessbuilding.buildmode.TwoClicksBuildMode; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.helper.ReachHelper; + +import java.util.*; + +public class Wall extends TwoClicksBuildMode { + + static class Criteria { + Vec3d planeBound; + double distToPlayerSq; + double angle; + + Criteria(Vec3d planeBound, BlockPos firstPos, Vec3d start, Vec3d look) { + this.planeBound = planeBound; + this.distToPlayerSq = this.planeBound.subtract(start).lengthSquared(); + Vec3d wall = this.planeBound.subtract(new Vec3d(firstPos)); + this.angle = wall.x * look.x + wall.z * look.z; //dot product ignoring y (looking up/down should not affect this angle) + } + + //check if its not behind the player and its not too close and not too far + //also check if raytrace from player to block does not intersect blocks + public boolean isValid(Vec3d start, Vec3d look, int reach, EntityPlayer player, boolean skipRaytrace) { + + return BuildModes.isCriteriaValid(start, look, reach, player, skipRaytrace, planeBound, planeBound, distToPlayerSq); + } + } + + @Override + protected BlockPos findSecondPos(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + return findWall(player, firstPos, skipRaytrace); + } + + public static BlockPos findWall(EntityPlayer player, BlockPos firstPos, boolean skipRaytrace) { + Vec3d look = player.getLookVec(); + Vec3d start = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ); + + List criteriaList = new ArrayList<>(3); + + //X + Vec3d xBound = BuildModes.findXBound(firstPos.getX(), start, look); + criteriaList.add(new Criteria(xBound, firstPos, start, look)); + + //Z + Vec3d zBound = BuildModes.findZBound(firstPos.getZ(), start, look); + criteriaList.add(new Criteria(zBound, firstPos, start, look)); + + //Remove invalid criteria + int reach = ReachHelper.getPlacementReach(player) * 4; //4 times as much as normal placement reach + criteriaList.removeIf(criteria -> !criteria.isValid(start, look, reach, player, skipRaytrace)); + + //If none are valid, return empty list of blocks + if (criteriaList.isEmpty()) return null; + + //If only 1 is valid, choose that one + Criteria selected = criteriaList.get(0); + + //If multiple are valid, choose based on criteria + if (criteriaList.size() > 1) { + //Select the one that is closest + //Limit the angle to not be too extreme + for (int i = 1; i < criteriaList.size(); i++) { + Criteria criteria = criteriaList.get(i); + if (criteria.distToPlayerSq < selected.distToPlayerSq && Math.abs(criteria.angle) - Math.abs(selected.angle) < 3) + selected = criteria; + } + } + + return new BlockPos(selected.planeBound); + } + + @Override + protected List getAllBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + return getWallBlocks(player, x1, y1, z1, x2, y2, z2); + } + + public static List getWallBlocks(EntityPlayer player, int x1, int y1, int z1, int x2, int y2, int z2) { + List list = new ArrayList<>(); + + if (x1 == x2) { + if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) + addXWallBlocks(list, x1, y1, y2, z1, z2); + else + addXHollowWallBlocks(list, x1, y1, y2, z1, z2); + } else { + if (ModeOptions.getFill() == ModeOptions.ActionEnum.FULL) + addZWallBlocks(list, x1, x2, y1, y2, z1); + else + addZHollowWallBlocks(list, x1, x2, y1, y2, z1); + } + + return list; + } + + public static void addXWallBlocks(List list, int x, int y1, int y2, int z1, int z2) { + + for (int z = z1; z1 < z2 ? z <= z2 : z >= z2; z += z1 < z2 ? 1 : -1) { + + for (int y = y1; y1 < y2 ? y <= y2 : y >= y2; y += y1 < y2 ? 1 : -1) { + list.add(new BlockPos(x, y, z)); + } + } + } + + public static void addZWallBlocks(List list, int x1, int x2, int y1, int y2, int z) { + + for (int x = x1; x1 < x2 ? x <= x2 : x >= x2; x += x1 < x2 ? 1 : -1) { + + for (int y = y1; y1 < y2 ? y <= y2 : y >= y2; y += y1 < y2 ? 1 : -1) { + list.add(new BlockPos(x, y, z)); + } + } + } + + public static void addXHollowWallBlocks(List list, int x, int y1, int y2, int z1, int z2) { + Line.addZLineBlocks(list, z1, z2, x, y1); + Line.addZLineBlocks(list, z1, z2, x, y2); + Line.addYLineBlocks(list, y1, y2, x, z1); + Line.addYLineBlocks(list, y1, y2, x, z2); + } + + public static void addZHollowWallBlocks(List list, int x1, int x2, int y1, int y2, int z) { + Line.addXLineBlocks(list, x1, x2, y1, z); + Line.addXLineBlocks(list, x1, x2, y2, z); + Line.addYLineBlocks(list, y1, y2, x1, z); + Line.addYLineBlocks(list, y1, y2, x2, z); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java index ada64e0..fc71ea0 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java @@ -206,6 +206,7 @@ public class UndoRedo { private static ItemStack findItemStackInInventory(EntityPlayer player, IBlockState blockState) { ItemStack itemStack = ItemStack.EMPTY; + if (blockState == null) return itemStack; //First try previousBlockStates //TODO try to find itemstack with right blockstate first diff --git a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java index 8b5a594..2b0ae8a 100644 --- a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java +++ b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java @@ -26,6 +26,7 @@ import net.minecraft.util.EnumFacing; import net.minecraftforge.client.model.ModelLoader; import static nl.requios.effortlessbuilding.buildmode.BuildModes.*; +import static nl.requios.effortlessbuilding.buildmode.ModeOptions.*; /** * From Chisels and Bits by AlgorithmX2 @@ -39,7 +40,7 @@ public class RadialMenu extends GuiScreen { private float visibility = 0.0f; private Stopwatch lastChange = Stopwatch.createStarted(); public BuildModeEnum switchTo = null; - public ModeOptions.ActionEnum doAction = null; + public ActionEnum doAction = null; public boolean actionUsed = false; private float clampVis(final float f) { @@ -77,11 +78,11 @@ public class RadialMenu extends GuiScreen { public double y1, y2; public boolean highlighted; - public final ModeOptions.ActionEnum action; + public final ActionEnum action; public String name; public EnumFacing textSide; - public MenuButton(final String name, final ModeOptions.ActionEnum action, final double x, final double y, + public MenuButton(final String name, final ActionEnum action, final double x, final double y, final EnumFacing textSide) { this.name = I18n.format(name); this.action = action; @@ -138,10 +139,10 @@ public class RadialMenu extends GuiScreen { final double mouseYCenter = mouseY - middleY; double mouseRadians = Math.atan2(mouseYCenter, mouseXCenter); - final double ringInnerEdge = 20; - final double ringOuterEdge = 50; - final double textDistance = 60; - final double buttonDistance = 90; + final double ringInnerEdge = 30; + final double ringOuterEdge = 65; + final double textDistance = 75; + final double buttonDistance = 105; final double quarterCircle = Math.PI / 2.0; if ( mouseRadians < -quarterCircle ) { @@ -157,16 +158,18 @@ public class RadialMenu extends GuiScreen { } //Add actions - buttons.add(new MenuButton(ModeOptions.ActionEnum.UNDO.name, ModeOptions.ActionEnum.UNDO, -buttonDistance - 26, -13, EnumFacing.UP)); - buttons.add(new MenuButton(ModeOptions.ActionEnum.REDO.name, ModeOptions.ActionEnum.REDO, -buttonDistance, -13, EnumFacing.UP)); - buttons.add(new MenuButton(ModeOptions.ActionEnum.OPEN_MODIFIER_SETTINGS.name, ModeOptions.ActionEnum.OPEN_MODIFIER_SETTINGS, -buttonDistance - 26, 13, EnumFacing.DOWN)); - buttons.add(new MenuButton(ModeOptions.ActionEnum.REPLACE.name, ModeOptions.ActionEnum.REPLACE, -buttonDistance, 13, EnumFacing.DOWN)); + buttons.add(new MenuButton(ActionEnum.UNDO.name, ActionEnum.UNDO, -buttonDistance - 26, -13, EnumFacing.UP)); + buttons.add(new MenuButton(ActionEnum.REDO.name, ActionEnum.REDO, -buttonDistance, -13, EnumFacing.UP)); + buttons.add(new MenuButton(ActionEnum.OPEN_MODIFIER_SETTINGS.name, ActionEnum.OPEN_MODIFIER_SETTINGS, -buttonDistance - 26, 13, EnumFacing.DOWN)); + buttons.add(new MenuButton(ActionEnum.REPLACE.name, ActionEnum.REPLACE, -buttonDistance, 13, EnumFacing.DOWN)); //Add buildmode dependent options - ModeOptions.ActionEnum[] options = currentBuildMode.options; + OptionEnum[] options = currentBuildMode.options; for (int i = 0; i < options.length; i++) { - ModeOptions.ActionEnum action = options[i]; - buttons.add(new MenuButton(action.name, action, buttonDistance + i * 26, -13, EnumFacing.DOWN)); + for (int j = 0; j < options[i].actions.length; j++) { + ActionEnum action = options[i].actions[j]; + buttons.add(new MenuButton(action.name, action, buttonDistance + j * 26, -13 + i * 39, EnumFacing.DOWN)); + } } switchTo = null; @@ -245,11 +248,12 @@ public class RadialMenu extends GuiScreen { float a = 0.5f; //highlight when active option - if (btn.action == ModeOptions.getBuildSpeed() || - btn.action == ModeOptions.getFill() || - btn.action == ModeOptions.getCubeFill() || - btn.action == ModeOptions.getRaisedEdge() || - btn.action == ModeOptions.getLineThickness()) { + if (btn.action == getBuildSpeed() || + btn.action == getFill() || + btn.action == getCubeFill() || + btn.action == getRaisedEdge() || + btn.action == getLineThickness() || + btn.action == getCircleStart()) { r = 0.0f; g = 0.5f; b = 1f; @@ -342,32 +346,12 @@ public class RadialMenu extends GuiScreen { //Draw strings //fontRenderer.drawStringWithShadow("Actions", (int) (middleX - buttonDistance - 13) - fontRenderer.getStringWidth("Actions") * 0.5f, (int) middleY - 38, 0xffffffff); - String title = ""; - if (currentBuildMode.options.length > 0) { - switch (currentBuildMode.options[0]) { - case NORMAL_SPEED: - case FAST_SPEED: - title = I18n.format("effortlessbuilding.action.build_speed"); - break; - case FULL: - case HOLLOW: - case CUBE_FULL: - case CUBE_HOLLOW: - case CUBE_SKELETON: - title = I18n.format("effortlessbuilding.action.filling"); - break; - case SHORT_EDGE: - case LONG_EDGE: - title = I18n.format("effortlessbuilding.action.raised_edge"); - break; - case THICKNESS_1: - case THICKNESS_3: - case THICKNESS_5: - title = I18n.format("effortlessbuilding.action.thickness"); - break; - } + + //Draw option strings + for (int i = 0; i < currentBuildMode.options.length; i++) { + OptionEnum option = options[i]; + fontRenderer.drawStringWithShadow(I18n.format(option.name), (int) (middleX + buttonDistance - 9), (int) middleY - 37 + i * 39, 0xeeeeeeff); } - fontRenderer.drawStringWithShadow(title, (int) (middleX + buttonDistance - 9), (int) middleY - 37, 0xeeeeeeff); String credits = "Effortless Building"; fontRenderer.drawStringWithShadow(credits, width - fontRenderer.getStringWidth(credits) - 4, height - 10, 0x88888888); @@ -402,18 +386,26 @@ public class RadialMenu extends GuiScreen { String keybindFormatted = ""; //Add keybind in brackets - if (button.action == ModeOptions.ActionEnum.UNDO) { + if (button.action == ActionEnum.UNDO) { keybind = ClientProxy.keyBindings[4].getDisplayName(); } - if (button.action == ModeOptions.ActionEnum.REDO) { + if (button.action == ActionEnum.REDO) { keybind = ClientProxy.keyBindings[5].getDisplayName(); } - if (button.action == ModeOptions.ActionEnum.REPLACE) { + if (button.action == ActionEnum.REPLACE) { keybind = ClientProxy.keyBindings[1].getDisplayName(); } - if (button.action == ModeOptions.ActionEnum.OPEN_MODIFIER_SETTINGS) { + if (button.action == ActionEnum.OPEN_MODIFIER_SETTINGS) { keybind = ClientProxy.keyBindings[0].getDisplayName(); } + if (currentBuildMode.options.length > 0) { + //Add (ctrl) to first two actions of first option + if (button.action == currentBuildMode.options[0].actions[0] + || button.action == currentBuildMode.options[0].actions[1]) { + keybind = ClientProxy.keyBindings[6].getDisplayName(); + if (keybind.equals("LCONTROL")) keybind = "Ctrl"; + } + } if (!keybind.isEmpty()) keybindFormatted = TextFormatting.GRAY + "(" + WordUtils.capitalizeFully(keybind) + ")"; if (button.textSide == EnumFacing.WEST) { diff --git a/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java b/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java index 8c1fc0c..c5004eb 100644 --- a/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java +++ b/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java @@ -81,7 +81,7 @@ public class ClientProxy implements IProxy { @Override public void init(FMLInitializationEvent event) { // register key bindings - keyBindings = new KeyBinding[6]; + keyBindings = new KeyBinding[7]; // instantiate the key bindings keyBindings[0] = new KeyBinding("key.effortlessbuilding.hud.desc", KeyConflictContext.UNIVERSAL, Keyboard.KEY_ADD, "key.effortlessbuilding.category"); @@ -97,7 +97,8 @@ public class ClientProxy implements IProxy { }; keyBindings[4] = new KeyBinding("key.effortlessbuilding.undo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, Keyboard.KEY_Z, "key.effortlessbuilding.category"); keyBindings[5] = new KeyBinding("key.effortlessbuilding.redo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, Keyboard.KEY_Y, "key.effortlessbuilding.category"); -// keyBindings[6] = new KeyBinding("Reload shaders", Keyboard.KEY_TAB, "key.effortlessbuilding.category"); + keyBindings[6] = new KeyBinding("key.effortlessbuilding.altplacement.desc", KeyConflictContext.IN_GAME, Keyboard.KEY_LCONTROL, "key.effortlessbuilding.category"); +// keyBindings[7] = new KeyBinding("Reload shaders", Keyboard.KEY_TAB, "key.effortlessbuilding.category"); // register all the key bindings for (int i = 0; i < keyBindings.length; ++i) { @@ -344,8 +345,26 @@ public class ClientProxy implements IProxy { EffortlessBuilding.packetHandler.sendToServer(new ModeActionMessage(action)); } + //Change placement mode + if (keyBindings[6].isPressed()) { + //Toggle between first two actions of the first option of the current build mode + BuildModes.BuildModeEnum currentBuildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); + if (currentBuildMode.options.length > 0) { + ModeOptions.OptionEnum option = currentBuildMode.options[0]; + if (option.actions.length >= 2) { + if (ModeOptions.getOptionSetting(option) == option.actions[0]) { + ModeOptions.performAction(player, option.actions[1]); + EffortlessBuilding.packetHandler.sendToServer(new ModeActionMessage(option.actions[1])); + } else { + ModeOptions.performAction(player, option.actions[0]); + EffortlessBuilding.packetHandler.sendToServer(new ModeActionMessage(option.actions[0])); + } + } + } + } + //For shader development - if (keyBindings.length >= 7 && keyBindings[6].isPressed()) { + if (keyBindings.length >= 8 && keyBindings[7].isPressed()) { ShaderHandler.init(); EffortlessBuilding.log(player, "Reloaded shaders"); } diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java index da251c6..f40eba5 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java @@ -195,13 +195,15 @@ public class BlockPreviewRenderer { //if so, renew randomness of randomizer bag ItemRandomizerBag.renewRandomness(); //and play sound (max once every tick) - if (startCoordinates.size() > 1 && blockStates.size() > 1 && soundTime < ClientProxy.ticksInGame - 0) { + if (newCoordinates.size() > 1 && blockStates.size() > 1 && soundTime < ClientProxy.ticksInGame - 0) { soundTime = ClientProxy.ticksInGame; - SoundType soundType = blockStates.get(0).getBlock().getSoundType(blockStates.get(0), player.world, - newCoordinates.get(0), player); - player.world.playSound(player, player.getPosition(), breaking ? soundType.getBreakSound() : soundType.getPlaceSound(), - SoundCategory.BLOCKS, 0.3f, 0.8f); + if (blockStates.get(0) != null) { + SoundType soundType = blockStates.get(0).getBlock().getSoundType(blockStates.get(0), player.world, + newCoordinates.get(0), player); + player.world.playSound(player, player.getPosition(), breaking ? soundType.getBreakSound() : soundType.getPlaceSound(), + SoundCategory.BLOCKS, 0.3f, 0.8f); + } } } @@ -236,8 +238,20 @@ public class BlockPreviewRenderer { //Display block count and dimensions in actionbar if (BuildModes.isActive(player)) { - BlockPos dim = secondPos.subtract(firstPos); - dim = new BlockPos(Math.abs(dim.getX()) + 1, Math.abs(dim.getY()) + 1, Math.abs(dim.getZ()) + 1); + + //Find min and max values (not simply firstPos and secondPos because that doesn't work with circles) + int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; + int minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE; + for (BlockPos pos : startCoordinates) { + if (pos.getX() < minX) minX = pos.getX(); + if (pos.getX() > maxX) maxX = pos.getX(); + if (pos.getY() < minY) minY = pos.getY(); + if (pos.getY() > maxY) maxY = pos.getY(); + if (pos.getZ() < minZ) minZ = pos.getZ(); + if (pos.getZ() > maxZ) maxZ = pos.getZ(); + } + BlockPos dim = new BlockPos(maxX - minX + 1, maxY - minY + 1, maxZ - minZ + 1); String dimensions = "("; if (dim.getX() > 1) dimensions += dim.getX() + "x"; diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index 8386d7e..83b610e 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -206,6 +206,8 @@ public class RenderHandler implements IWorldEventListener { } protected static void renderBlockPreview(BlockRendererDispatcher dispatcher, BlockPos blockPos, IBlockState blockState) { + if (blockState == null) return; + GlStateManager.pushMatrix(); GlStateManager.translate(blockPos.getX(), blockPos.getY(), blockPos.getZ()); GlStateManager.rotate(-90.0F, 0.0F, 1.0F, 0.0F); diff --git a/src/main/resources/assets/effortlessbuilding/lang/en_us.lang b/src/main/resources/assets/effortlessbuilding/lang/en_us.lang index f292005..662ce46 100644 --- a/src/main/resources/assets/effortlessbuilding/lang/en_us.lang +++ b/src/main/resources/assets/effortlessbuilding/lang/en_us.lang @@ -2,9 +2,10 @@ key.effortlessbuilding.category=Effortless Building key.effortlessbuilding.hud.desc=Modifier Menu key.effortlessbuilding.replace.desc=Toggle QuickReplace key.effortlessbuilding.creative.desc=Toggle Survival/Creative Mode -key.effortlessbuilding.mode.desc=Radial Menu (C&B compatible) +key.effortlessbuilding.mode.desc=Radial Menu key.effortlessbuilding.undo.desc=Undo key.effortlessbuilding.redo.desc=Redo +key.effortlessbuilding.altplacement.desc=Alternative placement item.effortlessbuilding:randomizer_bag.name=Randomizer Bag item.effortlessbuilding:reach_upgrade1.name=Reach Upgrade 1 @@ -20,6 +21,9 @@ effortlessbuilding.mode.diagonal_line=Diagonal Line effortlessbuilding.mode.diagonal_wall=Diagonal Wall effortlessbuilding.mode.slope_floor=Slope Floor effortlessbuilding.mode.cube=Cube +effortlessbuilding.mode.circle=Circle +effortlessbuilding.mode.cylinder=Cylinder +effortlessbuilding.mode.sphere=Sphere effortlessbuilding.action.undo=Undo effortlessbuilding.action.redo=Redo @@ -30,6 +34,7 @@ effortlessbuilding.action.build_speed=Build Speed effortlessbuilding.action.filling=Filling effortlessbuilding.action.raised_edge=Raised Edge effortlessbuilding.action.thickness=Line Thickness +effortlessbuilding.action.circle_start=Start Point effortlessbuilding.action.normal_speed=Normal effortlessbuilding.action.fast_speed=Fast @@ -41,5 +46,7 @@ effortlessbuilding.action.long_edge=Long Edge effortlessbuilding.action.thickness_1=1 Block Thick effortlessbuilding.action.thickness_3=3 Blocks Thick effortlessbuilding.action.thickness_5=5 Blocks Thick +effortlessbuilding.action.start_center=Middle +effortlessbuilding.action.start_corner=Corner commands.reach.usage=/reach \ No newline at end of file diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/circle.png b/src/main/resources/assets/effortlessbuilding/textures/icons/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..66d5a04068bac57a2c39c8e384645458fce76bc8 GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)1F==&(na6DY!2;1OBO zz`(a3gc&VZgH?cn>?NMQuI!I_Sojr`wKP|o1BK>!x;TbdoIX1FH1A;r9tYp6yHmF{ zr0&?rq`iT$xRLDN~mCO);DJN?3k>I;V6)efveexy4zVI%K%n3aqp&y3Vl8 zV_>Lyul06s{Eo;Zw+j9{f|7!apLO?4wOLp8(2mh~ep^)b>0g#{2F40XEdn7Mu5r11 zxq1DkV)40#j@2yLZ?pVe1d?mY9cMQOb!mBMzAP>{t@7`exZZJYpUoDzO$W<<0bR=A M>FVdQ&MBb@01U-kE&u=k literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_center.png b/src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_center.png new file mode 100644 index 0000000000000000000000000000000000000000..6f1a62328ca9e25415b2ff7b9e0e6890a2fdc957 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)1F==&(na6DY!2;1OBO zz`(a3gc&VZgH?cn>?NMQuI!I_SoqaBPF^{(0Vq`J>Eak-ar$h~ZQcV495%D`4u`CH zEamgm;+uPqW7O;`%l7Km6corDn4R#<==Ww@-A&)wL$_{VaPZ<>l@%+%&~d`DQMH?6 zCQG-r;8~R|Ed5pS)6O@%^SiD5+@|anug;c>`}HS2;M7=E^wcROS-JDugZkBfZ25{; W>hF>Jv<2um1_n=8KbLh*2~7Z1jZanp literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_corner.png b/src/main/resources/assets/effortlessbuilding/textures/icons/circle_start_corner.png new file mode 100644 index 0000000000000000000000000000000000000000..f64e3eb6050980c2e9ed953a236548a032a55995 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)1F==&(na6DY!2;1OBO zz`(a3gc&VZgH?cn>?NMQuI!I_SoqbfBqWv;0)?_YT^vI!PT!q$oAZDHhim`R(dJkFL|+DhhAETWc`ef!Eaz^#rgh!kz&QWx!uvZlcU@%({}C^}DN1@%+L5<2fYvg2 My85}Sb4q9e0JNh>TL1t6 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/cylinder.png b/src/main/resources/assets/effortlessbuilding/textures/icons/cylinder.png new file mode 100644 index 0000000000000000000000000000000000000000..4964d3312fd8f12ce89f1130c00946a098b8320c GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE({An@NE9d0-y+Ifk$L9 z0|VcF5N5Ps4ORgPvX^-Jy0Sm!Vd3YNd1YUn3>4bv>Eak-aeC?GgS;&YA};dww;M_p zvQ|FH(hO>sz0v(}`eV5=#S@FP3-kW`naRVGY~gtSdv#q{Xz*?0^dka?676^nFAY%o z@BGrIq9fcgVj3&M^E-+Aa&Ox#+vdLal9{r;u2sNEXku8A z<|hl*lkxjl4MjB{XDc#yta`LJzs#UddDXAN3tB=a?NfibZDv06x#ow*Ws{{$Zbe&r hmj>LsGe_bho3T&oL&mw;FMy6`@O1TaS?83{1OTpbZG8X$ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/sphere.png b/src/main/resources/assets/effortlessbuilding/textures/icons/sphere.png new file mode 100644 index 0000000000000000000000000000000000000000..73a804a5fb566ebaa244032d36d5dd7cc766ce29 GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE({An@NE9d0-y+Ifk$L9 z0|VcF5N5Ps4ORgPvX^-Jy0Sm!Vd3Xy_-CiC0~Gq^>Eak-aeD4#+f^+FBCY!^F0<>h z2McFjHiS!Z3k zlG>Z!cFWQw+RP_#9F4sm7%Cd%#V{pE{eVRu`w3?k9~rLM%Pg;5Hj!1}_dF!NWJ*e; znCS6u4E-OzZqo6*{AI_P&wqS3NsBVr&+S})JMQ||drj{j-4-fW{B&RP*3Iv-*}CU;Dzl&0zG&G##pqV2xODQ4 z-cA3*bP0l+XkK DZWWN$ literal 0 HcmV?d00001