diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java index dad3bed30..1c284b38c 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.block.custom.component; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.block.custom.component.placementfilter.PlacementFilter; import java.util.Map; import java.util.Set; @@ -74,6 +75,15 @@ public interface CustomBlockComponents { */ @NonNull Map materialInstances(); + + /** + * Gets the placement filter component + * Equivalent to "minecraft:material_instances" + * + * @return The placement filter. + */ + PlacementFilter placementFilter(); + /** * Gets the destructible by mining component * Equivalent to "minecraft:destructible_by_mining" @@ -114,6 +124,14 @@ public interface CustomBlockComponents { */ RotationComponent rotation(); + /** + * Gets the unit cube component + * Equivalent to "minecraft:unit_cube" + * + * @return The rotation. + */ + boolean unitCube(); + /** * Gets if the block should place only air * Equivalent to setting a dummy event to run on "minecraft:on_player_placing" @@ -141,6 +159,8 @@ public interface CustomBlockComponents { Builder materialInstance(@NonNull String name, @NonNull MaterialInstance materialInstance); + Builder placementFilter(PlacementFilter placementFilter); + Builder destructibleByMining(Float destructibleByMining); Builder friction(Float friction); @@ -151,6 +171,8 @@ public interface CustomBlockComponents { Builder rotation(RotationComponent rotation); + Builder unitCube(boolean unitCube); + Builder placeAir(boolean placeAir); Builder tags(Set tags); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/Conditions.java b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/Conditions.java new file mode 100644 index 000000000..b12238584 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/Conditions.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser +*/ + +package org.geysermc.geyser.api.block.custom.component.placementfilter; + +import java.util.LinkedHashMap; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/* + * This class is used to store conditions for a placement filter for a custom block. + */ +public record Conditions(@NonNull Set allowedFaces, @NonNull LinkedHashMap blockFilters) { + public enum Face { + DOWN(1), + UP(2), + NORTH(4), + SOUTH(8), + WEST(16), + EAST(32); + + private final byte value; + + Face(int value) { + this.value = (byte) value; + } + + public byte getValue() { + return value; + } + } + public enum BlockFilterType { + BLOCK, + TAG + } +} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/PlacementFilter.java b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/PlacementFilter.java new file mode 100644 index 000000000..14a25adeb --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/placementfilter/PlacementFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.block.custom.component.placementfilter; + +import java.util.List; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/* + * This class is used to store a placement filter for a custom block. + */ +public record PlacementFilter(@NonNull List conditions) { + +} diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java index dc9f6a39c..47ccb4f41 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.api.block.custom.component.BoxComponent; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; import org.geysermc.geyser.api.block.custom.component.MaterialInstance; import org.geysermc.geyser.api.block.custom.component.RotationComponent; +import org.geysermc.geyser.api.block.custom.component.placementfilter.PlacementFilter; import org.jetbrains.annotations.NotNull; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; @@ -49,11 +50,13 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { String displayName; String geometry; Map materialInstances; + PlacementFilter placementFilter; Float destructibleByMining; Float friction; Integer lightEmission; Integer lightDampening; RotationComponent rotation; + boolean unitCube; boolean placeAir; Set tags; @@ -67,11 +70,13 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { } else { this.materialInstances = Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(builder.materialInstances)); } + this.placementFilter = builder.placementFilter; this.destructibleByMining = builder.destructibleByMining; this.friction = builder.friction; this.lightEmission = builder.lightEmission; this.lightDampening = builder.lightDampening; this.rotation = builder.rotation; + this.unitCube = builder.unitCube; this.placeAir = builder.placeAir; if (builder.tags.isEmpty()) { this.tags = Set.of(); @@ -105,6 +110,11 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return materialInstances; } + @Override + public PlacementFilter placementFilter() { + return placementFilter; + } + @Override public Float destructibleByMining() { return destructibleByMining; @@ -130,6 +140,11 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return rotation; } + @Override + public boolean unitCube() { + return unitCube; + } + @Override public boolean placeAir() { return placeAir; @@ -146,11 +161,13 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { protected String displayName; protected String geometry; protected final Object2ObjectMap materialInstances = new Object2ObjectOpenHashMap<>(); + protected PlacementFilter placementFilter; protected Float destructibleByMining; protected Float friction; protected Integer lightEmission; protected Integer lightDampening; protected RotationComponent rotation; + protected boolean unitCube = false; protected boolean placeAir = false; protected final Set tags = new HashSet<>(); @@ -204,6 +221,12 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return this; } + @Override + public Builder placementFilter(PlacementFilter placementFilter) { + this.placementFilter = placementFilter; + return this; + } + @Override public Builder destructibleByMining(Float destructibleByMining) { if (destructibleByMining != null && destructibleByMining < 0) { @@ -255,6 +278,12 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return this; } + @Override + public Builder unitCube(boolean unitCube) { + this.unitCube = unitCube; + return this; + } + @Override public Builder placeAir(boolean placeAir) { this.placeAir = placeAir; diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index f72ef5986..4e1cc23b6 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -37,6 +37,10 @@ import org.geysermc.geyser.api.block.custom.component.BoxComponent; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; import org.geysermc.geyser.api.block.custom.component.MaterialInstance; import org.geysermc.geyser.api.block.custom.component.RotationComponent; +import org.geysermc.geyser.api.block.custom.component.placementfilter.Conditions; +import org.geysermc.geyser.api.block.custom.component.placementfilter.PlacementFilter; +import org.geysermc.geyser.api.block.custom.component.placementfilter.Conditions.BlockFilterType; +import org.geysermc.geyser.api.block.custom.component.placementfilter.Conditions.Face; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; @@ -56,6 +60,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -347,9 +352,11 @@ public class MappingsReader_v1 extends MappingsReader { builder.geometry(node.get("geometry").asText()); } + String displayName = name; if (node.has("display_name")) { - builder.displayName(node.get("display_name").asText()); + displayName = node.get("display_name").asText(); } + builder.displayName(displayName); if (node.has("light_emission")) { builder.lightEmission(node.get("light_emission").asInt()); @@ -373,6 +380,10 @@ public class MappingsReader_v1 extends MappingsReader { builder.rotation(new RotationComponent(rotationX, rotationY, rotationZ)); } + if (node.has("unit_cube")) { + builder.unitCube(node.get("unit_cube").asBoolean()); + } + if (node.has("material_instances")) { JsonNode materialInstances = node.get("material_instances"); if (materialInstances.isObject()) { @@ -387,6 +398,19 @@ public class MappingsReader_v1 extends MappingsReader { } } + if (node.has("placement_filter")) { + JsonNode placementFilter = node.get("placement_filter"); + if (placementFilter.isObject()) { + if (placementFilter.has("conditions")) { + JsonNode conditions = placementFilter.get("conditions"); + if (conditions.isArray()) { + PlacementFilter filter = createPlacementFilterComponent(conditions); + builder.placementFilter(filter); + } + } + } + } + if (node.has("tags")) { JsonNode tags = node.get("tags"); if (tags.isArray()) { @@ -446,6 +470,43 @@ public class MappingsReader_v1 extends MappingsReader { return new MaterialInstance(texture, renderMethod, faceDimming, ambientOcclusion); } + private PlacementFilter createPlacementFilterComponent(JsonNode node) { + List conditions = new ArrayList<>(); + + node.forEach(condition -> { + Set faces = new HashSet<>(); + if (condition.has("allowed_faces")) { + JsonNode allowedFaces = condition.get("allowed_faces"); + if (allowedFaces.isArray()) { + allowedFaces.forEach(face -> { + faces.add(Face.valueOf(face.asText().toUpperCase())); + }); + } + } + + LinkedHashMap blockFilters = new LinkedHashMap<>(); + if (condition.has("block_filter")) { + JsonNode blockFilter = condition.get("block_filter"); + if (blockFilter.isArray()) { + blockFilter.forEach(filter -> { + if (filter.isObject()) { + if (filter.has("tags")) { + JsonNode tags = filter.get("tags"); + blockFilters.put(tags.asText(), BlockFilterType.TAG); + } + } else if (filter.isTextual()) { + blockFilters.put(filter.asText(), BlockFilterType.BLOCK); + } + }); + } + } + + conditions.add(new Conditions(faces, blockFilters)); + }); + + return new PlacementFilter(conditions); + } + private String createCustomBlockPropertyQuery(String state) { String[] conditions = splitStateString(state); String[] queries = new String[conditions.length]; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java index 3ff0ac58e..a59af09d8 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java @@ -15,6 +15,8 @@ import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.component.BoxComponent; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; import org.geysermc.geyser.api.block.custom.component.MaterialInstance; +import org.geysermc.geyser.api.block.custom.component.placementfilter.PlacementFilter; +import org.geysermc.geyser.api.block.custom.component.placementfilter.Conditions.Face; import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty; import org.geysermc.geyser.api.block.custom.property.PropertyType; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent; @@ -176,6 +178,11 @@ public class CustomBlockRegistryPopulator { return NbtMap.EMPTY; } NbtMapBuilder builder = NbtMap.builder(); + if (components.displayName() != null) { + builder.putCompound("minecraft:display_name", NbtMap.builder() + .putString("value", components.displayName()) + .build()); + } if (components.selectionBox() != null) { builder.putCompound("minecraft:selection_box", convertBox(components.selectionBox())); } @@ -203,6 +210,11 @@ public class CustomBlockRegistryPopulator { .putCompound("materials", materialsBuilder.build()) .build()); } + if (components.placementFilter() != null) { + builder.putCompound("minecraft:placement_filter", NbtMap.builder() + .putList("conditions", NbtType.COMPOUND, convertPlacementFilter(components.placementFilter())) + .build()); + } if (components.destructibleByMining() != null) { builder.putCompound("minecraft:destructible_by_mining", NbtMap.builder() .putFloat("value", components.destructibleByMining()) @@ -230,6 +242,9 @@ public class CustomBlockRegistryPopulator { .putFloat("z", components.rotation().z()) .build()); } + if (components.unitCube()) { + builder.putCompound("minecraft:unit_cube", NbtMap.EMPTY); + } if (components.placeAir()) { builder.putCompound("minecraft:on_player_placing", NbtMap.builder() .putString("triggerType", "geyser:place_event") @@ -248,4 +263,29 @@ public class CustomBlockRegistryPopulator { .putList("size", NbtType.FLOAT, boxComponent.sizeX(), boxComponent.sizeY(), boxComponent.sizeZ()) .build(); } + + private static List convertPlacementFilter(PlacementFilter placementFilter) { + List conditions = new ArrayList<>(); + placementFilter.conditions().forEach((condition) -> { + NbtMapBuilder conditionBuilder = NbtMap.builder(); + + byte allowedFaces = 0; + for (Face face : condition.allowedFaces()) { allowedFaces |= face.getValue(); } + conditionBuilder.putByte("allowed_faces", allowedFaces); + + List blockFilters = new ArrayList<>(); + condition.blockFilters().forEach((value, type) -> { + NbtMapBuilder blockFilterBuilder = NbtMap.builder(); + switch (type) { + case BLOCK -> blockFilterBuilder.putString("name", value); + case TAG -> blockFilterBuilder.putString("tags", value).putInt("tags_version", 6); + } + blockFilters.add(blockFilterBuilder.build()); + }); + conditionBuilder.putList("block_filters", NbtType.COMPOUND, blockFilters); + conditions.add(conditionBuilder.build()); + }); + + return conditions; + } }