hollow-cube/placement-rule-improvement

* improve placement rule api and remove existing vanilla rules

* readme changes

(cherry picked from commit b0b640ccd7)
This commit is contained in:
Matt Worzala 2023-06-14 22:00:27 -04:00
parent 7c60e06e35
commit 128c525b7c
16 changed files with 179 additions and 484 deletions

36
.github/README.md vendored
View File

@ -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:<first 10 chars of commit hash>'
}
```
# 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).

View File

@ -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.
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)

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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) {}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<Shape, Facing> front = facing.getFront(instance, blockPosition);
Pair<Shape, Facing> 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<Shape, Facing> 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;
}
}
}

View File

@ -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();
}
}

View File

@ -17,8 +17,8 @@ import java.util.Objects;
import java.util.UUID;
public record PlayerHeadMeta(TagReadable readable) implements ItemMetaView<PlayerHeadMeta.Builder> {
private static final Tag<UUID> SKULL_OWNER = Tag.UUID("Id").path("SkullOwner");
private static final Tag<PlayerSkin> SKIN = Tag.Structure("Properties", new TagSerializer<PlayerSkin>() {
public static final Tag<UUID> SKULL_OWNER = Tag.UUID("Id").path("SkullOwner");
public static final Tag<PlayerSkin> SKIN = Tag.Structure("Properties", new TagSerializer<PlayerSkin>() {
private static final Tag<NBT> TEXTURES = Tag.NBT("textures");
@Override

View File

@ -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);

View File

@ -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<Material> 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;
}

View File

@ -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;
}

View File

@ -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<Vec, Block> blocks = new HashMap<>();
private final Block defaultBlock;
private MockBlockGetter(Map<Vec, Block> 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);
}
}