diff --git a/.gitignore b/.gitignore index c04d0e49..6c2b7156 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ world/ /datapack-tests/mojang-data /mojang-data/1.20.4 /mojang-data/1.21.1 + +.DS_Store diff --git a/block-update-system/build.gradle.kts b/block-update-system/build.gradle.kts deleted file mode 100644 index f99bf1f6..00000000 --- a/block-update-system/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - compileOnly(project(":core")) -} \ No newline at end of file diff --git a/block-update-system/src/main/java/net/minestom/vanilla/BlockUpdateFeature.java b/block-update-system/src/main/java/net/minestom/vanilla/BlockUpdateFeature.java deleted file mode 100644 index c4abc095..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/BlockUpdateFeature.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.minestom.vanilla; - - -import net.kyori.adventure.key.Key; -import net.minestom.vanilla.blockupdatesystem.BlockUpdateManager; -import net.minestom.vanilla.logging.Loading; -import net.minestom.vanilla.randomticksystem.RandomTickManager; -import org.jetbrains.annotations.NotNull; - -public class BlockUpdateFeature implements VanillaReimplementation.Feature { - @Override - public void hook(@NotNull HookContext context) { - Loading.start("Block Update Manager"); - BlockUpdateManager.init(context); - Loading.finish(); - - Loading.start("Random Tick Manager"); - RandomTickManager.init(context); - Loading.finish(); - } - - @Override - public @NotNull Key key() { - return Key.key("vri:blockupdate"); - } -} diff --git a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdatable.java b/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdatable.java deleted file mode 100644 index d372c56a..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdatable.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.minestom.vanilla.blockupdatesystem; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import org.jetbrains.annotations.NotNull; - -public interface BlockUpdatable { - /** - * Called when a block is updated. - * - * @param info The block update info. - */ - void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info); -} diff --git a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateInfo.java b/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateInfo.java deleted file mode 100644 index e91d7e3a..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateInfo.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.minestom.vanilla.blockupdatesystem; - -public interface BlockUpdateInfo { - - static DestroyBlock DESTROY_BLOCK() { - return new DestroyBlock(); - } - - static PlaceBlock PLACE_BLOCK() { - return new PlaceBlock(); - } - - static ChunkLoad CHUNK_LOAD() { - return new ChunkLoad(); - } - - static MoveBlock MOVE_BLOCK() { - return new MoveBlock(); - } - - record DestroyBlock() implements BlockUpdateInfo { - } - - record PlaceBlock() implements BlockUpdateInfo { - } - - record ChunkLoad() implements BlockUpdateInfo { - } - - record MoveBlock() implements BlockUpdateInfo { - } -} diff --git a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateManager.java b/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateManager.java deleted file mode 100644 index 99c7592f..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/blockupdatesystem/BlockUpdateManager.java +++ /dev/null @@ -1,158 +0,0 @@ -package net.minestom.vanilla.blockupdatesystem; - -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.event.Event; -import net.minestom.server.event.EventNode; -import net.minestom.server.event.instance.InstanceChunkLoadEvent; -import net.minestom.server.event.instance.InstanceTickEvent; -import net.minestom.server.event.player.PlayerBlockBreakEvent; -import net.minestom.server.event.player.PlayerBlockPlaceEvent; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.VanillaReimplementation; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * A utility class used to facilitate block updates - */ -public class BlockUpdateManager { - // Block update manager by instance - private static final Map instance2BlockUpdateManager = - Collections.synchronizedMap(new WeakHashMap<>()); - - // Block updatables - private static final Short2ObjectMap blockUpdatables = new Short2ObjectOpenHashMap<>(); - - public static void registerUpdatable(short stateId, @NotNull BlockUpdatable updatable) { - synchronized (blockUpdatables) { - blockUpdatables.put(stateId, updatable); - } - } - - public static void init(@NotNull VanillaReimplementation.Feature.HookContext context) { - EventNode eventNode = context.vri().process().eventHandler(); - - eventNode.addListener(InstanceTickEvent.class, BlockUpdateManager::instanceTick); - eventNode.addListener(PlayerBlockBreakEvent.class, event -> - BlockUpdateManager.from(event.getPlayer().getInstance()) - .scheduleNeighborsUpdate(event.getBlockPosition(), - BlockUpdateInfo.DESTROY_BLOCK()) - ); - eventNode.addListener(PlayerBlockPlaceEvent.class, event -> - BlockUpdateManager.from(event.getPlayer().getInstance()) - .scheduleNeighborsUpdate(event.getBlockPosition(), - BlockUpdateInfo.PLACE_BLOCK()) - ); - eventNode.addListener(InstanceChunkLoadEvent.class, event -> { - Chunk chunk = event.getChunk(); - int minY = chunk.getMinSection() * Chunk.CHUNK_SECTION_SIZE; - int maxY = chunk.getMaxSection() * Chunk.CHUNK_SECTION_SIZE; - int minX = chunk.getChunkX() * Chunk.CHUNK_SIZE_X; - int minZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z; - - Instance instance = event.getInstance(); - BlockUpdateManager.from(instance); - - synchronized (blockUpdatables) { - for (int x = minX; x < minX + Chunk.CHUNK_SIZE_X; x++) { - for (int z = minZ; z < minZ + Chunk.CHUNK_SIZE_Z; z++) { - for (int y = minY; y < maxY; y++) { - Block block = chunk.getBlock(x, y, z); - BlockUpdatable updatable = blockUpdatables.get((short) block.stateId()); - if (updatable == null) continue; - updatable.blockUpdate(instance, new Vec(x, y, z), BlockUpdateInfo.CHUNK_LOAD()); - } - } - } - } - }); - } - - private static void instanceTick(InstanceTickEvent event) { - Instance instance = event.getInstance(); - from(instance).tick(event.getDuration()); - } - - public static @NotNull BlockUpdateManager from(@NotNull Instance instance) { - return instance2BlockUpdateManager.computeIfAbsent(instance, BlockUpdateManager::new); - } - - private final Map updateNeighbors = Collections.synchronizedMap(new LinkedHashMap<>()); - private final BlockUpdateManager.UpdateHandler updateHandler; - - public BlockUpdateManager(@NotNull BlockUpdateManager.UpdateHandler updateHandler) { - this.updateHandler = updateHandler; - } - - private BlockUpdateManager(@NotNull Instance instance) { - this.updateHandler = (pos, info) -> { - if (instance.getBlock(pos).handler() instanceof BlockUpdatable updatable) { - updatable.blockUpdate(instance, pos, info); - } - }; - } - - public interface UpdateHandler { - void update(@NotNull Point pos, @NotNull BlockUpdateInfo info); - } - - // Public api methods - - /** - * Schedules this position's neighbors to be updated next tick. - */ - public void scheduleNeighborsUpdate(Point pos, BlockUpdateInfo info) { - updateNeighbors.put(pos, info); - } - - // Public api methods end - - private void tick(int duration) { - updateNeighbors(duration); - } - - private void updateNeighbors(int duration) { - if (updateNeighbors.isEmpty()) { - return; - } - - // Update all the neighbors - for (Map.Entry entry : updateNeighbors.entrySet()) { - Point pos = entry.getKey(); - BlockUpdateInfo info = entry.getValue(); - - int x = pos.blockX(); - int y = pos.blockY(); - int z = pos.blockZ(); - - // For each surrounding block - for (int offsetX = -1; offsetX < 2; offsetX++) { - for (int offsetY = -1; offsetY < 2; offsetY++) { - for (int offsetZ = -1; offsetZ < 2; offsetZ++) { - - // If block is not the original block - if (offsetX == 0 && offsetY == 0 && offsetZ == 0) { - continue; - } - - // Get the block handler at the position - Point blockPos = new Pos(x + offsetX, y + offsetY, z + offsetZ); - updateHandler.update(blockPos, info); - } - } - } - } - - updateNeighbors.clear(); - } -} diff --git a/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickManager.java b/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickManager.java deleted file mode 100644 index cb6cd2ef..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickManager.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.minestom.vanilla.randomticksystem; - -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.event.instance.InstanceTickEvent; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.VanillaReimplementation; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.Map; -import java.util.Random; -import java.util.WeakHashMap; - -public class RandomTickManager { - - private static final @NotNull String RANDOM_TICK_SYSTEM_PROPERTY = "vri.gamerule.randomtickspeed"; - - private static final Map vri2managers = - Collections.synchronizedMap(new WeakHashMap<>()); - private static final Short2ObjectMap randomTickables = new Short2ObjectOpenHashMap<>(); - - private final VanillaReimplementation vri; - private RandomTickManager(VanillaReimplementation vri) { - this.vri = vri; - } - - public static @NotNull RandomTickManager create(@NotNull VanillaReimplementation vri) { - return vri2managers.computeIfAbsent(vri, RandomTickManager::new); - } - - public static void init(VanillaReimplementation.Feature.@NotNull HookContext context) { - RandomTickManager manager = create(context.vri()); - context.vri().process().eventHandler().addListener(InstanceTickEvent.class, event -> { - int randomTickCount = Integer.parseInt(System.getProperty(RANDOM_TICK_SYSTEM_PROPERTY, "3")); - manager.handleInstanceTick(event, randomTickCount); - }); - } - - public static void registerRandomTickable(short stateId, RandomTickable randomTickable) { - synchronized (randomTickables) { - randomTickables.put(stateId, randomTickable); - } - } - - private void handleInstanceTick(InstanceTickEvent event, int randomTickCount) { - Instance instance = event.getInstance(); - Random instanceRandom = vri.random(instance); - synchronized (randomTickables) { - for (Chunk chunk : instance.getChunks()) { - int minSection = chunk.getMinSection(); - int maxSection = chunk.getMaxSection(); - for (int section = minSection; section < maxSection; section++) { - for (int i = 0; i < randomTickCount; i++) { - randomTickSection(instanceRandom, instance, chunk, section); - } - } - } - } - } - - private void randomTickSection(Random random, Instance instance, Chunk chunk, int minSection) { - int minX = chunk.getChunkX() * Chunk.CHUNK_SIZE_X; - int minZ = chunk.getChunkZ() * Chunk.CHUNK_SIZE_Z; - int minY = minSection * Chunk.CHUNK_SECTION_SIZE; - - int x = minX + random.nextInt(Chunk.CHUNK_SIZE_X); - int z = minZ + random.nextInt(Chunk.CHUNK_SIZE_Z); - int y = minY + random.nextInt(Chunk.CHUNK_SECTION_SIZE); - Point pos = new Vec(x, y, z); - - Block block = instance.getBlock(x, y, z); - RandomTickable randomTickable = randomTickables.get((short) block.stateId()); - if (randomTickable == null) return; - randomTickable.randomTick(new RandomTick(instance, pos, block)); - } - - private record RandomTick(Instance instance, Point position, Block block) implements RandomTickable.RandomTick {} -} diff --git a/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickable.java b/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickable.java deleted file mode 100644 index a475e280..00000000 --- a/block-update-system/src/main/java/net/minestom/vanilla/randomticksystem/RandomTickable.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.minestom.vanilla.randomticksystem; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import org.jetbrains.annotations.NotNull; - -public interface RandomTickable { - - void randomTick(@NotNull RandomTick randomTick); - - interface RandomTick { - @NotNull Instance instance(); - @NotNull Point position(); - @NotNull Block block(); - } -} diff --git a/blocks/build.gradle.kts b/blocks/build.gradle.kts index a95929e8..992e7e36 100644 --- a/blocks/build.gradle.kts +++ b/blocks/build.gradle.kts @@ -1,6 +1,3 @@ dependencies { - compileOnly(project(":core")) - compileOnly(project(":block-update-system")) - compileOnly(project(":entity-meta")) - compileOnly(project(":datapack-loading")) + api(project(":core")) } \ No newline at end of file diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/BlockBehaviorRuleRegistrations.java b/blocks/src/main/java/net/minestom/vanilla/blocks/BlockBehaviorRuleRegistrations.java new file mode 100644 index 00000000..0fcba14f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/BlockBehaviorRuleRegistrations.java @@ -0,0 +1,56 @@ +package net.minestom.vanilla.blocks; + +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import net.minestom.server.MinecraftServer; +import net.minestom.vanilla.blocks.group.VanillaBlockBehaviour; +import net.minestom.vanilla.blocks.group.behaviour.BehaviourGroup; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BlockBehaviorRuleRegistrations { + private static final ComponentLogger logger = MinecraftServer.LOGGER; + + private static final VanillaBlockBehaviour BLOCK_BEHAVIOUR = VanillaBlockBehaviour.INSTANCE; + + public static void registerDefault() { + register(VanillaBlockBehaviour.CRAFTING_TABLE); + register(VanillaBlockBehaviour.ANVIL); + register(VanillaBlockBehaviour.BREWING_STAND); + register(VanillaBlockBehaviour.LOOM); + register(VanillaBlockBehaviour.GRINDSTONE); + register(VanillaBlockBehaviour.SMITHING_TABLE); + register(VanillaBlockBehaviour.CARTOGRAPHY_TABLE); + register(VanillaBlockBehaviour.STONECUTTER); + register(VanillaBlockBehaviour.ENCHANTING_TABLE); + register(VanillaBlockBehaviour.TRAPDOOR); + register(VanillaBlockBehaviour.FENCE_GATE); + register(VanillaBlockBehaviour.COPPER); + register(VanillaBlockBehaviour.WOODEN_DOORS); + register(VanillaBlockBehaviour.SIGNS); + register(VanillaBlockBehaviour.CAKE); + register(VanillaBlockBehaviour.CANDLE_CAKE); + register(VanillaBlockBehaviour.STRIPPABLE_WOOD); + } + + public static void register(BehaviourGroup... blockGroups) { + var blockManager = MinecraftServer.getBlockManager(); + int count = 0; + + for (BehaviourGroup group : blockGroups) { + var blockGroup = group.getBlockGroup(); + for (var block : blockGroup.allMatching()) { + count++; + var handler = group.createHandler(block); + blockManager.registerHandler(block.key().asString(), () -> handler); + } + } + + logger.info("Registered {} handlers", count); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/BlockPlacementRuleRegistrations.java b/blocks/src/main/java/net/minestom/vanilla/blocks/BlockPlacementRuleRegistrations.java new file mode 100644 index 00000000..23177a8a --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/BlockPlacementRuleRegistrations.java @@ -0,0 +1,93 @@ +package net.minestom.vanilla.blocks; + +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import net.minestom.server.MinecraftServer; +import net.minestom.vanilla.blocks.group.VanillaPlacementRules; +import net.minestom.vanilla.blocks.group.placement.PlacementGroup; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BlockPlacementRuleRegistrations { + private static final ComponentLogger logger = MinecraftServer.LOGGER; + + private static final VanillaPlacementRules PLACEMENT_RULES = VanillaPlacementRules.INSTANCE; + + public static void registerDefault() { + register( + VanillaPlacementRules.ROTATED_PILLARS, + VanillaPlacementRules.SLAB, + VanillaPlacementRules.VERTICALLY_ROTATED, + VanillaPlacementRules.ROTATED_WORKSTATIONS, + VanillaPlacementRules.AMETHYST, + VanillaPlacementRules.BAMBOO, + VanillaPlacementRules.BANNER, + VanillaPlacementRules.FACING, + VanillaPlacementRules.OBSERVER, + VanillaPlacementRules.SIMPLE_WATERLOGGABLE, + VanillaPlacementRules.BEDS, + VanillaPlacementRules.CROPS, + VanillaPlacementRules.BELL, + VanillaPlacementRules.BIG_DRIPLEAF, + VanillaPlacementRules.BOTTOM_SUPPORTED, + VanillaPlacementRules.PIN_BOTTOM_SUPPORTED, + VanillaPlacementRules.BUTTONS, + VanillaPlacementRules.CACTUS, + VanillaPlacementRules.CAMPFIRE, + VanillaPlacementRules.CANDLES, + VanillaPlacementRules.VINES_TOP, + VanillaPlacementRules.TRAPDOOR, + VanillaPlacementRules.FENCE, + VanillaPlacementRules.FENCE_GATE, + VanillaPlacementRules.STAIRS, + VanillaPlacementRules.VERTICAL_SLIM, + VanillaPlacementRules.LADDERS, + VanillaPlacementRules.TORCHES, + VanillaPlacementRules.WALLS, + VanillaPlacementRules.DOORS, + VanillaPlacementRules.LANTERNS, + VanillaPlacementRules.GLAZED_TERRACOTTA, + VanillaPlacementRules.CHAINS, + VanillaPlacementRules.TALL_FLOWERS, + VanillaPlacementRules.SIGNS, + VanillaPlacementRules.CHESTS, + VanillaPlacementRules.HOPPERS, + VanillaPlacementRules.SHULKERBOXES, + VanillaPlacementRules.FLOOR_FLOWER, + VanillaPlacementRules.CORALS, + VanillaPlacementRules.WALL_CORALS, + VanillaPlacementRules.HEADS, + VanillaPlacementRules.SUGAR_CANE, + VanillaPlacementRules.GROUNDED_PLANTS, + VanillaPlacementRules.CRAFTER, + VanillaPlacementRules.LEVER, + VanillaPlacementRules.REDSTONE_STUFF, + VanillaPlacementRules.FARMLAND, + VanillaPlacementRules.SNOWY, + VanillaPlacementRules.MUSHROOM, + VanillaPlacementRules.RAIL, + VanillaPlacementRules.FEATURE_RAIL, + VanillaPlacementRules.GRINDSTONE + ); + } + + public static void register(PlacementGroup... blockGroups) { + var blockManager = MinecraftServer.getBlockManager(); + int count = 0; + + for (PlacementGroup placementGroup : blockGroups) { + var blockGroup = placementGroup.getBlockGroup(); + for (var block : blockGroup.allMatching()) { + count++; + blockManager.registerBlockPlacementRule(placementGroup.createRule(block)); + } + } + + logger.info("Registered {} block placement rules", count); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/PlacedHandlerRegistration.java b/blocks/src/main/java/net/minestom/vanilla/blocks/PlacedHandlerRegistration.java new file mode 100644 index 00000000..3d0c01b6 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/PlacedHandlerRegistration.java @@ -0,0 +1,24 @@ +package net.minestom.vanilla.blocks; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.event.player.PlayerBlockPlaceEvent; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class PlacedHandlerRegistration { + + public static void registerDefault() { + MinecraftServer.getGlobalEventHandler().addListener(PlayerBlockPlaceEvent.class, event -> { + var handler = MinecraftServer.getBlockManager().getHandler(event.getBlock().key().asString()); + if (event.getBlock().handler() != handler) { + event.setBlock(event.getBlock().withHandler(handler)); + } + }); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockBehaviour.java deleted file mode 100644 index 9a540a35..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockBehaviour.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.minestom.vanilla.blocks; - -import net.kyori.adventure.key.Key; -import net.minestom.server.coordinate.Point; -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.BlockHandler; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * Represents a singular vanilla block's logic. e.g. white bed, cake, furnace, etc. - */ -public abstract class VanillaBlockBehaviour implements BlockHandler { - - protected final @NotNull VanillaBlocks.BlockContext context; - protected final short baseBlock; - protected final @NotNull Key key; - - protected VanillaBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - this.context = context; - this.baseBlock = context.stateId(); - this.key = Objects.requireNonNull(Block.fromStateId(context.stateId())).key(); - } - - /** - * DO NOT USE THIS. - * @see #onPlace(VanillaPlacement) instead. - */ - @Override - @Deprecated - public void onPlace(@NotNull BlockHandler.Placement placement) { - } - - public void onPlace(@NotNull VanillaPlacement placement) { - } - - @Override - public @NotNull Key getKey() { - return key; - } - - public interface VanillaPlacement { - - /** - * @return the block that will be placed - */ - @NotNull Block blockToPlace(); - - /** - * @return the instance that will be modified - */ - @NotNull Instance instance(); - - /** - * @return the position of the block that will be placed - */ - @NotNull Point position(); - - /** - * Overrides the current block to be placed. - * - * @param newBlock the new block to be placed - */ - void blockToPlace(@NotNull Block newBlock); - - interface HasPlayer { - @NotNull Player player(); - } - } - -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockLoot.java b/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockLoot.java deleted file mode 100644 index 4f37ea84..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlockLoot.java +++ /dev/null @@ -1,191 +0,0 @@ -package net.minestom.vanilla.blocks; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.ItemEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.event.player.PlayerBlockBreakEvent; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.vanilla.VanillaReimplementation; -import net.minestom.vanilla.datapack.Datapack; -import net.minestom.vanilla.datapack.loot.LootTable; -import net.minestom.vanilla.datapack.loot.context.LootContext; -import net.minestom.vanilla.datapack.loot.function.LootFunction; -import net.minestom.vanilla.datapack.loot.function.Predicate; -import net.minestom.vanilla.files.FileSystem; -import net.minestom.vanilla.logging.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Random; -import java.util.function.Consumer; -import java.util.random.RandomGenerator; - -public record VanillaBlockLoot(VanillaReimplementation vri, Datapack datapack) { - - private record LootEntry(@Nullable List functions, List items, double weight) { - } - - public void spawnLoot(@NotNull PlayerBlockBreakEvent event) { - String blockName = event.getBlock().key().value(); - datapack.namespacedData().forEach((namespace, data) -> { - FileSystem blocks = data.loot_tables().folder("blocks"); - var lootTable = blocks.file(blockName + ".json"); - if (lootTable == null) return; - - Block blockState = event.getBlock(); - Point origin = event.getBlockPosition(); - ItemStack tool = event.getPlayer().getItemInMainHand(); - Player entity = event.getPlayer(); - Block blockEntity = blockState.registry().blockEntity() == null ? null : blockState; - Random random = vri.random(entity); - - LootContext context = new LootContext.Block(blockState, origin, tool, entity, blockEntity, null); - - List items = new ArrayList<>(); - generateLootItems(lootTable, context, random, items::add); - - for (ItemStack item : items) { - ItemEntity itemEntity = new ItemEntity(item); - itemEntity.setInstance(entity.getInstance(), origin.add(0.5)); - } - }); - } - - public List getLoot(LootTable lootTable, LootContext context) { - return getLoot(lootTable, context, vri.random(0)); - } - - public List getLoot(LootTable lootTable, LootContext context, Random random) { - List items = new ArrayList<>(); - generateLootItems(lootTable, context, random, items::add); - return items; - } - - private void generateLootItems(LootTable lootTable, LootContext context, Random random, Consumer out) { - if (lootTable.pools() == null) return; // TODO: handle random_sequence - for (LootTable.Pool pool : lootTable.pools()) { - - // Ensure all conditions are met - if (fails(pool.conditions(), context)) continue; - - int rolls = pool.rolls().asInt().apply(() -> random); - - // collect all of the loot entries - List entries = new ArrayList<>(); - for (LootTable.Pool.Entry entry : pool.entries()) { - - // Ensure all conditions are met - if (fails(entry.conditions(), context)) continue; - - // now we can add the entries - addEntries(context, entry, itemGenerator -> { - double weight = itemGenerator.weight() == null ? 1 : Objects.requireNonNull(itemGenerator.weight()).asDouble().apply(() -> random); - var lootEntries = itemGenerator.apply(datapack, context); - for (List lootEntryItems : lootEntries) { - entries.add(new LootEntry(itemGenerator.functions(), lootEntryItems, weight)); - } - }); - } - - // if there is no entries, we can skip this pool - if (entries.isEmpty()) continue; - - // now that we have all the entries, we can roll for them - double totalWeight = entries.stream().mapToDouble(LootEntry::weight).sum(); - for (int i = 0; i < rolls; i++) { - LootEntry chosenLootEntry = null; - double roll = random.nextDouble() * totalWeight; - for (LootEntry lootEntry : entries) { - roll -= lootEntry.weight(); - if (roll <= 0) { - chosenLootEntry = lootEntry; - break; - } - } - Objects.requireNonNull(chosenLootEntry); - - // we now have the loot entry, we need to apply the loot functions - LootEntry finalChosenLootEntry = chosenLootEntry; - chosenLootEntry.items() - .stream() - // loot entry functions - .map(item -> { - if (finalChosenLootEntry.functions() == null) return item; - for (LootFunction function : finalChosenLootEntry.functions()) { - item = function.apply(new LootFunctionContext(random, item, context)); - } - return item; - }) - // pool functions - .map(item -> { - if (pool.functions() == null) return item; - for (LootFunction function : pool.functions()) { - item = function.apply(new LootFunctionContext(random, item, context)); - } - return item; - }) - // table functions - .map(item -> { - if (lootTable.functions() == null) return item; - for (LootFunction function : lootTable.functions()) { - item = function.apply(new LootFunctionContext(random, item, context)); - } - return item; - }) - // add all of the items to the list - .forEach(out); - } - } - } - - private record LootFunctionContext(RandomGenerator random, ItemStack itemStack, LootContext context) implements LootFunction.Context { - @Override - public @Nullable T get(Trait trait) { - return context.get(trait); - } - } - - private static boolean fails(@Nullable List predicates, LootContext context) { - if (predicates == null) return false; - for (Predicate predicate : predicates) { - if (!predicate.test(context)) { - return true; - } - } - return false; - } - - private void addEntries(LootContext context, LootTable.Pool.Entry entry, Consumer out) { - if (fails(entry.conditions(), context)) return; - switch (entry.type().toString()) { - case "minecraft:item", "minecraft:tag", "minecraft:dynamic", "minecraft:empty" -> out.accept((LootTable.Pool.Entry.ItemGenerator) entry); - case "minecraft:loot_table" -> // TODO: recursive loot tables - Logger.debug("Recursive loot tables are not yet supported"); - case "minecraft:group" -> { - LootTable.Pool.Entry.Group group = (LootTable.Pool.Entry.Group) entry; - for (LootTable.Pool.Entry groupEntry : group.children()) { - addEntries(context, groupEntry, out); - } - } - case "minecraft:alternatives" -> { - LootTable.Pool.Entry.Alternatives alternatives = (LootTable.Pool.Entry.Alternatives) entry; - for (LootTable.Pool.Entry alternative : alternatives.children()) { - if (fails(alternative.conditions(), context)) continue; - addEntries(context, alternative, out); - break; - } - } - case "minecraft:sequence" -> { - LootTable.Pool.Entry.Sequence sequence = (LootTable.Pool.Entry.Sequence) entry; - for (LootTable.Pool.Entry alternative : sequence.children()) { - if (fails(alternative.conditions(), context)) break; - addEntries(context, alternative, out); - } - } - } - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocks.java b/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocks.java deleted file mode 100644 index e64db7a8..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocks.java +++ /dev/null @@ -1,298 +0,0 @@ -package net.minestom.vanilla.blocks; - -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.GameMode; -import net.minestom.server.entity.Player; -import net.minestom.server.event.Event; -import net.minestom.server.event.EventListener; -import net.minestom.server.event.EventNode; -import net.minestom.server.event.player.PlayerBlockBreakEvent; -import net.minestom.server.event.player.PlayerBlockPlaceEvent; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.VanillaReimplementation; -import net.minestom.vanilla.blocks.behaviours.*; -import net.minestom.vanilla.blocks.behaviours.oxidisable.OxidatableBlockBehaviour; -import net.minestom.vanilla.blocks.behaviours.oxidisable.WaxedBlockBehaviour; -import net.minestom.vanilla.blocks.behaviours.recipe.*; -import net.minestom.vanilla.blockupdatesystem.BlockUpdatable; -import net.minestom.vanilla.blockupdatesystem.BlockUpdateManager; -import net.minestom.vanilla.datapack.DatapackLoadingFeature; -import net.minestom.vanilla.randomticksystem.RandomTickManager; -import net.minestom.vanilla.randomticksystem.RandomTickable; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * All blocks available in the vanilla reimplementation - */ -public enum VanillaBlocks { - - SAND(Block.SAND, GravityBlockBehaviour::new), - RED_SAND(Block.RED_SAND, GravityBlockBehaviour::new), - GRAVEL(Block.GRAVEL, GravityBlockBehaviour::new), - - // Start of concrete powders - WHITE_CONCRETE_POWDER(Block.WHITE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.WHITE_CONCRETE)), - BLACK_CONCRETE_POWDER(Block.BLACK_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BLACK_CONCRETE)), - LIGHT_BLUE_CONCRETE_POWDER(Block.LIGHT_BLUE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIGHT_BLUE_CONCRETE)), - BLUE_CONCRETE_POWDER(Block.BLUE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BLUE_CONCRETE)), - RED_CONCRETE_POWDER(Block.RED_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.RED_CONCRETE)), - GREEN_CONCRETE_POWDER(Block.GREEN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.GREEN_CONCRETE)), - YELLOW_CONCRETE_POWDER(Block.YELLOW_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.YELLOW_CONCRETE)), - PURPLE_CONCRETE_POWDER(Block.PURPLE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.PURPLE_CONCRETE)), - MAGENTA_CONCRETE_POWDER(Block.MAGENTA_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.MAGENTA_CONCRETE)), - CYAN_CONCRETE_POWDER(Block.CYAN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.CYAN_CONCRETE)), - PINK_CONCRETE_POWDER(Block.PINK_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.PINK_CONCRETE)), - GRAY_CONCRETE_POWDER(Block.GRAY_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.GRAY_CONCRETE)), - LIGHT_GRAY_CONCRETE_POWDER(Block.LIGHT_GRAY_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIGHT_GRAY_CONCRETE)), - ORANGE_CONCRETE_POWDER(Block.ORANGE_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.ORANGE_CONCRETE)), - BROWN_CONCRETE_POWDER(Block.BROWN_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.BROWN_CONCRETE)), - LIME_CONCRETE_POWDER(Block.LIME_CONCRETE_POWDER, (context) -> new ConcretePowderBlockBehaviour(context, Block.LIME_CONCRETE)), - // End of concrete powders - - // Start of oxidisable copper - // Blocks - COPPER_BLOCK(Block.COPPER_BLOCK, (context) -> new OxidatableBlockBehaviour(context, Block.COPPER_BLOCK, Block.EXPOSED_COPPER, Block.WAXED_COPPER_BLOCK, 0)), - EXPOSED_COPPER(Block.EXPOSED_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.COPPER_BLOCK, Block.WEATHERED_COPPER, Block.WAXED_EXPOSED_COPPER, 1)), - WEATHERED_COPPER(Block.WEATHERED_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_COPPER, Block.OXIDIZED_COPPER, Block.WAXED_WEATHERED_COPPER, 2)), - OXIDIZED_COPPER(Block.OXIDIZED_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.WEATHERED_COPPER, Block.OXIDIZED_COPPER, Block.WAXED_OXIDIZED_COPPER, 3)), - // Cut Blocks - CUT_COPPER(Block.CUT_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER, Block.EXPOSED_CUT_COPPER, Block.WAXED_CUT_COPPER, 0)), - EXPOSED_CUT_COPPER(Block.EXPOSED_CUT_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER, Block.WEATHERED_CUT_COPPER, Block.WAXED_EXPOSED_CUT_COPPER, 1)), - WEATHERED_CUT_COPPER(Block.WEATHERED_CUT_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER, Block.OXIDIZED_CUT_COPPER, Block.WAXED_WEATHERED_CUT_COPPER, 2)), - OXIDIZED_CUT_COPPER(Block.OXIDIZED_CUT_COPPER, (context) -> new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER, Block.OXIDIZED_CUT_COPPER, Block.WAXED_OXIDIZED_CUT_COPPER, 3)), - // Stairs - CUT_COPPER_STAIRS(Block.CUT_COPPER_STAIRS, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER_STAIRS, Block.EXPOSED_CUT_COPPER_STAIRS, Block.WAXED_CUT_COPPER_STAIRS, 0)), - EXPOSED_CUT_COPPER_STAIRS(Block.EXPOSED_CUT_COPPER_STAIRS, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER_STAIRS, Block.WEATHERED_CUT_COPPER_STAIRS, Block.WAXED_EXPOSED_CUT_COPPER_STAIRS, 1)), - WEATHERED_CUT_COPPER_STAIRS(Block.WEATHERED_CUT_COPPER_STAIRS, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_STAIRS, Block.OXIDIZED_CUT_COPPER_STAIRS, Block.WAXED_WEATHERED_CUT_COPPER_STAIRS, 2)), - OXIDIZED_CUT_COPPER_STAIRS(Block.OXIDIZED_CUT_COPPER_STAIRS, (context) -> new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_STAIRS, Block.OXIDIZED_CUT_COPPER_STAIRS, Block.WAXED_OXIDIZED_CUT_COPPER_STAIRS, 3)), - // Slabs - CUT_COPPER_SLAB(Block.CUT_COPPER_SLAB, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER_SLAB, Block.EXPOSED_CUT_COPPER_SLAB, Block.WAXED_CUT_COPPER_SLAB, 0)), - EXPOSED_CUT_COPPER_SLAB(Block.EXPOSED_CUT_COPPER_SLAB, (context) -> new OxidatableBlockBehaviour(context, Block.CUT_COPPER_SLAB, Block.WEATHERED_CUT_COPPER_SLAB, Block.WAXED_EXPOSED_CUT_COPPER_SLAB, 1)), - WEATHERED_CUT_COPPER_SLAB(Block.WEATHERED_CUT_COPPER_SLAB, (context) -> new OxidatableBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_SLAB, Block.OXIDIZED_CUT_COPPER_SLAB, Block.WAXED_WEATHERED_CUT_COPPER_SLAB, 2)), - OXIDIZED_CUT_COPPER_SLAB(Block.OXIDIZED_CUT_COPPER_SLAB, (context) -> new OxidatableBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_SLAB, Block.OXIDIZED_CUT_COPPER_SLAB, Block.WAXED_OXIDIZED_CUT_COPPER_SLAB, 3)), - // End of copper - - // Start of waxed copper - // Blocks - WAXED_COPPER_BLOCK(Block.WAXED_COPPER_BLOCK, (context) -> new WaxedBlockBehaviour(context, Block.COPPER_BLOCK, 0)), - WAXED_EXPOSED_COPPER(Block.WAXED_EXPOSED_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.EXPOSED_COPPER, 1)), - WAXED_WEATHERED_COPPER(Block.WAXED_WEATHERED_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_COPPER, 2)), - WAXED_OXIDIZED_COPPER(Block.WAXED_OXIDIZED_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.OXIDIZED_COPPER, 3)), - // Cut Blocks - WAXED_CUT_COPPER(Block.WAXED_CUT_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.CUT_COPPER, 0)), - WAXED_EXPOSED_CUT_COPPER(Block.WAXED_EXPOSED_CUT_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER, 1)), - WAXED_WEATHERED_CUT_COPPER(Block.WAXED_WEATHERED_CUT_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER, 2)), - WAXED_OXIDIZED_CUT_COPPER(Block.WAXED_OXIDIZED_CUT_COPPER, (context) -> new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER, 3)), - // Stairs - WAXED_CUT_COPPER_STAIRS(Block.WAXED_CUT_COPPER_STAIRS, (context) -> new WaxedBlockBehaviour(context, Block.CUT_COPPER_STAIRS, 0)), - WAXED_EXPOSED_CUT_COPPER_STAIRS(Block.WAXED_EXPOSED_CUT_COPPER_STAIRS, (context) -> new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_STAIRS, 1)), - WAXED_WEATHERED_CUT_COPPER_STAIRS(Block.WAXED_WEATHERED_CUT_COPPER_STAIRS, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_STAIRS, 2)), - WAXED_OXIDIZED_CUT_COPPER_STAIRS(Block.WAXED_OXIDIZED_CUT_COPPER_STAIRS, (context) -> new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER_STAIRS, 3)), - // Slabs - WAXED_CUT_COPPER_SLAB(Block.WAXED_CUT_COPPER_SLAB, (context) -> new WaxedBlockBehaviour(context, Block.CUT_COPPER_SLAB, 0)), - WAXED_EXPOSED_CUT_COPPER_SLAB(Block.WAXED_EXPOSED_CUT_COPPER_SLAB, (context) -> new WaxedBlockBehaviour(context, Block.EXPOSED_CUT_COPPER_SLAB, 1)), - WAXED_WEATHERED_CUT_COPPER_SLAB(Block.WAXED_WEATHERED_CUT_COPPER_SLAB, (context) -> new WaxedBlockBehaviour(context, Block.WEATHERED_CUT_COPPER_SLAB, 2)), - WAXED_OXIDIZED_CUT_COPPER_SLAB(Block.WAXED_OXIDIZED_CUT_COPPER_SLAB, (context) -> new WaxedBlockBehaviour(context, Block.OXIDIZED_CUT_COPPER_SLAB, 3)), - // End of waxed copper - - // Start of beds - WHITE_BED(Block.WHITE_BED, BedBlockBehaviour::new), - BLACK_BED(Block.BLACK_BED, BedBlockBehaviour::new), - LIGHT_BLUE_BED(Block.LIGHT_BLUE_BED, BedBlockBehaviour::new), - BLUE_BED(Block.BLUE_BED, BedBlockBehaviour::new), - RED_BED(Block.RED_BED, BedBlockBehaviour::new), - GREEN_BED(Block.GREEN_BED, BedBlockBehaviour::new), - YELLOW_BED(Block.YELLOW_BED, BedBlockBehaviour::new), - PURPLE_BED(Block.PURPLE_BED, BedBlockBehaviour::new), - MAGENTA_BED(Block.MAGENTA_BED, BedBlockBehaviour::new), - CYAN_BED(Block.CYAN_BED, BedBlockBehaviour::new), - PINK_BED(Block.PINK_BED, BedBlockBehaviour::new), - GRAY_BED(Block.GRAY_BED, BedBlockBehaviour::new), - LIGHT_GRAY_BED(Block.LIGHT_GRAY_BED, BedBlockBehaviour::new), - ORANGE_BED(Block.ORANGE_BED, BedBlockBehaviour::new), - BROWN_BED(Block.BROWN_BED, BedBlockBehaviour::new), - LIME_BED(Block.LIME_BED, BedBlockBehaviour::new), - // End of beds - - FIRE(Block.FIRE, FireBlockBehaviour::new), - NETHER_PORTAL(Block.NETHER_PORTAL, NetherPortalBlockBehaviour::new), - END_PORTAL(Block.END_PORTAL, EndPortalBlockBehaviour::new), - - TNT(Block.TNT, TNTBlockBehaviour::new), - - CHEST(Block.CHEST, ChestBlockBehaviour::new), - TRAPPED_CHEST(Block.TRAPPED_CHEST, TrappedChestBlockBehaviour::new), - ENDER_CHEST(Block.ENDER_CHEST, EnderChestBlockBehaviour::new), - JUKEBOX(Block.JUKEBOX, JukeboxBlockBehaviour::new), - - // recipes - CRAFTING_TABLE(Block.CRAFTING_TABLE, CraftingTableBehaviour::new), - FURNACE(Block.FURNACE, FurnaceBehaviour::new), - SMOKER(Block.SMOKER, SmokerBehaviour::new), - BLAST_FURNACE(Block.BLAST_FURNACE, BlastingFurnaceBehaviour::new), - - STONE_CUTTER(Block.STONECUTTER, StonecutterBehaviour::new), - CAMPFIRE(Block.CAMPFIRE, CampfireBehaviour::new), - SOUL_CAMPFIRE(Block.SOUL_CAMPFIRE, CampfireBehaviour::new), - SMITHING_TABLE(Block.SMITHING_TABLE, SmithingTableBehaviour::new), - - // Start of cakes - CAKE(Block.CAKE, CakeBlockBehaviour::new), - CANDLE_CAKE(Block.CANDLE_CAKE, CakeBlockBehaviour::new), - WHITE_CANDLE_CAKE(Block.WHITE_CANDLE_CAKE, CakeBlockBehaviour::new), - ORANGE_CANDLE_CAKE(Block.ORANGE_CANDLE_CAKE, CakeBlockBehaviour::new), - MAGENTA_CANDLE_CAKE(Block.MAGENTA_CANDLE_CAKE, CakeBlockBehaviour::new), - LIGHT_BLUE_CANDLE_CAKE(Block.LIGHT_BLUE_CANDLE_CAKE, CakeBlockBehaviour::new), - YELLOW_CANDLE_CAKE(Block.YELLOW_CANDLE_CAKE, CakeBlockBehaviour::new), - LIME_CANDLE_CAKE(Block.LIME_CANDLE_CAKE, CakeBlockBehaviour::new), - PINK_CANDLE_CAKE(Block.PINK_CANDLE_CAKE, CakeBlockBehaviour::new), - GRAY_CANDLE_CAKE(Block.GRAY_CANDLE_CAKE, CakeBlockBehaviour::new), - LIGHT_GRAY_CANDLE_CAKE(Block.LIGHT_GRAY_CANDLE_CAKE, CakeBlockBehaviour::new), - CYAN_CANDLE_CAKE(Block.CYAN_CANDLE_CAKE, CakeBlockBehaviour::new), - PURPLE_CANDLE_CAKE(Block.PURPLE_CANDLE_CAKE, CakeBlockBehaviour::new), - BLUE_CANDLE_CAKE(Block.BLUE_CANDLE_CAKE, CakeBlockBehaviour::new), - BROWN_CANDLE_CAKE(Block.BROWN_CANDLE_CAKE, CakeBlockBehaviour::new), - GREEN_CANDLE_CAKE(Block.GREEN_CANDLE_CAKE, CakeBlockBehaviour::new), - BLACK_CANDLE_CAKE(Block.BLACK_CANDLE_CAKE, CakeBlockBehaviour::new) - // End of cakes - - ; - private final short stateId; - private final @NotNull Context2Handler context2handler; - - VanillaBlocks(@NotNull Block minestomBlock, @NotNull Context2Handler context2handler) { - this.stateId = (short) minestomBlock.stateId(); - this.context2handler = context -> { - if (context.stateId() != minestomBlock.stateId()) { - throw new IllegalStateException("Block registry mismatch. Registered block: " + minestomBlock.stateId() + - " != Given block:" + context.stateId()); - } - return context2handler.apply(context); - }; - } - - interface Context2Handler { - @NotNull VanillaBlockBehaviour apply(@NotNull BlockContext context); - } - - /** - * Used to provide context for creating block handlers - */ - public interface BlockContext { - short stateId(); - - @NotNull VanillaReimplementation vri(); - } - - /** - * Creates a block handler from the context - * - * @param context the context - * @return the block handler - */ - public @NotNull VanillaBlockBehaviour create(@NotNull BlockContext context) { - return context2handler.apply(context); - } - - /** - * Register all vanilla blocks. ConnectionManager will handle replacing the basic - * block with its custom variant. - * - * @param vri the vanilla reimplementation object - */ - public static void registerAll(@NotNull VanillaReimplementation vri) { - - EventNode events = EventNode.all("vanilla-blocks"); - - // block loot - VanillaBlockLoot loot = new VanillaBlockLoot(vri, vri.feature(DatapackLoadingFeature.class).current()); - events.addListener(EventListener.builder(PlayerBlockBreakEvent.class) - .filter(event -> !event.isCancelled()) - .filter(event -> event.getPlayer().getGameMode() != GameMode.CREATIVE) - .handler(loot::spawnLoot) - .build()); - - Short2ObjectMap stateId2behaviour = new Short2ObjectOpenHashMap<>(); - - for (VanillaBlocks vb : values()) { - BlockContext context = new BlockContext() { - @Override - public short stateId() { - return vb.stateId; - } - - @Override - public @NotNull VanillaReimplementation vri() { - return vri; - } - }; - VanillaBlockBehaviour behaviour = vb.context2handler.apply(context); - - for (Block possibleState : Objects.requireNonNull(Block.fromStateId(vb.stateId)).possibleStates()) { - short possibleStateId = (short) possibleState.stateId(); - stateId2behaviour.put(possibleStateId, behaviour); - } - - if (behaviour instanceof BlockUpdatable updatable) - BlockUpdateManager.registerUpdatable(vb.stateId, updatable); - - if (behaviour instanceof RandomTickable randomTickable) - RandomTickManager.registerRandomTickable(vb.stateId, randomTickable); - } - - registerEvents(events, stateId2behaviour); - - vri.process().eventHandler().addChild(events); - } - - private static void registerEvents(EventNode node, Short2ObjectMap behaviours) { - node.addListener(EventListener.builder(PlayerBlockPlaceEvent.class) - .filter(event -> behaviours.containsKey((short) event.getBlock().stateId())) - .handler(event -> { - short stateId = (short) event.getBlock().stateId(); - Block block = Objects.requireNonNull(Block.fromStateId(stateId)); - var behaviour = behaviours.get(stateId); - - behaviour.onPlace(new PlayerPlacement(event)); - Block blockToPlace = event.getBlock(); - if (blockToPlace.compare(block)) { - event.setBlock(blockToPlace.withHandler(behaviour)); - } - }) - .build()); - } - - private record PlayerPlacement(PlayerBlockPlaceEvent event) implements VanillaBlockBehaviour.VanillaPlacement, - VanillaBlockBehaviour.VanillaPlacement.HasPlayer { - - @Override - public @NotNull Block blockToPlace() { - return event.getBlock(); - } - - @Override - public @NotNull Instance instance() { - return event.getInstance(); - } - - @Override - public @NotNull Point position() { - return event.getBlockPosition(); - } - - @Override - public void blockToPlace(@NotNull Block newBlock) { - event.setBlock(newBlock); - } - - @Override - public @NotNull Player player() { - return event.getPlayer(); - } - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocksFeature.java b/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocksFeature.java deleted file mode 100644 index ffdca04f..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/VanillaBlocksFeature.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.minestom.vanilla.blocks; - -import net.kyori.adventure.key.Key; -import net.minestom.server.coordinate.Point; -import net.minestom.server.event.player.PlayerBlockPlaceEvent; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.BlockUpdateFeature; -import net.minestom.vanilla.VanillaReimplementation; -import net.minestom.vanilla.datapack.DatapackLoadingFeature; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -public class VanillaBlocksFeature implements VanillaReimplementation.Feature { - - @Override - public void hook(@NotNull HookContext context) { - VanillaReimplementation vri = context.vri(); - VanillaBlocks.registerAll(vri); - - vri.process().eventHandler().addListener(PlayerBlockPlaceEvent.class, event -> { - Block block = event.getBlock(); - Instance instance = event.getInstance(); - Point position = event.getBlockPosition(); - AtomicReference blockToPlace = new AtomicReference<>(block); - - if (block.handler() instanceof VanillaBlockBehaviour vanillaHandler) { - // Create the new placement object - VanillaBlockBehaviour.VanillaPlacement placement = new PlacementImpl(blockToPlace, instance, position); - vanillaHandler.onPlace(placement); - } - - event.setBlock(blockToPlace.get()); - }); - } - - private record PlacementImpl(AtomicReference blockToPlaceRef, Instance instance, Point position) implements VanillaBlockBehaviour.VanillaPlacement { - @Override - public @NotNull Block blockToPlace() { - return blockToPlaceRef.get(); - } - - @Override - public @NotNull Instance instance() { - return instance; - } - - @Override - public @NotNull Point position() { - return position; - } - - @Override - public void blockToPlace(@NotNull Block newBlock) { - blockToPlaceRef.getAndSet(newBlock); - // TODO: Run vanillaHandler.onPlace again on the new block if it's a vanilla block - } - } - - @Override - public @NotNull Key key() { - return Key.key("vri:blocks"); - } - - @Override - public @NotNull Set> dependencies() { - return Set.of(BlockUpdateFeature.class, DatapackLoadingFeature.class); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CakeEatRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CakeEatRule.java new file mode 100644 index 00000000..64fbcd24 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CakeEatRule.java @@ -0,0 +1,66 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.vanilla.blocks.event.CakeEatEvent; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CakeEatRule implements BlockHandler { + private final Block block; + private final int maxSlices = 7; + private final RegistryTag candles = Block.staticRegistry().getTag(TagKey.ofHash("#minecraft:candles")); + + public CakeEatRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return block.key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking()) { + return BlockHandler.super.onInteract(interaction); + } + + int currentSlices = Integer.parseInt(interaction.getBlock().getProperty("bites")); + Block heldBlock = interaction.getPlayer().getItemInHand(interaction.getHand()).material().block(); + if (currentSlices == 0 && heldBlock != Block.AIR && candles.contains(heldBlock)) { + return BlockHandler.super.onInteract(interaction); + } + + EventDispatcher.callCancellable( + new CakeEatEvent( + interaction.getPlayer(), + interaction.getBlock(), + new BlockVec(interaction.getBlockPosition()) + ), + () -> { + if (currentSlices < (maxSlices - 1)) { + interaction.getInstance().setBlock( + interaction.getBlockPosition(), + interaction.getBlock().withProperty("bites", String.valueOf(currentSlices + 1)) + ); + } else { + interaction.getInstance().setBlock(interaction.getBlockPosition(), Block.AIR); + } + } + ); + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CandleCakeRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CandleCakeRule.java new file mode 100644 index 00000000..4d3f88ae --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CandleCakeRule.java @@ -0,0 +1,78 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.vanilla.blocks.event.CakeEatEvent; +import net.minestom.vanilla.blocks.placement.CandlePlacementRule; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.BlockUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CandleCakeRule implements BlockHandler { + private final Block block; + private static final Map CAKE_VARIANTS = CandlePlacementRule.getCakeVariants() + .entrySet() + .stream() + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getValue, + Map.Entry::getKey, + (a, b) -> b, + java.util.LinkedHashMap::new + )); + + public CandleCakeRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return block.key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking()) { + return BlockHandler.super.onInteract(interaction); + } + + EventDispatcher.callCancellable( + new CakeEatEvent( + interaction.getPlayer(), + interaction.getBlock(), + new BlockVec(interaction.getBlockPosition()) + ), + () -> { + interaction.getInstance().setBlock( + interaction.getBlockPosition(), + BlockUtil + .withDefaultHandler(Block.CAKE) + .withProperty("bites", "1") + ); + + Block candle = CAKE_VARIANTS.get(block); + if (candle != null) { + DroppedItemFactory.maybeDrop( + interaction.getInstance(), + interaction.getBlockPosition(), + candle + ); + } + } + ); + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CopperOxidationRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CopperOxidationRule.java new file mode 100644 index 00000000..7feaf132 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/CopperOxidationRule.java @@ -0,0 +1,125 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.coordinate.Point; +import net.minestom.server.event.EventDispatcher; +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.BlockHandler; +import net.minestom.vanilla.blocks.event.CopperOxidationEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CopperOxidationRule implements BlockHandler { + private final Block block; + public static final Map oxidationStages = Map.copyOf(new HashMap<>() {{ + put(Block.COPPER_BLOCK, Block.EXPOSED_COPPER); + put(Block.EXPOSED_COPPER, Block.WEATHERED_COPPER); + put(Block.WEATHERED_COPPER, Block.OXIDIZED_COPPER); + + put(Block.CUT_COPPER, Block.EXPOSED_CUT_COPPER); + put(Block.EXPOSED_CUT_COPPER, Block.WEATHERED_CUT_COPPER); + put(Block.WEATHERED_CUT_COPPER, Block.OXIDIZED_CUT_COPPER); + + put(Block.CUT_COPPER_STAIRS, Block.EXPOSED_CUT_COPPER_STAIRS); + put(Block.EXPOSED_CUT_COPPER_STAIRS, Block.WEATHERED_CUT_COPPER_STAIRS); + put(Block.WEATHERED_CUT_COPPER_STAIRS, Block.OXIDIZED_CUT_COPPER_STAIRS); + + put(Block.CUT_COPPER_SLAB, Block.EXPOSED_CUT_COPPER_SLAB); + put(Block.EXPOSED_CUT_COPPER_SLAB, Block.WEATHERED_CUT_COPPER_SLAB); + put(Block.WEATHERED_CUT_COPPER_SLAB, Block.OXIDIZED_CUT_COPPER_SLAB); + + put(Block.CHISELED_COPPER, Block.EXPOSED_CHISELED_COPPER); + put(Block.EXPOSED_CHISELED_COPPER, Block.WEATHERED_CHISELED_COPPER); + put(Block.WEATHERED_CHISELED_COPPER, Block.OXIDIZED_CHISELED_COPPER); + + put(Block.COPPER_GRATE, Block.EXPOSED_COPPER_GRATE); + put(Block.EXPOSED_COPPER_GRATE, Block.WEATHERED_COPPER_GRATE); + put(Block.WEATHERED_COPPER_GRATE, Block.OXIDIZED_COPPER_GRATE); + + put(Block.COPPER_BULB, Block.EXPOSED_COPPER_BULB); + put(Block.EXPOSED_COPPER_BULB, Block.WEATHERED_COPPER_BULB); + put(Block.WEATHERED_COPPER_BULB, Block.OXIDIZED_COPPER_BULB); + }}); + + public CopperOxidationRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return Key.key("vri:copper_oxidation"); + } + + public Block getNextOxidationStage() { + return oxidationStages.get(block); + } + + @Override + public void tick(@NotNull Tick tick) { + if (ThreadLocalRandom.current().nextInt(10000) > 1) return; + + Instance instance = tick.getInstance(); + Point blockPosition = tick.getBlockPosition(); + int exposedToAir = countExposedSides(instance, blockPosition); + + if (exposedToAir > 0 && ThreadLocalRandom.current().nextInt(100) < 20) { + oxidizeBlock(instance, blockPosition, tick.getBlock()); + } + } + + @Override + public boolean isTickable() { + return true; + } + + private int countExposedSides(Instance instance, Point blockPosition) { + int exposedSides = 0; + + for (BlockFace face : BlockFace.values()) { + Point neighborPos = blockPosition.add( + face.toDirection().vec() + ); + Block neighborBlock = instance.getBlock(neighborPos); + if (neighborBlock == Block.AIR) { + exposedSides++; + } + } + return exposedSides; + } + + private void oxidizeBlock(Instance instance, Point blockPosition, Block block) { + Block nextStage = getNextOxidationStage(); + if (nextStage == null) return; + + BlockHandler handler = MinecraftServer.getBlockManager().getHandler(nextStage.key().asString()); + if (handler != null) { + nextStage = nextStage.withHandler(handler); + } + + CopperOxidationEvent event = new CopperOxidationEvent( + block, + nextStage, + new BlockVec(blockPosition), + instance + ); + + EventDispatcher.callCancellable(event, () -> { + instance.setBlock(blockPosition, event.getBlockAfterOxidation()); + }); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/DoorOpenRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/DoorOpenRule.java new file mode 100644 index 00000000..4b086282 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/DoorOpenRule.java @@ -0,0 +1,72 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class DoorOpenRule implements BlockHandler { + private final Block baseDoorBlock; + + public DoorOpenRule(Block baseDoorBlock) { + this.baseDoorBlock = baseDoorBlock; + } + + @Override + public @NotNull Key getKey() { + return baseDoorBlock.key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + var instance = interaction.getInstance(); + Point clickedPosition = interaction.getBlockPosition(); + Block clickedBlock = interaction.getBlock(); + + String half = clickedBlock.getProperty("half"); + boolean isPowered = Boolean.parseBoolean(clickedBlock.getProperty("powered")); + + if (isPowered) { + return false; + } + + if (interaction.getPlayer().isSneaking() && !interaction.getPlayer().getItemInMainHand().isAir()) { + return BlockHandler.super.onInteract(interaction); + } + + boolean currentOpen = Boolean.parseBoolean(clickedBlock.getProperty("open")); + boolean newOpen = !currentOpen; + Point otherHalfPos = "lower".equals(half) ? + clickedPosition.add(0.0, 1.0, 0.0) : + clickedPosition.sub(0.0, 1.0, 0.0); + + Block otherHalfBlock = instance.getBlock(otherHalfPos); + + if (!otherHalfBlock.compare(clickedBlock) || half.equals(otherHalfBlock.getProperty("half"))) { + return false; + } + + String facing = clickedBlock.getProperty("facing"); + String hinge = clickedBlock.getProperty("hinge"); + + Block updatedBlockState = clickedBlock + .withProperty("facing", facing) + .withProperty("hinge", hinge) + .withProperty("open", String.valueOf(newOpen)) + .withProperty("powered", "false"); + + instance.setBlock(clickedPosition, updatedBlockState.withProperty("half", half)); + instance.setBlock(otherHalfPos, updatedBlockState.withProperty("half", "lower".equals(half) ? "upper" : "lower")); + + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GateOpenRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GateOpenRule.java new file mode 100644 index 00000000..7257ef25 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GateOpenRule.java @@ -0,0 +1,54 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collection; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class GateOpenRule implements BlockHandler { + private final Block block; + + public GateOpenRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return block != null ? block.key() : getKey().key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking() && !interaction.getPlayer().getItemInMainHand().isAir()) + return true; + + Collection allowedDirections = getAllowedDirections(interaction.getBlock()); + Direction direction = DirectionUtils.getNearestLookingDirection(interaction, allowedDirections).opposite(); + String bool = String.valueOf(!Boolean.parseBoolean(interaction.getBlock().getProperty("open"))); + + interaction.getInstance().setBlock(interaction.getBlockPosition(), + interaction.getBlock() + .withProperty("open", bool) + .withProperty("facing", direction.toString().toLowerCase()) + ); + return false; + } + + public Collection getAllowedDirections(Block block) { + Direction currentDirection = Direction.valueOf(block.getProperty("facing").toUpperCase()); + return Arrays.asList(currentDirection, currentDirection.opposite()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GenericWorkStationRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GenericWorkStationRule.java new file mode 100644 index 00000000..befe5258 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/GenericWorkStationRule.java @@ -0,0 +1,44 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class GenericWorkStationRule implements BlockHandler { + private final Block block; + private final InventoryType type; + private final String title; + + public GenericWorkStationRule(Block block, InventoryType type, String title) { + this.block = block; + this.type = type; + this.title = title; + } + + @Override + public @NotNull Key getKey() { + return block.key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking() && !interaction.getPlayer().getItemInMainHand().isAir()) { + return BlockHandler.super.onInteract(interaction); + } + + interaction.getPlayer().openInventory(new Inventory(type, Component.translatable(title))); + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/SignEditRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/SignEditRule.java new file mode 100644 index 00000000..7477a058 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/SignEditRule.java @@ -0,0 +1,110 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.coordinate.Point; +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.network.packet.server.play.OpenSignEditorPacket; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.server.tag.Tag; +import net.minestom.vanilla.blocks.event.PlayerOpenSignEditorEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collection; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SignEditRule implements BlockHandler { + private final Block block; + private final @Nullable RegistryTag wallSigns = Block.staticRegistry().getTag(TagKey.ofHash("#minecraft:wall_signs")); + + public SignEditRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return block.key(); + } + + @Override + public @NotNull Collection> getBlockEntityTags() { + return Arrays.asList( + Tag.NBT("front_text"), + Tag.NBT("back_text"), + Tag.Boolean("is_waxed") + ); + } + + @Override + public void onPlace(Placement placement) { + if (!(placement instanceof PlayerPlacement)) return; + PlayerPlacement playerPlacement = (PlayerPlacement) placement; + if (playerPlacement.getPlayer().isSneaking()) return; + + Player player = playerPlacement.getPlayer(); + if (wallSigns.contains(playerPlacement.getBlock())) { + openEditor(playerPlacement.getBlock(), playerPlacement.getBlockPosition(), player, true); + } else { + Point position = playerPlacement.getBlockPosition(); + String rotationProperty = playerPlacement.getBlock().getProperty("rotation"); + int rotation = rotationProperty != null ? Integer.parseInt(rotationProperty) : 0; + boolean front = getSide(player, position, rotation); + openEditor(playerPlacement.getBlock(), position, player, front); + } + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking()) return BlockHandler.super.onInteract(interaction); + + Block block = interaction.getBlock(); + Point position = interaction.getBlockPosition(); + + if (wallSigns.contains(block)) { + String rotationProperty = block.getProperty("rotation"); + int rotation = rotationProperty != null ? Integer.parseInt(rotationProperty) : 0; + boolean front = getSide(interaction.getPlayer(), position, rotation); + openEditor(block, position, interaction.getPlayer(), front); + } else { + String rotationProperty = block.getProperty("rotation"); + int rotation = rotationProperty != null ? Integer.parseInt(rotationProperty) : 0; + boolean side = getSide(interaction.getPlayer(), position, rotation); + openEditor(block, position, interaction.getPlayer(), side); + } + + return false; + } + + private boolean getSide(Player player, Point position, int rotation) { + double angleInRadians = Math.atan2( + player.getPosition().x() - position.x(), + position.z() - player.getPosition().z() + ); + int playerAngle = (int) Math.toDegrees(angleInRadians); + double signAngle = rotation * 22.5; + int relativeDegrees = (playerAngle - (int)signAngle + 360) % 360; + return relativeDegrees >= 0 && relativeDegrees <= 180; + } + + private void openEditor(Block block, Point position, Player player, boolean front) { + EventDispatcher.callCancellable( + new PlayerOpenSignEditorEvent(player, new BlockVec(position), block), + () -> player.sendPacket(new OpenSignEditorPacket(position, front)) + ); + } + + // TODO: Write back updated signs +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/StrippingBehaviorRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/StrippingBehaviorRule.java new file mode 100644 index 00000000..aee9621f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/StrippingBehaviorRule.java @@ -0,0 +1,154 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.minestom.server.component.DataComponents; +import net.minestom.server.entity.GameMode; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.sound.SoundEvent; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class StrippingBehaviorRule implements BlockHandler { + + public StrippingBehaviorRule(Block block) { + // Block parameter kept for consistency with other handlers + } + + @Override + public @NotNull Key getKey() { + return Key.key("vri:stripping_behavior"); + } + + @Override + public boolean onInteract(Interaction interaction) { + Player player = interaction.getPlayer(); + ItemStack itemInHand = player.getItemInMainHand(); + + if (!isAxe(itemInHand.material())) { + return true; + } + + Block currentBlock = interaction.getInstance().getBlock(interaction.getBlockPosition()); + Block strippedBaseBlock = getStrippedVariant(currentBlock); + if (strippedBaseBlock == null) { + return false; + } + + Block strippedBlockWithProperties = preserveBlockProperties(currentBlock, strippedBaseBlock); + interaction.getInstance().setBlock(interaction.getBlockPosition(), strippedBlockWithProperties); + + if (player.getGameMode() != GameMode.CREATIVE) { + damageAxe(player, itemInHand); + } + + return false; + } + + private Block preserveBlockProperties(Block originalBlock, Block strippedBlock) { + Block resultBlock = strippedBlock; + String axis = originalBlock.getProperty("axis"); + if (axis != null) { + resultBlock = resultBlock.withProperty("axis", axis); + } + return resultBlock; + } + + private boolean isAxe(Material material) { + return material.name().endsWith("axe"); + } + + private Block getStrippedVariant(Block originalBlock) { + String baseBlockName = originalBlock.name(); + return switch (baseBlockName) { + case "minecraft:oak_log" -> Block.STRIPPED_OAK_LOG; + case "minecraft:spruce_log" -> Block.STRIPPED_SPRUCE_LOG; + case "minecraft:birch_log" -> Block.STRIPPED_BIRCH_LOG; + case "minecraft:jungle_log" -> Block.STRIPPED_JUNGLE_LOG; + case "minecraft:acacia_log" -> Block.STRIPPED_ACACIA_LOG; + case "minecraft:dark_oak_log" -> Block.STRIPPED_DARK_OAK_LOG; + case "minecraft:mangrove_log" -> Block.STRIPPED_MANGROVE_LOG; + case "minecraft:cherry_log" -> Block.STRIPPED_CHERRY_LOG; + case "minecraft:bamboo_block" -> Block.STRIPPED_BAMBOO_BLOCK; + case "minecraft:oak_wood" -> Block.STRIPPED_OAK_WOOD; + case "minecraft:spruce_wood" -> Block.STRIPPED_SPRUCE_WOOD; + case "minecraft:birch_wood" -> Block.STRIPPED_BIRCH_WOOD; + case "minecraft:jungle_wood" -> Block.STRIPPED_JUNGLE_WOOD; + case "minecraft:acacia_wood" -> Block.STRIPPED_ACACIA_WOOD; + case "minecraft:dark_oak_wood" -> Block.STRIPPED_DARK_OAK_WOOD; + case "minecraft:mangrove_wood" -> Block.STRIPPED_MANGROVE_WOOD; + case "minecraft:cherry_wood" -> Block.STRIPPED_CHERRY_WOOD; + case "minecraft:crimson_stem" -> Block.STRIPPED_CRIMSON_STEM; + case "minecraft:warped_stem" -> Block.STRIPPED_WARPED_STEM; + case "minecraft:crimson_hyphae" -> Block.STRIPPED_CRIMSON_HYPHAE; + case "minecraft:warped_hyphae" -> Block.STRIPPED_WARPED_HYPHAE; + default -> null; + }; + } + + private void damageAxe(Player player, ItemStack axe) { + Integer currentDamage = axe.getTag(Tag.Integer("Damage")); + if (currentDamage == null) { + currentDamage = 0; + } + + if (axe.has(DataComponents.MAX_DAMAGE)) { + Integer maxDamage = axe.get(DataComponents.MAX_DAMAGE); + if (maxDamage != null) { + if (currentDamage + 1 >= maxDamage) { + player.setItemInMainHand(ItemStack.AIR); + Sound breakSound = Sound.sound(SoundEvent.ENTITY_ITEM_BREAK, Sound.Source.PLAYER, 1.0f, 1.0f); + if (player.getInstance() != null) { + player.getInstance().playSound(breakSound, player.getPosition()); + } + } else { + ItemStack damagedAxe = axe.withTag(Tag.Integer("Damage"), currentDamage + 1); + player.setItemInMainHand(damagedAxe); + } + } + } + } + + public static List getStrippableBlocks() { + return Arrays.asList( + Block.OAK_LOG, + Block.SPRUCE_LOG, + Block.BIRCH_LOG, + Block.JUNGLE_LOG, + Block.ACACIA_LOG, + Block.DARK_OAK_LOG, + Block.MANGROVE_LOG, + Block.CHERRY_LOG, + Block.BAMBOO_BLOCK, + + Block.OAK_WOOD, + Block.SPRUCE_WOOD, + Block.BIRCH_WOOD, + Block.JUNGLE_WOOD, + Block.ACACIA_WOOD, + Block.DARK_OAK_WOOD, + Block.MANGROVE_WOOD, + Block.CHERRY_WOOD, + + Block.CRIMSON_STEM, + Block.WARPED_STEM, + Block.CRIMSON_HYPHAE, + Block.WARPED_HYPHAE + ); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/WoodenTrapDoorOpenRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/WoodenTrapDoorOpenRule.java new file mode 100644 index 00000000..06f12631 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/behavior/WoodenTrapDoorOpenRule.java @@ -0,0 +1,46 @@ +package net.minestom.vanilla.blocks.behavior; + +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class WoodenTrapDoorOpenRule implements BlockHandler { + private final Block block; + + public WoodenTrapDoorOpenRule(Block block) { + this.block = block; + } + + @Override + public @NotNull Key getKey() { + return block != null ? block.key() : getKey().key(); + } + + @Override + public boolean onInteract(Interaction interaction) { + if (interaction.getPlayer().isSneaking() && !interaction.getPlayer().getItemInMainHand().isAir()) { + return BlockHandler.super.onInteract(interaction); + } + + String bool = interaction.getBlock().getProperty("open"); + if ("true".equals(bool)) { + bool = "false"; + } else { + bool = "true"; + } + + interaction.getInstance().setBlock(interaction.getBlockPosition(), + interaction.getBlock().withProperty("open", bool)); + + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/BedBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/BedBlockBehaviour.java deleted file mode 100644 index 4b1e1586..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/BedBlockBehaviour.java +++ /dev/null @@ -1,136 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.EntityPose; -import net.minestom.server.entity.Player; -import net.minestom.server.entity.metadata.PlayerMeta; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.utils.Direction; -import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.time.TimeUnit; -import net.minestom.server.world.DimensionType; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.instance.VanillaExplosion; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("UnstableApiUsage") -public class BedBlockBehaviour extends VanillaBlockBehaviour { - public BedBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - -// @Override -// protected BlockPropertyList createPropertyValues() { -// return new BlockPropertyList().facingProperty("facing").booleanProperty("occupied").property("part", "foot", "head"); -// } - - - @Override - public void onPlace(@NotNull VanillaPlacement placement) { - if (!(placement instanceof VanillaPlacement.HasPlayer hasPlayer)) { - return; - } - - Instance instance = placement.instance(); - Point pos = placement.position(); - Player player = hasPlayer.player(); - - ItemStack itemStack = player.getItemInMainHand(); // TODO: Hand determination - - Block bedBlock = itemStack.material().block(); - - // TODO: Proper block placement management - Direction playerDirection = MathUtils.getHorizontalDirection(player.getPosition().yaw()); - - Point bedHeadPosition = pos.add(playerDirection.normalX(), playerDirection.normalY(), playerDirection.normalZ()); - Block blockAtPotentialBedHead = instance.getBlock(bedHeadPosition); - - if (isReplaceable(blockAtPotentialBedHead)) { - Block foot = placeBed(instance, bedBlock, bedHeadPosition, playerDirection); - placement.blockToPlace(foot); - } else { - placement.blockToPlace(placement.instance().getBlock(placement.position())); - } - - } - - private boolean isReplaceable(Block blockAtPosition) { - return blockAtPosition.isAir() || blockAtPosition.isLiquid(); - } - - private Block placeBed(Instance instance, Block bedBlock, Point headPosition, Direction facing) { - Block correctFacing = bedBlock.withProperty("facing", facing.name().toLowerCase()); - - Block footBlock = correctFacing.withProperty("part", "foot"); - Block headBlock = correctFacing.withProperty("part", "head").withHandler(new BedBlockBehaviour(this.context)); - instance.setBlock(headPosition, headBlock); - return footBlock; - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Player player = interaction.getPlayer(); - var dimensionKey = instance.getDimensionType(); - DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(dimensionKey); - - if (dimension.bedWorks()) { - // TODO: make player sleep - // TODO: checks for mobs - // TODO: check for day - - // If time is not day -// long dayTime = instance.getTime() % 24000L; -// if (!(dayTime > 12541L && dayTime < 23458L)) { -// return true; -// } - - // Make player sleep - PlayerMeta meta = player.getPlayerMeta(); - meta.setBedInWhichSleepingPosition(pos); - meta.setPose(EntityPose.SLEEPING); - - // Schedule player getting out of bed - MinecraftServer.getSchedulerManager().buildTask(() -> { - if (!player.getPlayerConnection().isOnline()) { - return; - } - - meta.setBedInWhichSleepingPosition(null); - meta.setPose(EntityPose.STANDING); - }) - .delay(101, TimeUnit.SERVER_TICK) - .schedule(); - return true; - } - - VanillaExplosion.builder(pos.add(0.5), 5) - .isFlaming(true) - .build() - .apply(instance); - return true; - } - - @Override - public void onDestroy(@NotNull Destroy destroy) { - Instance instance = destroy.getInstance(); - Block block = destroy.getBlock(); - Point pos = destroy.getBlockPosition(); - - boolean isHead = "head".equals(block.getProperty("part")); - Direction facing = Direction.valueOf(block.getProperty("facing").toUpperCase()); - - if (isHead) { - facing = facing.opposite(); - } - - Point otherPartPosition = pos.add(facing.normalX(), facing.normalY(), facing.normalZ()); - instance.setBlock(pos, Block.AIR); - instance.setBlock(otherPartPosition, Block.AIR); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/CakeBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/CakeBlockBehaviour.java deleted file mode 100644 index d62cca06..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/CakeBlockBehaviour.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.ItemEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -import static java.util.Map.entry; - -public class CakeBlockBehaviour extends VanillaBlockBehaviour { - - private static final Map candleCakes = Map.ofEntries( - entry(Block.CANDLE_CAKE, Material.CANDLE), - entry(Block.WHITE_CANDLE_CAKE, Material.WHITE_CANDLE), - entry(Block.ORANGE_CANDLE_CAKE, Material.ORANGE_CANDLE), - entry(Block.MAGENTA_CANDLE_CAKE, Material.MAGENTA_CANDLE), - entry(Block.LIGHT_BLUE_CANDLE_CAKE, Material.LIGHT_BLUE_CANDLE), - entry(Block.YELLOW_CANDLE_CAKE, Material.YELLOW_CANDLE), - entry(Block.LIME_CANDLE_CAKE, Material.LIME_CANDLE), - entry(Block.PINK_CANDLE_CAKE, Material.PINK_CANDLE), - entry(Block.GRAY_CANDLE_CAKE, Material.GRAY_CANDLE), - entry(Block.LIGHT_GRAY_CANDLE_CAKE, Material.LIGHT_GRAY_CANDLE), - entry(Block.CYAN_CANDLE_CAKE, Material.CYAN_CANDLE), - entry(Block.PURPLE_CANDLE_CAKE, Material.PURPLE_CANDLE), - entry(Block.BLUE_CANDLE_CAKE, Material.BLUE_CANDLE), - entry(Block.BROWN_CANDLE_CAKE, Material.BROWN_CANDLE), - entry(Block.GREEN_CANDLE_CAKE, Material.GREEN_CANDLE), - entry(Block.BLACK_CANDLE_CAKE, Material.BLACK_CANDLE) - ); - - private static final ItemStack flint_and_steel = ItemStack.of(Material.FLINT_AND_STEEL); - - public CakeBlockBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context); - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - Player player = interaction.getPlayer(); - Block block = interaction.getBlock(); - Point point = interaction.getBlockPosition(); - Instance instance = interaction.getInstance(); - - int food = player.getFood(); - float saturation = player.getFoodSaturation(); - ItemStack item = player.getItemInMainHand(); - - // Player is trying to light candle cake - if (item.isSimilar(flint_and_steel) && candleCakes.containsKey(block) && - !Boolean.parseBoolean(block.getProperty("lit"))) { - instance.setBlock(point, block.withProperty("lit", "true")); - - // TODO: Handle tool durability - return true; - } - - // Player is eating cake - if (food < 20) { - tryDropCandle(block, instance, point); - - // Update hunger values - int newFood = Math.max(20, food + 2); - float newSaturation = Math.max(20f, saturation + 0.4f); - player.setFood(newFood); - player.setFoodSaturation(newSaturation); - } - return true; - } - - @Override - public void onDestroy(@NotNull Destroy destroy) { - Block block = destroy.getBlock(); - Instance instance = destroy.getInstance(); - Point point = destroy.getBlockPosition(); - - tryDropCandle(block, instance, point); - } - - private void tryDropCandle(Block block, Instance instance, Point point) { - if (block != Block.CAKE) { - instance.setBlock(point, Block.CAKE.withProperty("bites", "1")); - ItemStack candle = ItemStack.of(candleCakes.get(block)); - new ItemEntity(candle).setInstance(instance); - } - } -} \ No newline at end of file diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ChestBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ChestBlockBehaviour.java deleted file mode 100644 index 62951c05..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ChestBlockBehaviour.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public class ChestBlockBehaviour extends InventoryBlockBehaviour { - public ChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context, InventoryType.CHEST_3_ROW, Component.text("Chest")); - } - - @Override - public boolean dropContentsOnDestroy() { - return true; - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ConcretePowderBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ConcretePowderBlockBehaviour.java deleted file mode 100644 index 6d7ac686..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/ConcretePowderBlockBehaviour.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -// TODO: When placing concrete powder in water, it turns to the solid block correctly, however it falls like a regular concrete powder block -public class ConcretePowderBlockBehaviour extends GravityBlockBehaviour { - private final Block solidifiedBlock; - - public ConcretePowderBlockBehaviour(@NotNull VanillaBlocks.BlockContext context, Block solidifiedBlock) { - super(context); - this.solidifiedBlock = solidifiedBlock; - } - - @Override - public void onPlace(@NotNull VanillaBlockBehaviour.VanillaPlacement placement) { - super.onPlace(placement); - tryConvert(placement.instance(), placement.position()); - } - - @Override - public void tick(@NotNull Tick tick) { - tryConvert(tick.getInstance(), tick.getBlockPosition()); - } - - private void tryConvert(Instance instance, Point blockPosition) { - - int x = blockPosition.blockX(); - int y = blockPosition.blockY(); - int z = blockPosition.blockZ(); - - // TODO: support block tags - - if ( - instance.getBlock(x, y + 1, z).compare(Block.WATER) || // above - instance.getBlock(x - 1, y, z).compare(Block.WATER) || // west - instance.getBlock(x + 1, y, z).compare(Block.WATER) || // east - instance.getBlock(x, y, z - 1).compare(Block.WATER) || // north - instance.getBlock(x, y, z + 1).compare(Block.WATER) // south - ) { - instance.setBlock(blockPosition, solidifiedBlock); - } - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EndPortalBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EndPortalBlockBehaviour.java deleted file mode 100644 index 36f7e919..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EndPortalBlockBehaviour.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.world.DimensionType; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.dimensions.VanillaDimensionTypes; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -public class EndPortalBlockBehaviour extends VanillaBlockBehaviour { - public EndPortalBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - -// @Override -// protected BlockPropertyList createPropertyValues() { -// return new BlockPropertyList(); -// } - - @Override - public void onTouch(@NotNull Touch touch) { - Instance instance = touch.getInstance(); - Entity touching = touch.getTouching(); - var key = instance.getDimensionType(); - DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(key); - - DimensionType targetDimension = VanillaDimensionTypes.OVERWORLD; - Optional potentialTargetInstance = MinecraftServer.getInstanceManager().getInstances().stream() - .filter(in -> { - var key1 = in.getDimensionType(); - return MinecraftServer.getDimensionTypeRegistry().get(key1) == targetDimension; - }) - .findFirst(); - - // TODO: event - if (potentialTargetInstance.isPresent()) { - Instance targetInstance = potentialTargetInstance.get(); - Pos spawnPoint; - final int obsidianPlatformX = 100; - final int obsidianPlatformY = 48; - final int obsidianPlatformZ = 0; - - if (targetDimension == VanillaDimensionTypes.OVERWORLD) { // teleport to spawn point - if (touching instanceof Player) { - spawnPoint = ((Player) touching).getRespawnPoint(); - } else { // TODO: world spawnpoint - spawnPoint = new Pos(0, 80, 0); - } - } else { - // teleport to the obsidian platform, and recreate it if necessary - int yLevel = touching instanceof Player ? 49 : 50; - spawnPoint = new Pos(obsidianPlatformX, yLevel, obsidianPlatformZ); - } - - if (targetDimension.effects().equals("the_end")) { - for (int x = -1; x <= 1; x++) { - for (int z = -1; z <= 1; z++) { - targetInstance.loadChunk(obsidianPlatformX / 16 + x, obsidianPlatformZ / 16 + z); - } - } - - // clear 5x3x5 area around platform - for (int x = 0; x < 5; x++) { - for (int z = 0; z < 5; z++) { - for (int y = 0; y < 3; y++) { - targetInstance.setBlock(obsidianPlatformX + x, obsidianPlatformY + y + 1, obsidianPlatformZ + z, Block.AIR); - } - } - } - } - touching.setInstance(targetInstance); - touching.teleport(spawnPoint); - } - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EnderChestBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EnderChestBlockBehaviour.java deleted file mode 100644 index f1e96759..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/EnderChestBlockBehaviour.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.system.EnderChestSystem; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class EnderChestBlockBehaviour extends InventoryBlockBehaviour { - public EnderChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context, InventoryType.CHEST_3_ROW, Component.text("Ender Chest")); - } - - @Override - public boolean dropContentsOnDestroy() { - return false; - } - - @Override - protected List getAllItems(Instance instance, Point pos, Player player) { - return EnderChestSystem.getInstance().getItems(player); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/FireBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/FireBlockBehaviour.java deleted file mode 100644 index ec39c2af..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/FireBlockBehaviour.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.LivingEntity; -import net.minestom.server.entity.damage.DamageType; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.system.NetherPortal; -import org.jetbrains.annotations.NotNull; - -public class FireBlockBehaviour extends VanillaBlockBehaviour { - public FireBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - -// @Override -// protected BlockPropertyList createPropertyValues() { -// return new BlockPropertyList().intRange("age", 0, 15); -// } - - @Override - public void onTouch(@NotNull Touch touch) { - Entity touching = touch.getTouching(); - - if (!(touching instanceof LivingEntity livingEntity)) { - return; - } - - if (livingEntity.isOnFire()) { - return; - } - - livingEntity.damage(DamageType.IN_FIRE, 1.0f); - livingEntity.setFireTicks(8 * 20); - } - - public void checkForPortal(Instance instance, Point pos, Block block) { - NetherPortal portal = NetherPortal.findPortalFrameFromFrameBlock(instance, pos); - - if (portal == null) { - return; - } - - if (portal.tryFillFrame(instance)) { - portal.register(instance); - } - } - - @Override - public void onPlace(@NotNull VanillaPlacement placement) { - // check for Nether portal immediately - checkForPortal(placement.instance(), placement.position(), placement.blockToPlace()); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/GravityBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/GravityBlockBehaviour.java deleted file mode 100644 index 4f97587b..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/GravityBlockBehaviour.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.EntityType; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.VanillaRegistry; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blockupdatesystem.BlockUpdatable; -import net.minestom.vanilla.blockupdatesystem.BlockUpdateInfo; -import net.minestom.vanilla.blockupdatesystem.BlockUpdateManager; -import net.minestom.vanilla.entitymeta.EntityTags; -import org.jetbrains.annotations.NotNull; - -public class GravityBlockBehaviour extends VanillaBlockBehaviour implements BlockUpdatable { - public GravityBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - - @Override - public void onPlace(@NotNull VanillaPlacement placement) { - Instance instance = placement.instance(); - Point position = placement.position(); - Block block = placement.blockToPlace(); - - if (checkFall(instance, position, block)) { - placement.blockToPlace(Block.AIR); - } - } - - /** - * Checks if a block should fall - * - * @param instance the instance the block is in - * @param position the position of the block - * @return true if the block should fall - */ - public boolean checkFall(Instance instance, Point position, Block block) { - Block below = instance.getBlock(position.blockX(), position.blockY() - 1, position.blockZ()); - - // Exit out now if block below is solid - if (below.isSolid()) { - return false; - } - - // Schedule block update - BlockUpdateManager.from(instance).scheduleNeighborsUpdate(position, BlockUpdateInfo.MOVE_BLOCK()); - - // Create the context - Pos initialPosition = new Pos(position.x() + 0.5f, Math.round(position.y()), position.z() + 0.5f); - VanillaRegistry.EntityContext entityContext = context.vri().entityContext(EntityType.FALLING_BLOCK, - initialPosition, nbt -> nbt.setTag(EntityTags.FallingBlock.BLOCK, block)); - Entity entity = context.vri().createEntityOrDummy(entityContext); - - // Spawn the entity - entity.setInstance(instance, initialPosition); - return true; - } - - @Override - public void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info) { - checkFall(instance, pos, instance.getBlock(pos)); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/InventoryBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/InventoryBlockBehaviour.java deleted file mode 100644 index 1ab36963..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/InventoryBlockBehaviour.java +++ /dev/null @@ -1,229 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.ItemEntity; -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.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.tag.Tag; -import net.minestom.server.utils.Direction; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnknownNullability; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -/** - * Base class for blocks with an inventory. - *

- * This class needs onPlace to be able to change the block being placed - */ -public abstract class InventoryBlockBehaviour extends VanillaBlockBehaviour { - - public static final Tag> TAG_ITEMS = Tag.ItemStack("vri:chest_items").list(); - protected static final Random rng = new Random(); - protected final InventoryType type; - protected final Component title; - - public InventoryBlockBehaviour(@NotNull VanillaBlocks.BlockContext context, InventoryType type, Component title) { - super(context); - this.type = type; - this.title = title; - } - - @Override - public void onPlace(@NotNull VanillaPlacement placement) { - Block block = placement.blockToPlace(); - Instance instance = placement.instance(); - Point pos = placement.position(); - - @UnknownNullability List items = block.getTag(TAG_ITEMS); - - if (items != null) { - return; - } - - ItemStack[] itemsArray = new ItemStack[type.getSize()]; - Arrays.fill(itemsArray, ItemStack.AIR); - - // Override the block to set - Block blockToSet = block.withTag(TAG_ITEMS, List.of(itemsArray)); - placement.blockToPlace(blockToSet); - } - - @Override - public void onDestroy(@NotNull Destroy destroy) { - Instance instance = destroy.getInstance(); - Point pos = destroy.getBlockPosition(); - Block block = destroy.getBlock(); - - // TODO: Introduce a way to get the block this is getting replaced with, enabling us to remove the tick delay. - destroy.getInstance().scheduleNextTick(ignored -> { - if (instance.getBlock(pos).compare(block)) { - // Same block, don't remove chest inventory - return; - } - - // Different block, remove chest inventory - List items = BlockInventory.remove(instance, pos); - - if (!dropContentsOnDestroy()) { - return; - } - - for (ItemStack item : items) { - - if (item == null) { - continue; - } - - ItemEntity entity = new ItemEntity(item); - - entity.setInstance(destroy.getInstance()); - entity.teleport(new Pos(pos.x() + rng.nextDouble(), pos.y() + .5f, pos.z() + rng.nextDouble())); - } - }); - } - -// @Override -// public short getVisualBlockForPlacement(Player player, Player.Hand hand, BlockPosition position) { -// // TODO: handle double chests -// boolean waterlogged = Block.fromStateId(player.getInstance().getBlockStateId(position.getX(), position.getY(), position.getZ())) == Block.WATER; -// float yaw = player.getPosition().getYaw(); -// Direction direction = MathUtils.getHorizontalDirection(yaw).opposite(); -// return getBaseBlockState().with("facing", direction.name().toLowerCase()).with("waterlogged", String.valueOf(waterlogged)).getBlockId(); -// } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - // TODO: handle double chests - // TODO: Handle crouching players - - Block block = interaction.getBlock(); - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Player player = interaction.getPlayer(); - - Block above = instance.getBlock(pos.blockX(), pos.blockY() + 1, pos.blockZ()); - - if (above.isSolid()) { // FIXME: chests below transparent blocks cannot be opened - return false; - } - - Inventory chestInventory = BlockInventory.from(instance, pos, type, title); - player.openInventory(chestInventory); - return true; - } - - public abstract boolean dropContentsOnDestroy(); - - /** - * Gets the items in this block only - * - * @param block the block - * @return the items - */ - protected @NotNull List getItems(Block block) { - List items = block.getTag(TAG_ITEMS); - if (items == null) { - throw new IllegalStateException("Chest block has no items"); - } - if (items.size() != this.type.getSize()) { - throw new IllegalStateException("Invalid items size"); - } - return items; - } - - /** - * Sets the items in this block only - * - * @param block the block - * @param items the items - */ - protected Block setItems(Block block, List items) { - if (items.size() != this.type.getSize()) { - throw new IllegalStateException("Invalid items size"); - } - return block.withTag(TAG_ITEMS, items); - } - - /** - * Gets all items represented by this position in this instance - * - * @param instance the instance - * @param pos the position - * @return all items in the position in the instance - */ - protected List getAllItems(Instance instance, Point pos, Player player) { - Block block = instance.getBlock(pos); - List items = new ArrayList<>(getItems(block)); - - Point positionOfOtherChest = pos; - Direction facing = Direction.valueOf(block.getProperty("facing").toUpperCase()); - String type = block.getProperty("type"); - - switch (type) { - case "single" -> { - return List.copyOf(items); - } - case "left" -> positionOfOtherChest = positionOfOtherChest.add(-facing.normalZ(), 0, facing.normalX()); - case "right" -> positionOfOtherChest = positionOfOtherChest.add(facing.normalZ(), 0, -facing.normalX()); - default -> throw new IllegalArgumentException("Invalid chest type: " + type); - } - - Block otherBlock = instance.getBlock(positionOfOtherChest); - BlockHandler handler = otherBlock.handler(); - - if (handler instanceof InventoryBlockBehaviour chestLike) { - items.addAll(chestLike.getItems(otherBlock)); - } - - return List.copyOf(items); - } - -// @Override -// public Data readBlockEntity(NBTCompound nbt, Instance instance, BlockPosition position, Data originalData) { -// ChestBlockEntity data; -// if (originalData instanceof ChestBlockEntity) { -// data = (ChestBlockEntity) originalData; -// } else { -// data = new ChestBlockEntity(position.copy()); -// } -// -// // TODO: CustomName -// // TODO: Lock -// // TODO: LootTable -// // TODO: LootTableSeed -// -// if (nbt.containsKey("Items")) { -// NBTUtils.loadAllItems(nbt.getList("Items"), data.getInventory()); -// } -// -// return data; -// } - -// @Override -// public void writeBlockEntity(BlockPosition position, Data blockData, NBTCompound nbt) { -// // TODO: CustomName -// // TODO: Lock -// // TODO: LootTable -// // TODO: LootTableSeed -// if (blockData instanceof ChestBlockEntity) { -// ChestBlockEntity data = (ChestBlockEntity) blockData; -// NBTList list = new NBTList<>(NBTTypes.TAG_Compound); -// NBTUtils.saveAllItems(list, data.getInventory()); -// nbt.set("Items", list); -// } -// } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/JukeboxBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/JukeboxBlockBehaviour.java deleted file mode 100644 index 97bd563f..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/JukeboxBlockBehaviour.java +++ /dev/null @@ -1,183 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.component.DataComponents; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.entity.ItemEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.jukebox.JukeboxSong; -import net.minestom.server.item.ItemStack; -import net.minestom.server.registry.DynamicRegistry; -import net.minestom.server.tag.Tag; -import net.minestom.server.worldevent.WorldEvent; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.inventory.InventoryManipulation; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.Random; - -/** - * Reimplementation of the jukebox block - *

- * Requires onPlace enhancements - */ -public class JukeboxBlockBehaviour extends VanillaBlockBehaviour { - - public static final Tag DISC_KEY = Tag.ItemStack("minestom:jukebox_disc"); - - public JukeboxBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - - @Override - public void onDestroy(@NotNull Destroy destroy) { - if (!(destroy instanceof PlayerDestroy)) return; - stopPlayback(destroy.getInstance(), destroy.getBlockPosition(), destroy.getBlock()); - } - - public @Nullable ItemStack getDisc(Block block) { - return block.getTag(DISC_KEY); - } - - public @NotNull Block withDisc(Block block, @NotNull ItemStack disc) { - if (isNotMusicDisc(disc)) { - throw new IllegalArgumentException("disc passed to JukeboxBlockHandle#withDisc was not a music disc."); - } - return block.withTag(DISC_KEY, disc); - } - - private boolean isNotMusicDisc(ItemStack itemStack) { - return !itemStack.has(DataComponents.JUKEBOX_PLAYABLE); - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - Player player = interaction.getPlayer(); - PlayerHand hand = interaction.getHand(); - Instance instance = interaction.getInstance(); - Block block = interaction.getBlock(); - Point pos = interaction.getBlockPosition(); - ItemStack heldItem = player.getItemInMainHand(); - - ItemStack stack = this.getDisc(block); - - if (stack != null && !stack.isAir()) { - stopPlayback(instance, pos, block); - block = block.withTag(DISC_KEY, ItemStack.AIR); - instance.setBlock(pos, block.withProperty("has_record", "false")); - return true; - } - - if (isNotMusicDisc(heldItem)) { - return true; - } - - instance.setBlock(pos, withDisc(block, heldItem).withProperty("has_record", "true")); - - InventoryManipulation.consumeItemIfNotCreative(player, heldItem, hand); - - JukeboxSong song = heldItem.get(DataComponents.JUKEBOX_PLAYABLE).holder().resolve(MinecraftServer.getJukeboxSongRegistry()); - DynamicRegistry.Key songKey = MinecraftServer.getJukeboxSongRegistry().getKey(song); - int songId = MinecraftServer.getJukeboxSongRegistry().getId(songKey); - - // TODO: Group packet? - instance.getPlayers() - .stream() - .filter(player1 -> player1.getDistance(pos) < 64) - .forEach(player1 -> - player1.playEffect( - WorldEvent.SOUND_PLAY_JUKEBOX_SONG, - pos.blockX(), - pos.blockY(), - pos.blockZ(), - songId, - false - ) - ); - - return true; - } - - @Override - public boolean isTickable() { - return true; - } - - public void tick(@NotNull Tick tick) { - Instance instance = tick.getInstance(); - - long age = instance.getWorldAge(); - - // Continue only every 3 seconds - if (age % (MinecraftServer.TICK_PER_SECOND * 3L) != 0) { - } - - // TODO: Play sound to all players without the sound playing - } - -// @Override -// public Data readBlockEntity(NBTCompound nbt, Instance instance, BlockPosition position, Data originalData) { -// JukeboxBlockEntity data; -// if (originalData instanceof JukeboxBlockEntity) { -// data = (JukeboxBlockEntity) originalData; -// } else { -// data = new JukeboxBlockEntity(position.copy()); -// } -// -// if(nbt.containsKey("RecordItem")) { -// data.setDisc(ItemStack.fromNBT(nbt.getCompound("RecordItem"))); -// } -// return super.readBlockEntity(nbt, instance, position, originalData); -// } -// -// @Override -// public void writeBlockEntity(BlockPosition position, Data blockData, NBTCompound nbt) { -// if(blockData instanceof JukeboxBlockEntity) { -// JukeboxBlockEntity data = (JukeboxBlockEntity) blockData; -// nbt.set("RecordItem", data.getDisc().toNBT()); -// } -// } - - /** - * Stops playback in an instance - */ - private void stopPlayback(Instance instance, Point pos, Block block) { - ItemEntity discEntity = new ItemEntity(Objects.requireNonNull(getDisc(block))); - discEntity.setInstance(instance); - discEntity.teleport(new Pos(pos.x() + 0.5f, pos.y() + 1f, pos.z() + 0.5f)); - discEntity.setPickable(true); - - Random rng = new Random(); - final float horizontalSpeed = 2f; - final float verticalSpeed = 5f; - - discEntity.setVelocity(new Vec( - rng.nextGaussian() * horizontalSpeed, - rng.nextFloat() * verticalSpeed, - rng.nextGaussian() * horizontalSpeed - )); - - discEntity.setInstance(instance); - - // TODO: Group Packet? - instance.getPlayers().forEach(playerInInstance -> { - // stop playback - playerInInstance.playEffect( - WorldEvent.SOUND_STOP_JUKEBOX_SONG, - pos.blockX(), - pos.blockY(), - pos.blockZ(), - -1, - false - ); - }); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/NetherPortalBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/NetherPortalBlockBehaviour.java deleted file mode 100644 index ada1be76..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/NetherPortalBlockBehaviour.java +++ /dev/null @@ -1,328 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.MinecraftServer; -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.entity.Entity; -import net.minestom.server.event.Event; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.tag.Tag; -import net.minestom.server.world.DimensionType; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blockupdatesystem.BlockUpdatable; -import net.minestom.vanilla.blockupdatesystem.BlockUpdateInfo; -import net.minestom.vanilla.dimensions.VanillaDimensionTypes; -import net.minestom.vanilla.system.NetherPortal; -import net.minestom.vanilla.system.nether.EntityEnterNetherPortalEvent; -import net.minestom.vanilla.system.nether.NetherPortalTeleportEvent; -import net.minestom.vanilla.system.nether.NetherPortalUpdateEvent; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Optional; - -public class NetherPortalBlockBehaviour extends VanillaBlockBehaviour implements BlockUpdatable { - - /** - * Time the entity has spent inside a portal. Reset when entering a different portal or by - * reentering a portal after leaving one - */ - public static final Tag TICKS_SPENT_IN_PORTAL_KEY = Tag.Long("minestom:time_spent_in_nether_portal").defaultValue(0L); - - /** - * Prevents multiple updates from different portal blocks - */ - public static final Tag LAST_PORTAL_UPDATE_KEY = Tag.Long("minestom:last_nether_portal_update_time").defaultValue(Long.MAX_VALUE); - - /** - * Used to check whether the last portal entered is corresponding to this portal block or not - */ - public static final Tag LAST_PORTAL_KEY = Tag.Long("minestom:last_nether_portal"); - - /** - * Time before teleporting an entity - */ - public static final Tag PORTAL_COOLDOWN_TIME_KEY = Tag.Long("minestom:nether_portal_cooldown_time").defaultValue(0L); - - /** - * The portal related to this block - */ - public static final Tag RELATED_PORTAL_KEY = Tag.Long("minestom:related_portal"); - - public NetherPortalBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - - @Override - public void onTouch(@NotNull Touch touch) { - Block block = touch.getBlock(); - Instance instance = touch.getInstance(); - Point pos = touch.getBlockPosition(); - Entity touching = touch.getTouching(); - - Long lastPortalUpdate = block.getTag(LAST_PORTAL_UPDATE_KEY); - - if (lastPortalUpdate == null) { - return; - } - - if (lastPortalUpdate < touching.getAliveTicks() - 2) { // if a tick happened with no portal update, that means the entity left the portal at some point - Block newBlock = block - .withTag(LAST_PORTAL_UPDATE_KEY, 0L) - .withTag(TICKS_SPENT_IN_PORTAL_KEY, 0L); - - instance.setBlock(pos, newBlock); - return; - } - - if (lastPortalUpdate == touching.getAliveTicks()) { - return; - } - - NetherPortal portal = getPortal(block); - long ticksSpentInPortal = updateTimeInPortal(instance, pos, touching, block, portal); - - Long portalCooldownTime = block.getTag(PORTAL_COOLDOWN_TIME_KEY); - - if (portalCooldownTime == null) { - portalCooldownTime = 0L; - } - - if (ticksSpentInPortal >= portalCooldownTime) { - attemptTeleport(instance, touching, block, ticksSpentInPortal, portal); - } - } - - private long updateTimeInPortal(Instance instance, Point position, Entity touching, Block block, NetherPortal portal) { - Block newBlock = block; - - newBlock = newBlock.withTag(LAST_PORTAL_UPDATE_KEY, touching.getAliveTicks()); - Long ticksSpentInPortal = block.getTag(TICKS_SPENT_IN_PORTAL_KEY); - - if (ticksSpentInPortal == null) { - ticksSpentInPortal = 0L; - } - - NetherPortal portalEntityWasIn = NetherPortal.fromId(newBlock.getTag(LAST_PORTAL_KEY)); - - if (portal != portalEntityWasIn) { - ticksSpentInPortal = 0L; // reset counter - } - - newBlock = newBlock.withTag(LAST_PORTAL_KEY, portal.id()); // data.set(, portal, NetherPortal.class); - - if (ticksSpentInPortal == 0) { - Event event = new EntityEnterNetherPortalEvent(touching, position, portal); - - MinecraftServer.getGlobalEventHandler().call(event); - } - - ticksSpentInPortal++; - - newBlock = newBlock.withTag(TICKS_SPENT_IN_PORTAL_KEY, ticksSpentInPortal); - - instance.setBlock(position, newBlock); - - Event event = new NetherPortalUpdateEvent(touching, position, portal, instance, ticksSpentInPortal); - - MinecraftServer.getGlobalEventHandler().call(event); - return ticksSpentInPortal; - } - - private void attemptTeleport(Instance instance, Entity touching, Block block, long ticksSpentInPortal, NetherPortal portal) { - DimensionType targetDimension; - Point position = touching.getPosition(); - - double targetX = position.x() / 8; - double targetY = position.y(); - double targetZ = position.z() / 8; - - var key = instance.getDimensionType(); - DimensionType dimension = MinecraftServer.getDimensionTypeRegistry().get(key); - if (dimension.effects().equals("nether")) { - targetDimension = MinecraftServer.getDimensionTypeRegistry().get(DimensionType.OVERWORLD); - targetX = position.x() * 8; - targetZ = position.z() * 8; - } else { - targetDimension = VanillaDimensionTypes.OVERWORLD; - } - - // TODO: event to change portal linking - final DimensionType finalTargetDimension = targetDimension; - Optional potentialTargetInstance = MinecraftServer.getInstanceManager().getInstances().stream() - .filter(in -> { - var key1 = in.getDimensionType(); - return MinecraftServer.getDimensionTypeRegistry().get(key1) == targetDimension; - }) - .findFirst(); - - if (potentialTargetInstance.isEmpty()) { - return; - } - - Instance targetInstance = potentialTargetInstance.get(); - Pos targetPosition = new Pos(targetX, targetY, targetZ); - NetherPortal targetPortal = getCorrespondingNetherPortal(targetInstance, targetPosition); - - boolean generatePortal = false; - if (targetPortal == null) { // no existing portal, will create one - - NetherPortal.Axis axis = portal.getAxis(); - Pos bottomRight = new Pos( - targetX - axis.xMultiplier, - targetY - 1, - targetZ - axis.zMultiplier - ); - - Pos topLeft = new Pos( - targetX + 2 * axis.xMultiplier, - targetY + 3, - targetZ + 2 * axis.zMultiplier - ); - - targetPortal = new NetherPortal(portal.getAxis(), bottomRight, topLeft); - generatePortal = true; - } - - targetPosition = calculateTargetPosition(touching, portal, targetPortal); - - NetherPortalTeleportEvent event = new NetherPortalTeleportEvent(touching, position, portal, ticksSpentInPortal, targetInstance, targetPosition, targetPortal, generatePortal); - MinecraftServer.getGlobalEventHandler().call(event); - - if (!event.isCancelled()) { - Block newBlock = block - .withTag(LAST_PORTAL_UPDATE_KEY, 0L) - .withTag(LAST_PORTAL_KEY, portal.id()) - .withTag(TICKS_SPENT_IN_PORTAL_KEY, 0L); - instance.setBlock(position, newBlock); - teleport(instance, touching, event); - } - } - - private @Nullable NetherPortal getCorrespondingNetherPortal(Instance targetInstance, Point targetPosition) { - Block block = targetInstance.getBlock(targetPosition); - return NetherPortal.fromId(block.getTag(RELATED_PORTAL_KEY)); - } - - private Pos calculateTargetPosition(Entity touching, NetherPortal portal, NetherPortal targetPortal) { - Point targetCenter = targetPortal.getCenter(); - - if (portal == null) { // if this block is not isolated - return new Pos( - targetCenter.x() + 0.5, - targetCenter.y(), - targetCenter.z() + 0.5 - ); - } - - Pos touchingPos = touching.getPosition(); - double touchingX = touchingPos.x(); - double touchingY = touchingPos.y(); - double touchingZ = touchingPos.z(); - - Vec portalCenter = portal.getCenter(); - double portalCenterX = portalCenter.x(); - double portalCenterY = portalCenter.y(); - double portalCenterZ = portalCenter.z(); - - NetherPortal.Axis portalAxis = portal.getAxis(); - double portalAxisXMultiplier = portalAxis.xMultiplier; - double portalAxisZMultiplier = portalAxis.zMultiplier; - - NetherPortal.Axis targetAxis = targetPortal.getAxis(); - double targetAxisXMultiplier = targetAxis.xMultiplier; - double targetAxisZMultiplier = targetAxis.zMultiplier; - - double relativeX = (touchingX - portalCenterX) / (portal.computeWidth() * portalAxisXMultiplier + portalAxisZMultiplier); - double relativeY = (touchingY - portalCenterY) / portal.computeHeight(); - double relativeZ = (touchingZ - portalCenterZ) / (portal.computeWidth() * portalAxisZMultiplier + portalAxisXMultiplier); - - double targetMultiplierX = (targetPortal.computeWidth() * targetAxisXMultiplier + targetAxisZMultiplier); - double targetMultiplierY = targetPortal.computeHeight(); - double targetMultiplierZ = (targetPortal.computeWidth() * targetAxisZMultiplier + targetAxisXMultiplier); - - return new Pos( - targetCenter.x() + relativeX * targetMultiplierX, - targetCenter.y() + relativeY * targetMultiplierY, - targetCenter.z() + relativeZ * targetMultiplierZ - ); - } - - private void teleport(Instance instance, Entity touching, NetherPortalTeleportEvent event) { - Instance targetInstance = event.getTargetInstance(); - if (event.createsNewPortal()) { - event.getTargetPortal().generate(targetInstance); - } - - if (targetInstance != instance) { - touching.setInstance(targetInstance); - } - - Pos targetTeleportationPosition = new Pos(event.getTargetPosition()); - - touching.teleport(targetTeleportationPosition).thenRun(() -> { - Vec velocity = touching.getVelocity(); - - if ( - event.getPortal() != null && - event.getPortal().getAxis() - != event.getTargetPortal().getAxis() - ) { - double swapTmp = velocity.x(); - - touching.setVelocity(new Vec( - swapTmp, - velocity.z(), - swapTmp - )); - } - }); - } - - @Override - public void onDestroy(@NotNull Destroy destroy) { - Block block = destroy.getBlock(); - Instance instance = destroy.getInstance(); - - NetherPortal netherPortal = getPortal(block); - if (netherPortal != null) { - netherPortal.breakFrame(instance); - netherPortal.unregister(instance); - } - } - - private NetherPortal getPortal(Block block) { - return NetherPortal.fromId(block.getTag(RELATED_PORTAL_KEY)); - } - -// @Override -// public void updateFromNeighbor(Instance instance, Point thisPosition, Point neighborPosition, boolean directNeighbor) { -// -// } - - private void breakPortalIfNoLongerValid(Instance instance, Point blockPosition) { - NetherPortal netherPortal = getPortal(instance.getBlock(blockPosition)); - - if (netherPortal == null) { - return; - } - - if (netherPortal.isStillValid(instance)) { - return; - } - - netherPortal.breakFrame(instance); - } - - public void setRelatedPortal(Instance instance, Point blockPosition, Block block, NetherPortal portal) { - instance.setBlock(blockPosition, block.withTag(RELATED_PORTAL_KEY, portal.id())); - } - - @Override - public void blockUpdate(@NotNull Instance instance, @NotNull Point pos, @NotNull BlockUpdateInfo info) { - breakPortalIfNoLongerValid(instance, pos); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TNTBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TNTBlockBehaviour.java deleted file mode 100644 index 13cea37a..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TNTBlockBehaviour.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.EntityType; -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Material; -import net.minestom.vanilla.VanillaRegistry; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.entitymeta.EntityTags; -import org.jetbrains.annotations.NotNull; - -import java.util.Random; - -public class TNTBlockBehaviour extends VanillaBlockBehaviour { - - public static final Random TNT_RANDOM = new Random(); - - public TNTBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context); - } - -// @Override -// protected BlockPropertyList createPropertyValues() { -// return new BlockPropertyList().booleanProperty("unstable"); -// } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - Point blockPosition = interaction.getBlockPosition(); - Player player = interaction.getPlayer(); - PlayerHand hand = interaction.getHand(); - - if (player.getItemInHand(hand).material() != Material.FLINT_AND_STEEL) { - return true; - } - - player.getInstance().setBlock(blockPosition, Block.AIR); - spawnPrimedTNT(player.getInstance(), blockPosition, 80); - - return true; - } - - private void spawnPrimedTNT(Instance instance, Point blockPosition, int fuseTime) { - Pos initialPosition = new Pos(blockPosition.blockX() + 0.5f, blockPosition.blockY() + 0f, blockPosition.blockZ() + 0.5f); - - // Create the entity - VanillaRegistry.EntityContext entityContext = context.vri().entityContext(EntityType.TNT, initialPosition, - writable -> writable.setTag(EntityTags.PrimedTnt.FUSE_TIME, fuseTime)); - Entity primedTnt = context.vri().createEntityOrDummy(entityContext); - - // Spawn it with random velocity - primedTnt.setInstance(instance, initialPosition); - primedTnt.setVelocity(new Vec(TNT_RANDOM.nextFloat() * 2f - 1f, TNT_RANDOM.nextFloat() * 5f, TNT_RANDOM.nextFloat() * 2f - 1f)); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TrappedChestBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TrappedChestBlockBehaviour.java deleted file mode 100644 index 7d56cfc6..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/TrappedChestBlockBehaviour.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours; - -import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public class TrappedChestBlockBehaviour extends InventoryBlockBehaviour { - // TODO: redstone signal - - public TrappedChestBlockBehaviour(@NotNull VanillaBlocks.BlockContext context) { - super(context, InventoryType.CHEST_3_ROW, Component.text("Trapped Chest")); - } - -// @Override -// protected BlockPropertyList createPropertyValues() { -// return super.createPropertyValues().property("type", "single", "left", "right"); -// } - - @Override - public boolean dropContentsOnDestroy() { - return true; - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockInventory.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockInventory.java deleted file mode 100644 index dd416448..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockInventory.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.chestlike; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class BlockInventory extends Inventory { - private static final Map BLOCK_INVENTORY_MAP = new ConcurrentHashMap<>(); - - protected final Instance instance; - protected final Point pos; - - private BlockInventory(Instance instance, Point pos, InventoryType inventoryType, Component title) { - super(inventoryType, title); - - this.instance = instance; - this.pos = pos; - - // Set items - List itemsList = instance.getBlock(pos).getTag(InventoryBlockBehaviour.TAG_ITEMS); - if (itemsList != null) { - for (int i = 0; i < itemsList.size(); i++) { - this.itemStacks[i] = itemsList.get(i); - } - } - } - - public static BlockInventory from(Instance instance, Point pos, InventoryType inventoryType, Component title) { - BlockInventory inv = BLOCK_INVENTORY_MAP.get(pos); - if (inv == null) { - inv = new BlockInventory(instance, pos, inventoryType, title); - BLOCK_INVENTORY_MAP.put(pos, inv); - } - if (inv.getInventoryType() != inventoryType) { - throw new IllegalStateException("Inventory type mismatch"); - } - if (!inv.getTitle().equals(title)) { - throw new IllegalStateException("Inventory title mismatch"); - } - return inv; - } - - public static @NotNull List remove(Instance instance, Point pos) { - BlockInventory inv = BLOCK_INVENTORY_MAP.get(pos); - if (inv == null) { - return List.of(); - } - BLOCK_INVENTORY_MAP.remove(pos); - return List.of(inv.itemStacks); - } - - @Override - public void setItemStack(int slot, @NotNull ItemStack itemStack) { - super.setItemStack(slot, itemStack); - instance.setBlock(pos, instance.getBlock(pos).withTag(InventoryBlockBehaviour.TAG_ITEMS, List.of(itemStacks))); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockItems.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockItems.java deleted file mode 100644 index 0d3a047a..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/BlockItems.java +++ /dev/null @@ -1,99 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.chestlike; - -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.tag.Tag; -import net.minestom.vanilla.tag.Tags; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * The key difference between BlockItems and BlockInventory is that BlockItems - * does not require specifying an inventoryType or title. It is designed for simpler - * container block management. - */ -public class BlockItems { - - private static final Tag> FALLBACK_TAG_ITEMS = Tag.ItemStack("vri:container_items") - .list(); - - private static Map>> TAG_ITEMS_BY_BLOCK; - - private static Tag> getTagForBlock(Block block) { - return TAG_ITEMS_BY_BLOCK.getOrDefault(block.name(), FALLBACK_TAG_ITEMS).defaultValue(List.of()); - } - - private final List items; - - private BlockItems(List items) { - this.items = new ArrayList<>(items); - } - - public static BlockItems from(Block block) { - return new BlockItems(block.getTag(getTagForBlock(block))); - } - - public static BlockItems from(Block block, int requireStacks) { - BlockItems items = from(block); - if (items.size() != requireStacks) { - items.requireStacks(requireStacks); - } - return items; - } - - private void requireStacks(int requireStacks) { - // remove from the top, or add to the top. - if (items.size() < requireStacks) { - items.addAll(Collections.nCopies(requireStacks - items.size(), ItemStack.AIR)); - } else if (items.size() > requireStacks) { - items.subList(requireStacks, items.size()).clear(); - } - } - - public @UnmodifiableView @NotNull List itemStacks() { - return Collections.unmodifiableList(items); - } - - public int size() { - return items.size(); - } - - public boolean isEmpty() { - return items.isEmpty(); - } - - public boolean isAir() { - return items.isEmpty() || items.stream().allMatch(ItemStack::isAir); - } - - public Block apply(Block block) { - return block.withTag(getTagForBlock(block), items); - } - - public void setItems(List itemStacks) { - items.clear(); - items.addAll(itemStacks); - } - - public ItemStack get(int index) { - return items.get(index); - } - - public void set(int index, ItemStack item) { - items.set(index, item); - } - - static { - Map>> tagItemsByBlock = new HashMap<>(); - - // some blocks need to be represented by a specific nbt tag to be visible to the client. - tagItemsByBlock.put(Block.CAMPFIRE, Tags.Blocks.Campfire.ITEMS); - - BlockItems.TAG_ITEMS_BY_BLOCK = Map.copyOf(tagItemsByBlock.entrySet().stream() - .map(entry -> Map.entry(entry.getKey().name(), entry.getValue())) - .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue))); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/DoubleChestInventory.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/DoubleChestInventory.java deleted file mode 100644 index 2b076bed..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/chestlike/DoubleChestInventory.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.chestlike; - -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.List; -import java.util.stream.Stream; - -public class DoubleChestInventory extends Inventory { - private final BlockInventory left; - private final BlockInventory right; - - public DoubleChestInventory(BlockInventory left, BlockInventory right, String title) { - super(InventoryType.CHEST_6_ROW, title); - this.left = left; - this.right = right; - } - - @Override - public @NotNull ItemStack getItemStack(int slot) { - if (slot < left.getSize()) { - return left.getItemStack(slot); - } - return right.getItemStack(slot - left.getSize()); - } - - @Override - public void setItemStack(int slot, @NotNull ItemStack itemStack) { - if (slot < left.getSize()) { - left.setItemStack(slot, itemStack); - } else { - right.setItemStack(slot - left.getSize(), itemStack); - } - } - - @Override - @Deprecated - public ItemStack[] getItemStacks() { - return itemStacks().toArray(ItemStack[]::new); - } - - public List itemStacks() { - return Stream.of(left, right) - .map(BlockInventory::getItemStacks) - .map(List::of) - .flatMap(Collection::stream) - .toList(); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatableBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatableBlockBehaviour.java deleted file mode 100644 index 1bfe4de2..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatableBlockBehaviour.java +++ /dev/null @@ -1,144 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.oxidisable; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.inventory.InventoryManipulation; -import net.minestom.vanilla.randomticksystem.RandomTickable; -import net.minestom.vanilla.utils.MathUtils; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Objects; -import java.util.Random; - -/** - * Oxidation (Source) - *

- * Non-waxed copper blocks have four stages of oxidation (including the initial normal state). Lightning bolts and - * axes can remove the oxidation on copper blocks. As the block begins to oxidize (exposed copper), it gets - * discolored and green spots begin to appear. As the oxidation continues (weathered copper), the block is a green - * color with brown spots. In the last stage (oxidized copper), the block is teal with several green spots. - * Oxidation of copper blocks relies only on random ticks. Rain or water does not accelerate oxidation, and covering - * copper blocks with other blocks does not prevent oxidation. In Java Edition, groups of non-waxed copper blocks - * oxidize far more slowly than single copper blocks that are spaced at least 4 blocks apart. This is because a - * block in a group being less oxidized than the others slows down the oxidation process for all other blocks within - * 4 blocks of taxicab distance. However, if one wishes to increase the oxidation speed, placing oxidized copper - * blocks around less oxidized copper blocks does not offer a speed improvement over simply placing the blocks 4 - * apart. The calculations for the oxidation behavior are as follows: - * In Java Edition, when a random tick is given, a copper block has a 64/1125 chance to enter a state called - * pre-oxidation. This means a copper block enters pre-oxidation after approximately 20 minutes. - * In pre-oxidation, the copper block searches its nearby non-waxed copper blocks for a distance of 4 blocks taxicab - * distance. If there is any copper block that has a lower oxidation level, then the pre-oxidation ends, meaning - * that this copper block does not weather. Let a be the number of all nearby non-waxed copper blocks, and b be the - * number of nearby non-waxed copper blocks that have a higher oxidation level. We derive the value of c from this - * equation: c = b + 1/a + 1. We also let the modifying factor m be 0.75 if the copper block has no oxidation level, - * or 1 if the copper block is exposed or weathered.[1] Then the oxidation probability is mc2. For example, an - * unweathered copper block surrounded by 6 unweathered copper blocks and 6 exposed copper blocks has a 21.7% chance - * to oxidize if it enters the pre-oxidation state. In this case, a = 12, b = 6, and m = 0.75.[2] The most efficient - * way of laying out the copper blocks for oxidation is in a 7×7×6 face-centered cubic (fcc)/cubic close-packed - * (ccp) lattice. - *

- */ -public class OxidatableBlockBehaviour extends WaxableBlockBehaviour implements RandomTickable, OxygenSensitive { - - private final short previous; - private final short oxidised; - private final int oxidisedLevel; - - - public OxidatableBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block previous, Block oxidised, Block waxed, int oxidisedLevel) { - super(context, waxed); - this.previous = (short) previous.stateId(); - this.oxidised = (short) oxidised.stateId(); - this.oxidisedLevel = oxidisedLevel; - } - - @Override - public void randomTick(@NotNull RandomTick randomTick) { - // Exit now if the block cannot be oxidised anymore - if (oxidised == context.stateId()) return; - - Random random = context.vri().random(randomTick.instance()); - // In Java Edition, when a random tick is given, a copper block has a 64/1125 chance to enter a state called pre-oxidation. - // This means a copper block enters pre-oxidation after approximately 20 minutes. - if (random.nextInt(1125) >= 64) { - return; - } - - // In pre-oxidation, the copper block searches its nearby non-waxed copper blocks for a distance of 4 blocks - // taxicab distance. If there is any copper block that has a lower oxidation level, then the pre-oxidation ends, - // meaning that this copper block does not weather. - List nearbyBlocks = MathUtils.getWithinManhattanDistance(randomTick.position(), 4) - .stream() - .map(point -> randomTick.instance().isChunkLoaded(point) ? - randomTick.instance().getBlock(point) : Block.AIR) - .toList(); - int minOxidisedAround = nearbyBlocks.stream() - .filter(block -> block.handler() instanceof OxygenSensitive) - .map(block -> (OxygenSensitive) block.handler()) - .mapToInt(OxygenSensitive::oxidisedLevel) - .min() - .orElse(Integer.MAX_VALUE); - - if (minOxidisedAround < oxidisedLevel) { - return; - } - - // Let a be the number of all nearby non-waxed copper blocks, and b be the number of nearby non-waxed copper - // blocks that have a higher oxidation level. We derive the value of c from this equation: c = b + 1/a + 1. - // We also let the modifying factor m be 0.75 if the copper block has no oxidation level, or 1 if the copper - // block is exposed or weathered.[1] Then the oxidation probability is mc2. - // For example, an unweathered copper block surrounded by 6 unweathered copper blocks and 6 exposed copper - // blocks has a 21.7% chance to oxidize if it enters the pre-oxidation state. In this case, a = 12, b = 6, and - // m = 0.75.[2] - double a = (int) nearbyBlocks.stream() - .filter(block -> block.handler() instanceof OxygenSensitive os - && os.oxidisedLevel() == oxidisedLevel()) // Filter out unrelated blocks - .count(); - double b = (int) nearbyBlocks.stream() - .filter(block -> !(block.handler() instanceof WaxedBlockBehaviour)) - .filter(block -> block.handler() instanceof OxygenSensitive os - && os.oxidisedLevel() > oxidisedLevel()) // Filter out unrelated blocks - .map(block -> (OxygenSensitive) block.handler()).filter(Objects::nonNull) - .filter(handler -> handler.oxidisedLevel() > oxidisedLevel) - .count(); - double m = oxidisedLevel == 0 ? 0.75 : 1; - double c = (b + 1) / (a + 1); - double probability = m * c * c; - - if (random.nextDouble() < probability) { - Block block = Block.fromStateId(oxidised); - Objects.requireNonNull(block, "Block with state id " + oxidised + " was not found"); - randomTick.instance().setBlock(randomTick.position(), block); - } - } - - @Override - public int oxidisedLevel() { - return oxidisedLevel; - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - PlayerHand hand = interaction.getHand(); - Player player = interaction.getPlayer(); - Block interactionBlock = interaction.getBlock(); - - ItemStack item = player.getItemInHand(hand); - Material material = item.material(); - - if (interactionBlock.stateId() != previous - && material.key().value().toLowerCase().contains("_axe")) { // TODO: Better way to check if it's an axe - Block previousBlock = Block.fromStateId(previous); - Objects.requireNonNull(previousBlock, "Block with state id " + previous + " was not found"); - interaction.getInstance().setBlock(interaction.getBlockPosition(), previousBlock); - InventoryManipulation.damageItemIfNotCreative(player, hand, 1); - return false; - } - return super.onInteract(interaction); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatedBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatedBlockBehaviour.java deleted file mode 100644 index 553a70a4..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxidatedBlockBehaviour.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.oxidisable; - -import net.minestom.server.instance.block.Block; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public abstract class OxidatedBlockBehaviour extends WaxableBlockBehaviour implements OxygenSensitive { - - private final int oxidisedLevel; - - public OxidatedBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block waxed, int oxidisedLevel) { - super(context, waxed); - this.oxidisedLevel = oxidisedLevel; - } - - @Override - public int oxidisedLevel() { - return oxidisedLevel; - } -} \ No newline at end of file diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxygenSensitive.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxygenSensitive.java deleted file mode 100644 index 26c105d4..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/OxygenSensitive.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.oxidisable; - -public interface OxygenSensitive { - int oxidisedLevel(); -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxableBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxableBlockBehaviour.java deleted file mode 100644 index 53dc6679..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxableBlockBehaviour.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.oxidisable; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.inventory.InventoryManipulation; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public abstract class WaxableBlockBehaviour extends VanillaBlockBehaviour { - protected final short waxedBlock; - protected WaxableBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block waxedTarget) { - super(context); - this.waxedBlock = (short) waxedTarget.stateId(); - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - PlayerHand hand = interaction.getHand(); - Player player = interaction.getPlayer(); - - ItemStack item = player.getItemInHand(hand); - Material material = item.material(); - - if (Material.HONEYCOMB.equals(material)) { - Block block = Block.fromStateId(waxedBlock); - Objects.requireNonNull(block, "Waxed block with state id " + waxedBlock + " does not exist"); - interaction.getInstance().setBlock(interaction.getBlockPosition(), block); - InventoryManipulation.consumeItemIfNotCreative(player, hand, 1); - return false; - } - return super.onInteract(interaction); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxedBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxedBlockBehaviour.java deleted file mode 100644 index dab90c68..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/oxidisable/WaxedBlockBehaviour.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.oxidisable; - -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.inventory.InventoryManipulation; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class WaxedBlockBehaviour extends OxidatedBlockBehaviour { - private final short unWaxed; - - public WaxedBlockBehaviour(VanillaBlocks.@NotNull BlockContext context, Block unWaxed, int oxidisedLevel) { - super(context, Block.fromStateId(context.stateId()), oxidisedLevel); - this.unWaxed = (short) unWaxed.stateId(); - } - - @Override - public boolean onInteract(@NotNull Interaction interaction) { - PlayerHand hand = interaction.getHand(); - Player player = interaction.getPlayer(); - - ItemStack item = player.getItemInHand(hand); - Material material = item.material(); - - if (material.key().value().toLowerCase().contains("_axe")) { // TODO: Better way to check if it's an axe - Block previousBlock = Block.fromStateId(unWaxed); - Objects.requireNonNull(previousBlock, "Previous block with state id " + unWaxed + " was not found"); - interaction.getInstance().setBlock(interaction.getBlockPosition(), previousBlock); - InventoryManipulation.damageItemIfNotCreative(player, hand, 1); - return false; - } - return super.onInteract(interaction); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/BlastingFurnaceBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/BlastingFurnaceBehaviour.java deleted file mode 100644 index f463d775..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/BlastingFurnaceBehaviour.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.BlockVec; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour; -import net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory; -import net.minestom.vanilla.events.BlastingFurnaceTickEvent; -import org.jetbrains.annotations.NotNull; - -public class BlastingFurnaceBehaviour extends InventoryBlockBehaviour { - public BlastingFurnaceBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context, InventoryType.BLAST_FURNACE, Component.text("Blast Furnace")); - } - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.BLAST_FURNACE, Component.text("Blast Furnace")); - Player player = interaction.getPlayer(); - player.openInventory(inventory); - return false; - } - - @Override - public boolean dropContentsOnDestroy() { - return true; - } - - @Override - public boolean isTickable() { - return true; - } - - @Override - public void tick(@NotNull BlockHandler.Tick tick) { - var events = this.context.vri().process().eventHandler(); - if (!events.hasListener(BlastingFurnaceTickEvent.class)) return; // fast exit since this is hot code - Instance instance = tick.getInstance(); - Point pos = tick.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.BLAST_FURNACE, Component.text("Blast Furnace")); - BlastingFurnaceTickEvent event = new BlastingFurnaceTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory); - events.call(event); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CampfireBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CampfireBehaviour.java deleted file mode 100644 index 7105a45a..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CampfireBehaviour.java +++ /dev/null @@ -1,231 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.coordinate.Pos; -import net.minestom.server.entity.ItemEntity; -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.BlockHandler; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.recipe.*; -import net.minestom.server.recipe.display.RecipeDisplay; -import net.minestom.server.recipe.display.SlotDisplay; -import net.minestom.server.tag.Tag; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blocks.behaviours.chestlike.BlockItems; -import net.minestom.vanilla.inventory.InventoryManipulation; -import net.minestom.vanilla.tag.Tags.Blocks.Campfire; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.stream.IntStream; - -public class CampfireBehaviour extends VanillaBlockBehaviour { - - private static final int CONTAINER_SIZE = 4; - private static final Random RNG = new Random(); - private final RecipeManager recipeManager; - - public CampfireBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context); - this.recipeManager = context.vri().process().recipe(); - } - - public BlockItems getBlockItems(Block block) { - return BlockItems.from(block, CONTAINER_SIZE); - } - - public @NotNull Block withCookingProgress(Block block, int slotIndex, int cookingTime) { - List cookingProgress = new ArrayList<>(block.getTag(Campfire.COOKING_PROGRESS)); - cookingProgress.set(slotIndex, cookingTime); - return block.withTag(Campfire.COOKING_PROGRESS, cookingProgress); - } - - /** - * Appends an id to the first available slot in the campfire. - * @return the index of the slot the id was appended to. - */ - public int appendItem(BlockItems items, @NotNull Material material) { - OptionalInt freeSlot = findFirstFreeSlot(items.itemStacks()); - - if (freeSlot.isEmpty()) - throw new IllegalArgumentException("Campfire doesn't have free slot for appending an id in CampfireBehaviour#appendItem"); - - ItemStack ingredient = ItemStack.of(material); - - int index = freeSlot.getAsInt(); - items.set(index, ingredient); - - return index; - } - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Block block = interaction.getBlock(); - Player player = interaction.getPlayer(); - ItemStack input = player.getItemInHand(interaction.getHand()); - Optional recipeOptional = findCampfireCookingRecipe(input); - - if (recipeOptional.isEmpty()) - return true; - - BlockItems items = getBlockItems(block); - if (findFirstFreeSlot(items.itemStacks()).isEmpty()) - return true; - - boolean itemNotConsumed = !InventoryManipulation.consumeItemIfNotCreative(player, interaction.getHand(), 1); - - if (itemNotConsumed) - return true; - - Recipe recipe = recipeOptional.get(); - if (!(recipe.createRecipeDisplays().getFirst() instanceof RecipeDisplay.Furnace furnaceRecipe)) return false; - Material material = getMaterialFromSlotDisplay(furnaceRecipe.ingredient()); - if (material == null) return false; - int index = appendItem(items, material); - block = withCookingProgress(block, index, furnaceRecipe.duration()); - instance.setBlock(pos, items.apply(block)); - return false; - } - - @Override - public void onDestroy(@NotNull BlockHandler.Destroy destroy) { - Instance instance = destroy.getInstance(); - Point pos = destroy.getBlockPosition(); - Block block = destroy.getBlock(); - - // TODO: Introduce a way to get the block this is getting replaced with, enabling us to remove the tick delay. - instance.scheduleNextTick(ignored -> { - Block newBlock = instance.getBlock(pos); - if (newBlock.compare(block)) { - // Same block, don't remove campfire - return; - } - - // Different block, remove campfire - List items = BlockItems.from(newBlock).itemStacks(); - - for (ItemStack item : items) { - - if (item == null) { - continue; - } - - dropItem(instance, pos, item); - } - }); - } - - @Override - public void tick(@NotNull BlockHandler.Tick tick) { - Instance instance = tick.getInstance(); - Block block = tick.getBlock(); - Point pos = tick.getBlockPosition(); - BlockItems items = getBlockItems(block); - - if (items.isAir()) - return; - - List cookingProgress = new ArrayList<>(block.getTag(Campfire.COOKING_PROGRESS)); - - boolean lit = Boolean.parseBoolean(block.getProperty("lit")); - if (!lit) { - for (ItemStack item : items.itemStacks()) - dropItem(instance, pos, item); - items.setItems(Collections.nCopies(4, ItemStack.AIR)); - instance.setBlock(pos, items.apply(block)); - return; - } - - for (ListIterator i = cookingProgress.listIterator(); i.hasNext(); ) { - int index = i.nextIndex(); - Integer progress = i.next(); - Material inputMaterial = items.get(index).material(); - - if (Material.AIR.equals(inputMaterial)) - continue; - - if (progress <= 0) { - endCampfireCookingProgress(tick.getInstance(), tick.getBlockPosition(), ItemStack.of(inputMaterial)); - items.set(index, ItemStack.AIR); - continue; - } - - progress -= 1; - i.set(progress); - } - - block = items.apply(block); - block = block.withTag(Campfire.COOKING_PROGRESS, cookingProgress); - instance.setBlock(pos, block); - } - - @Override - public @NotNull Collection> getBlockEntityTags() { - return List.of(Campfire.ITEMS); - } - - @Override - public boolean isTickable() { - return true; - } - - private void endCampfireCookingProgress(Instance instance, Point pos, ItemStack input) { - Optional recipeOptional = findCampfireCookingRecipe(input); - if (recipeOptional.isEmpty()) - throw new IllegalArgumentException("Cannot end campfire cooking progress because input recipe doesn't found"); - Material material = getRecipeResult(recipeOptional.get()); - if (material == null) { - return; - } - dropItem(instance, pos, ItemStack.of(material)); - } - - private void dropItem(Instance instance, Point pos, ItemStack item) { - ItemEntity resultItemEntity = new ItemEntity(item); - resultItemEntity.setInstance(instance); - resultItemEntity.teleport(new Pos(pos.x() + RNG.nextDouble(), pos.y() + .5f, pos.z() + RNG.nextDouble())); - } - - private OptionalInt findFirstFreeSlot(List items) { - return IntStream.range(0, items.size()).filter(index -> items.get(index).isAir()).findFirst(); - } - - private Material getRecipeResult(Recipe recipe) { - RecipeDisplay d = recipe.createRecipeDisplays().getFirst(); - if (d instanceof RecipeDisplay.Furnace f) { - return getMaterialFromSlotDisplay(f.result()); - } - return null; - } - - private Material getRecipeInput(Recipe recipe) { - RecipeDisplay d = recipe.createRecipeDisplays().getFirst(); - if (d instanceof RecipeDisplay.Furnace f) { - return getMaterialFromSlotDisplay(f.ingredient()); - } - return null; - } - - private Material getMaterialFromSlotDisplay(SlotDisplay slotDisplay) { - return switch (slotDisplay) { - case SlotDisplay.Item i -> i.material(); - case SlotDisplay.ItemStack i -> i.itemStack().material(); - default -> null; - }; - } - - private Optional findCampfireCookingRecipe(ItemStack input) { - if (input == null) - return Optional.empty(); - return recipeManager - .getRecipes().stream() - .filter(r -> r.recipeBookCategory() == RecipeBookCategory.CAMPFIRE) - .filter(recipe -> input.material().equals(getRecipeInput(recipe))).findFirst(); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CraftingTableBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CraftingTableBehaviour.java deleted file mode 100644 index 707c18ad..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/CraftingTableBehaviour.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.EventListener; -import net.minestom.server.event.inventory.InventoryCloseEvent; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public class CraftingTableBehaviour extends VanillaBlockBehaviour { - public CraftingTableBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context); - } - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Inventory inventory = new Inventory(InventoryType.CRAFTING, "Crafting Table"); - Player player = interaction.getPlayer(); - player.openInventory(inventory); - player.eventNode().addListener( - EventListener.builder(InventoryCloseEvent.class) - .filter(event -> inventory == event.getInventory()) - .expireCount(1) - .handler(event -> { - // TODO: Drop all items instead of adding them to the player's inventory? - for (ItemStack itemStack : inventory.getItemStacks()) { - player.getInventory().addItemStack(itemStack); - } - }) - .build() - ); - return false; - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/FurnaceBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/FurnaceBehaviour.java deleted file mode 100644 index 10c034a8..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/FurnaceBehaviour.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.BlockVec; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour; -import net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory; -import net.minestom.vanilla.events.FurnaceTickEvent; -import org.jetbrains.annotations.NotNull; - -public class FurnaceBehaviour extends InventoryBlockBehaviour { - public FurnaceBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context, InventoryType.FURNACE, Component.text("Furnace")); - } - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.FURNACE, Component.text("Furnace")); - Player player = interaction.getPlayer(); - player.openInventory(inventory); - return false; - } - - @Override - public boolean dropContentsOnDestroy() { - return true; - } - - @Override - public boolean isTickable() { - return true; - } - - @Override - public void tick(@NotNull BlockHandler.Tick tick) { - var events = this.context.vri().process().eventHandler(); - if (!events.hasListener(FurnaceTickEvent.class)) return; // fast exit since this is hot code - Instance instance = tick.getInstance(); - Point pos = tick.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.FURNACE, Component.text("Furnace")); - FurnaceTickEvent event = new FurnaceTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory); - events.call(event); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmithingTableBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmithingTableBehaviour.java deleted file mode 100644 index 6d4aae21..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmithingTableBehaviour.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.EventListener; -import net.minestom.server.event.inventory.InventoryCloseEvent; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public class SmithingTableBehaviour extends VanillaBlockBehaviour { - public SmithingTableBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context); - } - - // TODO: block placement facing - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Inventory inventory = new Inventory(InventoryType.SMITHING, "Upgrade Gear"); - - Player player = interaction.getPlayer(); - player.openInventory(inventory); - player.eventNode().addListener( - EventListener.builder(InventoryCloseEvent.class) - .filter(event -> inventory == event.getInventory()) - .expireCount(1) - .handler(event -> { - // TODO: Drop all items instead of adding them to the player's inventory? - for (int i = 0; i < 3; i++) { - player.getInventory().addItemStack(inventory.getItemStack(i)); - } - }) - .build() - ); - return false; - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmokerBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmokerBehaviour.java deleted file mode 100644 index edb2ce9e..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/SmokerBehaviour.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.kyori.adventure.text.Component; -import net.minestom.server.coordinate.BlockVec; -import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Player; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlocks; -import net.minestom.vanilla.blocks.behaviours.InventoryBlockBehaviour; -import net.minestom.vanilla.blocks.behaviours.chestlike.BlockInventory; -import net.minestom.vanilla.events.SmokerTickEvent; -import org.jetbrains.annotations.NotNull; - -public class SmokerBehaviour extends InventoryBlockBehaviour { - public SmokerBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context, InventoryType.SMOKER, Component.text("Smoker")); - } - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Instance instance = interaction.getInstance(); - Point pos = interaction.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.SMOKER, Component.text("Smoker")); - Player player = interaction.getPlayer(); - player.openInventory(inventory); - return false; - } - - @Override - public boolean dropContentsOnDestroy() { - return true; - } - - @Override - public boolean isTickable() { - return true; - } - - @Override - public void tick(@NotNull BlockHandler.Tick tick) { - var events = this.context.vri().process().eventHandler(); - if (!events.hasListener(SmokerTickEvent.class)) return; // fast exit since this is hot code - Instance instance = tick.getInstance(); - Point pos = tick.getBlockPosition(); - Inventory inventory = BlockInventory.from(instance, pos, InventoryType.SMOKER, Component.text("Smoker")); - SmokerTickEvent event = new SmokerTickEvent(tick.getBlock(), tick.getInstance(), new BlockVec(tick.getBlockPosition()), inventory); - events.call(event); - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/StonecutterBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/StonecutterBehaviour.java deleted file mode 100644 index ca8b3da4..00000000 --- a/blocks/src/main/java/net/minestom/vanilla/blocks/behaviours/recipe/StonecutterBehaviour.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.minestom.vanilla.blocks.behaviours.recipe; - -import net.minestom.server.entity.Player; -import net.minestom.server.event.EventListener; -import net.minestom.server.event.inventory.InventoryCloseEvent; -import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.vanilla.blocks.VanillaBlockBehaviour; -import net.minestom.vanilla.blocks.VanillaBlocks; -import org.jetbrains.annotations.NotNull; - -public class StonecutterBehaviour extends VanillaBlockBehaviour { - public StonecutterBehaviour(VanillaBlocks.@NotNull BlockContext context) { - super(context); - } - - // TODO: block placement facing - - @Override - public boolean onInteract(@NotNull BlockHandler.Interaction interaction) { - Inventory inventory = new Inventory(InventoryType.STONE_CUTTER, "Stonecutter"); - - Player player = interaction.getPlayer(); - player.openInventory(inventory); - player.eventNode().addListener( - EventListener.builder(InventoryCloseEvent.class) - .filter(event -> inventory == event.getInventory()) - .expireCount(1) - .handler(event -> { - // TODO: Drop all items instead of adding them to the player's inventory? - player.getInventory().addItemStack(inventory.getItemStack(0)); - }) - .build() - ); - return false; - } -} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/event/CakeEatEvent.java b/blocks/src/main/java/net/minestom/vanilla/blocks/event/CakeEatEvent.java new file mode 100644 index 00000000..380f4494 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/event/CakeEatEvent.java @@ -0,0 +1,62 @@ +package net.minestom.vanilla.blocks.event; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.entity.Player; +import net.minestom.server.event.Event; +import net.minestom.server.event.trait.BlockEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CakeEatEvent implements Event, PlayerInstanceEvent, CancellableEvent, BlockEvent { + private final Player player; + private final Block block; + private final BlockVec blockPosition; + private boolean cancelled = false; + + public CakeEatEvent(Player player, Block block, BlockVec blockPosition) { + this.player = player; + this.block = block; + this.blockPosition = blockPosition; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } + + @Override + public @NotNull Block getBlock() { + return block; + } + + @Override + public @NotNull BlockVec getBlockPosition() { + return blockPosition; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public @NotNull Instance getInstance() { + return player.getInstance(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/event/CopperOxidationEvent.java b/blocks/src/main/java/net/minestom/vanilla/blocks/event/CopperOxidationEvent.java new file mode 100644 index 00000000..d394c141 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/event/CopperOxidationEvent.java @@ -0,0 +1,71 @@ +package net.minestom.vanilla.blocks.event; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.Event; +import net.minestom.server.event.trait.BlockEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.InstanceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CopperOxidationEvent implements Event, CancellableEvent, BlockEvent, InstanceEvent { + private final Block block; + private Block blockAfterOxidation; + private final BlockVec position; + private final Instance instance; + private boolean cancelled = false; + + public CopperOxidationEvent( + Block block, + Block blockAfterOxidation, + BlockVec position, + Instance instance + ) { + this.block = block; + this.blockAfterOxidation = blockAfterOxidation; + this.position = position; + this.instance = instance; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public Block getBlockAfterOxidation() { + return blockAfterOxidation; + } + + public void setBlockAfterOxidation(Block block) { + this.blockAfterOxidation = block; + } + + @Override + public @NotNull Block getBlock() { + return block; + } + + @Override + public @NotNull BlockVec getBlockPosition() { + return position; + } + + @Override + public @NotNull Instance getInstance() { + return instance; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/event/PlayerOpenSignEditorEvent.java b/blocks/src/main/java/net/minestom/vanilla/blocks/event/PlayerOpenSignEditorEvent.java new file mode 100644 index 00000000..a75b2f64 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/event/PlayerOpenSignEditorEvent.java @@ -0,0 +1,62 @@ +package net.minestom.vanilla.blocks.event; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.entity.Player; +import net.minestom.server.event.Event; +import net.minestom.server.event.trait.BlockEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class PlayerOpenSignEditorEvent implements Event, BlockEvent, CancellableEvent, PlayerInstanceEvent { + private final Player player; + private final BlockVec blockPosition; + private final Block block; + private boolean cancelled = false; + + public PlayerOpenSignEditorEvent(Player player, BlockVec blockPosition, Block block) { + this.player = player; + this.blockPosition = blockPosition; + this.block = block; + } + + @Override + public @NotNull Block getBlock() { + return block; + } + + @Override + public @NotNull BlockVec getBlockPosition() { + return blockPosition; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public @NotNull Instance getInstance() { + return player.getInstance(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaBlockBehaviour.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaBlockBehaviour.java new file mode 100644 index 00000000..f47f1034 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaBlockBehaviour.java @@ -0,0 +1,161 @@ +package net.minestom.vanilla.blocks.group; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.inventory.InventoryType; +import net.minestom.vanilla.blocks.behavior.*; +import net.minestom.vanilla.blocks.group.behaviour.BehaviourGroup; + +import java.util.function.Function; +import net.minestom.vanilla.blocks.group.block.BlockGroup; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class VanillaBlockBehaviour extends VanillaRuleset> { + + public static final VanillaBlockBehaviour INSTANCE = new VanillaBlockBehaviour(); + + public static final BehaviourGroup CRAFTING_TABLE = INSTANCE.group( + INSTANCE.byBlock(Block.CRAFTING_TABLE), + new GenericWorkStationRule( + Block.CRAFTING_TABLE, + InventoryType.CRAFTING, + "container.crafting" + ) + ); + + public static final BehaviourGroup ANVIL = INSTANCE.group( + INSTANCE.byBlock(Block.ANVIL), + new GenericWorkStationRule( + Block.ANVIL, + InventoryType.ANVIL, + "container.repair" + ) + ); + + public static final BehaviourGroup BREWING_STAND = INSTANCE.group( + INSTANCE.byBlock(Block.BREWING_STAND), + new GenericWorkStationRule( + Block.BREWING_STAND, + InventoryType.BREWING_STAND, + "container.brewing" + ) + ); + + public static final BehaviourGroup LOOM = INSTANCE.group( + INSTANCE.byBlock(Block.LOOM), + new GenericWorkStationRule( + Block.LOOM, + InventoryType.LOOM, + "container.loom" + ) + ); + + public static final BehaviourGroup GRINDSTONE = INSTANCE.group( + INSTANCE.byBlock(Block.GRINDSTONE), + new GenericWorkStationRule( + Block.GRINDSTONE, + InventoryType.GRINDSTONE, + "container.grindstone" + ) + ); + + public static final BehaviourGroup SMITHING_TABLE = INSTANCE.group( + INSTANCE.byBlock(Block.SMITHING_TABLE), + new GenericWorkStationRule( + Block.SMITHING_TABLE, + InventoryType.SMITHING, + "container.upgrade" + ) + ); + + public static final BehaviourGroup CARTOGRAPHY_TABLE = INSTANCE.group( + INSTANCE.byBlock(Block.CARTOGRAPHY_TABLE), + new GenericWorkStationRule( + Block.CARTOGRAPHY_TABLE, + InventoryType.CARTOGRAPHY, + "container.cartography_table" + ) + ); + + public static final BehaviourGroup STONECUTTER = INSTANCE.group( + INSTANCE.byBlock(Block.STONECUTTER), + new GenericWorkStationRule( + Block.STONECUTTER, + InventoryType.STONE_CUTTER, + "container.stonecutter" + ) + ); + + public static final BehaviourGroup ENCHANTING_TABLE = INSTANCE.group( + INSTANCE.byBlock(Block.ENCHANTING_TABLE), + new GenericWorkStationRule( + Block.ENCHANTING_TABLE, + InventoryType.ENCHANTMENT, + "container.enchant" + ) + ); + + public static final BehaviourGroup TRAPDOOR = INSTANCE.group( + INSTANCE.byTag("minecraft:wooden_trapdoors"), + WoodenTrapDoorOpenRule::new + ); + + public static final BehaviourGroup FENCE_GATE = INSTANCE.group( + INSTANCE.byTag("minecraft:fence_gates"), + GateOpenRule::new + ); + + public static final BehaviourGroup COPPER = INSTANCE.group( + INSTANCE.byList(CopperOxidationRule.oxidationStages.keySet()), + CopperOxidationRule::new + ); + + public static final BehaviourGroup WOODEN_DOORS = INSTANCE.group( + INSTANCE.byExclusion( + INSTANCE.byTag("minecraft:doors"), + INSTANCE.byBlock(Block.IRON_DOOR) + ), + DoorOpenRule::new + ); + + public static final BehaviourGroup SIGNS = INSTANCE.group( + INSTANCE.byTag("minecraft:all_signs"), + SignEditRule::new + ); + + public static final BehaviourGroup CAKE = INSTANCE.group( + INSTANCE.byBlock(Block.CAKE), + CakeEatRule::new + ); + + public static final BehaviourGroup CANDLE_CAKE = INSTANCE.group( + INSTANCE.byTag("minecraft:candle_cakes"), + CandleCakeRule::new + ); + + public static final BehaviourGroup STRIPPABLE_WOOD = INSTANCE.group( + INSTANCE.byList(StrippingBehaviorRule.getStrippableBlocks()), + StrippingBehaviorRule::new + ); + + @Override + protected BehaviourGroup createGroup( + BlockGroup blockGroup, + Function valueFunction + ) { + return new BehaviourGroup(blockGroup, valueFunction); + } + + public BehaviourGroup group(BlockGroup blockGroup, BlockHandler handler) { + BehaviourGroup group = new BehaviourGroup(blockGroup, block -> handler); + ALL.add(group); + return group; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaPlacementRules.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaPlacementRules.java new file mode 100644 index 00000000..143daae9 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaPlacementRules.java @@ -0,0 +1,495 @@ +package net.minestom.vanilla.blocks.group; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.blocks.group.block.BlockGroup; +import net.minestom.vanilla.blocks.group.placement.PlacementGroup; +import net.minestom.vanilla.blocks.placement.*; + +import java.util.function.Function; + + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class VanillaPlacementRules extends VanillaRuleset> { + + public static final VanillaPlacementRules INSTANCE = new VanillaPlacementRules(); + + public static final PlacementGroup ROTATED_PILLARS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:logs"), + INSTANCE.byBlock(Block.MUDDY_MANGROVE_ROOTS), + INSTANCE.byBlock(Block.BAMBOO_BLOCK), + INSTANCE.byBlock(Block.STRIPPED_BAMBOO_BLOCK), + INSTANCE.byBlock(Block.BASALT), + INSTANCE.byBlock(Block.POLISHED_BASALT), + INSTANCE.byBlock(Block.QUARTZ_PILLAR), + INSTANCE.byBlock(Block.PURPUR_PILLAR), + INSTANCE.byBlock(Block.BONE_BLOCK), + INSTANCE.byBlock(Block.DEEPSLATE), + INSTANCE.byBlock(Block.INFESTED_DEEPSLATE), + INSTANCE.byBlock(Block.OCHRE_FROGLIGHT), + INSTANCE.byBlock(Block.VERDANT_FROGLIGHT), + INSTANCE.byBlock(Block.PEARLESCENT_FROGLIGHT), + INSTANCE.byBlock(Block.HAY_BLOCK) + ), + RotatedPillarPlacementRule::new + ); + + public static final PlacementGroup SLAB = INSTANCE.group( + INSTANCE.byTag("minecraft:slabs"), + SlabPlacementRule::new + ); + + public static final PlacementGroup VERTICALLY_ROTATED = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.FURNACE), + INSTANCE.byBlock(Block.BLAST_FURNACE), + INSTANCE.byBlock(Block.SMOKER), + INSTANCE.byBlock(Block.LECTERN), + INSTANCE.byBlock(Block.ENDER_CHEST), + INSTANCE.byBlock(Block.CHISELED_BOOKSHELF), + INSTANCE.byBlock(Block.CARVED_PUMPKIN), + INSTANCE.byBlock(Block.JACK_O_LANTERN), + INSTANCE.byBlock(Block.BEEHIVE), + INSTANCE.byBlock(Block.STONECUTTER), + INSTANCE.byBlock(Block.LOOM), + INSTANCE.byBlock(Block.BEE_NEST), + INSTANCE.byBlock(Block.END_PORTAL_FRAME), + INSTANCE.byBlock(Block.VAULT) + ), + VerticallyRotatedPlacementRule::new + ); + + public static final PlacementGroup ROTATED_WORKSTATIONS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.ANVIL), + INSTANCE.byBlock(Block.CHIPPED_ANVIL), + INSTANCE.byBlock(Block.DAMAGED_ANVIL) + ), + InverseWorkstationPlacementRule::new + ); + + public static final PlacementGroup AMETHYST = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.AMETHYST_CLUSTER), + INSTANCE.byBlock(Block.SMALL_AMETHYST_BUD), + INSTANCE.byBlock(Block.MEDIUM_AMETHYST_BUD), + INSTANCE.byBlock(Block.LARGE_AMETHYST_BUD) + ), + AmethystPlacementRule::new + ); + + public static final PlacementGroup BAMBOO = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.BAMBOO), + INSTANCE.byBlock(Block.BAMBOO_SAPLING) + ), + BambooPlantPlacementRule::new + ); + + public static final PlacementGroup BANNER = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:banners") + ), + BannerPlacementRule::new + ); + + public static final PlacementGroup FACING = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.BARREL), + INSTANCE.byBlock(Block.PISTON), + INSTANCE.byBlock(Block.STICKY_PISTON), + INSTANCE.byBlock(Block.COMMAND_BLOCK), + INSTANCE.byBlock(Block.CHAIN_COMMAND_BLOCK), + INSTANCE.byBlock(Block.REPEATING_COMMAND_BLOCK), + INSTANCE.byBlock(Block.DROPPER), + INSTANCE.byBlock(Block.DISPENSER) + ), + FacingPlacementRule::new + ); + + public static final PlacementGroup OBSERVER = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.OBSERVER) + ), + ObserverPlacementRule::new + ); + + public static final PlacementGroup SIMPLE_WATERLOGGABLE = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.BARRIER), + INSTANCE.byBlock(Block.COPPER_GRATE), + INSTANCE.byBlock(Block.EXPOSED_COPPER_GRATE), + INSTANCE.byBlock(Block.WEATHERED_COPPER_GRATE), + INSTANCE.byBlock(Block.OXIDIZED_COPPER_GRATE), + INSTANCE.byBlock(Block.WAXED_COPPER_GRATE), + INSTANCE.byBlock(Block.WAXED_EXPOSED_COPPER_GRATE), + INSTANCE.byBlock(Block.WAXED_WEATHERED_COPPER_GRATE), + INSTANCE.byBlock(Block.WAXED_OXIDIZED_COPPER_GRATE), +// INSTANCE.byBlock(Block.DRIED_GHAST), + INSTANCE.byBlock(Block.HEAVY_CORE) + ), + SimpleWaterloggablePlacementRule::new + ); + + public static final PlacementGroup BEDS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:beds") + ), + BedPlacementRule::new + ); + + public static final PlacementGroup CROPS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:crops") + ), + CropPlacementRule::new + ); + + public static final PlacementGroup BELL = INSTANCE.group( + INSTANCE.byBlock(Block.BELL), + BellPlacementRule::new + ); + + public static final PlacementGroup BIG_DRIPLEAF = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.BIG_DRIPLEAF), + INSTANCE.byBlock(Block.BIG_DRIPLEAF_STEM) + ), + BigDripleafPlacementRule::new + ); + + public static final PlacementGroup BOTTOM_SUPPORTED = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:pressure_plates"), + INSTANCE.byBlock(Block.CAKE) + ), + SupportedBelowPlacementRule::new + ); + + public static final PlacementGroup PIN_BOTTOM_SUPPORTED = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:wool_carpets") + ), + PinSupportedBelowPlacementRule::new + ); + + public static final PlacementGroup BUTTONS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:buttons") + ), + FacedFacingPlacementRule::new + ); + + public static final PlacementGroup CACTUS = INSTANCE.group( + INSTANCE.byBlock(Block.CACTUS), + CactusPlacementRule::new + ); + + public static final PlacementGroup CAMPFIRE = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.CAMPFIRE), + INSTANCE.byBlock(Block.SOUL_CAMPFIRE) + ), + CampfireBlockPlacementRule::new + ); + + public static final PlacementGroup CANDLES = INSTANCE.group( + INSTANCE.byTag("minecraft:candles"), + CandlePlacementRule::new + ); + + public static final PlacementGroup VINES_TOP = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.CAVE_VINES), + INSTANCE.byBlock(Block.WEEPING_VINES) + ), + TopAttachedVinePlacementRule::new + ); + + public static final PlacementGroup TRAPDOOR = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:trapdoors") + ), + TrapdoorPlacementRule::new + ); + + public static final PlacementGroup FENCE = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:fences") + ), + FencePlacementRule::new + ); + + public static final PlacementGroup FENCE_GATE = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:fence_gates") + ), + FenceGatePlacementRule::new + ); + + public static final PlacementGroup STAIRS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:stairs") + ), + StairsPlacementRule::new + ); + + public static final PlacementGroup VERTICAL_SLIM = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.IRON_BARS), + INSTANCE.byTag("vri:glass_panes") + ), + VerticalSlimBlockPlacementRule::new + ); + + public static final PlacementGroup LADDERS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:ladders") + ), + LadderPlacementRule::new + ); + + public static final PlacementGroup TORCHES = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.TORCH), + INSTANCE.byBlock(Block.SOUL_TORCH), + INSTANCE.byBlock(Block.REDSTONE_TORCH), + INSTANCE.byBlock(Block.WALL_TORCH), + INSTANCE.byBlock(Block.SOUL_WALL_TORCH), + INSTANCE.byBlock(Block.SOUL_FIRE) + ), + TorchPlacementRule::new + ); + + + public static final PlacementGroup WALLS = INSTANCE.group( + INSTANCE.byTag("minecraft:walls"), + WallBlockPlacementRule::new + ); + + public static final PlacementGroup DOORS = INSTANCE.group( + INSTANCE.byTag("minecraft:doors"), + DoorPlacementRule::new + ); + + public static final PlacementGroup LANTERNS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.LANTERN), + INSTANCE.byBlock(Block.SOUL_LANTERN) + ), + LanternPlacementRule::new + ); + + public static final PlacementGroup GLAZED_TERRACOTTA = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.MAGENTA_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.WHITE_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.LIGHT_GRAY_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.GRAY_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.BLACK_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.BROWN_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.RED_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.ORANGE_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.YELLOW_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.LIME_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.GREEN_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.CYAN_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.LIGHT_BLUE_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.BLUE_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.PURPLE_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.MAGENTA_GLAZED_TERRACOTTA), + INSTANCE.byBlock(Block.PINK_GLAZED_TERRACOTTA) + ), + GlazedTerracottaPlacementRule::new + ); + + public static final PlacementGroup CHAINS = INSTANCE.group( + INSTANCE.byBlock(Block.CHAIN), + ChainPlacementRule::new + ); + + public static final PlacementGroup TALL_FLOWERS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.PEONY), + INSTANCE.byBlock(Block.TALL_GRASS), + INSTANCE.byBlock(Block.LARGE_FERN), + INSTANCE.byBlock(Block.SUNFLOWER), + INSTANCE.byBlock(Block.LILAC), + INSTANCE.byBlock(Block.ROSE_BUSH) + ), + TallFlowerPlacementRule::new + ); + + public static final PlacementGroup SIGNS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:all_signs") + ), + SignPlacementRule::new + ); + + public static final PlacementGroup CHESTS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:chests"), + INSTANCE.byBlock(Block.CHEST), + INSTANCE.byBlock(Block.TRAPPED_CHEST) + ), + ChestPlacementRule::new + ); + + public static final PlacementGroup HOPPERS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.HOPPER) + ), + HopperPlacementRule::new + ); + + public static final PlacementGroup SHULKERBOXES = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:shulker_boxes") + ), + ShulkerPlacementRule::new + ); + + public static final PlacementGroup FLOOR_FLOWER = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.WILDFLOWERS), + INSTANCE.byBlock(Block.LEAF_LITTER), + INSTANCE.byBlock(Block.PINK_PETALS) + ), + FloorFillerPlacementRule::new + ); + + public static final PlacementGroup CORALS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:corals"), + INSTANCE.byBlock(Block.DEAD_TUBE_CORAL), + INSTANCE.byBlock(Block.DEAD_BRAIN_CORAL), + INSTANCE.byBlock(Block.DEAD_BUBBLE_CORAL), + INSTANCE.byBlock(Block.DEAD_FIRE_CORAL), + INSTANCE.byBlock(Block.DEAD_HORN_CORAL), + INSTANCE.byBlock(Block.DEAD_TUBE_CORAL_FAN), + INSTANCE.byBlock(Block.DEAD_BRAIN_CORAL_FAN), + INSTANCE.byBlock(Block.DEAD_BUBBLE_CORAL_FAN), + INSTANCE.byBlock(Block.DEAD_FIRE_CORAL_FAN), + INSTANCE.byBlock(Block.DEAD_HORN_CORAL_FAN) + ), + CoralPlacementRule::new + ); + + public static final PlacementGroup WALL_CORALS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.TUBE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.BRAIN_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.BUBBLE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.FIRE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.HORN_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.DEAD_TUBE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.DEAD_BRAIN_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.DEAD_BUBBLE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.DEAD_FIRE_CORAL_WALL_FAN), + INSTANCE.byBlock(Block.DEAD_HORN_CORAL_WALL_FAN) + ), + WallCoralPlacementRule::new + ); + + public static final PlacementGroup HEADS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.SKELETON_SKULL), + INSTANCE.byBlock(Block.WITHER_SKELETON_SKULL), + INSTANCE.byBlock(Block.ZOMBIE_HEAD), + INSTANCE.byBlock(Block.CREEPER_HEAD), + INSTANCE.byBlock(Block.DRAGON_HEAD), + INSTANCE.byBlock(Block.PLAYER_HEAD), + INSTANCE.byBlock(Block.PIGLIN_HEAD) + ), + HeadPlacementRule::new + ); + + public static final PlacementGroup SUGAR_CANE = INSTANCE.group( + INSTANCE.byBlock(Block.SUGAR_CANE), + SugarCanePlacementRule::new + ); + + public static final PlacementGroup GROUNDED_PLANTS = INSTANCE.group( + INSTANCE.all( + INSTANCE.byTag("minecraft:saplings"), + INSTANCE.byTag("minecraft:small_flowers") + ), + GroundedPlantBlockPlacementRule::new + ); + + public static final PlacementGroup CRAFTER = INSTANCE.group( + INSTANCE.byBlock(Block.CRAFTER), + CrafterPlacementRule::new + ); + + public static final PlacementGroup LEVER = INSTANCE.group( + INSTANCE.byBlock(Block.LEVER), + LeverPlacementRule::new + ); + + public static final PlacementGroup REDSTONE_STUFF = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.COMPARATOR), + INSTANCE.byBlock(Block.REPEATER) + ), + RedstoneStuffPlacementRule::new + ); + + public static final PlacementGroup FARMLAND = INSTANCE.group( + INSTANCE.byBlock(Block.FARMLAND), + FarmlandPlacementRule::new + ); + + public static final PlacementGroup SNOWY = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.GRASS_BLOCK), + INSTANCE.byBlock(Block.PODZOL), + INSTANCE.byBlock(Block.MYCELIUM) + ), + SnowyUpdateRule::new + ); + + public static final PlacementGroup MUSHROOM = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.MUSHROOM_STEM), + INSTANCE.byBlock(Block.BROWN_MUSHROOM_BLOCK), + INSTANCE.byBlock(Block.RED_MUSHROOM_BLOCK) + ), + MushroomPlacementRule::new + ); + + public static final PlacementGroup RAIL = INSTANCE.group( + INSTANCE.byBlock(Block.RAIL), + RailPlacementRule::new + ); + + public static final PlacementGroup FEATURE_RAIL = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.ACTIVATOR_RAIL), + INSTANCE.byBlock(Block.DETECTOR_RAIL), + INSTANCE.byBlock(Block.POWERED_RAIL) + ), + FeatureRailPlacementRule::new + ); + + public static final PlacementGroup GRINDSTONE = INSTANCE.group( + INSTANCE.all( + INSTANCE.byBlock(Block.GRINDSTONE) + ), + GrindstonePlacementRule::new + ); + + @Override + protected PlacementGroup createGroup(BlockGroup blockGroup, Function valueFunction) { + return new PlacementGroup(blockGroup, valueFunction); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaRuleset.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaRuleset.java new file mode 100644 index 00000000..266fc24f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/VanillaRuleset.java @@ -0,0 +1,92 @@ +package net.minestom.vanilla.blocks.group; + +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.block.Block; +import net.minestom.vanilla.blocks.group.block.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * Base class for rule sets that manage block groups and associated functionality + * @param The implementation type for the group + * @param The function type associated with the group + */ +public abstract class VanillaRuleset { + public final List ALL = new ArrayList<>(); + + /** + * Creates and registers a group with a block group and value function + * @param blockGroup The block group to associate with + * @param valueFunction The function to associate with the blocks + * @return The created group implementation + */ + protected GroupImpl group(BlockGroup blockGroup, Functor valueFunction) { + GroupImpl result = createGroup(blockGroup, valueFunction); + ALL.add(result); + return result; + } + + /** + * Combines multiple block groups into a single aggregate group + * @param blockGroups The block groups to combine + * @return The combined block group + */ + protected BlockGroup all(BlockGroup... blockGroups) { + return new AggregateTagBlockGroup(blockGroups); + } + + /** + * Creates a block group based on a tag + * @param tag The tag name to match + * @return A block group for the tag + */ + protected BlockGroup byTag(String tag) { + return new TagBlockGroup(Key.key(tag)); + } + + /** + * Creates a block group for a single block + * @param block The block to match + * @return A block group for the block + */ + protected BlockGroup byBlock(Block block) { + return new BlockBlockGroup(block); + } + + /** + * Creates a block group from a collection of blocks + * @param blocks The blocks to include in the group + * @return A block group for the blocks + */ + protected BlockGroup byList(Collection blocks) { + return new ListBlockGroup(blocks); + } + + /** + * Creates a block group that excludes blocks from another group + * @param positive The group of blocks to include + * @param negative The group of blocks to exclude + * @return A block group with the exclusion applied + */ + protected BlockGroup byExclusion(BlockGroup positive, BlockGroup negative) { + return new ExcludeBlockRule(positive, negative); + } + + /** + * Abstract method to create a specific group implementation + * @param blockGroup The block group to associate with + * @param valueFunction The function to associate with the blocks + * @return The created group implementation + */ + protected abstract GroupImpl createGroup(BlockGroup blockGroup, Functor valueFunction); +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/behaviour/BehaviourGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/behaviour/BehaviourGroup.java new file mode 100644 index 00000000..71a1b028 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/behaviour/BehaviourGroup.java @@ -0,0 +1,52 @@ +package net.minestom.vanilla.blocks.group.behaviour; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.vanilla.blocks.group.block.BlockGroup; +import net.minestom.vanilla.blocks.group.block.IntoBlockGroup; + +import java.util.function.Function; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A group that pairs a block group with a function to create block handlers + */ +public class BehaviourGroup implements IntoBlockGroup { + private final BlockGroup blockGroup; + private final Function valueFunction; + + /** + * Creates a new behaviour group + * @param blockGroup The group of blocks to apply behavior to + * @param valueFunction The function to create handlers for blocks + */ + public BehaviourGroup(BlockGroup blockGroup, Function valueFunction) { + this.blockGroup = blockGroup; + this.valueFunction = valueFunction; + } + + /** + * Gets the block group this behavior applies to + * @return The block group + */ + @Override + public BlockGroup getBlockGroup() { + return blockGroup; + } + + /** + * Creates a handler for the given block + * @param block The block to create a handler for + * @return The block handler + */ + public BlockHandler createHandler(Block block) { + return valueFunction.apply(block); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/AggregateTagBlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/AggregateTagBlockGroup.java new file mode 100644 index 00000000..2b807f47 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/AggregateTagBlockGroup.java @@ -0,0 +1,38 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.minestom.server.instance.block.Block; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A block group that combines multiple child block groups + */ +public class AggregateTagBlockGroup implements BlockGroup { + private final BlockGroup[] children; + + /** + * Creates a new aggregate block group + * @param children The child block groups to aggregate + */ + public AggregateTagBlockGroup(BlockGroup... children) { + this.children = children; + } + + @Override + public Collection allMatching() { + Collection result = new ArrayList<>(); + for (BlockGroup child : children) { + result.addAll(child.allMatching()); + } + return result; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockBlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockBlockGroup.java new file mode 100644 index 00000000..82955428 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockBlockGroup.java @@ -0,0 +1,34 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.minestom.server.instance.block.Block; + +import java.util.Collection; +import java.util.Collections; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A block group that contains a single block + */ +public class BlockBlockGroup implements BlockGroup { + private final Block block; + + /** + * Creates a new block group for a single block + * @param block The block to include in this group + */ + public BlockBlockGroup(Block block) { + this.block = block; + } + + @Override + public Collection allMatching() { + return Collections.singletonList(block); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockGroup.java new file mode 100644 index 00000000..9b06069a --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/BlockGroup.java @@ -0,0 +1,33 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.minestom.server.instance.block.Block; + +import java.util.Collection; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * Interface for a group of blocks with common characteristics + */ +public interface BlockGroup extends IntoBlockGroup { + /** + * Gets all blocks that match this group + * @return Collection of matching blocks + */ + Collection allMatching(); + + /** + * Gets the block group (self-reference for IntoBlockGroup implementation) + * @return This block group + */ + @Override + default BlockGroup getBlockGroup() { + return this; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ExcludeBlockRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ExcludeBlockRule.java new file mode 100644 index 00000000..0e1bae88 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ExcludeBlockRule.java @@ -0,0 +1,48 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.minestom.server.instance.block.Block; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A block group that excludes blocks from another group + */ +public class ExcludeBlockRule implements BlockGroup { + private final BlockGroup blocks; + private final BlockGroup excludedBlocks; + + /** + * Creates a new block group with exclusions + * @param blocks The base block group + * @param excludedBlocks The blocks to exclude + */ + public ExcludeBlockRule(BlockGroup blocks, BlockGroup excludedBlocks) { + this.blocks = blocks; + this.excludedBlocks = excludedBlocks; + } + + @Override + public Collection allMatching() { + Collection excludedMatching = excludedBlocks.allMatching(); + Collection baseMatching = blocks.allMatching(); + + // Create a new collection that contains blocks from baseMatching not in excludedMatching + Collection result = new ArrayList<>(); + for (Block block : baseMatching) { + if (!excludedMatching.contains(block)) { + result.add(block); + } + } + + return result; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/IntoBlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/IntoBlockGroup.java new file mode 100644 index 00000000..27ef521d --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/IntoBlockGroup.java @@ -0,0 +1,20 @@ +package net.minestom.vanilla.blocks.group.block; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * Interface for objects that can provide a block group + */ +public interface IntoBlockGroup { + /** + * Gets the block group associated with this object + * @return The block group + */ + BlockGroup getBlockGroup(); +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ListBlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ListBlockGroup.java new file mode 100644 index 00000000..023ff081 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/ListBlockGroup.java @@ -0,0 +1,34 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.minestom.server.instance.block.Block; + +import java.util.Collection; +import java.util.Collections; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A block group that contains a fixed list of blocks + */ +public class ListBlockGroup implements BlockGroup { + private final Collection all; + + /** + * Creates a new block group with the given blocks + * @param all The blocks to include in this group + */ + public ListBlockGroup(Collection all) { + this.all = Collections.unmodifiableCollection(all); + } + + @Override + public Collection allMatching() { + return all; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/TagBlockGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/TagBlockGroup.java new file mode 100644 index 00000000..f9860cb7 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/block/TagBlockGroup.java @@ -0,0 +1,35 @@ +package net.minestom.vanilla.blocks.group.block; + +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.block.Block; + +import java.util.Collection; +import net.minestom.vanilla.common.utils.TagHelper; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A block group that contains blocks matching a specific tag + */ +public class TagBlockGroup implements BlockGroup { + private final Key key; + + /** + * Creates a new block group for blocks with a specific tag + * @param key The tag key + */ + public TagBlockGroup(Key key) { + this.key = key; + } + + @Override + public Collection allMatching() { + return TagHelper.getInstance().getTaggedWith(key.asString()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/IntoPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/IntoPlacementRule.java new file mode 100644 index 00000000..cbccd567 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/IntoPlacementRule.java @@ -0,0 +1,24 @@ +package net.minestom.vanilla.blocks.group.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * Interface for objects that can create block placement rules + */ +public interface IntoPlacementRule { + /** + * Creates a block placement rule for the given block + * @param block The block to create a rule for + * @return The created block placement rule + */ + BlockPlacementRule createRule(Block block); +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/PlacementGroup.java b/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/PlacementGroup.java new file mode 100644 index 00000000..24dc2076 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/group/placement/PlacementGroup.java @@ -0,0 +1,44 @@ +package net.minestom.vanilla.blocks.group.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.blocks.group.block.BlockGroup; +import net.minestom.vanilla.blocks.group.block.IntoBlockGroup; + +import java.util.function.Function; + +/** + * + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + *

+ * A group that pairs a block group with a function to create block placement rules + */ +public class PlacementGroup implements IntoBlockGroup, IntoPlacementRule { + private final BlockGroup blockGroup; + private final Function valueFunction; + + /** + * Creates a new placement group + * @param blockGroup The group of blocks to apply rules to + * @param valueFunction The function to create rules for blocks + */ + public PlacementGroup(BlockGroup blockGroup, Function valueFunction) { + this.blockGroup = blockGroup; + this.valueFunction = valueFunction; + } + + @Override + public BlockGroup getBlockGroup() { + return blockGroup; + } + + @Override + public BlockPlacementRule createRule(Block block) { + return valueFunction.apply(block); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/AmethystPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/AmethystPlacementRule.java new file mode 100644 index 00000000..ba80c742 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/AmethystPlacementRule.java @@ -0,0 +1,34 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class AmethystPlacementRule extends BlockPlacementRule { + + public AmethystPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + if (!DirectionUtils.canAttach(placementState)) return null; + + Block currentBlock = placementState.instance().getBlock(placementState.placePosition()); + boolean waterlogged = FluidUtils.isWater(currentBlock); + + return placementState.block() + .withProperty("waterlogged", String.valueOf(waterlogged)) + .withProperty("facing", placementState.blockFace().toDirection().name().toLowerCase()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BambooPlantPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BambooPlantPlacementRule.java new file mode 100644 index 00000000..14969ebe --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BambooPlantPlacementRule.java @@ -0,0 +1,84 @@ +package net.minestom.vanilla.blocks.placement; + +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.registry.Registry; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.TagHelper; +import net.minestom.vanilla.tag.Tags.Blocks; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BambooPlantPlacementRule extends BlockPlacementRule { + private final Set plantableOn; + private final Set bamboo; + + public BambooPlantPlacementRule(Block block) { + super(block); + this.plantableOn = TagHelper.getInstance().getHashed("#bamboo_plantable_on"); + this.bamboo = Set.of( + Block.BAMBOO, + Block.BAMBOO_SAPLING + ); + } + + @Override + public Block blockPlace(PlacementState placementState) { + var positionBelow = placementState.placePosition().sub(0.0, 1.0, 0.0); + var blockBelow = placementState.instance().getBlock(positionBelow); + var instance = (Instance) placementState.instance(); + + for (Block bambooPart : bamboo) { + if (blockBelow.compare(bambooPart)) { + if (blockBelow.compare(Block.BAMBOO_SAPLING)) { + instance.setBlock(positionBelow, Block.BAMBOO); + } + return placementState.block(); + } + } + + for (Block plantable : plantableOn) { + if (blockBelow.compare(plantable)) { + return Block.BAMBOO_SAPLING; + } + } + + return null; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + var below = updateState.instance().getBlock(updateState.blockPosition().sub(0.0, 1.0, 0.0)); + + boolean canStay = false; + for (Block plantable : plantableOn) { + if (plantable.compare(below)) { + canStay = true; + break; + } + } + + if (!canStay) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } + + @Override + public int maxUpdateDistance() { + return 500; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BannerPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BannerPlacementRule.java new file mode 100644 index 00000000..1a0a9ccb --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BannerPlacementRule.java @@ -0,0 +1,74 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.utils.DirectionUtils; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BannerPlacementRule extends BlockPlacementRule { + + public BannerPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (placementState.blockFace() == null || placementState.blockFace() == BlockFace.BOTTOM) + return null; + + if (placementState.blockFace() == BlockFace.TOP) { + int rotation = (DirectionUtils.sixteenStepRotation(placementState) + 8) % 16; + return block.withProperty("rotation", String.valueOf(rotation)); + } + + Block wallBanner; + var material = placementState.block().registry().material(); + + if (material.equals(Block.ORANGE_BANNER.registry().material())) { + wallBanner = Block.ORANGE_WALL_BANNER; + } else if (material.equals(Block.MAGENTA_BANNER.registry().material())) { + wallBanner = Block.MAGENTA_WALL_BANNER; + } else if (material.equals(Block.LIGHT_BLUE_BANNER.registry().material())) { + wallBanner = Block.LIGHT_BLUE_WALL_BANNER; + } else if (material.equals(Block.YELLOW_BANNER.registry().material())) { + wallBanner = Block.YELLOW_WALL_BANNER; + } else if (material.equals(Block.LIME_BANNER.registry().material())) { + wallBanner = Block.LIME_WALL_BANNER; + } else if (material.equals(Block.PINK_BANNER.registry().material())) { + wallBanner = Block.PINK_WALL_BANNER; + } else if (material.equals(Block.GRAY_BANNER.registry().material())) { + wallBanner = Block.GRAY_WALL_BANNER; + } else if (material.equals(Block.LIGHT_GRAY_BANNER.registry().material())) { + wallBanner = Block.LIGHT_GRAY_WALL_BANNER; + } else if (material.equals(Block.CYAN_BANNER.registry().material())) { + wallBanner = Block.CYAN_WALL_BANNER; + } else if (material.equals(Block.PURPLE_BANNER.registry().material())) { + wallBanner = Block.PURPLE_WALL_BANNER; + } else if (material.equals(Block.BLUE_BANNER.registry().material())) { + wallBanner = Block.BLUE_WALL_BANNER; + } else if (material.equals(Block.BROWN_BANNER.registry().material())) { + wallBanner = Block.BROWN_WALL_BANNER; + } else if (material.equals(Block.GREEN_BANNER.registry().material())) { + wallBanner = Block.GREEN_WALL_BANNER; + } else if (material.equals(Block.RED_BANNER.registry().material())) { + wallBanner = Block.RED_WALL_BANNER; + } else if (material.equals(Block.BLACK_BANNER.registry().material())) { + wallBanner = Block.BLACK_WALL_BANNER; + } else if (material.equals(Block.WHITE_BANNER.registry().material())) { + wallBanner = Block.WHITE_WALL_BANNER; + } else { + return null; + } + + return wallBanner.withNbt(placementState.block().nbtOrEmpty()) + .withProperty("facing", placementState.blockFace().name().toLowerCase()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BedPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BedPlacementRule.java new file mode 100644 index 00000000..8d83331b --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BedPlacementRule.java @@ -0,0 +1,78 @@ +package net.minestom.vanilla.blocks.placement; + +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.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BedPlacementRule extends BlockPlacementRule { + + public BedPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + Direction direction = DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite(); + var additionalReplacementBlock = placementState.placePosition().add(direction.vec()); + + if (!placementState.instance().getBlock(additionalReplacementBlock).registry().isReplaceable()) { + return null; + } + + Instance instance = (Instance) placementState.instance(); + + instance.setBlock( + additionalReplacementBlock, + placementState.block() + .withProperty("facing", direction.name().toLowerCase()) + .withProperty("part", "head") + ); + + return placementState.block() + .withProperty("facing", direction.name().toLowerCase()) + .withProperty("part", "foot"); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Direction facing = Direction.valueOf( + updateState.currentBlock() + .getProperty("facing") + .toUpperCase() + ); + BlockFace neighbourFacing; + String neighbourPart; + + if ("foot".equals(updateState.currentBlock().getProperty("part"))) { + neighbourFacing = BlockFace.fromDirection(facing); + neighbourPart = "head"; + } else { + neighbourFacing = BlockFace.fromDirection(facing).getOppositeFace(); + neighbourPart = "foot"; + } + + Block neighbour = updateState.instance().getBlock(updateState.blockPosition().relative(neighbourFacing)); + if (!neighbour.compare(block, Block.Comparator.ID)) { + return Block.AIR; + } + + String realNeighbourPart = neighbour.getProperty("part"); + if (!neighbourPart.equals(realNeighbourPart)) { + return Block.AIR; + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BellPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BellPlacementRule.java new file mode 100644 index 00000000..be5b39b8 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BellPlacementRule.java @@ -0,0 +1,94 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BellPlacementRule extends BlockPlacementRule { + + public BellPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace == null) return block; + + if (blockFace == BlockFace.BOTTOM) { + return block.withProperty("attachment", "ceiling"); + } else if (blockFace == BlockFace.TOP) { + return block + .withProperty("attachment", "floor") + .withProperty("facing", DirectionUtils.getNearestHorizontalLookingDirection(placementState).name().toLowerCase()); + } else { + String direction = blockFace.getOppositeFace().name().toLowerCase(); + boolean doubleWall = placementState.instance().getBlock( + placementState.placePosition().add( + blockFace.toDirection().vec() + ) + ).isSolid(); + + return block + .withProperty("facing", direction) + .withProperty("attachment", doubleWall ? "double_wall" : "single_wall"); + } + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + String attachment = updateState.currentBlock().getProperty("attachment"); + if ("ceiling".equals(attachment) && !updateState.instance().getBlock( + updateState.blockPosition().add( + 0.0, + 1.0, + 0.0 + ) + ).isSolid()) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + if ("floor".equals(attachment) && !updateState.instance().getBlock( + updateState.blockPosition().add( + 0.0, + -1.0, + 0.0 + ) + ).isSolid()) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + BlockFace attachmentDirection = BlockFace.valueOf(updateState.currentBlock().getProperty("facing").toUpperCase()); + Block blockInFront = updateState.instance().getBlock(updateState.blockPosition().add(attachmentDirection.toDirection().vec())); + Block blockBehind = updateState.instance().getBlock(updateState.blockPosition().add(attachmentDirection.getOppositeFace().toDirection().vec())); + + if (blockInFront.isSolid() && blockBehind.isSolid()) { + return updateState.currentBlock() + .withProperty("attachment", "double_wall"); + } else if (blockInFront.isSolid()) { + return updateState.currentBlock() + .withProperty("attachment", "single_wall") + .withProperty("facing", attachmentDirection.name().toLowerCase()); + } else if (blockBehind.isSolid()) { + return updateState.currentBlock() + .withProperty("attachment", "single_wall") + .withProperty("facing", attachmentDirection.getOppositeFace().name().toLowerCase()); + } else { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BigDripleafPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BigDripleafPlacementRule.java new file mode 100644 index 00000000..6d51ad5f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/BigDripleafPlacementRule.java @@ -0,0 +1,98 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.FluidUtils; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BigDripleafPlacementRule extends BlockPlacementRule { + private final Set plantableOn; + + public BigDripleafPlacementRule(Block block) { + super(block); + this.plantableOn = TagHelper.getInstance().getTaggedWith("minecraft:big_dripleaf_placeable"); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block blockBelow = placementState.instance().getBlock(placementState.placePosition().sub(0.0, 1.0, 0.0)); + boolean placingInsideWater = FluidUtils.isWater(placementState.instance().getBlock(placementState.placePosition())); + + // Check if placing on valid ground + for (Block validBlock : plantableOn) { + if (validBlock.compare(blockBelow)) { + Direction direction = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + return Block.BIG_DRIPLEAF + .withProperty("facing", direction.name().toLowerCase()) + .withProperty("waterlogged", String.valueOf(placingInsideWater)); + } + } + + // Check if placing on another dripleaf + if (blockBelow.compare(Block.BIG_DRIPLEAF)) { + String direction = blockBelow.getProperty("facing"); + boolean bottomInsideWater = Boolean.parseBoolean(blockBelow.getProperty("waterlogged")); + Instance instance = (Instance) placementState.instance(); + + instance.setBlock( + placementState.placePosition().sub(0.0, 1.0, 0.0), + Block.BIG_DRIPLEAF_STEM + .withProperty("facing", direction) + .withProperty("waterlogged", String.valueOf(bottomInsideWater)) + ); + + return Block.BIG_DRIPLEAF + .withProperty("facing", direction) + .withProperty("waterlogged", String.valueOf(placingInsideWater)); + } + + return null; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block blockAbove = updateState.instance().getBlock(updateState.blockPosition().add(0.0, 1.0, 0.0)); + if (block.compare(Block.BIG_DRIPLEAF_STEM) + && !(blockAbove.compare(Block.BIG_DRIPLEAF_STEM) || blockAbove.compare(Block.BIG_DRIPLEAF)) + ) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + Block blockBelow = updateState.instance().getBlock(updateState.blockPosition().sub(0.0, 1.0, 0.0)); + + boolean validBelow = false; + if (blockBelow.compare(Block.BIG_DRIPLEAF_STEM)) { + validBelow = true; + } else { + for (Block validBlock : plantableOn) { + if (validBlock.compare(blockBelow)) { + validBelow = true; + break; + } + } + } + + if (!validBelow) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CactusPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CactusPlacementRule.java new file mode 100644 index 00000000..1be1d831 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CactusPlacementRule.java @@ -0,0 +1,72 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CactusPlacementRule extends BlockPlacementRule { + private final Set plantableOn; + + public CactusPlacementRule(Block block) { + super(block); + this.plantableOn = TagHelper.getInstance().getTaggedWith("minecraft:sand"); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (checkEligibility(placementState.instance(), placementState.placePosition())) { + return placementState.block(); + } + return null; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (checkEligibility(updateState.instance(), updateState.blockPosition())) { + return updateState.currentBlock(); + } + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + public boolean checkEligibility(Block.Getter instance, Point position) { + Block blockBelow = instance.getBlock(position.sub(0.0, 1.0, 0.0)); + + // Check if on sand or another cactus + boolean validBelow = false; + if (blockBelow.compare(Block.CACTUS)) { + validBelow = true; + } else { + for (Block validBlock : plantableOn) { + if (validBlock.compare(blockBelow)) { + validBelow = true; + break; + } + } + } + if (!validBelow) return false; + + // Check if no blocks adjacent + for (Direction direction : Direction.HORIZONTAL) { + if (!instance.getBlock(position.add(direction.vec())).isAir()) { + return false; + } + } + + return true; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CampfireBlockPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CampfireBlockPlacementRule.java new file mode 100644 index 00000000..e7a91c09 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CampfireBlockPlacementRule.java @@ -0,0 +1,46 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CampfireBlockPlacementRule extends BlockPlacementRule { + + public CampfireBlockPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + BlockFace facing = BlockFace.fromDirection(DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite()); + boolean waterlogged = FluidUtils.isWater(placementState.instance().getBlock(placementState.placePosition())); + Block blockBelow = placementState.instance().getBlock(placementState.placePosition().add(0.0, -1.0, 0.0)); + boolean signalFire = blockBelow.compare(Block.HAY_BLOCK); + + return placementState.block() + .withProperty("facing", facing.name().toLowerCase()) + .withProperty("waterlogged", String.valueOf(waterlogged)) + .withProperty("lit", String.valueOf(!waterlogged)) + .withProperty("signal_fire", String.valueOf(signalFire)); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block blockBelow = updateState.instance().getBlock(updateState.blockPosition().add(0.0, -1.0, 0.0)); + boolean signalFire = blockBelow.compare(Block.HAY_BLOCK); + + return updateState.currentBlock() + .withProperty("signal_fire", String.valueOf(signalFire)); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CandlePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CandlePlacementRule.java new file mode 100644 index 00000000..154c7710 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CandlePlacementRule.java @@ -0,0 +1,100 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.utils.BlockUtil; +import net.minestom.vanilla.common.utils.FluidUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CandlePlacementRule extends BlockPlacementRule { + + private static final Map CAKE_VARIANTS = Map.ofEntries( + Map.entry(Block.CANDLE, Block.CANDLE_CAKE), + Map.entry(Block.WHITE_CANDLE, Block.WHITE_CANDLE_CAKE), + Map.entry(Block.ORANGE_CANDLE, Block.ORANGE_CANDLE_CAKE), + Map.entry(Block.MAGENTA_CANDLE, Block.MAGENTA_CANDLE_CAKE), + Map.entry(Block.LIGHT_BLUE_CANDLE, Block.LIGHT_BLUE_CANDLE_CAKE), + Map.entry(Block.YELLOW_CANDLE, Block.YELLOW_CANDLE_CAKE), + Map.entry(Block.LIME_CANDLE, Block.LIME_CANDLE_CAKE), + Map.entry(Block.PINK_CANDLE, Block.PINK_CANDLE_CAKE), + Map.entry(Block.GRAY_CANDLE, Block.GRAY_CANDLE_CAKE), + Map.entry(Block.LIGHT_GRAY_CANDLE, Block.LIGHT_GRAY_CANDLE_CAKE), + Map.entry(Block.CYAN_CANDLE, Block.CYAN_CANDLE_CAKE), + Map.entry(Block.PURPLE_CANDLE, Block.PURPLE_CANDLE_CAKE), + Map.entry(Block.BLUE_CANDLE, Block.BLUE_CANDLE_CAKE), + Map.entry(Block.BROWN_CANDLE, Block.BROWN_CANDLE_CAKE), + Map.entry(Block.GREEN_CANDLE, Block.GREEN_CANDLE_CAKE), + Map.entry(Block.RED_CANDLE, Block.RED_CANDLE_CAKE), + Map.entry(Block.BLACK_CANDLE, Block.BLACK_CANDLE_CAKE) + ); + + public static Map getCakeVariants() { + return CAKE_VARIANTS; + } + + public CandlePlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + var positionBelow = placementState.placePosition().sub(0.0, 1.0, 0.0); + var blockBelow = placementState.instance().getBlock(positionBelow); + + // Handle placing candle on cake + if (blockBelow.compare(Block.CAKE, Block.Comparator.ID)) { + Block candleCake = CAKE_VARIANTS.get(block); + if (candleCake == null) { + return null; + } + + ((Instance) placementState.instance()).setBlock( + positionBelow, + BlockUtil.withDefaultHandler(candleCake) + ); + return Block.AIR; + } + + // Check for valid support + if (!blockBelow.registry().collisionShape().isFaceFull(BlockFace.TOP)) { + return null; + } + + // Handle stacking or waterlogging + Block oldBlock = placementState.instance().getBlock(placementState.placePosition()); + if (!oldBlock.compare(block, Block.Comparator.ID)) { + if (FluidUtils.isWater(oldBlock)) { + return block.withProperty("waterlogged", "true"); + } else { + return block; + } + } + + // Handle stacking candles + String candlesProperty = oldBlock.getProperty("candles"); + int oldCandles = candlesProperty != null ? Integer.parseInt(candlesProperty) : 0; + return oldBlock.withProperty("candles", String.valueOf(oldCandles + 1)); + } + + @Override + public boolean isSelfReplaceable(Replacement replacement) { + String candlesProperty = replacement.block().getProperty("candles"); + if (candlesProperty == null) { + return false; + } + int candles = Integer.parseInt(candlesProperty); + return candles < 4; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChainPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChainPlacementRule.java new file mode 100644 index 00000000..1e5c6304 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChainPlacementRule.java @@ -0,0 +1,44 @@ +package net.minestom.vanilla.blocks.placement; + +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; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class ChainPlacementRule extends BlockPlacementRule { + + public ChainPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace placementFace = placementState.blockFace(); + String axis; + + if (placementFace == BlockFace.TOP || placementFace == BlockFace.BOTTOM) { + axis = "y"; + } else if (placementFace == BlockFace.NORTH || placementFace == BlockFace.SOUTH) { + axis = "z"; + } else if (placementFace == BlockFace.EAST || placementFace == BlockFace.WEST) { + axis = "x"; + } else { + axis = "y"; + } + + return placementState.block().withProperty("axis", axis); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChestPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChestPlacementRule.java new file mode 100644 index 00000000..6b163c55 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ChestPlacementRule.java @@ -0,0 +1,171 @@ +package net.minestom.vanilla.blocks.placement; + +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.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class ChestPlacementRule extends BlockPlacementRule { + + public ChestPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block clicked = placementState.instance().getBlock( + placementState.placePosition().relative(placementState.blockFace().getOppositeFace()) + ); + + Direction clickedFacing = getClickedChestFacing(placementState, clicked); + Direction facing = clickedFacing != null ? clickedFacing : + DirectionUtils.getNearestHorizontalLookingDirection(placementState); + + boolean waterlogged = FluidUtils.isWater(placementState.instance().getBlock(placementState.placePosition())); + Block facedBlock = placementState.block() + .withProperty("facing", facing.toString().toLowerCase()) + .withProperty("waterlogged", String.valueOf(waterlogged)); + + if (placementState.isPlayerShifting() && clickedFacing == null) { + return facedBlock; + } + + if (clickedFacing != null && + canConnect(placementState, facing, placementState.blockFace().getOppositeFace().toDirection())) { + return connect( + placementState, + facing, + placementState.blockFace().getOppositeFace().toDirection(), + facedBlock + ); + } + + if (canConnect(placementState, facing, DirectionUtils.rotateR(facing))) { + return connect( + placementState, + facing, + DirectionUtils.rotateR(facing), + facedBlock + ); + } + + if (canConnect(placementState, facing, DirectionUtils.rotateL(facing))) { + return connect( + placementState, + facing, + DirectionUtils.rotateL(facing), + facedBlock + ); + } + + return facedBlock; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + String type = updateState.currentBlock().getProperty("type"); + if ("single".equals(type)) { + return super.blockUpdate(updateState); + } + + Direction facing = Direction.valueOf(updateState.currentBlock().getProperty("facing").toUpperCase()); + + Direction neighbourPosition; + String expectedNeighbourType; + + if ("left".equals(type)) { + neighbourPosition = DirectionUtils.rotateR(facing); + expectedNeighbourType = "right"; + } else { + neighbourPosition = DirectionUtils.rotateL(facing); + expectedNeighbourType = "left"; + } + + BlockFace neighbourBlockFace = BlockFace.fromDirection(neighbourPosition); + Block neighbourBlock = updateState.instance().getBlock(updateState.blockPosition().relative(neighbourBlockFace)); + + if (!neighbourBlock.compare(block, Block.Comparator.ID) + || !neighbourBlock.getProperty("facing").equals(facing.toString().toLowerCase()) + || !neighbourBlock.getProperty("type").equals(expectedNeighbourType) + ) { + return updateState.currentBlock() + .withProperty("type", "single"); + } + + return super.blockUpdate(updateState); + } + + private Direction getClickedChestFacing(PlacementState placementState, Block clicked) { + if (!clicked.compare(block, Block.Comparator.ID)) { + return null; + } + + Direction facing = Direction.valueOf(clicked.getProperty("facing").toUpperCase()); + Direction rotateLeft = DirectionUtils.rotateL(facing); + Direction rotateRight = DirectionUtils.rotateR(facing); + + List lrFaces = Arrays.asList( + BlockFace.fromDirection(rotateLeft), + BlockFace.fromDirection(rotateRight) + ); + + return lrFaces.contains(placementState.blockFace()) ? facing : null; + } + + private boolean canConnect(PlacementState placementState, Direction facingSelf, Direction connecting) { + Block currentBlock = placementState.instance().getBlock( + placementState.placePosition().relative(BlockFace.fromDirection(connecting)) + ); + + if (!currentBlock.compare(block, Block.Comparator.ID)) { + return false; + } + + Direction facing = Direction.valueOf(currentBlock.getProperty("facing").toUpperCase()); + if (facing != facingSelf) { + return false; + } + + String type = currentBlock.getProperty("type"); + return "single".equals(type); + } + + private Block connect(PlacementState placementState, Direction facingSelf, Direction connecting, Block facedBlock) { + String selfType; + String otherType; + + if (connecting == DirectionUtils.rotateL(facingSelf)) { + selfType = "right"; + otherType = "left"; + } else { + selfType = "left"; + otherType = "right"; + } + + Block connectingBlock = placementState.instance().getBlock( + placementState.placePosition().relative(BlockFace.fromDirection(connecting)) + ); + + ((Instance) placementState.instance()).setBlock( + placementState.placePosition().relative(BlockFace.fromDirection(connecting)), + connectingBlock.withProperty("type", otherType) + ); + + return facedBlock.withProperty("type", selfType); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CoralPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CoralPlacementRule.java new file mode 100644 index 00000000..c1db066c --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CoralPlacementRule.java @@ -0,0 +1,73 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.utils.BlockUtil; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CoralPlacementRule extends BlockPlacementRule { + + private static final Map WALL_CORALS = Map.of( + Block.TUBE_CORAL_FAN, Block.TUBE_CORAL_WALL_FAN, + Block.BRAIN_CORAL_FAN, Block.BRAIN_CORAL_WALL_FAN, + Block.BUBBLE_CORAL_FAN, Block.BUBBLE_CORAL_WALL_FAN, + Block.FIRE_CORAL_FAN, Block.FIRE_CORAL_WALL_FAN, + Block.HORN_CORAL_FAN, Block.HORN_CORAL_WALL_FAN, + Block.DEAD_TUBE_CORAL_FAN, Block.DEAD_TUBE_CORAL_WALL_FAN, + Block.DEAD_BRAIN_CORAL_FAN, Block.DEAD_BRAIN_CORAL_WALL_FAN, + Block.DEAD_BUBBLE_CORAL_FAN, Block.DEAD_BUBBLE_CORAL_WALL_FAN, + Block.DEAD_FIRE_CORAL_FAN, Block.DEAD_FIRE_CORAL_WALL_FAN, + Block.DEAD_HORN_CORAL_FAN, Block.DEAD_HORN_CORAL_WALL_FAN + ); + + public CoralPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + String waterlogged = String.valueOf(FluidUtils.isWater(placementState.instance().getBlock(placementState.placePosition()))); + + if (placementState.blockFace().toDirection().horizontal() && + placementState.instance().getBlock(placementState.placePosition().relative(placementState.blockFace().getOppositeFace())) + .registry().collisionShape().isFaceFull(placementState.blockFace())) { + + Block wallCoralBlock = WALL_CORALS.get(block); + if (wallCoralBlock != null) { + return BlockUtil.withDefaultHandler(wallCoralBlock) + .withProperty("facing", placementState.blockFace().toDirection().toString().toLowerCase()) + .withProperty("waterlogged", waterlogged); + } + } + + if (!placementState.instance().getBlock(placementState.placePosition().relative(BlockFace.BOTTOM)).registry() + .collisionShape().isFaceFull(BlockFace.TOP)) { + return null; + } + + return block.withProperty("waterlogged", waterlogged); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!updateState.instance().getBlock(updateState.blockPosition().relative(BlockFace.BOTTOM)).registry() + .collisionShape().isFaceFull(BlockFace.TOP)) { + return "true".equals(updateState.currentBlock().getProperty("waterlogged")) ? Block.WATER : Block.AIR; + } + + return super.blockUpdate(updateState); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CrafterPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CrafterPlacementRule.java new file mode 100644 index 00000000..4c0602c4 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CrafterPlacementRule.java @@ -0,0 +1,39 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CrafterPlacementRule extends BlockPlacementRule { + + public CrafterPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + Direction direction = DirectionUtils.getNearestLookingDirection(placementState); + Direction horizontalDirection = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + + if (direction == Direction.DOWN) { + return placementState.block() + .withProperty("orientation", "down_" + horizontalDirection.name().toLowerCase()); + } else if (direction == Direction.UP) { + return placementState.block() + .withProperty("orientation", "up_" + horizontalDirection.opposite().name().toLowerCase()); + } else { + return placementState.block() + .withProperty("orientation", direction.name().toLowerCase() + "_up"); + } + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CropPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CropPlacementRule.java new file mode 100644 index 00000000..9388d500 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/CropPlacementRule.java @@ -0,0 +1,40 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class CropPlacementRule extends BlockPlacementRule { + + public CropPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block blockBelow = placementState.instance().getBlock(placementState.placePosition().add(0.0, -1.0, 0.0)); + if (!blockBelow.compare(Block.FARMLAND)) { + return null; + } + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block blockBelow = updateState.instance().getBlock(updateState.blockPosition().add(0.0, -1.0, 0.0)); + if (!blockBelow.compare(Block.FARMLAND)) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/DoorPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/DoorPlacementRule.java new file mode 100644 index 00000000..50d8e938 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/DoorPlacementRule.java @@ -0,0 +1,212 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +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.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +import static java.lang.Math.abs; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class DoorPlacementRule extends BlockPlacementRule { + + public DoorPlacementRule(Block baseDoorBlock) { + super(baseDoorBlock); + } + + private int countSolidFaces(Instance instance, Point centerPos, BlockFace horizontalDirection) { + int solidFaces = 0; + + if (instance.getBlock(centerPos.relative(horizontalDirection)).isSolid()) { + solidFaces++; + } + + Direction directionVector = horizontalDirection.toDirection(); + + BlockFace diagClockwiseDir = BlockFace.fromDirection(DirectionUtils.rotateR(directionVector)); + if (instance.getBlock(centerPos.relative(diagClockwiseDir)).isSolid()) { + solidFaces++; + } + + BlockFace diagCounterClockwiseDir = BlockFace.fromDirection(DirectionUtils.rotateL(directionVector)); + if (instance.getBlock(centerPos.relative(diagCounterClockwiseDir)).isSolid()) { + solidFaces++; + } + + return solidFaces; + } + + private String getHingeSide(Instance instance, Point placePos, Pos playerPos, BlockFace playerFacing) { + BlockFace doorFrontDirection = playerFacing.getOppositeFace(); + + BlockFace leftOfDoor = BlockFace.fromDirection(DirectionUtils.rotateL(doorFrontDirection.toDirection())); + BlockFace rightOfDoor = BlockFace.fromDirection(DirectionUtils.rotateR(doorFrontDirection.toDirection())); + + Point leftBlockPos = placePos.relative(leftOfDoor); + Point rightBlockPos = placePos.relative(rightOfDoor); + + Block leftNeighborBlock = instance.getBlock(leftBlockPos); + Block rightNeighborBlock = instance.getBlock(rightBlockPos); + + if (leftNeighborBlock.key().equals(block.key())) { + String existingDoorHalf = leftNeighborBlock.getProperty("half"); + String existingDoorHinge = leftNeighborBlock.getProperty("hinge"); + + if ("lower".equals(existingDoorHalf)) { + if ("right".equals(existingDoorHinge)) { + return "left"; + } + } + } + + if (rightNeighborBlock.key().equals(block.key())) { + String existingDoorHalf = rightNeighborBlock.getProperty("half"); + String existingDoorHinge = rightNeighborBlock.getProperty("hinge"); + + if ("lower".equals(existingDoorHalf)) { + if ("left".equals(existingDoorHinge)) { + return "right"; + } + } + } + + BlockFace playerRightSideBlockFace = BlockFace.fromDirection(DirectionUtils.rotateR(playerFacing.toDirection())); + BlockFace playerLeftSideBlockFace = BlockFace.fromDirection(DirectionUtils.rotateL(playerFacing.toDirection())); + + int leftSupportScore = countSolidFaces(instance, placePos, playerLeftSideBlockFace); + int rightSupportScore = countSolidFaces(instance, placePos, playerRightSideBlockFace); + + if (leftSupportScore > rightSupportScore) { + return "left"; + } + if (rightSupportScore > leftSupportScore) { + return "right"; + } + + float playerYaw = playerPos.yaw(); + + float yawToRightHandleSide = DirectionUtils.getYaw(rightOfDoor.toDirection()); + float yawToLeftHandleSide = DirectionUtils.getYaw(leftOfDoor.toDirection()); + + float normalizedPlayerYaw = normalizeYaw(playerYaw); + float normalizedYawToRightHandle = normalizeYaw(yawToRightHandleSide); + float normalizedYawToLeftHandle = normalizeYaw(yawToLeftHandleSide); + + float diffToRightHandle = abs(normalizedPlayerYaw - normalizedYawToRightHandle); + float diffToLeftHandle = abs(normalizedPlayerYaw - normalizedYawToLeftHandle); + + float finalDiffToRightHandle = Math.min(diffToRightHandle, abs(diffToRightHandle - 360)); + float finalDiffToLeftHandle = Math.min(diffToLeftHandle, abs(diffToLeftHandle - 360)); + + return finalDiffToRightHandle < finalDiffToLeftHandle ? "left" : "right"; + } + + private float normalizeYaw(float yaw) { + float normalized = yaw % 360; + if (normalized > 180) normalized -= 360; + if (normalized < -180) normalized += 360; + return normalized; + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block.Getter instance = placementState.instance(); + Point placePos = placementState.placePosition(); + + Point upperPos = placePos.add(0.0, 1.0, 0.0); + if (!instance.getBlock(upperPos).registry().isReplaceable()) { + return null; + } + + Point lowerPos = placePos.sub(0.0, 1.0, 0.0); + if (!instance.getBlock(lowerPos).registry().collisionShape().isFaceFull(BlockFace.TOP)) { + return null; + } + + Direction facing = DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite(); + + String hinge = getHingeSide( + (Instance) instance, + placePos, + (Pos) placementState.playerPosition(), + BlockFace.fromDirection(facing) + ); + + String open = "false"; + String powered = "false"; + + Block lowerDoorBlock = placementState.block() + .withProperty("facing", facing.name().toLowerCase()) + .withProperty("half", "lower") + .withProperty("hinge", hinge) + .withProperty("open", open) + .withProperty("powered", powered); + + Block upperDoorBlock = placementState.block() + .withProperty("facing", facing.name().toLowerCase()) + .withProperty("half", "upper") + .withProperty("hinge", hinge) + .withProperty("open", open) + .withProperty("powered", powered); + + ((Instance) instance).setBlock(upperPos, upperDoorBlock); + + return lowerDoorBlock; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block.Getter instance = updateState.instance(); + Block currentBlock = updateState.currentBlock(); + Point blockPosition = updateState.blockPosition(); + + String half = currentBlock.getProperty("half"); + + Point neighborPos; + String expectedOtherHalf; + + if ("lower".equals(half)) { + neighborPos = blockPosition.add(0.0, 1.0, 0.0); + expectedOtherHalf = "upper"; + } else { // half == "upper" + neighborPos = blockPosition.sub(0.0, 1.0, 0.0); + expectedOtherHalf = "lower"; + } + + Block neighborBlock = instance.getBlock(neighborPos); + + if (!neighborBlock.compare(updateState.currentBlock()) || + !expectedOtherHalf.equals(neighborBlock.getProperty("half"))) { + + if (neighborBlock.compare(updateState.currentBlock())) { + ((Instance) instance).setBlock(neighborPos, Block.AIR); + } + return Block.AIR; + } + + Block blockBelow = instance.getBlock(blockPosition.relative(BlockFace.BOTTOM)); + if ("lower".equals(updateState.currentBlock().getProperty("half")) && + !blockBelow.registry().collisionShape().isFaceFull(BlockFace.TOP)) { + + DroppedItemFactory.maybeDrop(updateState); + Instance realInstance = (Instance) instance; + realInstance.setBlock(neighborPos, Block.AIR); + realInstance.setBlock(updateState.blockPosition(), Block.AIR); + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacedFacingPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacedFacingPlacementRule.java new file mode 100644 index 00000000..79d83b51 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacedFacingPlacementRule.java @@ -0,0 +1,81 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FacedFacingPlacementRule extends BlockPlacementRule { + + public FacedFacingPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace == null) return null; + + String face; + BlockFace facing; + + if (blockFace == BlockFace.TOP) { + face = "floor"; + facing = BlockFace.fromDirection(DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite()); + } else if (blockFace == BlockFace.BOTTOM) { + face = "ceiling"; + facing = BlockFace.fromDirection(DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite()); + } else { + face = "wall"; + facing = blockFace; + } + + Point supporting = getSupportingBlockPosition(face, facing, placementState.placePosition()); + if (needSupport() && !placementState.instance().getBlock(supporting).isSolid()) { + return null; + } + + return block + .withProperty("facing", facing.name().toLowerCase()) + .withProperty("face", face); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + String face = updateState.currentBlock().getProperty("face"); + BlockFace facing = BlockFace.valueOf(updateState.currentBlock().getProperty("facing").toUpperCase()); + Point supportingBlockPos = getSupportingBlockPosition(face, facing, updateState.blockPosition()); + + if (needSupport() && !updateState.instance().getBlock(supportingBlockPos).isSolid()) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } + + public Point getSupportingBlockPosition(String face, BlockFace facing, Point blockPosition) { + if ("ceiling".equals(face)) { + return blockPosition.add(0.0, 1.0, 0.0); + } else if ("floor".equals(face)) { + return blockPosition.sub(0.0, 1.0, 0.0); + } else { + return blockPosition.add(facing.getOppositeFace().toDirection().vec()); + } + } + + public boolean needSupport() { + return true; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacingPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacingPlacementRule.java new file mode 100644 index 00000000..8f1d5655 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FacingPlacementRule.java @@ -0,0 +1,30 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.utils.DirectionUtils; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FacingPlacementRule extends BlockPlacementRule { + + public FacingPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (placementState.playerPosition() == null) { + return placementState.block(); + } + + return placementState.block() + .withProperty("facing", DirectionUtils.getNearestLookingDirection(placementState).name().toLowerCase()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FarmlandPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FarmlandPlacementRule.java new file mode 100644 index 00000000..7499b1e8 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FarmlandPlacementRule.java @@ -0,0 +1,39 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FarmlandPlacementRule extends BlockPlacementRule { + + public FarmlandPlacementRule(Block block) { + super(block); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Point abovePosition = updateState.blockPosition().relative(BlockFace.TOP); + Block aboveBlock = updateState.instance().getBlock(abovePosition); + + if (aboveBlock.isSolid()) { + return Block.DIRT; + } + + return updateState.currentBlock(); + } + + @Override + public Block blockPlace(PlacementState placementState) { + return placementState.block(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FeatureRailPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FeatureRailPlacementRule.java new file mode 100644 index 00000000..31b2aef5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FeatureRailPlacementRule.java @@ -0,0 +1,65 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.blocks.placement.common.AbstractRailPlacementRule; +import net.minestom.vanilla.common.utils.DirectionUtils; + +import java.util.Collections; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FeatureRailPlacementRule extends AbstractRailPlacementRule { + + public FeatureRailPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!isSupported(placementState.instance(), placementState.placePosition())) { + return null; + } + + Direction primaryDirection = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + + FixedPlacementResult fixed = getFixedPlacement(placementState); + Direction lockedDirection; + + if (fixed instanceof FixedPlacementResult.DefinitiveBlock) { + return ((FixedPlacementResult.DefinitiveBlock) fixed).getBlock(); + } else { + lockedDirection = ((FixedPlacementResult.LockedDirection) fixed).getDirection(); + } + + BlockFace face = BlockFace.fromDirection(lockedDirection != null ? lockedDirection : primaryDirection); + + for (RailShape shape : RailShape.values()) { + if (shape.isStraight() && !shape.isAscending() && shape.getSides().contains(face)) { + for (BlockFace rotated : shape.getSides()) { + Block result = createSidedConnection(placementState, rotated.toDirection(), lockedDirection); + if (result != null) { + return result; + } + } + } + } + + RailShape shape = connectVertical( + RailShape.fromSides(Collections.singletonList( + BlockFace.fromDirection(lockedDirection != null ? lockedDirection : primaryDirection) + )), + placementState + ); + + return placementState.block() + .withProperty("shape", shape.toString()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FenceGatePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FenceGatePlacementRule.java new file mode 100644 index 00000000..bb40179c --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FenceGatePlacementRule.java @@ -0,0 +1,52 @@ +package net.minestom.vanilla.blocks.placement; + +import java.util.Set; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FenceGatePlacementRule extends BlockPlacementRule { + private final Set walls; + + public FenceGatePlacementRule(Block block) { + super(block); + this.walls = TagHelper.getInstance().getHashed("#minecraft:walls"); + } + + @Override + public Block blockPlace(@NotNull PlacementState state) { + Direction direction = DirectionUtils.getNearestHorizontalLookingDirection(state).opposite(); + Block block = state.block() + .withProperty("facing", direction.toString().toLowerCase()); + + return integrateInWalls(state.instance(), state.placePosition(), block); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return integrateInWalls(updateState.instance(), updateState.blockPosition(), updateState.currentBlock()); + } + + private Block integrateInWalls(Block.Getter instance, Point pos, Block block) { + Direction direction = Direction.valueOf(block.getProperty("facing").toUpperCase()); + Block leftBlock = instance.getBlock(pos.add(DirectionUtils.rotateR(direction).vec())); + Block rightBlock = instance.getBlock(pos.add(DirectionUtils.rotateL(direction).vec())); + boolean inWall = walls.contains(leftBlock) || walls.contains(rightBlock); + + return block.withProperty("in_wall", String.valueOf(inWall)); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FencePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FencePlacementRule.java new file mode 100644 index 00000000..98391734 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FencePlacementRule.java @@ -0,0 +1,59 @@ +package net.minestom.vanilla.blocks.placement; + +import java.util.Set; +import net.kyori.adventure.key.Key; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.blocks.placement.common.AbstractConnectingBlockPlacementRule; +import net.minestom.vanilla.blocks.placement.util.States; +import net.minestom.vanilla.common.utils.TagHelper; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FencePlacementRule extends AbstractConnectingBlockPlacementRule { + + private final Set fences = TagHelper.getInstance().getTaggedWith("minecraft:fences"); + private final Set woodenFences = TagHelper.getInstance().getTaggedWith("minecraft:wooden_fences"); + private final Set fenceGates = TagHelper.getInstance().getTaggedWith("minecraft:fence_gates"); + + public FencePlacementRule(Block block) { + super(block); + } + + @Override + public boolean canConnect(Block.Getter instance, Point pos, BlockFace blockFace) { + Block instanceBlock = instance.getBlock(pos); + boolean isBlockNetherBrickFence = block.name().endsWith("_brick_fence"); + boolean isInstanceBlockNetherBrickFence = instanceBlock.name().endsWith("_brick_fence"); + boolean canConnectToFence = canConnectToFence(instanceBlock); + + Direction blockFaceDirection = blockFace.toDirection(); + Direction rotatedDirection = States.rotateYClockwise(blockFaceDirection); + + boolean canFenceGateConnect = fenceGates.contains(instanceBlock) && + States.getAxis(States.getFacing(instanceBlock).toDirection()) == + States.getAxis(rotatedDirection); + + boolean isFaceFull = instanceBlock.registry().collisionShape().isFaceFull(blockFace); + + return (!cannotConnect.contains(instanceBlock) && isFaceFull) || + (canConnectToFence && !isBlockNetherBrickFence) || + canFenceGateConnect || + (isBlockNetherBrickFence && isInstanceBlockNetherBrickFence); + } + + private boolean canConnectToFence(Block block) { + boolean isFence = fences.contains(block); + boolean isWoodenFence = woodenFences.contains(block); + return isFence && isWoodenFence; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FloorFillerPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FloorFillerPlacementRule.java new file mode 100644 index 00000000..8a20b6e5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/FloorFillerPlacementRule.java @@ -0,0 +1,75 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FloorFillerPlacementRule extends BlockPlacementRule { + + public FloorFillerPlacementRule(Block block) { + super(block); + } + + public String getPropertyName() { + if (block == Block.LEAF_LITTER) { + return "segment_amount"; + } + return "flower_amount"; + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!isSupported(placementState.instance(), placementState.placePosition())) { + return null; + } + + String facing = DirectionUtils.getNearestHorizontalLookingDirection(placementState).toString().toLowerCase(); + Block previousBlock = placementState.instance().getBlock(placementState.placePosition()); + boolean isSelf = previousBlock.compare(block, Block.Comparator.ID); + + String amountProperty = previousBlock.getProperty(getPropertyName()); + int petals = (amountProperty != null) ? Integer.parseInt(amountProperty) : 0; + + Block resultBlock; + if (isSelf) { + resultBlock = previousBlock; + } else { + resultBlock = placementState.block().withProperty("facing", facing); + } + + return resultBlock.withProperty(getPropertyName(), String.valueOf(petals + 1)); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!isSupported(updateState.instance(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return super.blockUpdate(updateState); + } + + @Override + public boolean isSelfReplaceable(Replacement replacement) { + String amountProperty = replacement.block().getProperty(getPropertyName()); + int petals = (amountProperty != null) ? Integer.parseInt(amountProperty) : 0; + return petals < 4; + } + + public boolean isSupported(Block.Getter instance, Point block) { + Block below = instance.getBlock(block.sub(0.0, 1.0, 0.0)); + return below.registry().collisionShape().isFaceFull(BlockFace.TOP); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GlazedTerracottaPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GlazedTerracottaPlacementRule.java new file mode 100644 index 00000000..461df196 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GlazedTerracottaPlacementRule.java @@ -0,0 +1,27 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class GlazedTerracottaPlacementRule extends BlockPlacementRule { + + public GlazedTerracottaPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + var horizontalLookingDirection = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + return block.withProperty("facing", horizontalLookingDirection.name().toLowerCase()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GrindstonePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GrindstonePlacementRule.java new file mode 100644 index 00000000..e5145a38 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GrindstonePlacementRule.java @@ -0,0 +1,23 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class GrindstonePlacementRule extends FacedFacingPlacementRule { + + public GrindstonePlacementRule(Block block) { + super(block); + } + + @Override + public boolean needSupport() { + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GroundedPlantBlockPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GroundedPlantBlockPlacementRule.java new file mode 100644 index 00000000..e109138c --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/GroundedPlantBlockPlacementRule.java @@ -0,0 +1,49 @@ +package net.minestom.vanilla.blocks.placement; + +import java.util.Set; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class GroundedPlantBlockPlacementRule extends BlockPlacementRule { + private final Set dirtBlocks; + + public GroundedPlantBlockPlacementRule(Block block) { + super(block); + this.dirtBlocks = TagHelper.getInstance().getHashed("#minecraft:dirt"); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block blockBelow = placementState.instance().getBlock(placementState.placePosition().add(0.0, -1.0, 0.0)); + + if (dirtBlocks.contains(blockBelow)) { + return placementState.block(); + } + return null; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block blockBelow = updateState.instance().getBlock(updateState.blockPosition().add(0.0, -1.0, 0.0)); + + if (!dirtBlocks.contains(blockBelow)) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HeadPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HeadPlacementRule.java new file mode 100644 index 00000000..4da6dcdc --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HeadPlacementRule.java @@ -0,0 +1,59 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class HeadPlacementRule extends BlockPlacementRule { + private static final Map WALL_VARIANTS = Map.of( + Block.PLAYER_HEAD, Block.PLAYER_WALL_HEAD, + Block.SKELETON_SKULL, Block.SKELETON_WALL_SKULL, + Block.WITHER_SKELETON_SKULL, Block.WITHER_SKELETON_WALL_SKULL, + Block.ZOMBIE_HEAD, Block.ZOMBIE_WALL_HEAD, + Block.CREEPER_HEAD, Block.CREEPER_WALL_HEAD, + Block.DRAGON_HEAD, Block.DRAGON_WALL_HEAD + ); + + public HeadPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace clickedFace = placementState.blockFace(); + if (clickedFace == null) { + return block; + } + + if (clickedFace == BlockFace.TOP || clickedFace == BlockFace.BOTTOM) { + int rotation = DirectionUtils.sixteenStepRotation(placementState); + return block.withProperty("rotation", String.valueOf(rotation)); + } else { + Block wallVariant = WALL_VARIANTS.get(block); + if (wallVariant == null) { + return block; + } + String facing = clickedFace.name().toLowerCase(Locale.ROOT); + return wallVariant.withProperty("facing", facing); + } + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HopperPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HopperPlacementRule.java new file mode 100644 index 00000000..17503b84 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/HopperPlacementRule.java @@ -0,0 +1,41 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.instance.block.rule.BlockPlacementRule; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class HopperPlacementRule extends BlockPlacementRule { + + public HopperPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace placementFace = placementState.blockFace(); + String facing; + + if (placementFace == BlockFace.BOTTOM || placementFace == BlockFace.TOP) { + facing = "down"; + } else if (placementFace == BlockFace.NORTH || + placementFace == BlockFace.SOUTH || + placementFace == BlockFace.EAST || + placementFace == BlockFace.WEST) { + facing = placementFace.getOppositeFace().toDirection().name().toLowerCase(); + } else { + facing = "down"; + } + + return placementState.block() + .withProperty("facing", facing) + .withProperty("enabled", "true"); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/InverseWorkstationPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/InverseWorkstationPlacementRule.java new file mode 100644 index 00000000..ba8762d1 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/InverseWorkstationPlacementRule.java @@ -0,0 +1,36 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class InverseWorkstationPlacementRule extends BlockPlacementRule { + + public InverseWorkstationPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + Direction direction = DirectionUtils.getHorizontalPlacementDirection(placementState); + if (direction == null) { + return placementState.block(); + } + + // Triple rotation to get the inverse direction + Direction rotated = DirectionUtils.rotateR(DirectionUtils.rotateR(DirectionUtils.rotateR(direction))); + + return placementState.block() + .withProperty("facing", rotated.name().toLowerCase()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LadderPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LadderPlacementRule.java new file mode 100644 index 00000000..3bc74462 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LadderPlacementRule.java @@ -0,0 +1,49 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class LadderPlacementRule extends BlockPlacementRule { + + public LadderPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace == null) { + return null; + } + + var supporting = placementState.placePosition().add(blockFace.getOppositeFace().toDirection().vec()); + if (!placementState.instance().getBlock(supporting).registry().collisionShape().isFaceFull(blockFace)) { + return null; + } + + return block.withProperty("facing", blockFace.name().toLowerCase()); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + BlockFace facing = BlockFace.valueOf(updateState.currentBlock().getProperty("facing").toUpperCase()); + var supportingBlockPos = updateState.blockPosition().add(facing.getOppositeFace().toDirection().vec()); + + if (!updateState.instance().getBlock(supportingBlockPos).isSolid()) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LanternPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LanternPlacementRule.java new file mode 100644 index 00000000..4fd275ba --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LanternPlacementRule.java @@ -0,0 +1,89 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class LanternPlacementRule extends BlockPlacementRule { + private static final Set SPECIAL_SUPPORT_BLOCKS = new HashSet<>(); + + static { + SPECIAL_SUPPORT_BLOCKS.add("minecraft:chain"); + SPECIAL_SUPPORT_BLOCKS.add("minecraft:iron_bars"); + } + + public LanternPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block blockBelow = placementState.instance().getBlock(placementState.placePosition().add(0.0, -1.0, 0.0)); + Block blockAbove = placementState.instance().getBlock(placementState.placePosition().add(0.0, 1.0, 0.0)); + boolean canStandOnBlock = canSupport(blockBelow, BlockFace.TOP); + boolean canHangFromBlock = canSupport(blockAbove, BlockFace.BOTTOM); + + if (canHangFromBlock && (placementState.blockFace() == BlockFace.BOTTOM)) { + return placementState.block().withProperty("hanging", "true"); + } else if (canStandOnBlock) { + return placementState.block().withProperty("hanging", "false"); + } else if (canHangFromBlock) { + return placementState.block().withProperty("hanging", "true"); + } else { + return null; + } + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block currentBlock = updateState.currentBlock(); + String hangingProperty = currentBlock.getProperty("hanging"); + boolean isHanging = hangingProperty != null && hangingProperty.equals("true"); + + Block supportBlock; + if (isHanging) { + supportBlock = updateState.instance().getBlock(updateState.blockPosition().add(0.0, 1.0, 0.0)); + } else { + supportBlock = updateState.instance().getBlock(updateState.blockPosition().add(0.0, -1.0, 0.0)); + } + + BlockFace requiredFace = isHanging ? BlockFace.BOTTOM : BlockFace.TOP; + if (!canSupport(supportBlock, requiredFace)) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return currentBlock; + } + + private boolean canSupport(Block supportBlock, BlockFace requiredFace) { + if (supportBlock.registry().collisionShape().isFaceFull(requiredFace)) { + return true; + } + + String blockName = supportBlock.name(); + if ("minecraft:chain".equals(blockName)) { + return "y".equals(supportBlock.getProperty("axis")); + } else if ("minecraft:iron_bars".equals(blockName)) { + return true; + } else { + return blockName.contains("glass_pane"); + } + } + + private static boolean isSpecialSupportBlock(String blockName) { + return SPECIAL_SUPPORT_BLOCKS.contains(blockName) || blockName.contains("glass_pane"); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LeverPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LeverPlacementRule.java new file mode 100644 index 00000000..88b73ac0 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/LeverPlacementRule.java @@ -0,0 +1,97 @@ +package net.minestom.vanilla.blocks.placement; + +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.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class LeverPlacementRule extends BlockPlacementRule { + + public LeverPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace clickedFace = placementState.blockFace(); + if (clickedFace == null) { + return null; + } + + if (!DirectionUtils.canAttach(placementState)) { + return null; + } + + Block newBlock = block.withProperty("powered", "false"); + + if (clickedFace == BlockFace.NORTH || clickedFace == BlockFace.SOUTH || + clickedFace == BlockFace.EAST || clickedFace == BlockFace.WEST) { + return newBlock + .withProperty("face", "wall") + .withProperty("facing", clickedFace.name().toLowerCase(Locale.ROOT)); + } else if (clickedFace == BlockFace.TOP) { + Direction playerFacing = DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite(); + return newBlock + .withProperty("face", "floor") + .withProperty("facing", playerFacing.name().toLowerCase(Locale.ROOT)); + } else if (clickedFace == BlockFace.BOTTOM) { + Direction playerFacing = DirectionUtils.getNearestHorizontalLookingDirection(placementState).opposite(); + return newBlock + .withProperty("face", "ceiling") + .withProperty("facing", playerFacing.name().toLowerCase(Locale.ROOT)); + } + + return null; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block currentBlock = updateState.currentBlock(); + String face = currentBlock.getProperty("face"); + String facing = currentBlock.getProperty("facing"); + + if (face == null || facing == null) { + return Block.AIR; + } + + BlockFace supportDirection; + + switch (face) { + case "floor": + supportDirection = BlockFace.BOTTOM; + break; + case "ceiling": + supportDirection = BlockFace.TOP; + break; + case "wall": + supportDirection = BlockFace.valueOf(facing.toUpperCase(Locale.ROOT)).getOppositeFace(); + break; + default: + return Block.AIR; + } + + var supportBlockPosition = updateState.blockPosition().relative(supportDirection); + var supportBlock = updateState.instance().getBlock(supportBlockPosition); + var attachedFace = supportDirection.getOppositeFace(); + + if (!supportBlock.registry().collisionShape().isFaceFull(attachedFace)) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/MushroomPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/MushroomPlacementRule.java new file mode 100644 index 00000000..9eb9d54b --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/MushroomPlacementRule.java @@ -0,0 +1,78 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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 java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class MushroomPlacementRule extends BlockPlacementRule { + private final Map faceMap = new HashMap<>(); + + public MushroomPlacementRule(Block block) { + super(block); + faceMap.put("north", BlockFace.NORTH); + faceMap.put("south", BlockFace.SOUTH); + faceMap.put("east", BlockFace.EAST); + faceMap.put("west", BlockFace.WEST); + faceMap.put("up", BlockFace.TOP); + faceMap.put("down", BlockFace.BOTTOM); + } + + @Override + public Block blockPlace(PlacementState placementState) { + return getState(placementState.instance(), placementState.placePosition(), placementState.block(), true); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return getState(updateState.instance(), updateState.blockPosition(), updateState.currentBlock(), false); + } + + private Block getState(Block.Getter instance, Point position, Block currentBlock, boolean isPlacement) { + Block newBlock = currentBlock; + for (Map.Entry entry : faceMap.entrySet()) { + String facePropertyName = entry.getKey(); + BlockFace face = entry.getValue(); + + if (isPlacement || "true".equals(newBlock.getProperty(facePropertyName))) { + Point neighborPos = position.relative(face); + Block neighborBlock = instance.getBlock(neighborPos); + boolean shouldConnect = canConnect(currentBlock, neighborBlock); + String propertyValue = shouldConnect ? "false" : "true"; + newBlock = newBlock.withProperty(facePropertyName, propertyValue); + } + } + return newBlock; + } + + private boolean canConnect(Block currentBlock, Block neighborBlock) { + if (neighborBlock.compare(Block.MUSHROOM_STEM)) { + return true; + } + + boolean isCurrentBrown = currentBlock.compare(Block.BROWN_MUSHROOM_BLOCK); + boolean isNeighborBrown = neighborBlock.compare(Block.BROWN_MUSHROOM_BLOCK); + if (isCurrentBrown) { + return isNeighborBrown; + } + + boolean isCurrentRed = currentBlock.compare(Block.RED_MUSHROOM_BLOCK); + boolean isNeighborRed = neighborBlock.compare(Block.RED_MUSHROOM_BLOCK); + if (isCurrentRed) { + return isNeighborRed; + } + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ObserverPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ObserverPlacementRule.java new file mode 100644 index 00000000..3bfdb985 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ObserverPlacementRule.java @@ -0,0 +1,30 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class ObserverPlacementRule extends BlockPlacementRule { + + public ObserverPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (placementState.blockFace() == null) { + return placementState.block(); + } + + return placementState.block() + .withProperty("facing", placementState.blockFace().name().toLowerCase()) + .withProperty("powered", "false"); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/PinSupportedBelowPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/PinSupportedBelowPlacementRule.java new file mode 100644 index 00000000..70bc470f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/PinSupportedBelowPlacementRule.java @@ -0,0 +1,44 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class PinSupportedBelowPlacementRule extends BlockPlacementRule { + + public PinSupportedBelowPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!isSupported(placementState.instance(), placementState.placePosition())) { + return null; + } + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!isSupported(updateState.instance(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } + + protected boolean isSupported(Block.Getter instance, Point block) { + Block below = instance.getBlock(block.sub(0.0, 1.0, 0.0)); + return below.isSolid(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RailPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RailPlacementRule.java new file mode 100644 index 00000000..f32a7c93 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RailPlacementRule.java @@ -0,0 +1,54 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.blocks.placement.common.AbstractRailPlacementRule; +import net.minestom.vanilla.common.utils.DirectionUtils; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class RailPlacementRule extends AbstractRailPlacementRule { + + public RailPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!isSupported(placementState.instance(), placementState.placePosition())) { + return null; + } + + Direction primaryDirection = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + + FixedPlacementResult fixed = getFixedPlacement(placementState); + if (fixed instanceof FixedPlacementResult.DefinitiveBlock) { + return ((FixedPlacementResult.DefinitiveBlock) fixed).getBlock(); + } + + Direction lockedDirection = ((FixedPlacementResult.LockedDirection) fixed).getDirection(); + Direction direction = lockedDirection != null ? lockedDirection : primaryDirection; + + RailShape shape = RailShape.fromSides(java.util.Collections.singletonList( + BlockFace.fromDirection(direction))); + + if (shape == null) { + if (direction == Direction.NORTH || direction == Direction.SOUTH) { + shape = RailShape.NORTH_SOUTH; + } else { + shape = RailShape.EAST_WEST; + } + } + + shape = connectVertical(shape, placementState); + + return placementState.block().withProperty("shape", shape.toString()); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RedstoneStuffPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RedstoneStuffPlacementRule.java new file mode 100644 index 00000000..77d17d91 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RedstoneStuffPlacementRule.java @@ -0,0 +1,53 @@ +package net.minestom.vanilla.blocks.placement; + +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.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class RedstoneStuffPlacementRule extends BlockPlacementRule { + + public RedstoneStuffPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + var supportPosition = placementState.placePosition().relative(BlockFace.BOTTOM); + var supportBlock = placementState.instance().getBlock(supportPosition); + + if (supportBlock.isAir() || supportBlock.compare(block)) { + return null; + } + + Direction facing = DirectionUtils.getNearestHorizontalLookingDirection(placementState); + return block + .withProperty("facing", facing.name().toLowerCase(Locale.ROOT)) + .withProperty("powered", "false"); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + var supportPosition = updateState.blockPosition().relative(BlockFace.BOTTOM); + var supportBlock = updateState.instance().getBlock(supportPosition); + + if (supportBlock.isAir()) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RotatedPillarPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RotatedPillarPlacementRule.java new file mode 100644 index 00000000..192de16a --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/RotatedPillarPlacementRule.java @@ -0,0 +1,37 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.instance.block.rule.BlockPlacementRule; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class RotatedPillarPlacementRule extends BlockPlacementRule { + + public RotatedPillarPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace == null) return placementState.block(); + + String axis; + if (blockFace == BlockFace.TOP || blockFace == BlockFace.BOTTOM) { + axis = "y"; + } else if (blockFace == BlockFace.EAST || blockFace == BlockFace.WEST) { + axis = "x"; + } else { + axis = "z"; + } + + return placementState.block().withProperty("axis", axis); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ShulkerPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ShulkerPlacementRule.java new file mode 100644 index 00000000..71ab278d --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/ShulkerPlacementRule.java @@ -0,0 +1,40 @@ +package net.minestom.vanilla.blocks.placement; + +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; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class ShulkerPlacementRule extends BlockPlacementRule { + + public ShulkerPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + BlockFace facing = determineFacing(placementState); + return placementState.block().withProperty("facing", facing.toDirection().name().toLowerCase()); + } + + private BlockFace determineFacing(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace != null) { + return blockFace; + } + return BlockFace.NORTH; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SignPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SignPlacementRule.java new file mode 100644 index 00000000..6d894994 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SignPlacementRule.java @@ -0,0 +1,109 @@ +package net.minestom.vanilla.blocks.placement; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.DirectionUtils; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SignPlacementRule extends BlockPlacementRule { + + private static final Map WALL_SIGNS = Map.ofEntries( + Map.entry(Block.ACACIA_SIGN, Block.ACACIA_WALL_SIGN), + Map.entry(Block.BAMBOO_SIGN, Block.BAMBOO_WALL_SIGN), + Map.entry(Block.BIRCH_SIGN, Block.BIRCH_WALL_SIGN), + Map.entry(Block.CHERRY_SIGN, Block.CHERRY_WALL_SIGN), + Map.entry(Block.CRIMSON_SIGN, Block.CRIMSON_WALL_SIGN), + Map.entry(Block.DARK_OAK_SIGN, Block.DARK_OAK_WALL_SIGN), + Map.entry(Block.JUNGLE_SIGN, Block.JUNGLE_WALL_SIGN), + Map.entry(Block.MANGROVE_SIGN, Block.MANGROVE_WALL_SIGN), + Map.entry(Block.OAK_SIGN, Block.OAK_WALL_SIGN), + Map.entry(Block.SPRUCE_SIGN, Block.SPRUCE_WALL_SIGN), + Map.entry(Block.WARPED_SIGN, Block.WARPED_WALL_SIGN) + ); + private final RegistryTag wallSigns; + + public SignPlacementRule(Block block) { + super(block); + this.wallSigns = Block.staticRegistry().getTag(TagKey.ofHash("#minecraft:wall_signs")); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block currentBlock = placementState.instance().getBlock(placementState.placePosition()); + + if (placementState.blockFace() == BlockFace.TOP) { + int rotation = (DirectionUtils.sixteenStepRotation(placementState) + 8) % 16; + return supportedOrNull( + placementState.instance(), + placementState.placePosition(), + placementState.block() + .withProperty("rotation", String.valueOf(rotation)) + .withProperty("waterlogged", String.valueOf(FluidUtils.isWater(currentBlock))) + ); + } else if (placementState.blockFace() != null && + placementState.blockFace().toDirection().horizontal()) { + + String facing = placementState.blockFace().toString().toLowerCase(); + BlockHandler handler = placementState.block().handler(); + CompoundBinaryTag nbt = placementState.block().nbt(); + + Block wallSign = WALL_SIGNS.get(block); + if (wallSign == null) return null; + + return supportedOrNull( + placementState.instance(), + placementState.placePosition(), + wallSign + .withHandler(handler) + .withNbt(nbt) + .withProperty("facing", facing) + .withProperty("waterlogged", String.valueOf(FluidUtils.isWater(currentBlock))) + ); + } else { + return null; + } + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!isSupported(updateState.instance(), updateState.currentBlock(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } + + private Block supportedOrNull(Block.Getter instance, Point position, Block block) { + return isSupported(instance, block, position) ? block : null; + } + + private boolean isSupported(Block.Getter instance, Block block, Point position) { + if (wallSigns.contains(block)) { + BlockFace facing = BlockFace.valueOf(block.getProperty("facing").toUpperCase()); + Point supportingBlockPos = position.add(facing.getOppositeFace().toDirection().vec()); + return !instance.getBlock(supportingBlockPos).isAir(); + } else { + Block below = instance.getBlock(position.sub(0.0, 1.0, 0.0)); + return below.isSolid(); + } + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SimpleWaterloggablePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SimpleWaterloggablePlacementRule.java new file mode 100644 index 00000000..27565dd5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SimpleWaterloggablePlacementRule.java @@ -0,0 +1,29 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.common.utils.FluidUtils; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SimpleWaterloggablePlacementRule extends BlockPlacementRule { + + public SimpleWaterloggablePlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + boolean isWater = FluidUtils.isWater( + placementState.instance().getBlock(placementState.placePosition()) + ); + + return placementState.block().withProperty("waterlogged", String.valueOf(isWater)); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SlabPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SlabPlacementRule.java new file mode 100644 index 00000000..a559c88e --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SlabPlacementRule.java @@ -0,0 +1,73 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.instance.block.rule.BlockPlacementRule; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SlabPlacementRule extends BlockPlacementRule { + + public SlabPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block replacingBlock = placementState.instance().getBlock(placementState.placePosition()); + if (replacingBlock.compare(block)) { + return placementState.block().withProperty("type", "double").withProperty("waterlogged", "false"); + } + + String waterlogged = String.valueOf(replacingBlock.compare(Block.WATER)); + + if (placementState.blockFace() == BlockFace.BOTTOM || + (placementState.blockFace() != BlockFace.TOP && + placementState.cursorPosition() != null && + placementState.cursorPosition().y() > 0.5)) { + + return placementState.block().withProperty("type", "top").withProperty("waterlogged", waterlogged); + } + + return placementState.block().withProperty("type", "bottom").withProperty("waterlogged", waterlogged); + } + + @Override + public boolean isSelfReplaceable(Replacement replacement) { + Block blockToPlace = replacement.material().block(); + Block placedBlock = replacement.block(); + + if (!blockToPlace.compare(placedBlock)) { + return false; + } + + String type = placedBlock.getProperty("type"); + if (type == null || "double".equals(type)) { + return false; + } + + if (replacement.isOffset()) { + return true; + } + + if ("top".equals(type) && replacement.blockFace() == BlockFace.BOTTOM) { + return true; + } + + if ("bottom".equals(type) && replacement.blockFace() == BlockFace.TOP) { + return true; + } + + if ("top".equals(type) && replacement.cursorPosition().y() < 0.5) { + return true; + } + + return "bottom".equals(type) && replacement.cursorPosition().y() > 0.5; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SnowyUpdateRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SnowyUpdateRule.java new file mode 100644 index 00000000..705eddc2 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SnowyUpdateRule.java @@ -0,0 +1,43 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SnowyUpdateRule extends BlockPlacementRule { + + public SnowyUpdateRule(Block block) { + super(block); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + String snowy = getSnowyState(updateState.instance(), updateState.blockPosition()); + return updateState.currentBlock().withProperty("snowy", snowy); + } + + @Override + public Block blockPlace(PlacementState placementState) { + String snowy = getSnowyState(placementState.instance(), placementState.placePosition()); + return placementState.block().withProperty("snowy", snowy); + } + + private String getSnowyState(Block.Getter instance, Point position) { + Point abovePosition = position.relative(BlockFace.TOP); + Block aboveBlock = instance.getBlock(abovePosition); + boolean isSnow = aboveBlock.compare(Block.SNOW) || + aboveBlock.compare(Block.SNOW_BLOCK) || + aboveBlock.compare(Block.POWDER_SNOW); + return String.valueOf(isSnow); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/StairsPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/StairsPlacementRule.java new file mode 100644 index 00000000..38e295f5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/StairsPlacementRule.java @@ -0,0 +1,145 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +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.Direction; +import net.minestom.vanilla.blocks.placement.util.States; +import net.minestom.vanilla.common.utils.FluidUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class StairsPlacementRule extends BlockPlacementRule { + + public StairsPlacementRule(Block block) { + super(block); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + return updateState.currentBlock().withProperty( + States.SHAPE, + getShape(updateState.instance(), updateState.currentBlock(), updateState.blockPosition()) + ); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace placementFace = placementState.blockFace(); + Point placementPos = placementState.placePosition(); + Vec cursorPos = placementState.cursorPosition() != null ? (Vec) placementState.cursorPosition() : Vec.ZERO; + Pos playerPos = placementState.playerPosition() != null ? placementState.playerPosition() : Pos.ZERO; + + BlockFace half; + if (placementFace == BlockFace.BOTTOM || + (placementFace != BlockFace.TOP && cursorPos.y() > 0.5)) { + half = BlockFace.TOP; + } else { + half = BlockFace.BOTTOM; + } + + BlockFace facing = BlockFace.fromYaw(playerPos.yaw()); + + Block resultBlock = this.block.withProperties( + Map.of( + States.HALF, half.name().toLowerCase(Locale.getDefault()), + States.FACING, facing.name().toLowerCase(Locale.getDefault()) + ) + ); + + resultBlock = resultBlock.withProperty( + States.SHAPE, + getShape(placementState.instance(), resultBlock, placementPos) + ); + + boolean isInWater = FluidUtils.isWater(placementState.instance().getBlock(placementPos)); + return resultBlock.withProperty("waterlogged", String.valueOf(isInWater)); + } + + private String getShape(Block.Getter instance, Block block, Point blockPos) { + Direction direction = States.getFacing(block).toDirection(); + Block offsetBlock = instance.getBlock( + blockPos.add( + direction.normalX(), + direction.normalY(), + direction.normalZ() + ) + ); + + Direction offsetDirection = States.getFacing(offsetBlock).toDirection(); + Block oppositeOffsetBlock = instance.getBlock( + blockPos.add( + direction.opposite().normalX(), + direction.opposite().normalY(), + direction.opposite().normalZ() + ) + ); + + Direction oppositeOffsetDirection = States.getFacing(oppositeOffsetBlock).toDirection(); + + if (isStairs(offsetBlock) + && States.getHalf(block) == States.getHalf(offsetBlock) + && States.getAxis(offsetDirection) != States.getAxis(direction) + && isDifferentOrientation(instance, block, blockPos, offsetDirection.opposite())) { + + if (offsetDirection == States.rotateYCounterclockwise(direction)) { + return "outer_left"; + } else { + return "outer_right"; + } + } + + if (isStairs(oppositeOffsetBlock) + && States.getHalf(block) == States.getHalf(oppositeOffsetBlock) + && States.getAxis(oppositeOffsetDirection) != States.getAxis(direction) + && isDifferentOrientation(instance, block, blockPos, oppositeOffsetDirection)) { + + if (oppositeOffsetDirection == States.rotateYCounterclockwise(direction)) { + return "inner_left"; + } else { + return "inner_right"; + } + } + + return "straight"; + } + + private boolean isDifferentOrientation( + Block.Getter instance, + Block block, + Point blockPos, + Direction direction + ) { + BlockFace facing = States.getFacing(block); + BlockFace half = States.getHalf(block); + Block instanceBlock = instance.getBlock( + blockPos.add( + direction.normalX(), + direction.normalY(), + direction.normalZ() + ) + ); + + BlockFace instanceBlockFacing = States.getFacing(instanceBlock); + BlockFace instanceBlockHalf = States.getHalf(instanceBlock); + + return !isStairs(instanceBlock) || instanceBlockFacing != facing || instanceBlockHalf != half; + } + + private boolean isStairs(Block block) { + return block.name().endsWith("_stairs"); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SugarCanePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SugarCanePlacementRule.java new file mode 100644 index 00000000..ba32375a --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SugarCanePlacementRule.java @@ -0,0 +1,96 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.registry.Registry; +import net.minestom.server.registry.RegistryKey; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.FluidUtils; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SugarCanePlacementRule extends BlockPlacementRule { + + private final RegistryTag plantable = RegistryTag.direct( + new ArrayList<>() {{ + add(Block.DIRT); + add(Block.COARSE_DIRT); + add(Block.GRASS_BLOCK); + add(Block.ROOTED_DIRT); + add(Block.MUD); + add(Block.PODZOL); + add(Block.MYCELIUM); + add(Block.MOSS_BLOCK); + addAll(TagHelper.getInstance().getHashed("#sand")); + }} + ); + + private static final Set> VON_NEUMANN = Set.of( + Map.entry(0, 1), + Map.entry(0, -1), + Map.entry(1, 0), + Map.entry(-1, 0) + ); + + public SugarCanePlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!isSupported(placementState.instance(), placementState.placePosition())) { + return null; + } + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!isSupported(updateState.instance(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } + + private boolean isSupported(Block.Getter instance, Point blockPosition) { + Point posBelow = blockPosition.sub(0.0, 1.0, 0.0); + Block below = instance.getBlock(posBelow); + + if (below.compare(block, Block.Comparator.ID)) { + return true; + } + + if (!plantable.contains(below)) { + return false; + } + + for (var entry : VON_NEUMANN) { + int x = entry.getKey(); + int z = entry.getValue(); + Point pos = posBelow.add(x, 0.0, z); + Block neighborBlock = instance.getBlock(pos); + + if (FluidUtils.isWater(neighborBlock)) { + return true; + } + } + + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SupportedBelowPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SupportedBelowPlacementRule.java new file mode 100644 index 00000000..439b565f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/SupportedBelowPlacementRule.java @@ -0,0 +1,41 @@ +package net.minestom.vanilla.blocks.placement; + +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.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class SupportedBelowPlacementRule extends BlockPlacementRule { + + public SupportedBelowPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!placementState.instance().getBlock(placementState.placePosition().add(0.0, -1.0, 0.0)) + .registry().collisionShape().isFaceFull(BlockFace.TOP)) { + return null; + } + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!updateState.instance().getBlock(updateState.blockPosition().add(0.0, -1.0, 0.0)) + .registry().collisionShape().isFaceFull(BlockFace.TOP)) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TallFlowerPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TallFlowerPlacementRule.java new file mode 100644 index 00000000..94eaf7dd --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TallFlowerPlacementRule.java @@ -0,0 +1,92 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class TallFlowerPlacementRule extends BlockPlacementRule { + + public TallFlowerPlacementRule(Block baseFlowerBlock) { + super(baseFlowerBlock); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block.Getter instance = placementState.instance(); + Point placePos = placementState.placePosition(); + + Point upperPos = placePos.add(0.0, 1.0, 0.0); + if (!instance.getBlock(upperPos).registry().isReplaceable()) { + return null; + } + + Point lowerPos = placePos.sub(0.0, 1.0, 0.0); + if (!instance.getBlock(lowerPos).registry().collisionShape().isFaceFull(BlockFace.TOP)) { + return null; + } + + Block lowerFlowerBlock = placementState.block() + .withProperty("half", "lower"); + + Block upperFlowerBlock = placementState.block() + .withProperty("half", "upper"); + + ((Instance) instance).setBlock(upperPos, upperFlowerBlock); + + return lowerFlowerBlock; + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block.Getter instance = updateState.instance(); + Block currentBlock = updateState.currentBlock(); + Point blockPosition = updateState.blockPosition(); + + String half = currentBlock.getProperty("half"); + + Point neighborPos; + String expectedOtherHalf; + + if ("lower".equals(half)) { + neighborPos = blockPosition.add(0.0, 1.0, 0.0); + expectedOtherHalf = "upper"; + } else { // half == "upper" + neighborPos = blockPosition.sub(0.0, 1.0, 0.0); + expectedOtherHalf = "lower"; + } + + Block neighborBlock = instance.getBlock(neighborPos); + + if (!neighborBlock.compare(updateState.currentBlock()) || + !expectedOtherHalf.equals(neighborBlock.getProperty("half"))) { + + if (neighborBlock.compare(updateState.currentBlock())) { + ((Instance) instance).setBlock(neighborPos, Block.AIR); + } + return Block.AIR; + } + + Block blockBelow = instance.getBlock(blockPosition.relative(BlockFace.BOTTOM)); + if ("lower".equals(updateState.currentBlock().getProperty("half")) && + !blockBelow.registry().collisionShape().isFaceFull(BlockFace.TOP)) { + + DroppedItemFactory.maybeDrop(updateState); + ((Instance) instance).setBlock(neighborPos, Block.AIR); + ((Instance) instance).setBlock(updateState.blockPosition(), Block.AIR); + } + + return updateState.currentBlock(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TopAttachedVinePlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TopAttachedVinePlacementRule.java new file mode 100644 index 00000000..8cb0e317 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TopAttachedVinePlacementRule.java @@ -0,0 +1,55 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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.vanilla.common.item.DroppedItemFactory; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class TopAttachedVinePlacementRule extends BlockPlacementRule { + + public TopAttachedVinePlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + if (!validatePosition(placementState.instance(), placementState.placePosition())) { + return null; + } + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + if (!validatePosition(updateState.instance(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return updateState.currentBlock(); + } + + public boolean validatePosition(Block.Getter instance, Point position) { + Block above = instance.getBlock(position.add(0.0, 1.0, 0.0)); + + if (above.registry().collisionShape().isFaceFull(BlockFace.BOTTOM)) { + return true; + } + if (above.compare(block)) { + return true; + } + if (above.key().value().substring(0, 3).equals(block.key().value().substring(0, 3))) { + return true; + } + return false; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TorchPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TorchPlacementRule.java new file mode 100644 index 00000000..014baf01 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TorchPlacementRule.java @@ -0,0 +1,110 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +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.registry.RegistryTag; +import net.minestom.vanilla.common.item.DroppedItemFactory; +import net.minestom.vanilla.common.utils.BlockUtil; +import net.minestom.vanilla.common.utils.TagHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class TorchPlacementRule extends BlockPlacementRule { + + private final RegistryTag nonFullButPlaceable = RegistryTag.direct( + new ArrayList<>() {{ + addAll(TagHelper.getInstance().getHashed("#fences")); + addAll(TagHelper.getInstance().getHashed("#walls")); + addAll(BlockUtil.getGlassPanes()); + }} + ); + + public TorchPlacementRule(Block block) { + super(block); + } + + private boolean getIsNotFullFace(Block.Getter instance, Point position, BlockFace face) { + return !instance.getBlock(position).registry().collisionShape().isFaceFull(face); + } + + private boolean canSupportTorch(Block.Getter instance, Point position, BlockFace blockFace) { + Block block = instance.getBlock(position); + boolean isFullFace = !getIsNotFullFace(instance, position, blockFace); + // Certain blocks like fences and walls don't have full faces on the top but torches can be placed on them + return isFullFace || (blockFace == BlockFace.TOP && nonFullButPlaceable.contains(block)); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace blockFace = placementState.blockFace(); + if (blockFace == null) { + return null; + } + Point supporting = placementState.placePosition().add(blockFace.getOppositeFace().toDirection().vec()); + boolean isNotFullFace = getIsNotFullFace(placementState.instance(), supporting, blockFace); + + if (blockFace == BlockFace.BOTTOM) { + return null; + } + + if (isNotFullFace && blockFace != BlockFace.TOP) { + // placing on the side of a block with bottom support places the torch next to the block + blockFace = BlockFace.TOP; + supporting = placementState.placePosition().add(0.0, -1.0, 0.0); + } + + if (blockFace == BlockFace.TOP) { + if (!canSupportTorch(placementState.instance(), supporting, blockFace)) { + return null; + } + return block; + } + + Block torch; + var material = placementState.block().registry().material(); + if (material == Block.TORCH.registry().material()) { + torch = Block.WALL_TORCH; + } else if (material == Block.SOUL_TORCH.registry().material()) { + torch = Block.SOUL_WALL_TORCH; + } else if (material == Block.REDSTONE_TORCH.registry().material()) { + torch = Block.REDSTONE_WALL_TORCH; + } else { + return null; + } + + return torch.withNbt(placementState.block().nbtOrEmpty()) + .withProperty("facing", placementState.blockFace().name().toLowerCase(Locale.ROOT)); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + String facingProp = updateState.currentBlock().getProperty("facing"); + BlockFace supportingFace = (facingProp != null) + ? BlockFace.valueOf(facingProp.toUpperCase(Locale.ROOT)).getOppositeFace() + : BlockFace.BOTTOM; + + if (!canSupportTorch( + updateState.instance(), + updateState.blockPosition().add(supportingFace.toDirection().vec()), + supportingFace + )) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + + return updateState.currentBlock(); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TrapdoorPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TrapdoorPlacementRule.java new file mode 100644 index 00000000..17b04322 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/TrapdoorPlacementRule.java @@ -0,0 +1,51 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +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.vanilla.blocks.placement.util.States; + +import java.util.Locale; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class TrapdoorPlacementRule extends BlockPlacementRule { + + public TrapdoorPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + BlockFace placementFace = placementState.blockFace(); + Pos playerPos = placementState.playerPosition() != null ? placementState.playerPosition() : Pos.ZERO; + var direction = BlockFace.fromYaw(playerPos.yaw()).toDirection().opposite(); + Vec cursorPos = placementState.cursorPosition() != null ? (Vec) placementState.cursorPosition() : Vec.ZERO; + BlockFace facing = BlockFace.fromDirection(direction); + + BlockFace half; + if (placementFace == BlockFace.BOTTOM || + (placementFace != BlockFace.TOP && cursorPos.y() > 0.5)) { + half = BlockFace.TOP; + } else { + half = BlockFace.BOTTOM; + } + + return placementState.block().withProperties( + Map.of( + States.HALF, half.name().toLowerCase(Locale.getDefault()), + States.FACING, facing.name().toLowerCase(Locale.getDefault()) + ) + ); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticalSlimBlockPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticalSlimBlockPlacementRule.java new file mode 100644 index 00000000..75101bc5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticalSlimBlockPlacementRule.java @@ -0,0 +1,43 @@ +package net.minestom.vanilla.blocks.placement; + +import java.util.Map; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.registry.RegistryTag; +import net.minestom.vanilla.blocks.placement.common.AbstractConnectingBlockPlacementRule; + +import java.util.ArrayList; +import java.util.Set; +import net.minestom.vanilla.common.utils.BlockUtil; +import net.minestom.vanilla.common.utils.TagHelper; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class VerticalSlimBlockPlacementRule extends AbstractConnectingBlockPlacementRule { + + private final RegistryTag canConnect = RegistryTag.direct( + new ArrayList<>() {{ + addAll(TagHelper.getInstance().getHashed("#walls")); + addAll(BlockUtil.getGlassPanes()); + }} + ); + + public VerticalSlimBlockPlacementRule(Block block) { + super(block); + } + + @Override + public boolean canConnect(Block.Getter instance, Point pos, BlockFace blockFace) { + Block instanceBlock = instance.getBlock(pos); + boolean isFaceFull = instanceBlock.registry().collisionShape().isFaceFull(blockFace); + return (!cannotConnect.contains(instanceBlock) && isFaceFull) || canConnect.contains(instanceBlock) || instanceBlock.key().equals(this.block.key()); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticallyRotatedPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticallyRotatedPlacementRule.java new file mode 100644 index 00000000..9c4385ed --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/VerticallyRotatedPlacementRule.java @@ -0,0 +1,33 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.common.utils.DirectionUtils; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class VerticallyRotatedPlacementRule extends BlockPlacementRule { + + public VerticallyRotatedPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(@NotNull PlacementState placementState) { + Direction direction = DirectionUtils.getHorizontalPlacementDirection(placementState); + if (direction == null) { + return placementState.block(); + } + return placementState.block() + .withProperty("facing", direction.name().toLowerCase()); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallBlockPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallBlockPlacementRule.java new file mode 100644 index 00000000..49d0dc66 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallBlockPlacementRule.java @@ -0,0 +1,75 @@ +package net.minestom.vanilla.blocks.placement; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.registry.RegistryTag; +import net.minestom.vanilla.blocks.placement.common.AbstractConnectingBlockPlacementRule; +import net.minestom.vanilla.common.utils.BlockUtil; +import net.minestom.vanilla.common.utils.FluidUtils; + +import java.util.ArrayList; +import java.util.Set; +import net.minestom.vanilla.common.utils.TagHelper; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class WallBlockPlacementRule extends AbstractConnectingBlockPlacementRule { + + private final Set fenceGates = TagHelper.getInstance().getTaggedWith("minecraft:fence_gates"); + private final RegistryTag canConnect = RegistryTag.direct( + new ArrayList<>() {{ + addAll(TagHelper.getInstance().getHashed("#walls")); + addAll(BlockUtil.getGlassPanes()); + addAll(fenceGates); + }} + ); + + public WallBlockPlacementRule(Block block) { + super(block); + } + + @Override + public boolean canConnect(Block.Getter instance, Point pos, BlockFace blockFace) { + Block instanceBlock = instance.getBlock(pos); + boolean isFaceFull = instanceBlock.registry().collisionShape().isFaceFull(blockFace); + return (!cannotConnect.contains(instanceBlock) && isFaceFull) || canConnect.contains(instanceBlock) || instanceBlock.key().equals(this.block.key()); + } + + @Override + public String stringify(boolean connect, Block.Getter instance, Point pos, BlockFace direction) { + if (!connect) return "none"; + Block above = instance.getBlock(pos.add(0.0, 1.0, 0.0)); + if (!above.isAir()) return "tall"; + return "low"; + } + + @Override + public Block transmute(Block.Getter instance, Point pos, Block block) { + Block instanceBlock = instance.getBlock(pos); + boolean north = canConnect(instance, pos.add(BlockFace.NORTH.toDirection().vec()), BlockFace.NORTH); + boolean east = canConnect(instance, pos.add(BlockFace.EAST.toDirection().vec()), BlockFace.EAST); + boolean south = canConnect(instance, pos.add(BlockFace.SOUTH.toDirection().vec()), BlockFace.SOUTH); + boolean west = canConnect(instance, pos.add(BlockFace.WEST.toDirection().vec()), BlockFace.WEST); + + boolean axis1 = north && south; + boolean axis2 = east && west; + + boolean hasPillar = !((!axis1 && axis2) || (!axis2 && axis1)) + || !(north || east || south || west) + || (north && south && east && west); + + Block blockAbove = instance.getBlock(pos.add(0.0, 1.0, 0.0)); + boolean blockAboveConnect = !blockAbove.isAir() || (blockAbove.key().equals(block.key()) && "false".equals(blockAbove.getProperty("up"))); + + return block + .withProperty("waterlogged", String.valueOf(FluidUtils.isWater(instanceBlock))) + .withProperty("up", String.valueOf(hasPillar || blockAboveConnect)); + } +} \ No newline at end of file diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallCoralPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallCoralPlacementRule.java new file mode 100644 index 00000000..592e900e --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/WallCoralPlacementRule.java @@ -0,0 +1,51 @@ +package net.minestom.vanilla.blocks.placement; + +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.Direction; +import org.jetbrains.annotations.NotNull; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class WallCoralPlacementRule extends BlockPlacementRule { + + public WallCoralPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockPlace(PlacementState placementState) { + return placementState.block(); + } + + @Override + public @NotNull Block blockUpdate(UpdateState updateState) { + Block currentBlock = updateState.currentBlock(); + String facingStr = currentBlock.getProperty("facing"); + if (facingStr == null) { + return Block.AIR; + } + + BlockFace facing = BlockFace.fromDirection( + Direction.valueOf( + facingStr.toUpperCase() + ) + ); + + Block supportingBlock = updateState.instance().getBlock(updateState.blockPosition().relative(facing.getOppositeFace())); + + if (!supportingBlock.registry().collisionShape().isFaceFull(facing)) { + return "true".equals(currentBlock.getProperty("waterlogged")) ? Block.WATER : Block.AIR; + } + + return super.blockUpdate(updateState); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractConnectingBlockPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractConnectingBlockPlacementRule.java new file mode 100644 index 00000000..9e062222 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractConnectingBlockPlacementRule.java @@ -0,0 +1,91 @@ +package net.minestom.vanilla.blocks.placement.common; + +import net.minestom.server.coordinate.Point; +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.registry.Registry; +import net.minestom.server.registry.RegistryTag; +import net.minestom.vanilla.blocks.placement.util.States; + +import java.util.ArrayList; +import java.util.Map; + + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public abstract class AbstractConnectingBlockPlacementRule extends BlockPlacementRule { + + protected final Registry tagManager = Block.staticRegistry(); + + protected final RegistryTag cannotConnect = RegistryTag.direct( + new ArrayList<>() {{ + addAll(Block.values().stream().filter(it -> it.name().endsWith("_leaves")).toList()); + addAll(Block.values().stream().filter(it -> it.name().endsWith("_shulker_box")).toList()); + add(Block.BARRIER); + add(Block.CARVED_PUMPKIN); + add(Block.JACK_O_LANTERN); + add(Block.MELON); + add(Block.PUMPKIN); + }} + ); + + protected AbstractConnectingBlockPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockUpdate(UpdateState updateState) { + Block.Getter instance = updateState.instance(); + Point placePos = updateState.blockPosition(); + return transmute(instance, placePos, getProperty(updateState.currentBlock(), instance, placePos)); + } + + @Override + public Block blockPlace(PlacementState placementState) { + Block.Getter instance = placementState.instance(); + Point placePos = placementState.placePosition(); + return transmute(instance, placePos, getProperty(placementState.block(), instance, placePos)); + } + + private Block getProperty(Block block, Block.Getter instance, Point placePos) { + Point north = placePos.relative(BlockFace.NORTH); + Point east = placePos.relative(BlockFace.EAST); + Point south = placePos.relative(BlockFace.SOUTH); + Point west = placePos.relative(BlockFace.WEST); + + return block.withProperties( + Map.of( + States.NORTH, stringify(canConnect(instance, north, BlockFace.SOUTH), instance, north, BlockFace.SOUTH), + States.EAST, stringify(canConnect(instance, east, BlockFace.WEST), instance, east, BlockFace.WEST), + States.SOUTH, stringify(canConnect(instance, south, BlockFace.NORTH), instance, south, BlockFace.NORTH), + States.WEST, stringify(canConnect(instance, west, BlockFace.EAST), instance, west, BlockFace.EAST) + ) + ); + } + + /** + * Determines whether the block can connect to the block at the given position in the specified direction + */ + public abstract boolean canConnect(Block.Getter instance, Point pos, BlockFace blockFace); + + /** + * Converts the connection status to a string property value + */ + public String stringify(boolean connect, Block.Getter instance, Point pos, BlockFace direction) { + return String.valueOf(connect); + } + + /** + * Allows transmuting the block into a different block type based on position + */ + public Block transmute(Block.Getter instance, Point pos, Block block) { + return block; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractRailPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractRailPlacementRule.java new file mode 100644 index 00000000..8f14714c --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/common/AbstractRailPlacementRule.java @@ -0,0 +1,266 @@ +package net.minestom.vanilla.blocks.placement.common; + +import net.minestom.server.coordinate.Point; +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.Direction; +import net.minestom.vanilla.common.item.DroppedItemFactory; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public abstract class AbstractRailPlacementRule extends BlockPlacementRule { + + protected AbstractRailPlacementRule(Block block) { + super(block); + } + + @Override + public Block blockUpdate(UpdateState updateState) { + if (!isSupported(updateState.instance(), updateState.blockPosition())) { + DroppedItemFactory.maybeDrop(updateState); + return Block.AIR; + } + return super.blockUpdate(updateState); + } + + protected FixedPlacementResult getFixedPlacement(PlacementState placementState) { + List fixedSidesNorthSouth = getFixedSides(placementState.instance(), placementState.placePosition(), RailShape.NORTH_SOUTH); + List fixedSidesEastWest = getFixedSides(placementState.instance(), placementState.placePosition(), RailShape.EAST_WEST); + if (fixedSidesNorthSouth.size() == 2) { + return new FixedPlacementResult.DefinitiveBlock(placementState.block().withProperty("shape", RailShape.NORTH_SOUTH.toString())); + } else if (fixedSidesEastWest.size() == 2) { + return new FixedPlacementResult.DefinitiveBlock(placementState.block().withProperty("shape", RailShape.EAST_WEST.toString())); + } + + Direction lockedDirection = null; + if (!fixedSidesNorthSouth.isEmpty()) { + lockedDirection = fixedSidesNorthSouth.get(0).toDirection(); + } else if (!fixedSidesEastWest.isEmpty()) { + lockedDirection = fixedSidesEastWest.get(0).toDirection(); + } + + return new FixedPlacementResult.LockedDirection(lockedDirection); + } + + protected boolean isSupported(Block.Getter instance, Point blockPos) { + return instance + .getBlock(blockPos.sub(0.0, 1.0, 0.0)) + .registry() + .collisionShape() + .isFaceFull(BlockFace.BOTTOM); + } + + protected Block createSidedConnection(PlacementState placementState, Direction rotated, Direction lockedDirection) { + RailShape shape = getSideConnection( + placementState.instance(), + placementState.placePosition(), + BlockFace.fromDirection(rotated) + ); + + if (shape != null) { + Point sidePos = placementState.placePosition().add(rotated.vec()); + Block sideBlock = placementState.instance().getBlock(sidePos); + if (!sideBlock.compare(Block.RAIL, Block.Comparator.ID) && !shape.isStraight()) { + return null; + } + RailShape ownShape = RailShape.fromSides(Stream.of(lockedDirection, rotated).filter(Objects::nonNull).map(BlockFace::fromDirection).collect(Collectors.toList())); + ((Instance) placementState.instance()).setBlock( + sidePos, + sideBlock.withProperty("shape", shape.toString()) + ); + return placementState.block().withProperty("shape", ownShape.toString()); + } + + return null; + } + + protected RailShape getSideConnection(Block.Getter instance, Point point, BlockFace side) { + Point sidePos = point.add(side.toDirection().vec()); + Block sideBlock = instance.getBlock(sidePos); + String shapeStr = sideBlock.getProperty("shape"); + if (shapeStr == null) return null; + RailShape shape = RailShape.fromString(shapeStr); + if (shape == null) return null; + + List fixed = getFixedSides(instance, sidePos, shape); + if (fixed.size() == 2) return null; + + if (fixed.isEmpty()) { + // 0 bounds, rotate rail + switch (side) { + case NORTH: + case SOUTH: + return RailShape.NORTH_SOUTH; + case EAST: + case WEST: + return RailShape.EAST_WEST; + default: + return null; + } + } else { + // 1 bound, other is free to be rotated to us + return RailShape.fromSides(Arrays.asList(side.getOppositeFace(), fixed.get(0))); + } + } + + protected List getFixedSides(Block.Getter instance, Point point, RailShape shape) { + return shape.getSides().stream().filter(side -> { + Block neighborBlock = instance.getBlock(point.add(side.toDirection().vec())); + String neighborShapeStr = neighborBlock.getProperty("shape"); + RailShape neighborShape = neighborShapeStr != null ? RailShape.fromString(neighborShapeStr) : null; + boolean directNeighbour = neighborShape != null && neighborShape.getSides().contains(side.getOppositeFace()); + + Block lowerNeighborBlock = instance.getBlock(point.add(side.toDirection().vec().sub(0.0, 1.0, 0.0))); + String lowerNeighborShapeStr = lowerNeighborBlock.getProperty("shape"); + RailShape lowerNeighborShape = lowerNeighborShapeStr != null ? RailShape.fromString(lowerNeighborShapeStr) : null; + boolean lowerNeighbor = lowerNeighborShape != null && lowerNeighborShape.getSides().contains(side.getOppositeFace()) && lowerNeighborShape.isAscending(); + + return directNeighbour || lowerNeighbor; + }).collect(Collectors.toList()); + } + + protected RailShape connectVertical( + RailShape shape, + PlacementState placementState + ) { + RailShape mutShape = shape; + List initialSides = mutShape.getSides(); + for (BlockFace face : initialSides) { + Point position = placementState.placePosition().add(face.toDirection().vec().add(0.0, 1.0, 0.0)); + String upperShapeStr = placementState.instance().getBlock(position).getProperty("shape"); + if (upperShapeStr == null) continue; + RailShape upperShape = RailShape.fromString(upperShapeStr); + if (upperShape == null) continue; + + if (upperShape.getSides().stream().noneMatch(initialSides::contains)) continue; + // vertical placement + mutShape = RailShape.getAscendingTowards(face); + } + + //update verticals below + List finalSides = mutShape.getSides(); + for (BlockFace face : finalSides) { + Point position = placementState.placePosition().add(face.toDirection().vec().add(0.0, -1.0, 0.0)); + Block lowerBlock = placementState.instance().getBlock(position); + String lowerShapeStr = lowerBlock.getProperty("shape"); + if (lowerShapeStr == null) continue; + RailShape lowerShape = RailShape.fromString(lowerShapeStr); + if (lowerShape == null) continue; + + if (lowerShape.getSides().stream().noneMatch(finalSides::contains)) continue; + if (!lowerShape.isStraight() || lowerShape.isAscending()) continue; + ((Instance) placementState.instance()).setBlock( + position, + lowerBlock + .withProperty("shape", RailShape.getAscendingTowards(face.getOppositeFace()).toString()) + ); + } + return mutShape; + } + + public enum RailShape { + NORTH_SOUTH(BlockFace.NORTH, BlockFace.SOUTH), + EAST_WEST(BlockFace.EAST, BlockFace.WEST), + NORTH_EAST(BlockFace.NORTH, BlockFace.EAST), + NORTH_WEST(BlockFace.NORTH, BlockFace.WEST), + SOUTH_EAST(BlockFace.SOUTH, BlockFace.EAST), + SOUTH_WEST(BlockFace.SOUTH, BlockFace.WEST), + ASCENDING_EAST(BlockFace.EAST, BlockFace.WEST), + ASCENDING_WEST(BlockFace.WEST, BlockFace.EAST), + ASCENDING_NORTH(BlockFace.NORTH, BlockFace.SOUTH), + ASCENDING_SOUTH(BlockFace.SOUTH, BlockFace.NORTH); + + private final List sides; + + RailShape(BlockFace... sides) { + this.sides = Arrays.asList(sides); + } + + public List getSides() { + return sides; + } + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + + public boolean isAscending() { + return this == ASCENDING_EAST || this == ASCENDING_WEST || this == ASCENDING_NORTH || this == ASCENDING_SOUTH; + } + + public boolean isStraight() { + return this == NORTH_SOUTH || this == EAST_WEST || isAscending(); + } + + public static RailShape fromString(String value) { + try { + return valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + public static RailShape fromSides(List requiredSides) { + return Arrays.stream(values()) + .filter(shape -> new HashSet<>(shape.sides).containsAll(requiredSides)) + .min(Comparator.comparingInt(Enum::ordinal)) + .orElse(null); + } + + public static RailShape getAscendingTowards(BlockFace face) { + switch (face) { + case NORTH: + return ASCENDING_NORTH; + case SOUTH: + return ASCENDING_SOUTH; + case EAST: + return ASCENDING_EAST; + case WEST: + return ASCENDING_WEST; + default: + throw new IllegalArgumentException("Only horizontal faces are supported"); + } + } + } + + public sealed interface FixedPlacementResult { + final class DefinitiveBlock implements FixedPlacementResult { + private final Block block; + + public DefinitiveBlock(Block block) { + this.block = block; + } + + public Block getBlock() { + return block; + } + } + + final class LockedDirection implements FixedPlacementResult { + private final Direction direction; + + public LockedDirection(Direction direction) { + this.direction = direction; + } + + public Direction getDirection() { + return direction; + } + } + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/blocks/placement/util/States.java b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/util/States.java new file mode 100644 index 00000000..0cabdcff --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/blocks/placement/util/States.java @@ -0,0 +1,108 @@ +package net.minestom.vanilla.blocks.placement.util; + +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.utils.Direction; + +import java.util.Locale; + + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class States { + public static final String HALF = "half"; + public static final String FACING = "facing"; + public static final String FACE = "face"; + public static final String SHAPE = "shape"; + public static final String WATERLOGGED = "waterlogged"; + public static final String NORTH = "north"; + public static final String EAST = "east"; + public static final String SOUTH = "south"; + public static final String WEST = "west"; + + public static BlockFace getHalf(Block block) { + if (block.getProperty(HALF) == null) { + return BlockFace.BOTTOM; + } + return BlockFace.valueOf(block.getProperty(HALF).toUpperCase(Locale.getDefault())); + } + + public static BlockFace getFacing(Block block) { + if (block.getProperty(FACING) == null) { + return BlockFace.NORTH; + } + return BlockFace.valueOf(block.getProperty(FACING).toUpperCase(Locale.getDefault())); + } + + public static Direction getDirection(Block block) { + if (block.getProperty(FACE) == null) { + return Direction.NORTH; + } + switch (block.getProperty(FACE)) { + case "ceiling": + return Direction.DOWN; + case "floor": + return Direction.UP; + default: + return getFacing(block).toDirection(); + } + } + + public static Direction rotateYCounterclockwise(Direction direction) { + switch (direction.ordinal()) { + case 2: // NORTH + return Direction.WEST; + case 5: // WEST + return Direction.SOUTH; + case 3: // EAST + return Direction.NORTH; + case 4: // SOUTH + return Direction.EAST; + default: + throw new IllegalStateException("Unable to rotate " + direction); + } + } + + public static Direction rotateYClockwise(Direction direction) { + switch (direction.ordinal()) { + case 2: // NORTH + return Direction.EAST; + case 5: // WEST + return Direction.NORTH; + case 3: // EAST + return Direction.SOUTH; + case 4: // SOUTH + return Direction.WEST; + default: + throw new IllegalStateException("Unable to rotate " + direction); + } + } + + public static Axis getAxis(Direction direction) { + switch (direction) { + case DOWN: + case UP: + return Axis.Y; + case NORTH: + case SOUTH: + return Axis.Z; + case WEST: + case EAST: + return Axis.X; + default: + throw new IllegalStateException("Unknown direction: " + direction); + } + } + + public enum Axis { + X, + Y, + Z + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/item/DefaultDroppedItemFactory.java b/blocks/src/main/java/net/minestom/vanilla/common/item/DefaultDroppedItemFactory.java new file mode 100644 index 00000000..a53a7dcb --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/item/DefaultDroppedItemFactory.java @@ -0,0 +1,44 @@ +package net.minestom.vanilla.common.item; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.ItemEntity; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.utils.time.TimeUnit; + +import java.util.Random; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class DefaultDroppedItemFactory implements DroppedItemFactory { + private final Random random = new Random(); + + @Override + public void spawn(Instance instance, Point position, Block block) { + Material material = block.registry().material(); + if (material == null) return; + spawn(instance, position, ItemStack.of(material)); + } + + @Override + public void spawn(Instance instance, Point position, ItemStack item) { + ItemEntity entity = new ItemEntity(item); + entity.setPickupDelay(1, TimeUnit.SECOND); // 1s for natural drop + entity.scheduleRemove(5, TimeUnit.MINUTE); + entity.setVelocity(new Vec( + random.nextDouble() * 2 - 1, + 2.0, + random.nextDouble() * 2 - 1 + )); + entity.setInstance(instance, position.add(0.5, 0.5, 0.5)); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/item/DroppedItemFactory.java b/blocks/src/main/java/net/minestom/vanilla/common/item/DroppedItemFactory.java new file mode 100644 index 00000000..681d779b --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/item/DroppedItemFactory.java @@ -0,0 +1,75 @@ +package net.minestom.vanilla.common.item; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule.UpdateState; +import net.minestom.server.item.ItemStack; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public interface DroppedItemFactory { + void spawn(Instance instance, Point position, Block block); + void spawn(Instance instance, Point position, ItemStack item); + + class Companion { + private static DroppedItemFactory current = new DefaultDroppedItemFactory(); + private static boolean doDropItems = true; + + public static DroppedItemFactory getCurrent() { + return current; + } + + public static void setCurrent(DroppedItemFactory factory) { + current = factory; + } + + public static boolean getDoDropItems() { + return doDropItems; + } + + public static void setDoDropItems(boolean value) { + doDropItems = value; + } + + public static void maybeDrop(Instance instance, Point position, Block block) { + if (doDropItems) { + current.spawn(instance, position, block); + } + } + + public static void maybeDrop(UpdateState state) { + if (doDropItems) { + current.spawn((Instance)state.instance(), state.blockPosition(), state.currentBlock()); + } + } + + public static void maybeDrop(Instance instance, Point position, ItemStack item) { + if (doDropItems) { + current.spawn(instance, position, item); + } + } + } + + // Static members to access companion object methods directly + DroppedItemFactory current = Companion.getCurrent(); + boolean doDropItems = Companion.getDoDropItems(); + + static void maybeDrop(Instance instance, Point position, Block block) { + Companion.maybeDrop(instance, position, block); + } + + static void maybeDrop(UpdateState state) { + Companion.maybeDrop(state); + } + + static void maybeDrop(Instance instance, Point position, ItemStack item) { + Companion.maybeDrop(instance, position, item); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/utils/BlockUtil.java b/blocks/src/main/java/net/minestom/vanilla/common/utils/BlockUtil.java new file mode 100644 index 00000000..b05ebb9c --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/utils/BlockUtil.java @@ -0,0 +1,55 @@ +package net.minestom.vanilla.common.utils; + +import java.util.Set; +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockManager; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class BlockUtil { + + private static final BlockManager blocks = MinecraftServer.getBlockManager(); + + private static final Set glassPanes = Set.of( + Block.WHITE_STAINED_GLASS_PANE, + Block.LIGHT_GRAY_STAINED_GLASS_PANE, + Block.GRAY_STAINED_GLASS_PANE, + Block.BLACK_STAINED_GLASS_PANE, + Block.BROWN_STAINED_GLASS_PANE, + Block.RED_STAINED_GLASS_PANE, + Block.ORANGE_STAINED_GLASS_PANE, + Block.YELLOW_STAINED_GLASS_PANE, + Block.LIME_STAINED_GLASS_PANE, + Block.GREEN_STAINED_GLASS_PANE, + Block.CYAN_STAINED_GLASS_PANE, + Block.LIGHT_BLUE_STAINED_GLASS_PANE, + Block.BLUE_STAINED_GLASS_PANE, + Block.PURPLE_STAINED_GLASS_PANE, + Block.MAGENTA_STAINED_GLASS_PANE, + Block.PINK_STAINED_GLASS_PANE, + Block.GLASS_PANE + ); + + public static Set getGlassPanes() { + return glassPanes; + } + + /** + * Ensures a block has its default handler + * @param block The block to check + * @return The block with its default handler, or the original block if it already has a handler + */ + public static Block withDefaultHandler(Block block) { + if (block.handler() != null) { + return block; + } + return block.withHandler(blocks.getHandler(block.key().asString())); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/utils/DirectionUtils.java b/blocks/src/main/java/net/minestom/vanilla/common/utils/DirectionUtils.java new file mode 100644 index 00000000..4d663f43 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/utils/DirectionUtils.java @@ -0,0 +1,136 @@ +package net.minestom.vanilla.common.utils; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.instance.block.rule.BlockPlacementRule.PlacementState; +import net.minestom.server.utils.Direction; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class DirectionUtils { + + public static String getAxis(Direction direction) { + return switch (direction) { + case UP, DOWN -> "y"; + case NORTH, SOUTH -> "z"; + case EAST, WEST -> "x"; + default -> ""; + }; + } + + public static Direction getHorizontalPlacementDirection(PlacementState state) { + if (state.playerPosition() == null) return null; + + float yaw = state.playerPosition().yaw(); + if (yaw < 0) yaw += 360f; + + if (yaw >= 45f && yaw <= 135f) return Direction.EAST; + else if (yaw >= 135f && yaw <= 225f) return Direction.SOUTH; + else if (yaw >= 225f && yaw <= 315f) return Direction.WEST; + else return Direction.NORTH; + } + + public static int sixteenStepRotation(PlacementState state) { + if (state.playerPosition() == null) return 0; + + float yaw = state.playerPosition().yaw() + (22.5f / 2.0f); + if (yaw < 0) yaw += 360f; + + int rotation = (int)(yaw / 22.5f); + return Math.max(0, Math.min(rotation, 15)); + } + + public static boolean canAttach(PlacementState state) { + if (state.blockFace() == null) return false; + + Vec vec = state.blockFace().toDirection().vec(); + var anchor = state.placePosition().sub(vec); + var anchorBlock = state.instance().getBlock(anchor); + return anchorBlock.registry().collisionShape().isFaceFull(state.blockFace()); + } + + public static Direction getNearestLookingDirection(Pos position, Collection allowedDirections) { + Direction nearest = null; + double minAngle = Double.MAX_VALUE; + + for (Direction direction : allowedDirections) { + double angle = scalarProduct(direction.vec(), position.direction().normalize()); + if (nearest == null || angle < minAngle) { + nearest = direction; + minAngle = angle; + } + } + return nearest; + } + + public static Direction getNearestLookingDirection(PlacementState state, Collection allowedDirections) { + if (state.playerPosition() == null) return Direction.EAST; + return getNearestLookingDirection(state.playerPosition(), allowedDirections); + } + + public static Direction getNearestLookingDirection(PlacementState state) { + if (state.playerPosition() == null) return Direction.EAST; + return getNearestLookingDirection(state.playerPosition(), Arrays.asList(Direction.values())); + } + + public static Direction getNearestHorizontalLookingDirection(PlacementState state) { + if (state.playerPosition() == null) return Direction.EAST; + + List horizontal = StreamSupport.stream(Arrays.stream(Direction.HORIZONTAL).spliterator(), false) + .collect(Collectors.toList()); + return getNearestLookingDirection(state.playerPosition(), horizontal); + } + + public static Direction getNearestLookingDirection(BlockHandler.Interaction interaction, Collection allowedDirections) { + return getNearestLookingDirection(interaction.getPlayer().getPosition(), allowedDirections); + } + + public static Direction getNearestHorizontalLookingDirection(BlockHandler.Interaction interaction) { + List horizontal = StreamSupport.stream(Arrays.stream(Direction.HORIZONTAL).spliterator(), false) + .collect(Collectors.toList()); + return getNearestLookingDirection(interaction.getPlayer().getPosition(), horizontal); + } + + public static double scalarProduct(Vec a, Vec b) { + Vec componentsMultiplied = a.mul(b); + return componentsMultiplied.x() + componentsMultiplied.y() + componentsMultiplied.z(); + } + + public static Direction rotateR(Direction direction) { + return switch (direction) { + case NORTH -> Direction.EAST; + case EAST -> Direction.SOUTH; + case SOUTH -> Direction.WEST; + case WEST -> Direction.NORTH; + default -> direction; + }; + } + + public static Direction rotateL(Direction direction) { + return rotateR(direction.opposite()); + } + + public static float getYaw(Direction direction) { + return switch (direction) { + case UP, DOWN -> 0f; + case NORTH -> 180f; + case EAST -> -90f; + case SOUTH -> 0f; + case WEST -> 90f; + default -> 0f; + }; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/utils/EntityUtils.java b/blocks/src/main/java/net/minestom/vanilla/common/utils/EntityUtils.java new file mode 100644 index 00000000..12b24e2a --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/utils/EntityUtils.java @@ -0,0 +1,22 @@ +package net.minestom.vanilla.common.utils; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Entity; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class EntityUtils { + + public static Pos eyePosition(Entity entity) { + if (entity.isSneaking()) { + return entity.getPosition().add(0.0, 1.23, 0.0); + } + return entity.getPosition().add(0.0, 1.53, 0.0); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/common/utils/FluidUtils.java b/blocks/src/main/java/net/minestom/vanilla/common/utils/FluidUtils.java new file mode 100644 index 00000000..7d22e9e9 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/utils/FluidUtils.java @@ -0,0 +1,23 @@ +package net.minestom.vanilla.common.utils; + +import net.minestom.server.instance.block.Block; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + *

+ * Original authors: ChrisB, AEinNico, CreepyX + *

+ * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ +public class FluidUtils { + + public static boolean isWaterSource(Block block) { + return block.compare(Block.WATER); + } + + public static boolean isWater(Block block) { + return block.compare(Block.WATER) || block.compare(Block.BUBBLE_COLUMN); + } +} + diff --git a/blocks/src/main/java/net/minestom/vanilla/common/utils/TagHelper.java b/blocks/src/main/java/net/minestom/vanilla/common/utils/TagHelper.java new file mode 100644 index 00000000..c4ecb36e --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/common/utils/TagHelper.java @@ -0,0 +1,57 @@ +package net.minestom.vanilla.common.utils; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import net.kyori.adventure.key.Key; +import net.minestom.server.instance.block.Block; +import net.minestom.server.registry.Registry; +import net.minestom.server.registry.RegistryTag; +import net.minestom.server.registry.TagKey; +import org.jetbrains.annotations.NotNull; + +public class TagHelper { + + private static final TagHelper INSTANCE = new TagHelper(); + private static final @NotNull Registry staticRegistry = Block.staticRegistry(); + + private TagHelper() { + + } + + public static TagHelper getInstance() { + return INSTANCE; + } + + public boolean hasTag(Block element, String tag) { + RegistryTag data = staticRegistry.getTag(Key.key(tag)); + if (data == null) return false; + return data.contains(element); + } + + public Set getHashed(String tag) { + RegistryTag data = staticRegistry.getTag(TagKey.ofHash(tag)); + if (data == null) return new HashSet<>(); + + return StreamSupport.stream(data.spliterator(), false) + .map(obj -> Block.fromKey(obj.key())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + public Set getTaggedWith(String tag) { + RegistryTag data = staticRegistry.getTag(Key.key(tag)); + if (data == null) return new HashSet<>(); + + return StreamSupport.stream(data.spliterator(), false) + .map(obj -> Block.fromKey(obj.key())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + + + +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/MinestomFluids.java b/blocks/src/main/java/net/minestom/vanilla/fluids/MinestomFluids.java new file mode 100644 index 00000000..de0a206f --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/MinestomFluids.java @@ -0,0 +1,162 @@ +package net.minestom.vanilla.fluids; + +import java.util.Collections; +import java.util.WeakHashMap; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.Event; +import net.minestom.server.event.EventNode; +import net.minestom.server.event.instance.InstanceTickEvent; +import net.minestom.server.event.player.PlayerBlockBreakEvent; +import net.minestom.server.event.player.PlayerBlockInteractEvent; +import net.minestom.server.event.player.PlayerBlockPlaceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockHandler; +import net.minestom.server.item.Material; +import net.minestom.server.tag.Tag; +import net.minestom.vanilla.fluids.common.Fluid; +import net.minestom.vanilla.fluids.common.FluidState; +import net.minestom.vanilla.fluids.common.WaterlogHandler; +import net.minestom.vanilla.fluids.impl.EmptyFluid; +import net.minestom.vanilla.fluids.impl.LavaFluid; +import net.minestom.vanilla.fluids.impl.WaterFluid; +import net.minestom.vanilla.fluids.placement.FluidPlacementRule; +import net.minestom.vanilla.fluids.placement.LavaPlacementRule; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class MinestomFluids { + public static final Fluid WATER = new WaterFluid(); + public static final Fluid LAVA = new LavaFluid(); + public static final Fluid EMPTY = new EmptyFluid(); + + public static final FluidState AIR_STATE = new FluidState(Block.AIR, EMPTY); + + private static final Map WATERLOG_HANDLERS = Collections.synchronizedMap(new WeakHashMap<>()); + + private static final Tag>> TICK_UPDATES = Tag.Transient("fluid-tick-updates"); + + public static Fluid get(Block block) { + if (block.compare(Block.WATER) || FluidState.isWaterlogged(block)) { + return WATER; + } else if (block.compare(Block.LAVA)) { + return LAVA; + } else { + return EMPTY; + } + } + + public static void tick(InstanceTickEvent event) { + Instance instance = event.getInstance(); + long age = instance.getWorldAge(); + + var updates = instance.getTag(TICK_UPDATES); + if (updates == null) { + updates = new ConcurrentHashMap<>(); + instance.setTag(TICK_UPDATES, updates); + } + + Set currentUpdate = updates.remove(age); + if (currentUpdate == null) return; + + for (BlockVec point : currentUpdate) { + tick(event.getInstance(), point); + } + } + + public static void tick(Instance instance, BlockVec point) { + FluidState state = FluidState.of(instance.getBlock(point)); + state.fluid().onTick(instance, point, state); + } + + public static void scheduleTick(Instance instance, BlockVec point, FluidState state) { + scheduleTick(instance, point, state.fluid().getNextTickDelay(instance, point)); + } + + public static void scheduleTick(Instance instance, BlockVec point, int tickDelay) { + if (tickDelay == -1) return; + + var updates = instance.getTag(TICK_UPDATES); + if (updates == null) { + updates = new ConcurrentHashMap<>(); + instance.setTag(TICK_UPDATES, updates); + } + + long newAge = instance.getWorldAge() + tickDelay; + updates.computeIfAbsent(newAge, l -> new HashSet<>()).add(point); + } + + public static void registerWaterlog(Block block, WaterlogHandler handler) { + WATERLOG_HANDLERS.put(block.id(), handler); + } + + public static WaterlogHandler getWaterlog(Block block) { + return WATERLOG_HANDLERS.get(block.id()); + } + + public static void init() { + MinecraftServer.getBlockManager().registerBlockPlacementRule(new FluidPlacementRule(Block.WATER)); + MinecraftServer.getBlockManager().registerBlockPlacementRule(new LavaPlacementRule(Block.LAVA)); + + MinecraftServer.getGlobalEventHandler().addListener(PlayerBlockInteractEvent.class, event -> { + Material itemMaterial = event.getPlayer().getItemInHand(event.getHand()).material(); + Block block = event.getBlock(); + Instance instance = event.getInstance(); + BlockVec position = event.getBlockPosition(); + + if (itemMaterial == Material.WATER_BUCKET) { + WaterlogHandler handler = MinestomFluids.getWaterlog(block); + if (handler != null) { + handler.placeFluid(instance, position, MinestomFluids.WATER.getDefaultState()); + } else { + instance.placeBlock(new BlockHandler.Placement(Block.WATER, instance, position.relative(event.getBlockFace()))); + } + } else if (itemMaterial == Material.BUCKET) { + WaterlogHandler handler = MinestomFluids.getWaterlog(block); + FluidState state = FluidState.of(block); + if (handler != null && handler.canRemoveFluid(instance, position, state)) { + instance.setBlock(position, FluidState.setWaterlogged(block, false)); + } else if (block.isLiquid()) { + event.getPlayer().setItemInHand(event.getHand(), state.fluid().getBucket()); + instance.setBlock(position, Block.AIR); + } + } else if (itemMaterial == Material.LAVA_BUCKET) { + instance.placeBlock(new BlockHandler.Placement(Block.LAVA, instance, position.relative(event.getBlockFace()))); + } + }); + + MinecraftServer.getGlobalEventHandler().addListener(PlayerBlockBreakEvent.class, event -> { + if (FluidState.isWaterlogged(event.getBlock())) { + event.setResultBlock(Block.WATER); + } + }); + + MinecraftServer.getGlobalEventHandler().addListener(PlayerBlockPlaceEvent.class, event -> { + Block originalBlock = event.getInstance().getBlock(event.getBlockPosition()); + Fluid fluid = MinestomFluids.get(originalBlock); + if (fluid != MinestomFluids.EMPTY && FluidState.isSource(originalBlock) && FluidState.canBeWaterlogged(event.getBlock())) { + event.setBlock(FluidState.setWaterlogged(event.getBlock(), true)); + } + }); + + for (Block block : Block.values()) { + if (FluidState.canBeWaterlogged(block)) { + registerWaterlog(block, WaterlogHandler.DEFAULT); + } + } + } + + public static EventNode events() { + EventNode node = EventNode.all("fluid-events"); + node.addListener(InstanceTickEvent.class, MinestomFluids::tick); + return node; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/common/FlowableFluid.java b/blocks/src/main/java/net/minestom/vanilla/fluids/common/FlowableFluid.java new file mode 100644 index 00000000..94353e1d --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/common/FlowableFluid.java @@ -0,0 +1,336 @@ +package net.minestom.vanilla.fluids.common; + +import it.unimi.dsi.fastutil.shorts.Short2BooleanMap; +import it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap; +import net.kyori.adventure.key.Key; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.coordinate.Point; +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.BlockHandler; +import net.minestom.server.item.Material; +import net.minestom.server.registry.Registry; +import net.minestom.server.utils.Direction; +import net.minestom.vanilla.fluids.MinestomFluids; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumMap; +import java.util.Map; + +public abstract class FlowableFluid extends Fluid { + public FlowableFluid(Block defaultBlock, Material bucket) { + super(defaultBlock, bucket); + } + + @Override + public void onTick(Instance instance, BlockVec point, FluidState state) { + if (!state.isSource()) { + FluidState updated = getUpdatedState(instance, point, state); + if (updated.isEmpty()) { + state = updated; + instance.setBlock(point, Block.AIR); + } else if (!updated.equals(state)) { + state = updated; + instance.setBlock(point, updated.block()); + MinestomFluids.scheduleTick(instance, point, getNextSpreadDelay(instance, point, state, updated)); + } + } + trySpread(instance, point, state); + } + + protected void trySpread(Instance instance, BlockVec point, FluidState state) { + if (state.isEmpty()) return; + + BlockVec down = point.add(0, -1, 0); + FluidState downState = FluidState.of(instance.getBlock(down)); + if (canMaybeFlowThrough(state, downState, BlockFace.BOTTOM)) { + FluidState newState = getUpdatedState(instance, down, downState); + + if (downState.fluid().canBeReplacedWith(instance, down, downState, newState, BlockFace.BOTTOM) + && canFill(instance, down, downState.block(), newState)) { + flow(instance, down, newState, BlockFace.BOTTOM); + + if (getAdjacentSourceCount(instance, point) >= 3) + flowSides(instance, point, state); + } + } else if (state.isSource() || !isWaterHole(instance, state, down)) { + flowSides(instance, point, state); + } + } + + /** + * Flows to the sides whenever possible, or to a hole if found + *

+ * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + * + */ + private void flowSides(Instance instance, BlockVec point, FluidState flowing) { + int newLevel = flowing.getLevel() - getLevelDecreasePerBlock(instance); + if (flowing.isFalling()) newLevel = 7; + if (newLevel <= 0) return; + + Map map = getSpread(instance, point, flowing); + for (Map.Entry entry : map.entrySet()) { + BlockFace direction = entry.getKey(); + FluidState newState = entry.getValue(); + + BlockVec offset = point.relative(direction); + FluidState currentState = FluidState.of(instance.getBlock(offset)); + if (preventFlowTo(instance, offset, flowing, newState, currentState, direction)) continue; + flow(instance, offset, newState, direction); + } + } + + private static final BlockFace[] HORIZONTAL = new BlockFace[] { + BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST + }; + + /** + * Gets the updated state of a fluid block by taking into account its surrounding blocks. + */ + protected FluidState getUpdatedState(Instance instance, BlockVec point, FluidState original) { + int highestLevel = 0; + int stillCount = 0; + + for (BlockFace face : HORIZONTAL) { + FluidState directionState = FluidState.of(instance.getBlock(point.relative(face))); + if (directionState.fluid() != this || !receivesFlow(face, original, directionState)) + continue; + + if (directionState.isSource()) stillCount++; + highestLevel = Math.max(highestLevel, directionState.getLevel()); + } + + if (isInfinite() && stillCount >= 2) { + // If there's 2 or more still fluid blocks around + // and below is still or a solid block, make this block still + Block downBlock = instance.getBlock(point.add(0, -1, 0)); + if (downBlock.isSolid() || isMatchingSource(FluidState.of(downBlock))) { + return defaultState.asSource(false); + } + } + + BlockVec above = point.add(0, 1, 0); + FluidState aboveState = FluidState.of(instance.getBlock(above)); + if (!aboveState.isEmpty() && aboveState.fluid() == this && receivesFlow(BlockFace.TOP, original, aboveState)) + return defaultState.asFlowing(8, true); + + int newLevel = highestLevel - getLevelDecreasePerBlock(instance); + if (newLevel <= 0) return MinestomFluids.AIR_STATE; + return defaultState.asFlowing(newLevel, false); + } + + private boolean receivesFlow(BlockFace face, FluidState from, FluidState to) { + // Check if both block faces merged occupy the whole square + return !from.block().registry().collisionShape().isOccluded(to.block().registry().collisionShape(), face); + } + + /** + * Creates a unique id based on the relation between point and point2 + */ + private static short getID(BlockVec point, BlockVec point2) { + int i = (int) (point2.x() - point.x()); + int j = (int) (point2.z() - point.z()); + return (short) ((i + 128 & 0xFF) << 8 | j + 128 & 0xFF); + } + + /** + * Returns a map with the directions the water can flow in and the block the water will become in that direction. + * If a hole is found within {@code getHoleRadius()} blocks, the water will only flow in that direction. + * A weight is used to determine which hole is the closest. + */ + protected Map getSpread(Instance instance, BlockVec point, FluidState flowing) { + int weight = 1000; + EnumMap map = new EnumMap<>(BlockFace.class); + Short2BooleanOpenHashMap holeMap = new Short2BooleanOpenHashMap(); + + for (BlockFace direction : HORIZONTAL) { + BlockVec directionPoint = point.relative(direction); + FluidState directionState = FluidState.of(instance.getBlock(directionPoint)); + short id = FlowableFluid.getID(point, directionPoint); + + if (!canMaybeFlowThrough(flowing, directionState, direction)) + continue; + + FluidState newState = getUpdatedState(instance, directionPoint, directionState); + if (!canFill(instance, directionPoint, directionState.block(), newState)) + continue; + + boolean down = holeMap.computeIfAbsent(id, s -> { + BlockVec downPoint = directionPoint.add(0, -1, 0); + return isWaterHole(instance, defaultState.asFlowing(newState.getLevel(), false), downPoint); + }); + + int newWeight = down ? 0 : getWeight(instance, directionPoint, 1, + direction.getOppositeFace(), directionState, point, holeMap); + if (newWeight < weight) map.clear(); + + if (newWeight <= weight) { + if (directionState.fluid().canBeReplacedWith(instance, directionPoint, directionState, newState, direction)) + map.put(direction, newState); + + weight = newWeight; + } + } + + return map; + } + + protected int getWeight(Instance instance, BlockVec point, int initialWeight, BlockFace skipCheck, + FluidState flowing, BlockVec originalPoint, Short2BooleanMap short2BooleanMap) { + // NOTE: flowing will often be air + + int weight = 1000; + for (BlockFace direction : HORIZONTAL) { + if (direction == skipCheck) continue; + BlockVec directionPoint = point.relative(direction); + FluidState directionState = FluidState.of(instance.getBlock(directionPoint)); + short id = FlowableFluid.getID(originalPoint, directionPoint); + + if (preventFlowTo(instance, directionPoint, flowing, defaultState.asFlowing(7, false), directionState, direction)) + continue; + + boolean down = short2BooleanMap.computeIfAbsent(id, s -> { + BlockVec downPoint = directionPoint.add(0, -1, 0); + return isWaterHole(instance, defaultState.asFlowing(7, false), downPoint); + }); + if (down) return initialWeight; + + if (initialWeight < getHoleRadius(instance)) { + int newWeight = getWeight(instance, directionPoint, initialWeight + 1, + direction.getOppositeFace(), directionState, originalPoint, short2BooleanMap); + if (newWeight < weight) weight = newWeight; + } + } + return weight; + } + + private int getAdjacentSourceCount(Instance instance, BlockVec point) { + int i = 0; + for (Direction direction : Direction.HORIZONTAL) { + BlockVec currentPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); + Block block = instance.getBlock(currentPoint); + if (!isMatchingSource(FluidState.of(block))) continue; + ++i; + } + return i; + } + + /** + * @return whether this block can hold any fluid + */ + private boolean canHoldFluid(Block block) { + if (FluidState.canBeWaterlogged(block)) return true; + if (block.isSolid()) return false; + + Registry registry = MinecraftServer.process().blocks(); + + return !(block.compare(Block.LADDER) + || block.compare(Block.SUGAR_CANE) + || block.compare(Block.BUBBLE_COLUMN) + || block.compare(Block.NETHER_PORTAL) + || block.compare(Block.END_PORTAL) + || block.compare(Block.END_GATEWAY) + || block.compare(Block.STRUCTURE_VOID) + || registry.getTag(Key.key("minecraft:signs")).contains(block) + || registry.getTag(Key.key("minecraft:doors")).contains(block)); + } + + /** + * @return whether the specified position can be filled with the specified FluidState + */ + private boolean canFill(Instance instance, BlockVec point, Block block, FluidState newState) { + WaterlogHandler handler = MinestomFluids.getWaterlog(block); + if (handler != null) return handler.canPlaceFluid(instance, point, block, newState); + return canHoldFluid(block); + } + + /** + * Used to determine if water should prioritize going to this point. + * @return whether the specified point is a hole + */ + private boolean isWaterHole(Instance instance, FluidState flowing, BlockVec flowTo) { + FluidState flowToState = FluidState.of(instance.getBlock(flowTo)); + if (!receivesFlow(BlockFace.BOTTOM, flowing, flowToState)) return false; // Don't flow down if the path is obstructed + if (flowing.sameFluid(flowToState)) return true; // Always flow down when the fluid is the same + return canFill(instance, flowTo, flowToState.block(), flowing); // Flow down when the block beneath can be filled + } + + private boolean canMaybeFlowThrough(FluidState flowing, FluidState state, + BlockFace face) { + return !isMatchingSource(state) // Don't flow through if matching source + && receivesFlow(face, flowing, state) // Only flow through when the path is not obstructed + && canHoldFluid(state.block()); // Only flow through when the block can hold fluid + } + + protected boolean preventFlowTo(Instance instance, BlockVec flowTo, + FluidState flowing, FluidState newState, FluidState currentState, + BlockFace flowFace) { + return isMatchingSource(currentState) // Don't flow if matching source + || !receivesFlow(flowFace, flowing, currentState) // Only flow when the path is not obstructed + || !canFill(instance, flowTo, currentState.block(), newState); // Only flow when the block can be filled with this state + } + + /** + * Puts the new fluid at the position, executing {@code onBreakingBlock()} before breaking any non-air block. + */ + protected void flow(Instance instance, BlockVec point, FluidState newState, BlockFace direction) { + if (point.y() < MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()).minY()) + return; // Prevent errors when flowing into the void + + Block currentBlock = instance.getBlock(point); + WaterlogHandler handler = MinestomFluids.getWaterlog(currentBlock); + if (handler != null) { + handler.placeFluid(instance, point, newState); + } else { + if (currentBlock.equals(newState.block())) return; // Prevent unnecessary updates + + if (!currentBlock.isAir()) { + newState = onBreakingBlock(instance, point, direction, currentBlock, newState); + if (newState == null) { + // Event has been cancelled + return; + } + } + + instance.placeBlock(new BlockHandler.Placement(newState.block(), instance, point)); + } + } + + private boolean isMatchingSource(FluidState state) { + return state.fluid() == this && state.isSource(); + } + + protected abstract boolean isInfinite(); + + protected abstract int getLevelDecreasePerBlock(Instance instance); + + protected abstract int getHoleRadius(Instance instance); + + public int getNextSpreadDelay(Instance instance, BlockVec point, FluidState state, FluidState newState) { + return getNextTickDelay(instance, point); + } + + /** + * @return the FluidState that should be placed on the broken block position. + */ + protected abstract @Nullable FluidState onBreakingBlock(Instance instance, BlockVec point, BlockFace direction, + Block block, FluidState newState); + + private static boolean isSameFluidAbove(FluidState state, Instance instance, Point point) { + return state.sameFluid(instance.getBlock(point.add(0, 1, 0))); + } + + @Override + public double getHeight(FluidState state, Instance instance, BlockVec point) { + return isSameFluidAbove(state, instance, point) ? 1 : getHeight(state); + } + + @Override + public double getHeight(FluidState state) { + return state.getLevel() / 9.0; + } +} \ No newline at end of file diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/common/Fluid.java b/blocks/src/main/java/net/minestom/vanilla/fluids/common/Fluid.java new file mode 100644 index 00000000..e75091c9 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/common/Fluid.java @@ -0,0 +1,46 @@ +package net.minestom.vanilla.fluids.common; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public abstract class Fluid { + protected final FluidState defaultState; + private final ItemStack bucket; + + public Fluid(Block block, Material bucket) { + this.defaultState = new FluidState(block, this); + this.bucket = ItemStack.of(bucket); + } + + public FluidState getDefaultState() { + return defaultState; + } + + public ItemStack getBucket() { + return bucket; + } + + protected abstract boolean canBeReplacedWith(Instance instance, BlockVec point, FluidState currentState, + FluidState newState, BlockFace direction); + + public abstract int getNextTickDelay(Instance instance, BlockVec point); + + public void onTick(Instance instance, BlockVec point, FluidState state) {} + + protected boolean isEmpty() { + return false; + } + + protected abstract double getBlastResistance(); + + public abstract double getHeight(FluidState state, Instance instance, BlockVec point); + public abstract double getHeight(FluidState state); +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/common/FluidState.java b/blocks/src/main/java/net/minestom/vanilla/fluids/common/FluidState.java new file mode 100644 index 00000000..09710917 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/common/FluidState.java @@ -0,0 +1,106 @@ +package net.minestom.vanilla.fluids.common; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.vanilla.fluids.MinestomFluids; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public record FluidState(Block block, Fluid fluid) { + public static FluidState of(Block block) { + return new FluidState(block, MinestomFluids.get(block)); + } + + public boolean isSource() { + return isSource(block); + } + + public int getLevel() { + return getLevel(block); + } + + public boolean isFalling() { + return isFalling(block); + } + + public boolean canBeWaterLogged() { + return canBeWaterlogged(block); + } + + public boolean isWaterlogged() { + return isWaterlogged(block); + } + + public FluidState setWaterlogged(boolean waterlogged) { + return of(setWaterlogged(block, waterlogged)); + } + + public boolean isEmpty() { + return fluid.isEmpty(); + } + + public boolean isWater() { + return fluid == MinestomFluids.WATER; + } + + public boolean isLava() { + return fluid == MinestomFluids.LAVA; + } + + public boolean sameFluid(FluidState other) { + return fluid == other.fluid; + } + + public boolean sameFluid(Block other) { + return fluid == MinestomFluids.get(other); + } + + public double getHeight(Instance instance, BlockVec point) { + return fluid.getHeight(this, instance, point); + } + + public FluidState asFlowing(int level, boolean falling) { + return new FluidState(block.withProperty("level", String.valueOf((falling ? 8 : 0) + (level == 0 ? 0 : 8 - level))), fluid); + } + + public FluidState asSource(boolean falling) { + return new FluidState(block.withProperty("level", falling ? "8" : "0"), fluid); + } + + public static boolean isSource(Block block) { + if (isWaterlogged(block)) return true; + String levelStr = block.getProperty("level"); + return levelStr != null && Integer.parseInt(levelStr) == 0; + } + + public static int getLevel(Block block) { + if (isWaterlogged(block)) return 8; + String levelStr = block.getProperty("level"); + if (levelStr == null) return 0; + int level = Integer.parseInt(levelStr); + if (level >= 8) return 8; // Falling water + return 8 - level; + } + + public static boolean isFalling(Block block) { + String levelStr = block.getProperty("level"); + if (levelStr == null) return false; + return Integer.parseInt(levelStr) >= 8; + } + + public static boolean canBeWaterlogged(Block block) { + return block.properties().containsKey("waterlogged"); + } + + public static boolean isWaterlogged(Block block) { + String waterlogged = block.getProperty("waterlogged"); + return waterlogged != null && waterlogged.equals("true"); + } + + public static Block setWaterlogged(Block block, boolean waterlogged) { + return block.withProperty("waterlogged", waterlogged ? "true" : "false"); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/common/WaterlogHandler.java b/blocks/src/main/java/net/minestom/vanilla/fluids/common/WaterlogHandler.java new file mode 100644 index 00000000..8f2fe6d5 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/common/WaterlogHandler.java @@ -0,0 +1,34 @@ +package net.minestom.vanilla.fluids.common; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.vanilla.fluids.MinestomFluids; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public interface WaterlogHandler { + WaterlogHandler DEFAULT = new WaterlogHandler() {}; + + default boolean canPlaceFluid(Instance instance, BlockVec point, Block block, FluidState state) { + return state.isWater() && state.isSource(); + } + + default boolean canRemoveFluid(Instance instance, BlockVec point, FluidState state) { + return true; + } + + default boolean placeFluid(Instance instance, BlockVec point, FluidState state) { + Block currentBlock = instance.getBlock(point); + if (!canPlaceFluid(instance, point, currentBlock, state)) return false; + if (state.isWaterlogged()) return false; + + // The placed state (waterlogged block) is different from the original fluid state (probably just water) + FluidState placedState = FluidState.of(currentBlock).setWaterlogged(true); + instance.setBlock(point, placedState.block()); + MinestomFluids.scheduleTick(instance, point, placedState); + return true; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/event/FluidBlockBreakEvent.java b/blocks/src/main/java/net/minestom/vanilla/fluids/event/FluidBlockBreakEvent.java new file mode 100644 index 00000000..033e6fe2 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/event/FluidBlockBreakEvent.java @@ -0,0 +1,81 @@ +package net.minestom.vanilla.fluids.event; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.trait.BlockEvent; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.InstanceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.vanilla.fluids.common.FluidState; +import org.jetbrains.annotations.NotNull; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class FluidBlockBreakEvent implements InstanceEvent, BlockEvent, CancellableEvent { + private final Instance instance; + private final BlockVec blockPosition; + private final BlockFace direction; + private final Block block; + + private FluidState newState; + private boolean cancelled; + + public FluidBlockBreakEvent(@NotNull Instance instance, @NotNull BlockVec blockPosition, + @NotNull BlockFace direction, @NotNull Block block, @NotNull FluidState newState) { + this.instance = instance; + this.blockPosition = blockPosition; + this.direction = direction; + this.block = block; + this.newState = newState; + } + + @Override + public @NotNull Instance getInstance() { + return instance; + } + + public @NotNull BlockVec getBlockPosition() { + return blockPosition; + } + + /** + * Returns the direction in which the fluid has flown. + * @return the direction as a BlockFace + */ + public @NotNull BlockFace getDirection() { + return direction; + } + + @Override + public @NotNull Block getBlock() { + return block; + } + + /** + * @return the FluidState which will replace the block + */ + public FluidState getNewState() { + return newState; + } + + /** + * Sets the FluidState which will replace the block. + * @param newState the new FluidState + */ + public void setNewState(FluidState newState) { + this.newState = newState; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/event/LavaSolidifyEvent.java b/blocks/src/main/java/net/minestom/vanilla/fluids/event/LavaSolidifyEvent.java new file mode 100644 index 00000000..d8a326dd --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/event/LavaSolidifyEvent.java @@ -0,0 +1,66 @@ +package net.minestom.vanilla.fluids.event; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.InstanceEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class LavaSolidifyEvent implements InstanceEvent, CancellableEvent { + private final Instance instance; + private final BlockVec blockPosition; + private final BlockFace direction; + + private Block resultingBlock; + private boolean cancelled; + + public LavaSolidifyEvent(@NotNull Instance instance, @NotNull BlockVec blockPosition, + @NotNull BlockFace direction, @NotNull Block resultingBlock) { + this.instance = instance; + this.blockPosition = blockPosition; + this.direction = direction; + this.resultingBlock = resultingBlock; + } + + @Override + public @NotNull Instance getInstance() { + return instance; + } + + public @NotNull BlockVec getBlockPosition() { + return blockPosition; + } + + /** + * Returns the direction in which the fluid has flown. + * This could be the lava fluid itself (if lava on top of water) or the other fluid (if water on top of or next to lava). + * @return the direction as a BlockFace + */ + public @NotNull BlockFace getDirection() { + return direction; + } + + public @NotNull Block getResultingBlock() { + return resultingBlock; + } + + public void setResultingBlock(@NotNull Block resultingBlock) { + this.resultingBlock = resultingBlock; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/impl/EmptyFluid.java b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/EmptyFluid.java new file mode 100644 index 00000000..47e428c1 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/EmptyFluid.java @@ -0,0 +1,51 @@ +package net.minestom.vanilla.fluids.impl; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.item.Material; +import net.minestom.vanilla.fluids.common.Fluid; +import net.minestom.vanilla.fluids.common.FluidState; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class EmptyFluid extends Fluid { + + public EmptyFluid() { + super(Block.AIR, Material.BUCKET); + } + + @Override + protected boolean canBeReplacedWith(Instance instance, BlockVec point, FluidState currentState, + FluidState newState, BlockFace direction) { + return true; + } + + @Override + public int getNextTickDelay(Instance instance, BlockVec point) { + return -1; + } + + @Override + protected boolean isEmpty() { + return true; + } + + @Override + protected double getBlastResistance() { + return 0; + } + + @Override + public double getHeight(FluidState state, Instance instance, BlockVec point) { + return 0; + } + + @Override + public double getHeight(FluidState state) { + return 0; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/impl/LavaFluid.java b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/LavaFluid.java new file mode 100644 index 00000000..fe51d8f2 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/LavaFluid.java @@ -0,0 +1,115 @@ +package net.minestom.vanilla.fluids.impl; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.item.Material; +import net.minestom.server.network.packet.server.play.WorldEventPacket; +import net.minestom.server.utils.PacketSendingUtils; +import net.minestom.server.world.DimensionType; +import net.minestom.server.worldevent.WorldEvent; +import net.minestom.vanilla.fluids.common.FlowableFluid; +import net.minestom.vanilla.fluids.common.FluidState; +import net.minestom.vanilla.fluids.event.FluidBlockBreakEvent; +import net.minestom.vanilla.fluids.event.LavaSolidifyEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class LavaFluid extends FlowableFluid { + public static final float MIN_REPLACE_HEIGHT = 0.44444445F; + + public LavaFluid() { + super(Block.LAVA, Material.LAVA_BUCKET); + } + + @Override + protected boolean isInfinite() { + return false; // TODO customize + } + + @Override + protected int getLevelDecreasePerBlock(Instance instance) { + DimensionType dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()); + return (dimensionType != null && dimensionType.ultrawarm()) ? 1 : 2; + } + + @Override + protected int getHoleRadius(Instance instance) { + DimensionType dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()); + return (dimensionType != null && dimensionType.ultrawarm()) ? 4 : 2; + } + + @Override + protected @Nullable FluidState onBreakingBlock(Instance instance, BlockVec point, + BlockFace direction, Block block, FluidState newState) { + FluidBlockBreakEvent event = new FluidBlockBreakEvent(instance, point, direction, block, newState); + EventDispatcher.call(event); + if (event.isCancelled()) return null; + fizz(instance, point); + return event.getNewState(); + } + + @Override + protected boolean canBeReplacedWith(Instance instance, BlockVec point, FluidState currentState, + FluidState newState, BlockFace direction) { + return currentState.getHeight(instance, point) >= MIN_REPLACE_HEIGHT && newState.isWater(); + } + + @Override + public int getNextTickDelay(Instance instance, BlockVec point) { + DimensionType dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType()); + return (dimensionType != null && dimensionType.ultrawarm()) ? 10 : 30; + } + + @Override + public int getNextSpreadDelay(Instance instance, BlockVec point, FluidState state, FluidState newState) { + int tickDelay = getNextTickDelay(instance, point); + if (!state.isEmpty() && !newState.isEmpty() && !state.isFalling() && !newState.isFalling()) { + double currentHeight = state.getHeight(instance, point); + double newHeight = newState.getHeight(instance, point); + + if (newHeight > currentHeight && ThreadLocalRandom.current().nextInt(4) != 0) + tickDelay *= 4; + } + + return tickDelay; + } + + @Override + protected void flow(Instance instance, BlockVec point, FluidState newState, BlockFace direction) { + if (direction == BlockFace.BOTTOM) { + FluidState currentState = FluidState.of(instance.getBlock(point)); + if (currentState.isWater() && newState.isLava()) { + LavaSolidifyEvent event = new LavaSolidifyEvent(instance, point, direction, Block.STONE); + EventDispatcher.call(event); + if (event.isCancelled()) return; + + instance.setBlock(point, event.getResultingBlock()); + fizz(instance, point); + return; + } + } + + super.flow(instance, point, newState, direction); + } + + @Override + protected double getBlastResistance() { + return 100.0; + } + + public static void fizz(Instance instance, BlockVec point) { + PacketSendingUtils.sendGroupedPacket( + instance.getChunkAt(point).getViewers(), + new WorldEventPacket(WorldEvent.LAVA_FIZZ.id(), point, 0, false) + ); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/impl/WaterFluid.java b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/WaterFluid.java new file mode 100644 index 00000000..b5401bdc --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/impl/WaterFluid.java @@ -0,0 +1,61 @@ +package net.minestom.vanilla.fluids.impl; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.server.item.Material; +import net.minestom.vanilla.fluids.common.FlowableFluid; +import net.minestom.vanilla.fluids.common.FluidState; +import net.minestom.vanilla.fluids.event.FluidBlockBreakEvent; +import org.jetbrains.annotations.Nullable; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class WaterFluid extends FlowableFluid { + public WaterFluid() { + super(Block.WATER, Material.WATER_BUCKET); + } + + @Override + protected boolean isInfinite() { + return true; + } + + @Override + protected @Nullable FluidState onBreakingBlock(Instance instance, BlockVec point, + BlockFace direction, Block block, FluidState newState) { + FluidBlockBreakEvent event = new FluidBlockBreakEvent(instance, point, direction, block, newState); + EventDispatcher.call(event); + return event.isCancelled() ? null : event.getNewState(); + } + + @Override + protected int getHoleRadius(Instance instance) { + return 4; + } + + @Override + public int getLevelDecreasePerBlock(Instance instance) { + return 1; + } + + @Override + public int getNextTickDelay(Instance instance, BlockVec point) { + return 5; + } + + @Override + protected boolean canBeReplacedWith(Instance instance, BlockVec point, FluidState currentState, + FluidState newState, BlockFace direction) { + return direction == BlockFace.BOTTOM && !newState.isWater(); + } + + @Override + protected double getBlastResistance() { + return 100; + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/placement/FluidPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/fluids/placement/FluidPlacementRule.java new file mode 100644 index 00000000..5d2ac040 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/placement/FluidPlacementRule.java @@ -0,0 +1,47 @@ +package net.minestom.vanilla.fluids.placement; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.rule.BlockPlacementRule; +import net.minestom.vanilla.fluids.MinestomFluids; +import net.minestom.vanilla.fluids.common.FluidState; +import org.jetbrains.annotations.NotNull; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class FluidPlacementRule extends BlockPlacementRule { + public FluidPlacementRule(@NotNull Block block) { + super(block); + } + + @Override + public @NotNull Block blockUpdate(@NotNull UpdateState updateState) { + String waterlogged = updateState.currentBlock().properties().get("waterlogged"); + if (waterlogged == null || waterlogged.equals("true")) { + Instance instance = (Instance) updateState.instance(); + BlockVec blockVec = new BlockVec(updateState.blockPosition()); + MinestomFluids.scheduleTick( + instance, blockVec, + FluidState.of(updateState.currentBlock()) + ); + } + return super.blockUpdate(updateState); + } + + @Override + public @NotNull Block blockPlace(@NotNull PlacementState placementState) { + String waterlogged = placementState.block().properties().get("waterlogged"); + if (waterlogged == null || waterlogged.equals("true")) { + Instance instance = (Instance) placementState.instance(); + BlockVec blockVec = new BlockVec(placementState.placePosition()); + MinestomFluids.scheduleTick( + instance, blockVec, + FluidState.of(placementState.block()) + ); + } + return placementState.block(); + } +} diff --git a/blocks/src/main/java/net/minestom/vanilla/fluids/placement/LavaPlacementRule.java b/blocks/src/main/java/net/minestom/vanilla/fluids/placement/LavaPlacementRule.java new file mode 100644 index 00000000..fa5ac932 --- /dev/null +++ b/blocks/src/main/java/net/minestom/vanilla/fluids/placement/LavaPlacementRule.java @@ -0,0 +1,82 @@ +package net.minestom.vanilla.fluids.placement; + +import net.minestom.server.coordinate.BlockVec; +import net.minestom.server.event.EventDispatcher; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockFace; +import net.minestom.vanilla.fluids.common.FluidState; +import net.minestom.vanilla.fluids.event.LavaSolidifyEvent; +import net.minestom.vanilla.fluids.impl.LavaFluid; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * MinestomFluids implementation adapted from https://github.com/TogAr2/MinestomFluids + * Original author: TogAr2 + */ +public class LavaPlacementRule extends FluidPlacementRule { + public LavaPlacementRule(@NotNull Block block) { + super(block); + } + + @Override + public @NotNull Block blockUpdate(@NotNull UpdateState updateState) { + Instance instance = (Instance) updateState.instance(); + BlockVec blockVec = new BlockVec(updateState.blockPosition()); + + FluidState state = FluidState.of(updateState.currentBlock()); + Block interaction = handleInteraction(instance, blockVec, state); + if (interaction != null) return interaction; + + return super.blockUpdate(updateState); + } + + @Override + public @NotNull Block blockPlace(@NotNull PlacementState placementState) { + Instance instance = (Instance) placementState.instance(); + BlockVec blockVec = new BlockVec(placementState.placePosition()); + + FluidState state = FluidState.of(placementState.block()); + Block interaction = handleInteraction(instance, blockVec, state); + if (interaction != null) return interaction; + + return super.blockPlace(placementState); + } + + private static final BlockFace[] FLOW_DIRECTIONS = new BlockFace[] { + BlockFace.BOTTOM, BlockFace.SOUTH, BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST + }; + + /** + * Handles interaction with other fluids. + * @return the block, if any, that should replace the fluid + */ + private @Nullable Block handleInteraction(Instance instance, BlockVec point, FluidState state) { + boolean soil = instance.getBlock(point.sub(0, 1, 0)).compare(Block.SOUL_SOIL); + + for (BlockFace direction : FLOW_DIRECTIONS) { + FluidState directionState = FluidState.of(instance.getBlock(point.relative(direction.getOppositeFace()))); + if (directionState.isWater()) { + Block block = state.isSource() ? Block.OBSIDIAN : Block.COBBLESTONE; + LavaSolidifyEvent event = new LavaSolidifyEvent(instance, point, direction.getOppositeFace(), block); + EventDispatcher.call(event); + if (event.isCancelled()) continue; + + LavaFluid.fizz(instance, point); + return event.getResultingBlock(); + } + + if (soil && directionState.block().compare(Block.BLUE_ICE)) { + LavaSolidifyEvent event = new LavaSolidifyEvent(instance, point, direction.getOppositeFace(), Block.BASALT); + EventDispatcher.call(event); + if (event.isCancelled()) continue; + + LavaFluid.fizz(instance, point); + return event.getResultingBlock(); + } + } + + return null; + } +} diff --git a/fluid-simulation/build.gradle.kts b/fluid-simulation/build.gradle.kts deleted file mode 100644 index d42a4c1f..00000000 --- a/fluid-simulation/build.gradle.kts +++ /dev/null @@ -1,4 +0,0 @@ -dependencies { - compileOnly(project(":core")) - compileOnly(project(":block-update-system")) -} \ No newline at end of file diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/EmptyFluid.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/EmptyFluid.java deleted file mode 100644 index c3e9334d..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/EmptyFluid.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Material; -import net.minestom.server.utils.Direction; - -public class EmptyFluid extends Fluid { - - public EmptyFluid() { - super(Block.AIR, Material.BUCKET); - } - - @Override - protected boolean canBeReplacedWith(Instance instance, Point point, Fluid other, Direction direction) { - return true; - } - - @Override - public int getNextTickDelay(Instance instance, Point point, Block block) { - return -1; - } - - @Override - protected boolean isEmpty() { - return true; - } - - @Override - protected double getBlastResistance() { - return 0; - } - - @Override - public double getHeight(Block block, Instance instance, Point point) { - return 0; - } - - @Override - public double getHeight(Block block) { - return 0; - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/FlowableFluid.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/FlowableFluid.java deleted file mode 100644 index 2769093c..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/FlowableFluid.java +++ /dev/null @@ -1,338 +0,0 @@ -package io.github.togar2.fluids; - -import it.unimi.dsi.fastutil.shorts.Short2BooleanMap; -import it.unimi.dsi.fastutil.shorts.Short2BooleanOpenHashMap; -import net.minestom.server.MinecraftServer; -import net.minestom.server.coordinate.Point; -import net.minestom.server.gamedata.tags.Tag; -import net.minestom.server.gamedata.tags.TagManager; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Material; -import net.minestom.server.utils.Direction; - -import java.util.EnumMap; -import java.util.Map; -import java.util.Objects; - -public abstract class FlowableFluid extends Fluid { - - public FlowableFluid(Block defaultBlock, Material bucket) { - super(defaultBlock, bucket); - } - - @Override - public void onTick(Instance instance, Point point, Block block) { - if (!isSource(block)) { - Block updated = getUpdatedState(instance, point, block); - if (MinestomFluids.get(updated).isEmpty()) { - block = updated; - instance.setBlock(point, Block.AIR); - } else if (updated != block) { - block = updated; - instance.setBlock(point, updated); - } - } - tryFlow(instance, point, block); - } - - @Override - public int getNextTickDelay(Instance instance, Point point, Block block) { - return getTickRate(instance); - } - - protected void tryFlow(Instance instance, Point point, Block block) { - Fluid fluid = MinestomFluids.get(block); - if (fluid.isEmpty()) return; - - Point down = point.add(0, -1, 0); - Block downBlock = instance.getBlock(down); - Block updatedDownFluid = getUpdatedState(instance, down, downBlock); - if (canFlow(instance, point, block, Direction.DOWN, down, downBlock, updatedDownFluid)) { - flow(instance, down, downBlock, Direction.DOWN, updatedDownFluid); - if (getAdjacentSourceCount(instance, point) >= 3) { - flowSides(instance, point, block); - } - } else if (isSource(block) || !canFlowDown(instance, updatedDownFluid, point, block, down, downBlock)) { - flowSides(instance, point, block); - } - } - - /** - * Flows to the sides whenever possible, or to a hole if found - */ - private void flowSides(Instance instance, Point point, Block block) { - int newLevel = getLevel(block) - getLevelDecreasePerBlock(instance); - if (isFalling(block)) newLevel = 7; - if (newLevel <= 0) return; - - Map map = getSpread(instance, point, block); - for (Map.Entry entry : map.entrySet()) { - Direction direction = entry.getKey(); - Block newBlock = entry.getValue(); - Point offset = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); - Block currentBlock = instance.getBlock(offset); - if (!canFlow(instance, point, block, direction, offset, currentBlock, newBlock)) continue; - flow(instance, offset, currentBlock, direction, newBlock); - } - } - - /** - * Gets the updated state of a source block by taking into account its surrounding blocks. - */ - protected Block getUpdatedState(Instance instance, Point point, Block block) { - int highestLevel = 0; - int stillCount = 0; - for (Direction direction : Direction.HORIZONTAL) { - Point directionPos = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); - Block directionBlock = instance.getBlock(directionPos); - Fluid directionFluid = MinestomFluids.get(directionBlock); - if (directionFluid != this || !receivesFlow(direction, instance, point, block, directionPos, directionBlock)) - continue; - - if (isSource(directionBlock)) { - ++stillCount; - } - highestLevel = Math.max(highestLevel, getLevel(directionBlock)); - } - - if (isInfinite() && stillCount >= 2) { - // If there's 2 or more still fluid blocks around - // and below is still or a solid block, make this block still - Block downBlock = instance.getBlock(point.add(0, -1, 0)); - if (downBlock.isSolid() || isMatchingAndStill(downBlock)) { - return getSource(false); - } - } - - Point above = point.add(0, 1, 0); - Block aboveBlock = instance.getBlock(above); - Fluid aboveFluid = MinestomFluids.get(aboveBlock); - if (!aboveFluid.isEmpty() && aboveFluid == this - && receivesFlow(Direction.UP, instance, point, block, above, aboveBlock)) { - return getFlowing(8, true); - } - - int newLevel = highestLevel - getLevelDecreasePerBlock(instance); - if (newLevel <= 0) return Block.AIR; - return getFlowing(newLevel, false); - } - - private boolean receivesFlow(Direction face, Instance instance, Point point, - Block block, Point fromPoint, Block fromBlock) { - // Vanilla seems to check if the adjacent block shapes cover the same square, but this seems to work as well - // (Might not work with some special blocks) - // If there is anything wrong it is most likely this method :D - - if (block.isLiquid()) { - if (face == Direction.UP) { - if (fromBlock.isLiquid()) return true; - return block.isSolid() || block.isAir(); - //return isSource(block) || getLevel(block) == 8; - } else if (face == Direction.DOWN) { - if (fromBlock.isLiquid()) return true; - return fromBlock.isSolid() || fromBlock.isAir(); - //return isSource(fromBlock) || getLevel(fromBlock) == 8; - } else { - return true; - } - } else { - if (face == Direction.UP) { - return block.isSolid() || block.isAir(); - } else if (face == Direction.DOWN) { - return block.isSolid() || block.isAir(); - } else { - return block.isSolid() || block.isAir(); - } - } - } - - /** - * Creates a unique id based on the relation between point and point2 - */ - private static short getID(Point point, Point point2) { - int i = (int) (point2.x() - point.x()); - int j = (int) (point2.z() - point.z()); - return (short) ((i + 128 & 0xFF) << 8 | j + 128 & 0xFF); - } - - /** - * Returns a map with the directions the water can flow in and the block the water will become in that direction. - * If a hole is found within {@code getHoleRadius()} blocks, the water will only flow in that direction. - * A weight is used to determine which hole is the closest. - */ - protected Map getSpread(Instance instance, Point point, Block block) { - int weight = 1000; - EnumMap map = new EnumMap<>(Direction.class); - Short2BooleanOpenHashMap holeMap = new Short2BooleanOpenHashMap(); - - for (Direction direction : Direction.HORIZONTAL) { - Point directionPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); - Block directionBlock = instance.getBlock(directionPoint); - short id = FlowableFluid.getID(point, directionPoint); - - Block updatedBlock = getUpdatedState(instance, directionPoint, directionBlock); - if (!canFlowThrough(instance, updatedBlock, point, block, direction, directionPoint, directionBlock)) - continue; - - boolean down = holeMap.computeIfAbsent(id, s -> { - Point downPoint = directionPoint.add(0, -1, 0); - return canFlowDown( - instance, getFlowing(getLevel(updatedBlock), false), - directionPoint, directionBlock, downPoint, instance.getBlock(downPoint) - ); - }); - - int newWeight = down ? 0 : getWeight(instance, directionPoint, 1, - direction.opposite(), directionBlock, point, holeMap); - if (newWeight < weight) map.clear(); - - if (newWeight <= weight) { - map.put(direction, updatedBlock); - weight = newWeight; - } - } - return map; - } - - protected int getWeight(Instance instance, Point point, int initialWeight, Direction skipCheck, - Block block, Point originalPoint, Short2BooleanMap short2BooleanMap) { - int weight = 1000; - for (Direction direction : Direction.HORIZONTAL) { - if (direction == skipCheck) continue; - Point directionPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); - Block directionBlock = instance.getBlock(directionPoint); - short id = FlowableFluid.getID(originalPoint, directionPoint); - - if (!canFlowThrough(instance, getFlowing(getLevel(block), false), point, block, - direction, directionPoint, directionBlock)) continue; - - boolean down = short2BooleanMap.computeIfAbsent(id, s -> { - Point downPoint = directionPoint.add(0, -1, 0); - Block downBlock = instance.getBlock(downPoint); - return canFlowDown( - instance, getFlowing(getLevel(block), false), - directionPoint, downBlock, downPoint, downBlock - ); - }); - if (down) return initialWeight; - - if (initialWeight < getHoleRadius(instance)) { - int newWeight = getWeight(instance, directionPoint, initialWeight + 1, - direction.opposite(), directionBlock, originalPoint, short2BooleanMap); - if (newWeight < weight) weight = newWeight; - } - } - return weight; - } - - private int getAdjacentSourceCount(Instance instance, Point point) { - int i = 0; - for (Direction direction : Direction.HORIZONTAL) { - Point currentPoint = point.add(direction.normalX(), direction.normalY(), direction.normalZ()); - Block block = instance.getBlock(currentPoint); - if (!isMatchingAndStill(block)) continue; - ++i; - } - return i; - } - - /** - * Returns whether the fluid can flow through a specific block - */ - private boolean canFill(Instance instance, Point point, Block block, Block flowing) { - //TODO check waterloggable - TagManager tags = MinecraftServer.getTagManager(); - if (block.compare(Block.LADDER) - || block.compare(Block.SUGAR_CANE) - || block.compare(Block.BUBBLE_COLUMN) - || block.compare(Block.NETHER_PORTAL) - || block.compare(Block.END_PORTAL) - || block.compare(Block.END_GATEWAY) - || block.compare(Block.KELP) - || block.compare(Block.KELP_PLANT) - || block.compare(Block.SEAGRASS) - || block.compare(Block.TALL_SEAGRASS) - || block.compare(Block.SEA_PICKLE) - || Objects.requireNonNull(tags.getTag(Tag.BasicType.BLOCKS, "minecraft:signs")).contains(block.key()) - || block.name().contains("door") - || block.name().contains("coral")) { - return false; - } - return !block.isSolid(); - } - - private boolean canFlowDown(Instance instance, Block flowing, Point point, - Block block, Point fromPoint, Block fromBlock) { - if (!this.receivesFlow(Direction.DOWN, instance, point, block, fromPoint, fromBlock)) return false; - if (MinestomFluids.get(fromBlock) == this) return true; - return this.canFill(instance, fromPoint, fromBlock, flowing); - } - - private boolean canFlowThrough(Instance instance, Block flowing, Point point, Block block, - Direction face, Point fromPoint, Block fromBlock) { - return !isMatchingAndStill(fromBlock) - && receivesFlow(face, instance, point, block, fromPoint, fromBlock) - && canFill(instance, fromPoint, fromBlock, flowing); - } - - protected boolean canFlow(Instance instance, Point fluidPoint, Block flowingBlock, - Direction flowDirection, Point flowTo, Block flowToBlock, Block newFlowing) { - return MinestomFluids.get(flowToBlock).canBeReplacedWith(instance, flowTo, MinestomFluids.get(newFlowing), flowDirection) - && receivesFlow(flowDirection, instance, fluidPoint, flowingBlock, flowTo, flowToBlock) - && canFill(instance, flowTo, flowToBlock, newFlowing); - } - - /** - * Sets the position to the new block, executing {@code onBreakingBlock()} before breaking any non-air block. - */ - protected void flow(Instance instance, Point point, Block block, Direction direction, Block newBlock) { - //TODO waterloggable check - boolean cancel = false; - if (!block.isAir()) { - if (!onBreakingBlock(instance, point, block)) - cancel = true; - } - - if (!cancel) instance.setBlock(point, newBlock); - } - - private boolean isMatchingAndStill(Block block) { - return MinestomFluids.get(block) == this && isSource(block); - } - - public Block getFlowing(int level, boolean falling) { - return defaultBlock.withProperty("level", String.valueOf(falling ? 8 : level)); - } - - public Block getSource(boolean falling) { - return falling ? defaultBlock.withProperty("level", "8") : defaultBlock; - } - - protected abstract boolean isInfinite(); - - protected abstract int getLevelDecreasePerBlock(Instance instance); - - protected abstract int getHoleRadius(Instance instance); - - /** - * Returns whether the block can be broken - */ - protected abstract boolean onBreakingBlock(Instance instance, Point point, Block block); - - public abstract int getTickRate(Instance instance); - - private static boolean isFluidAboveEqual(Block block, Instance instance, Point point) { - return MinestomFluids.get(block) == MinestomFluids.get(instance.getBlock(point.add(0, 1, 0))); - } - - @Override - public double getHeight(Block block, Instance instance, Point point) { - return isFluidAboveEqual(block, instance, point) ? 1 : getHeight(block); - } - - @Override - public double getHeight(Block block) { - return getLevel(block) / 9.0; - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/Fluid.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/Fluid.java deleted file mode 100644 index e5da7d9c..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/Fluid.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.utils.Direction; - -public abstract class Fluid { - protected final Block defaultBlock; - private final ItemStack bucket; - - public Fluid(Block block, Material bucket) { - this.defaultBlock = block; - this.bucket = ItemStack.of(bucket); - } - - public Block getDefaultBlock() { - return defaultBlock; - } - - public ItemStack getBucket() { - return bucket; - } - - protected abstract boolean canBeReplacedWith(Instance instance, Point point, - Fluid other, Direction direction); - - public abstract int getNextTickDelay(Instance instance, Point point, Block block); - - public void onTick(Instance instance, Point point, Block block) { - } - - protected boolean isEmpty() { - return false; - } - - protected abstract double getBlastResistance(); - - public abstract double getHeight(Block block, Instance instance, Point point); - - public abstract double getHeight(Block block); - - public static boolean isSource(Block block) { - String levelStr = block.getProperty("level"); - return levelStr == null || Integer.parseInt(levelStr) == 0; - } - - public static int getLevel(Block block) { - String levelStr = block.getProperty("level"); - if (levelStr == null) return 8; - int level = Integer.parseInt(levelStr); - if (level == 0) return 8; // Source block - return level; - } - - public static boolean isFalling(Block block) { - String levelStr = block.getProperty("level"); - if (levelStr == null) return false; - return Integer.parseInt(levelStr) >= 8; - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidPlacementRule.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidPlacementRule.java deleted file mode 100644 index 52d6756f..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidPlacementRule.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.instance.block.rule.BlockPlacementRule; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class FluidPlacementRule extends BlockPlacementRule { - - public FluidPlacementRule(@NotNull Block block) { - super(block); - } - - @Override - public @Nullable Block blockPlace(@NotNull PlacementState placementState) { - if (placementState.instance() instanceof Instance instance) { - MinestomFluids.scheduleTick(instance, placementState.placePosition(), placementState.block()); - } - return placementState.block(); - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidSimulationFeature.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidSimulationFeature.java deleted file mode 100644 index 11170f87..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/FluidSimulationFeature.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.togar2.fluids; - - -import net.kyori.adventure.key.Key; -import net.minestom.vanilla.BlockUpdateFeature; -import net.minestom.vanilla.VanillaReimplementation; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -public class FluidSimulationFeature implements VanillaReimplementation.Feature { - - @Override - public void hook(@NotNull HookContext context) { - // TODO: Use the block-update-system - MinestomFluids.init(context.vri().process()); - } - - @Override - public @NotNull Key key() { - return Key.key("io.github.togar2:fluids"); - } - - @NotNull - public Set> dependencies() { - return Set.of(BlockUpdateFeature.class); - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/MinestomFluids.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/MinestomFluids.java deleted file mode 100644 index 909bd44b..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/MinestomFluids.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.ServerProcess; -import net.minestom.server.coordinate.Point; -import net.minestom.server.event.Event; -import net.minestom.server.event.EventNode; -import net.minestom.server.event.instance.InstanceTickEvent; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class MinestomFluids { - public static final Fluid WATER = new WaterFluid(); - public static final Fluid EMPTY = new EmptyFluid(); - - private static final Map>> UPDATES = new ConcurrentHashMap<>(); - - public static Fluid get(Block block) { - if (block.compare(Block.WATER)) { - return WATER; - } else if (block.compare(Block.LAVA)) { - return EMPTY; - } else { - return EMPTY; - } - } - - public static void tick(InstanceTickEvent event) { - Set currentUpdate = UPDATES.computeIfAbsent(event.getInstance(), i -> new ConcurrentHashMap<>()) - .get(event.getInstance().getWorldAge()); - if (currentUpdate == null) return; - for (Point point : currentUpdate) { - tick(event.getInstance(), point); - } - UPDATES.get(event.getInstance()).remove(event.getInstance().getWorldAge()); - } - - public static void tick(Instance instance, Point point) { - get(instance.getBlock(point)).onTick(instance, point, instance.getBlock(point)); - } - - public static void scheduleTick(Instance instance, Point point, Block block) { - int tickDelay = MinestomFluids.get(block).getNextTickDelay(instance, point, block); - if (tickDelay == -1) return; - - //TODO figure out a way to remove instance from map if unregistered? - long newAge = instance.getWorldAge() + tickDelay; - UPDATES.get(instance).computeIfAbsent(newAge, l -> new HashSet<>()).add(point); - } - - public static void init(ServerProcess process) { - process.block().registerBlockPlacementRule(new FluidPlacementRule(Block.WATER)); - process.eventHandler().addChild(events()); - } - - public static EventNode events() { - EventNode node = EventNode.all("fluid-events"); - node.addListener(InstanceTickEvent.class, MinestomFluids::tick); - return node; - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterBlockBreakEvent.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterBlockBreakEvent.java deleted file mode 100644 index e8d02e99..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterBlockBreakEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.coordinate.BlockVec; -import net.minestom.server.event.trait.BlockEvent; -import net.minestom.server.event.trait.CancellableEvent; -import net.minestom.server.event.trait.InstanceEvent; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import org.jetbrains.annotations.NotNull; - -public class WaterBlockBreakEvent implements InstanceEvent, BlockEvent, CancellableEvent { - private final Instance instance; - private final BlockVec blockPosition; - private final Block block; - - private boolean cancelled; - - public WaterBlockBreakEvent(@NotNull Instance instance, @NotNull BlockVec blockPosition, @NotNull Block block) { - this.instance = instance; - this.blockPosition = blockPosition; - this.block = block; - } - - @Override - public @NotNull Instance getInstance() { - return instance; - } - - public @NotNull BlockVec getBlockPosition() { - return blockPosition; - } - - @Override - public @NotNull Block getBlock() { - return block; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancel) { - this.cancelled = cancel; - } -} diff --git a/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterFluid.java b/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterFluid.java deleted file mode 100644 index 73883cd0..00000000 --- a/fluid-simulation/src/main/java/io/github/togar2/fluids/WaterFluid.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.togar2.fluids; - -import net.minestom.server.coordinate.BlockVec; -import net.minestom.server.coordinate.Point; -import net.minestom.server.instance.Instance; -import net.minestom.server.instance.block.Block; -import net.minestom.server.item.Material; -import net.minestom.server.utils.Direction; - -public class WaterFluid extends FlowableFluid { - - public WaterFluid() { - super(Block.WATER, Material.WATER_BUCKET); - } - - @Override - protected boolean isInfinite() { - return true; - } - - @Override - protected boolean onBreakingBlock(Instance instance, Point point, Block block) { - WaterBlockBreakEvent event = new WaterBlockBreakEvent(instance, new BlockVec(point), block); - return !event.isCancelled(); - } - - @Override - protected int getHoleRadius(Instance instance) { - return 4; - } - - @Override - public int getLevelDecreasePerBlock(Instance instance) { - return 1; - } - - @Override - public int getTickRate(Instance instance) { - return 5; - } - - @Override - protected boolean canBeReplacedWith(Instance instance, Point point, Fluid other, Direction direction) { - return direction == Direction.DOWN && this == other; - } - - @Override - protected double getBlastResistance() { - return 100; - } -} diff --git a/mojang-data/client-1.21.5.jar b/mojang-data/client-1.21.5.jar new file mode 100644 index 00000000..1107a671 Binary files /dev/null and b/mojang-data/client-1.21.5.jar differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 4b503dd6..72a0f32f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,9 +20,9 @@ include("datapack") include("loot-table") pluginManagement { - repositories { - mavenCentral() - maven("https://repo.spongepowered.org/repository/maven-public") - maven("https://repo.spongepowered.org/repository/maven-snapshots") - } -} + repositories { + mavenCentral() + maven("https://repo.spongepowered.org/repository/maven-public") + maven("https://repo.spongepowered.org/repository/maven-snapshots") + } +} \ No newline at end of file diff --git a/survival/build.gradle.kts b/survival/build.gradle.kts index baac46b4..57a276cf 100644 --- a/survival/build.gradle.kts +++ b/survival/build.gradle.kts @@ -3,4 +3,5 @@ dependencies { api(project(":datapack")) api(project(":loot-table")) api(project(":crafting")) + api(project(":blocks")) } \ No newline at end of file diff --git a/survival/src/main/java/net/minestom/vanilla/survival/Survival.java b/survival/src/main/java/net/minestom/vanilla/survival/Survival.java index 99a4a778..46568490 100644 --- a/survival/src/main/java/net/minestom/vanilla/survival/Survival.java +++ b/survival/src/main/java/net/minestom/vanilla/survival/Survival.java @@ -8,6 +8,7 @@ import net.minestom.server.component.DataComponents; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.GameMode; import net.minestom.server.entity.ItemEntity; import net.minestom.server.entity.Player; import net.minestom.server.event.item.ItemDropEvent; @@ -26,8 +27,12 @@ import net.minestom.server.item.enchant.Enchantment; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; +import net.minestom.vanilla.blocks.BlockBehaviorRuleRegistrations; +import net.minestom.vanilla.blocks.BlockPlacementRuleRegistrations; +import net.minestom.vanilla.blocks.PlacedHandlerRegistration; import net.minestom.vanilla.crafting.CraftingFeature; import net.minestom.vanilla.crafting.Recipe; +import net.minestom.vanilla.fluids.MinestomFluids; import net.minestom.vanilla.logging.Logger; import net.minestom.vanilla.loot.LootFeature; import net.minestom.vanilla.loot.LootTable; @@ -93,6 +98,7 @@ public void initialize() { }).addListener(PlayerSpawnEvent.class, event -> { final Player player = event.getPlayer(); + player.setGameMode(GameMode.CREATIVE); if (event.isFirstSpawn()) { this.broadcast(Component.translatable("multiplayer.player.joined", NamedTextColor.YELLOW).arguments(player.getName())); @@ -118,6 +124,12 @@ public void initialize() { Vec velocity = playerPos.direction().mul(6); itemEntity.setVelocity(velocity); }); + + BlockPlacementRuleRegistrations.registerDefault(); + BlockBehaviorRuleRegistrations.registerDefault(); + PlacedHandlerRegistration.registerDefault(); + MinestomFluids.init(); + process.eventHandler().addChild(MinestomFluids.events()); } private void broadcast(@NotNull Component message) { diff --git a/world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunk.java b/world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunk.java index 72b7c84e..625ed32f 100644 --- a/world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunk.java +++ b/world-generation/src/main/java/net/minestom/vanilla/generation/NoiseChunk.java @@ -13,6 +13,18 @@ import java.util.List; import java.util.Map; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file contains code ported from Kotlin to Java, adapted from the Blocks and Stuff project. + * Original source: https://github.com/everbuild-org/blocks-and-stuff + * + * Original authors: ChrisB, AEinNico, CreepyX + * + * Ported from Kotlin to Java and adapted for use in this project with modifications. + */ public class NoiseChunk { public final int cellWidth; public final int cellHeight;