diff --git a/.github/README.md b/.github/README.md index dd02e3150..b5fc430ac 100644 --- a/.github/README.md +++ b/.github/README.md @@ -3,9 +3,9 @@ # Minestom -[![license](https://img.shields.io/github/license/Minestom/Minestom?style=for-the-badge&color=b2204c)](../LICENSE) +[![license](https://img.shields.io/github/license/hollow-cube/minestom-ce?style=for-the-badge&color=b2204c)](../LICENSE) [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=for-the-badge)](https://github.com/RichardLitt/standard-readme) -[![javadocs](https://img.shields.io/badge/documentation-javadocs-4d7a97?style=for-the-badge)](https://minestom.github.io/Minestom/) +[![javadocs](https://img.shields.io/badge/documentation-javadocs-4d7a97?style=for-the-badge)](https://javadoc.minestom.net) [![wiki](https://img.shields.io/badge/documentation-wiki-74aad6?style=for-the-badge)](https://wiki.minestom.net/) [![discord-banner](https://img.shields.io/discord/706185253441634317?label=discord&style=for-the-badge&color=7289da)](https://discord.gg/pkFRvqB) @@ -16,6 +16,10 @@ However, we have a complete API which is designed to allow you to make anything This is a developer API not meant to be used by end-users. Replacing Bukkit/Forge/Sponge with this **will not work** since we do not implement any of their APIs. +> **Warning** +> +> `minestom-ce` is a fork with breaking changes from `Minestom/Minestom`. The list of changes can be found [here](/CHANGELOG.md). + # Table of contents - [Install](#install) - [Usage](#usage) @@ -31,16 +35,34 @@ Minestom is not installed like Bukkit/Forge/Sponge. As Minestom is a Java library, it must be loaded the same way any other Java library may be loaded. This means you need to add Minestom as a dependency, add your code and compile by yourself. -For adding the required repositories and dependencies, [check this out](https://wiki.minestom.net/setup/dependencies) +`minestom-ce` is available on [Maven Central](https://central.sonatype.com/artifact/dev.hollowcube/minestom-ce), +and can be installed like the following (Gradle/Groovy): -The newest version can be found [here: ![](https://jitpack.io/v/Minestom/Minestom.svg)](https://jitpack.io/#Minestom/Minestom) +```groovy +repositories { + mavenCentral() +} -> Our own WIP implementation for Vanilla can be found [here](https://github.com/Minestom/VanillaReimplementation). +dependencies { + implementation 'dev.hollowcube:minestom-ce:' +} +``` # Usage An example of how to use the Minestom library is available [here](/demo). Alternatively you can check the official [wiki](https://wiki.minestom.net/) or the [javadocs](https://minestom.github.io/Minestom/). +# What is `minestom-ce`? +`minestom-ce` is a fork of `Minestom/Minestom` with some controversial/breaking changes. It was originally started as @mworzala's +personal fork for making changes, but has since been used by a number of others and aims to be relatively stable. The high +level goal of many changes are to make Minestom more of a library and less of a server implementation. For example: +* Removing extensions +* Removing logging & terminal implementations + +The name "community edition" is not a reflection of an intentional rift between the two projects, it was just a joke between +a few people that stuck. I (@mworzala) am very happy for changes in `minestom-ce` to be merged back to `Minestom/Minestom`, I +just do not necessarily have the time to do so myself. + # Why Minestom? Minecraft has evolved a lot since its release, most of the servers today do not take advantage of vanilla features and even have to struggle because of them. Our target audience is those who want to make a server that benefits little from vanilla features. e.g. creative, kitpvp. @@ -90,7 +112,7 @@ It is a field where Minecraft evolved a lot, inventories are now used a lot as c Commands are the simplest way of communication between clients and server. Since 1.13 Minecraft has incorporated a new library denominated "Brigadier", we then integrated an API designed to use the full potential of args types. # Credits -* The [contributors](https://github.com/Minestom/Minestom/graphs/contributors) of the project +* The [contributors](https://github.com/hollow-cube/minestom-ce/graphs/contributors) of the project * [The Minecraft Coalition](https://wiki.vg/) and [`#mcdevs`](https://github.com/mcdevs) - protocol and file formats research. * [The Minecraft Wiki](https://minecraft.gamepedia.com/Minecraft_Wiki) for all their useful info @@ -102,5 +124,3 @@ All WIP features are previewed as Draft PRs # License This project is licensed under the [Apache License Version 2.0](../LICENSE). - - diff --git a/CHANGELOG.md b/CHANGELOG.md index c91a94d5d..187dc42f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,13 @@ Some of these are pending, some deserve PRs, others are just minor tweaks * **breaking** Delete extensions (`mworzala/Minestom` @ `no_more_extensions`) * **breaking** Block face in digging events (`mworzala/Minestom` @ `block_break_face`) -* **breaking** Add cursor position to block place and neighbor updates (`Moulberry/Minestom` @ `block_placement_rewrite_2`) * Change `Entity#getInstance` to @UnknownNullability * Support custom component translator for serverside translation -* **breaking** Replace permission system with a simple user pluggable alternative * **breaking** Remove tinylog and MinestomTerminal implementation * Add `Tag.Transient` * Optionally allow multiple parents in event nodes * **breaking** Add sender to argument parsing chain * This allows for argument parsing based on the sender, such as in argument map. This was already present for suggestions, but not for parsing. * This is a breaking change because it changes the signature of `Argument#parse`, but most use cases should not be affected. - Support has been maintained for the old argument map signature, so only completely custom arguments will be affected. \ No newline at end of file + Support has been maintained for the old argument map signature, so only completely custom arguments will be affected. +* **breaking** [Placement rule api changes](https://github.com/hollow-cube/minestom-ce/pull/20) diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 282f335f3..4247b0485 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -13,7 +13,6 @@ import net.minestom.server.extras.lan.OpenToLAN; import net.minestom.server.extras.lan.OpenToLANConfig; import net.minestom.server.extras.optifine.OptifineSupport; import net.minestom.server.instance.block.BlockManager; -import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule; import net.minestom.server.ping.ResponseData; import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.time.TimeUnit; @@ -27,8 +26,6 @@ public class Main { BlockManager blockManager = MinecraftServer.getBlockManager(); - blockManager.registerBlockPlacementRule(new RedstonePlacementRule()); - CommandManager commandManager = MinecraftServer.getCommandManager(); commandManager.register(new TestCommand()); commandManager.register(new EntitySelectorCommand()); diff --git a/src/main/java/net/minestom/server/extras/PlacementRules.java b/src/main/java/net/minestom/server/extras/PlacementRules.java deleted file mode 100644 index bb6d92905..000000000 --- a/src/main/java/net/minestom/server/extras/PlacementRules.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.minestom.server.extras; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockManager; -import net.minestom.server.instance.block.rule.vanilla.AxisPlacementRule; -import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule; -import net.minestom.server.instance.block.rule.vanilla.WallPlacementRule; - -public final class PlacementRules { - - public static void init() { - BlockManager blockManager = MinecraftServer.getBlockManager(); - blockManager.registerBlockPlacementRule(new RedstonePlacementRule()); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.BONE_BLOCK)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.HAY_BLOCK)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.OAK_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.SPRUCE_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.BIRCH_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.JUNGLE_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.ACACIA_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.DARK_OAK_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.CRIMSON_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.WARPED_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_OAK_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_SPRUCE_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_BIRCH_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_JUNGLE_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_ACACIA_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_DARK_OAK_LOG)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_CRIMSON_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_WARPED_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.PURPUR_PILLAR)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.QUARTZ_PILLAR)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.OAK_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.SPRUCE_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.BIRCH_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.JUNGLE_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.ACACIA_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.DARK_OAK_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.CRIMSON_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.WARPED_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_OAK_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_SPRUCE_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_BIRCH_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_JUNGLE_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_ACACIA_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_DARK_OAK_WOOD)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_CRIMSON_STEM)); - blockManager.registerBlockPlacementRule(new AxisPlacementRule(Block.STRIPPED_WARPED_STEM)); - blockManager.registerBlockPlacementRule(new WallPlacementRule(Block.COBBLESTONE_WALL)); - blockManager.registerBlockPlacementRule(new WallPlacementRule(Block.MOSSY_COBBLESTONE_WALL)); - } -} diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 6ec45737b..1d1db486e 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -131,7 +131,7 @@ public class InstanceContainer extends Instance { // Change id based on neighbors final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block); if (blockPlacementRule != null) { - block = blockPlacementRule.blockUpdate(this, blockPosition, block); + block = blockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, blockPosition, block)); } // Set the block @@ -614,8 +614,8 @@ public class InstanceContainer extends Instance { if (neighborBlockPlacementRule == null) continue; final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ); - final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(this, - neighborPosition, neighborBlock); + final Block newNeighborBlock = neighborBlockPlacementRule.blockUpdate(new BlockPlacementRule.UpdateState(this, + neighborPosition, neighborBlock)); if (neighborBlock != newNeighborBlock) { setBlock(neighborPosition, newNeighborBlock); } diff --git a/src/main/java/net/minestom/server/instance/block/BlockFace.java b/src/main/java/net/minestom/server/instance/block/BlockFace.java index 3310a01b1..680486866 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockFace.java +++ b/src/main/java/net/minestom/server/instance/block/BlockFace.java @@ -32,4 +32,32 @@ public enum BlockFace { case EAST -> WEST; }; } + + public boolean isSimilar(@NotNull BlockFace other) { + return this == other || this == other.getOppositeFace(); + } + + /** + * Gets the horizontal BlockFace from the given yaw angle + * + * @param yaw the yaw angle + * @return a horizontal BlockFace + */ + public static BlockFace fromYaw(float yaw) { + float degrees = (yaw - 90) % 360; + if (degrees < 0) { + degrees += 360; + } + if (0 <= degrees && degrees < 45) { + return BlockFace.WEST; + } else if (45 <= degrees && degrees < 135) { + return BlockFace.NORTH; + } else if (135 <= degrees && degrees < 225) { + return BlockFace.EAST; + } else if (225 <= degrees && degrees < 315) { + return BlockFace.SOUTH; + } else { // 315 <= degrees && degrees < 360 + return BlockFace.WEST; + } + } } diff --git a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java index cb354931a..c3229fd55 100644 --- a/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java +++ b/src/main/java/net/minestom/server/instance/block/rule/BlockPlacementRule.java @@ -1,45 +1,63 @@ package net.minestom.server.instance.block.rule; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.coordinate.Point; +import net.minestom.server.item.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class BlockPlacementRule { - private final Block block; +import java.util.concurrent.atomic.AtomicBoolean; - public BlockPlacementRule(@NotNull Block block) { +public abstract class BlockPlacementRule { + protected final Block block; + + protected BlockPlacementRule(@NotNull Block block) { this.block = block; } /** * Called when the block state id can be updated (for instance if a neighbour block changed). + * This is first called on a newly placed block, and then this is called for all neighbors of the block * - * @param instance the instance of the block - * @param blockPosition the block position - * @param currentBlock the current block + * @param updateState The current parameters to the block update * @return the updated block */ - public abstract @NotNull Block blockUpdate(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Block currentBlock); + public @NotNull Block blockUpdate(@NotNull UpdateState updateState) { + return updateState.currentBlock(); + } /** * Called when the block is placed. + * It is recommended that you only set up basic properties on the block for this placement, such as determining facing, etc * - * @param instance the instance of the block - * @param block the block placed - * @param blockFace the block face - * @param blockPosition the block position - * @param pl the player who placed the block + * @param placementState The current parameters to the block placement * @return the block to place, {@code null} to cancel */ - public abstract @Nullable Block blockPlace(@NotNull Instance instance, - @NotNull Block block, @NotNull BlockFace blockFace, @NotNull Point blockPosition, - @NotNull Player pl); + public abstract @Nullable Block blockPlace(@NotNull PlacementState placementState); + + public boolean isSelfReplaceable(@NotNull Block block, @NotNull BlockFace blockFace, @NotNull Point cursorPosition) { + return false; + } public @NotNull Block getBlock() { return block; } + + public record PlacementState( + @NotNull Block.Getter instance, + @NotNull Block block, + @NotNull BlockFace blockFace, + @NotNull Point placePosition, + @NotNull Point cursorPosition, + @NotNull Pos playerPosition, + @NotNull ItemMeta usedItemMeta, + boolean isPlayerShifting + ) {} + + public record UpdateState(@NotNull Block.Getter instance, + @NotNull Point blockPosition, + @NotNull Block currentBlock) {} } diff --git a/src/main/java/net/minestom/server/instance/block/rule/vanilla/AxisPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/vanilla/AxisPlacementRule.java deleted file mode 100644 index 01bc0eaa6..000000000 --- a/src/main/java/net/minestom/server/instance/block/rule/vanilla/AxisPlacementRule.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.minestom.server.instance.block.rule.vanilla; - -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.instance.block.rule.BlockPlacementRule; -import net.minestom.server.coordinate.Point; -import org.jetbrains.annotations.NotNull; - -public class AxisPlacementRule extends BlockPlacementRule { - - public AxisPlacementRule(@NotNull Block block) { - super(block); - } - - @Override - public @NotNull Block blockUpdate(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Block block) { - return block; - } - - @Override - public Block blockPlace(@NotNull Instance instance, - @NotNull Block block, @NotNull BlockFace blockFace, @NotNull Point blockPosition, - @NotNull Player pl) { - String axis = "y"; - if (blockFace == BlockFace.WEST || blockFace == BlockFace.EAST) { - axis = "x"; - } else if (blockFace == BlockFace.SOUTH || blockFace == BlockFace.NORTH) { - axis = "z"; - } - return block.withProperty("axis", axis); - } -} diff --git a/src/main/java/net/minestom/server/instance/block/rule/vanilla/RedstonePlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/vanilla/RedstonePlacementRule.java deleted file mode 100644 index c0a8a6837..000000000 --- a/src/main/java/net/minestom/server/instance/block/rule/vanilla/RedstonePlacementRule.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.minestom.server.instance.block.rule.vanilla; - -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.instance.block.rule.BlockPlacementRule; -import net.minestom.server.utils.block.BlockUtils; -import net.minestom.server.coordinate.Point; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -public class RedstonePlacementRule extends BlockPlacementRule { - - public RedstonePlacementRule() { - super(Block.REDSTONE_WIRE); - } - - @Override - public @NotNull Block blockUpdate(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Block block) { - BlockUtils blockUtils = new BlockUtils(instance, blockPosition); - - String east = "none"; - String north = "none"; - String power = "0"; - String south = "none"; - String west = "none"; - - // TODO Block should have method isRedstone, as redstone connects to more than itself. - - final BlockUtils blockNorth = blockUtils.north(); - final BlockUtils blockSouth = blockUtils.south(); - final BlockUtils blockEast = blockUtils.east(); - final BlockUtils blockWest = blockUtils.west(); - int connected = 0; - - if (blockNorth.equals(Block.REDSTONE_WIRE) || blockNorth.below().equals(Block.REDSTONE_WIRE)) { - connected++; - north = "side"; - } - if (blockSouth.equals(Block.REDSTONE_WIRE) || blockSouth.below().equals(Block.REDSTONE_WIRE)) { - connected++; - south = "side"; - } - if (blockEast.equals(Block.REDSTONE_WIRE) || blockEast.below().equals(Block.REDSTONE_WIRE)) { - connected++; - east = "side"; - } - if (blockWest.equals(Block.REDSTONE_WIRE) || blockWest.below().equals(Block.REDSTONE_WIRE)) { - connected++; - west = "side"; - } - if (blockNorth.above().equals(Block.REDSTONE_WIRE)) { - connected++; - north = "up"; - } - if (blockSouth.above().equals(Block.REDSTONE_WIRE)) { - connected++; - south = "up"; - } - if (blockEast.above().equals(Block.REDSTONE_WIRE)) { - connected++; - east = "up"; - } - if (blockWest.above().equals(Block.REDSTONE_WIRE)) { - connected++; - west = "up"; - } - if (connected == 0) { - north = "side"; - south = "side"; - east = "side"; - west = "side"; - } else if (connected == 1) { - if (!north.equals("none")) { - south = "side"; - } - if (!south.equals("none")) { - north = "side"; - } - if (!east.equals("none")) { - west = "side"; - } - if (!west.equals("none")) { - east = "side"; - } - } - - // TODO power - return Block.REDSTONE_WIRE.withProperties(Map.of( - "east", east, - "north", north, - "south", south, - "west", west, - "power", power)); - } - - @Override - public Block blockPlace(@NotNull Instance instance, - @NotNull Block block, @NotNull BlockFace blockFace, @NotNull Point blockPosition, - @NotNull Player pl) { - final Block belowBlock = instance.getBlock(blockPosition.sub(0, 1, 0)); - return belowBlock.isSolid() ? block : null; - } -} diff --git a/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java deleted file mode 100644 index 0e89510eb..000000000 --- a/src/main/java/net/minestom/server/instance/block/rule/vanilla/StairsPlacementRule.java +++ /dev/null @@ -1,174 +0,0 @@ -package net.minestom.server.instance.block.rule.vanilla; - -import it.unimi.dsi.fastutil.Pair; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.instance.block.rule.BlockPlacementRule; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class StairsPlacementRule extends BlockPlacementRule { - - public StairsPlacementRule(@NotNull Block block) { - super(block); - } - - @Override - public @NotNull Block blockUpdate(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Block block) { - return block; - } - - @Override - public Block blockPlace(@NotNull Instance instance, - @NotNull Block block, @NotNull BlockFace blockFace, - @NotNull Point blockPosition, @NotNull Player player) { - Facing facing = this.getFacing(player); - Shape shape = this.getShape(instance, blockPosition, facing); - BlockFace half = BlockFace.BOTTOM; // waiting for new block faces to be implemented - String waterlogged = "false"; // waiting for water to be implemented - - return block.withProperties(Map.of( - "facing", facing.toString(), - "half", half.toString(), - "shape", shape.toString(), - "waterlogged", waterlogged)); - - } - - private enum Shape { - STRAIGHT, - OUTER_LEFT, - OUTER_RIGHT, - INNER_LEFT, - INNER_RIGHT - } - - private enum Facing { - NORTH( - new Vec(0, 0, 1), - new Vec(0, 0, -1) - ), - EAST( - new Vec(-1, 0, 0), - new Vec(1, 0, 0) - ), - SOUTH( - new Vec(0, 0, -1), - new Vec(0, 0, 1) - ), - WEST( - new Vec(1, 0, 0), - new Vec(-1, 0, 0) - ); - - private final Point front; - private final Point back; - - Facing(@NotNull Point front, @NotNull Point back) { - this.front = front; - this.back = back; - } - - @NotNull - public Pair<@Nullable Shape, @Nullable Facing> getFront(@NotNull Instance instance, @NotNull Point blockPosition) { - // TODO FIX - return null; - //return this.getProperties(instance, blockPosition.clone().add(this.front)); - } - - @NotNull - public Pair<@Nullable Shape, @Nullable Facing> getBack(@NotNull Instance instance, @NotNull Point blockPosition) { - return this.getProperties(instance, blockPosition.add(this.back)); - } - - @NotNull - private Pair<@Nullable Shape, @Nullable Facing> getProperties(@NotNull Instance instance, @NotNull Point blockPosition) { - Block block = instance.getBlock(blockPosition); - if (block.isAir()) { - return Pair.of(null, null); - } - Block state = instance.getBlock(blockPosition); - try { - // TODO: Get properties from state -// Shape shape = Shape.valueOf(state.getProperty("shape").toUpperCase()); -// Facing facing = Facing.valueOf(state.getProperty("facing").toUpperCase()); -// return Pair.of(shape, facing); - return Pair.of(null, null); - } catch (Exception ex) { - return Pair.of(null, null); - } - } - } - - @NotNull - private Shape getShape(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Facing facing) { - // TODO FIX - return null; - /*Pair front = facing.getFront(instance, blockPosition); - Pair back = facing.getBack(instance, blockPosition); - Shape shape = this.getShapeFromSide(front, facing, Shape.INNER_RIGHT, Shape.INNER_LEFT); - if (shape == null) { - shape = this.getShapeFromSide(back, facing, Shape.OUTER_RIGHT, Shape.OUTER_LEFT); - } - return shape == null ? Shape.STRAIGHT : shape;*/ - } - - @Nullable - private Shape getShapeFromSide(@NotNull Pair side, @NotNull Facing facing, @NotNull Shape right, @NotNull Shape left) { - if (side.left() == null) { - return null; - } - Facing sideFacing = side.right(); - if (facing.equals(Facing.NORTH)) { - if (sideFacing.equals(Facing.EAST)) { - return right; - } else if (sideFacing.equals(Facing.WEST)) { - return left; - } - } else if (facing.equals(Facing.SOUTH)) { - if (sideFacing.equals(Facing.EAST)) { - return left; - } else if (sideFacing.equals(Facing.WEST)) { - return right; - } - } else if (facing.equals(Facing.EAST)) { - if (sideFacing.equals(Facing.SOUTH)) { - return right; - } else if (sideFacing.equals(Facing.NORTH)) { - return left; - } - } else if (facing.equals(Facing.WEST)) { - if (sideFacing.equals(Facing.SOUTH)) { - return left; - } else if (sideFacing.equals(Facing.NORTH)) { - return right; - } - } - return null; - } - - @NotNull - private Facing getFacing(@NotNull Player player) { - float degrees = (player.getPosition().yaw() - 90) % 360; - if (degrees < 0) { - degrees += 360; - } - if (0 <= degrees && degrees < 45) { - return Facing.WEST; - } else if (45 <= degrees && degrees < 135) { - return Facing.NORTH; - } else if (135 <= degrees && degrees < 225) { - return Facing.EAST; - } else if (225 <= degrees && degrees < 315) { - return Facing.SOUTH; - } else { // 315 <= degrees && degrees < 360 - return Facing.WEST; - } - } -} diff --git a/src/main/java/net/minestom/server/instance/block/rule/vanilla/WallPlacementRule.java b/src/main/java/net/minestom/server/instance/block/rule/vanilla/WallPlacementRule.java deleted file mode 100644 index b3a942682..000000000 --- a/src/main/java/net/minestom/server/instance/block/rule/vanilla/WallPlacementRule.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.minestom.server.instance.block.rule.vanilla; - -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.BlockFace; -import net.minestom.server.instance.block.rule.BlockPlacementRule; -import net.minestom.server.coordinate.Point; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -public class WallPlacementRule extends BlockPlacementRule { - - public WallPlacementRule(@NotNull Block block) { - super(block); - } - - @Override - public @NotNull Block blockUpdate(@NotNull Instance instance, @NotNull Point blockPosition, @NotNull Block block) { - final int x = blockPosition.blockX(); - final int y = blockPosition.blockY(); - final int z = blockPosition.blockZ(); - - String east = "none"; - String north = "none"; - String south = "none"; - String up = "true"; - String waterlogged = "false"; - String west = "none"; - - if (isBlock(instance, x + 1, y, z)) { - east = "low"; - } - - if (isBlock(instance, x - 1, y, z)) { - west = "low"; - } - - if (isBlock(instance, x, y, z + 1)) { - south = "low"; - } - - if (isBlock(instance, x, y, z - 1)) { - north = "low"; - } - - return block.withProperties(Map.of( - "east", east, - "north", north, - "south", south, - "west", west, - "up", up, - "waterlogged", waterlogged)); - } - - @Override - public Block blockPlace(@NotNull Instance instance, - @NotNull Block block, @NotNull BlockFace blockFace, @NotNull Point blockPosition, - @NotNull Player pl) { - return block; - } - - private boolean isBlock(Instance instance, int x, int y, int z) { - return instance.getBlock(x, y, z).isSolid(); - } -} diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java index 6dd857a56..b22c19577 100644 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java @@ -17,8 +17,8 @@ import java.util.Objects; import java.util.UUID; public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView { - private static final Tag SKULL_OWNER = Tag.UUID("Id").path("SkullOwner"); - private static final Tag SKIN = Tag.Structure("Properties", new TagSerializer() { + public static final Tag SKULL_OWNER = Tag.UUID("Id").path("SkullOwner"); + public static final Tag SKIN = Tag.Structure("Properties", new TagSerializer() { private static final Tag TEXTURES = Tag.NBT("textures"); @Override diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 6f4679b09..cf41ebe71 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -27,6 +27,8 @@ import net.minestom.server.network.packet.server.play.BlockChangePacket; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.validate.Check; +import java.util.concurrent.atomic.AtomicBoolean; + public class BlockPlacementListener { private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); @@ -34,7 +36,7 @@ public class BlockPlacementListener { final PlayerInventory playerInventory = player.getInventory(); final Player.Hand hand = packet.hand(); final BlockFace blockFace = packet.blockFace(); - final Point blockPosition = packet.blockPosition(); + Point blockPosition = packet.blockPosition(); final Instance instance = player.getInstance(); if (instance == null) @@ -87,10 +89,24 @@ public class BlockPlacementListener { } // Get the newly placed block position - final int offsetX = blockFace == BlockFace.WEST ? -1 : blockFace == BlockFace.EAST ? 1 : 0; - final int offsetY = blockFace == BlockFace.BOTTOM ? -1 : blockFace == BlockFace.TOP ? 1 : 0; - final int offsetZ = blockFace == BlockFace.NORTH ? -1 : blockFace == BlockFace.SOUTH ? 1 : 0; - final Point placementPosition = blockPosition.add(offsetX, offsetY, offsetZ); + //todo it feels like it should be possible to have better replacement rules than this, feels pretty scuffed. + Point placementPosition = blockPosition; + var interactedPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(interactedBlock); + if (interactedPlacementRule == null || !interactedPlacementRule.isSelfReplaceable(interactedBlock, blockFace, cursorPosition)) { + // If the block is not replaceable, try to place next to it. + final int offsetX = blockFace == BlockFace.WEST ? -1 : blockFace == BlockFace.EAST ? 1 : 0; + final int offsetY = blockFace == BlockFace.BOTTOM ? -1 : blockFace == BlockFace.TOP ? 1 : 0; + final int offsetZ = blockFace == BlockFace.NORTH ? -1 : blockFace == BlockFace.SOUTH ? 1 : 0; + placementPosition = blockPosition.add(offsetX, offsetY, offsetZ); + + var placementBlock = instance.getBlock(placementPosition); + var placementRule = BLOCK_MANAGER.getBlockPlacementRule(placementBlock); + if (!placementBlock.registry().isReplaceable() && (placementRule == null || + !placementRule.isSelfReplaceable(placementBlock, blockFace, cursorPosition))) { + // If the block is still not replaceable, cancel the placement + canPlaceBlock = false; + } + } if (!canPlaceBlock) { // Send a block change with the real block in the instance to keep the client in sync, @@ -136,7 +152,11 @@ public class BlockPlacementListener { final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(resultBlock); if (blockPlacementRule != null) { // Get id from block placement rule instead of the event - resultBlock = blockPlacementRule.blockPlace(instance, resultBlock, blockFace, blockPosition, player); + resultBlock = blockPlacementRule.blockPlace(new BlockPlacementRule.PlacementState( + instance, resultBlock, blockFace, + placementPosition, cursorPosition, + player.getPosition(), usedItem.meta(), player.isSneaking()) + ); } if (resultBlock == null) { refresh(player, chunk); diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index bf032291a..9886982c9 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -172,6 +172,7 @@ public final class Registry { private final boolean liquid; private final boolean occludes; private final int lightEmission; + private final boolean replaceable; private final String blockEntity; private final int blockEntityId; private final Supplier materialSupplier; @@ -191,9 +192,10 @@ public final class Registry { this.jumpFactor = main.getDouble("jumpFactor", 1); this.air = main.getBoolean("air", false); this.solid = main.getBoolean("solid"); - this.occludes = main.getBoolean("occludes", true); this.liquid = main.getBoolean("liquid", false); + this.occludes = main.getBoolean("occludes", true); this.lightEmission = main.getInt("lightEmission", 0); + this.replaceable = main.getBoolean("replaceable", false); { Properties blockEntity = main.section("blockEntity"); if (blockEntity != null) { @@ -259,18 +261,22 @@ public final class Registry { return solid; } - public boolean occludes() { - return occludes; - } - public boolean isLiquid() { return liquid; } + public boolean occludes() { + return occludes; + } + public int lightEmission() { return lightEmission; } + public boolean isReplaceable() { + return replaceable; + } + public boolean isBlockEntity() { return blockEntity != null; } diff --git a/src/main/java/net/minestom/server/utils/block/BlockUtils.java b/src/main/java/net/minestom/server/utils/block/BlockUtils.java index 12dae438d..dd6684925 100644 --- a/src/main/java/net/minestom/server/utils/block/BlockUtils.java +++ b/src/main/java/net/minestom/server/utils/block/BlockUtils.java @@ -17,10 +17,10 @@ import java.util.Objects; public class BlockUtils { - private final Instance instance; + private final Block.Getter instance; private final Point position; - public BlockUtils(Instance instance, Point position) { + public BlockUtils(Block.Getter instance, Point position) { this.instance = instance; this.position = position; } diff --git a/testing/src/main/java/net/minestom/testing/util/MockBlockGetter.java b/testing/src/main/java/net/minestom/testing/util/MockBlockGetter.java new file mode 100644 index 000000000..f923b658b --- /dev/null +++ b/testing/src/main/java/net/minestom/testing/util/MockBlockGetter.java @@ -0,0 +1,42 @@ +package net.minestom.testing.util; + +import net.minestom.server.coordinate.Vec; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.HashMap; +import java.util.Map; + +public class MockBlockGetter implements Block.Getter, Block.Setter { + + public static @NotNull MockBlockGetter empty() { + return new MockBlockGetter(Map.of(), Block.AIR); + } + + public static @NotNull MockBlockGetter single(@NotNull Block block) { + return new MockBlockGetter(Map.of(Vec.ZERO, block), Block.AIR); + } + + public static @NotNull MockBlockGetter all(@NotNull Block block) { + return new MockBlockGetter(Map.of(), block); + } + + private final Map blocks = new HashMap<>(); + private final Block defaultBlock; + + private MockBlockGetter(Map blocks, Block defaultBlock) { + blocks.forEach((pos, block) -> this.blocks.put(new Vec(pos.blockX(), pos.blockY(), pos.blockZ()), block)); + this.defaultBlock = defaultBlock; + } + + @Override + public @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) { + return blocks.getOrDefault(new Vec(x, y, z), defaultBlock); + } + + @Override + public void setBlock(int x, int y, int z, @NotNull Block block) { + blocks.put(new Vec(x, y, z), block); + } +}