mirror of https://github.com/GeyserMC/Geyser.git
Merge remote-tracking branch 'upstream/master' into feature/cloud
# Conflicts: # bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java # bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java # bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java # bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java # core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java # core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java # core/src/main/resources/languages # gradle/libs.versions.toml
This commit is contained in:
commit
42ada0bd87
|
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.19.80 - 1.20 and Minecraft Java 1.20.
|
||||
### Currently supporting Minecraft Bedrock 1.20.0 - 1.20.10 and Minecraft Java 1.20/1.20.1.
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.bedrock.camera;
|
||||
|
||||
public enum CameraShake {
|
||||
POSITIONAL,
|
||||
ROTATIONAL;
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class is used to store data for a custom block.
|
||||
*/
|
||||
public interface CustomBlockData {
|
||||
/**
|
||||
* Gets the name of the custom block
|
||||
*
|
||||
* @return The name of the custom block.
|
||||
*/
|
||||
@NonNull String name();
|
||||
|
||||
/**
|
||||
* Gets the identifier of the custom block
|
||||
*
|
||||
* @return The identifier of the custom block.
|
||||
*/
|
||||
@NonNull String identifier();
|
||||
|
||||
/**
|
||||
* Gets if the custom block is included in the creative inventory
|
||||
*
|
||||
* @return If the custom block is included in the creative inventory.
|
||||
*/
|
||||
boolean includedInCreativeInventory();
|
||||
|
||||
/**
|
||||
* Gets the item's creative category, or tab id.
|
||||
*
|
||||
* @return the item's creative category
|
||||
*/
|
||||
@Nullable CreativeCategory creativeCategory();
|
||||
|
||||
/**
|
||||
* Gets the item's creative group.
|
||||
*
|
||||
* @return the item's creative group
|
||||
*/
|
||||
@Nullable String creativeGroup();
|
||||
|
||||
/**
|
||||
* Gets the components of the custom block
|
||||
*
|
||||
* @return The components of the custom block.
|
||||
*/
|
||||
@Nullable CustomBlockComponents components();
|
||||
|
||||
/**
|
||||
* Gets the custom block's map of block property names to CustomBlockProperty
|
||||
* objects
|
||||
*
|
||||
* @return The custom block's map of block property names to CustomBlockProperty objects.
|
||||
*/
|
||||
@NonNull Map<String, CustomBlockProperty<?>> properties();
|
||||
|
||||
/**
|
||||
* Gets the list of the custom block's permutations
|
||||
*
|
||||
* @return The permutations of the custom block.
|
||||
*/
|
||||
@NonNull List<CustomBlockPermutation> permutations();
|
||||
|
||||
/**
|
||||
* Gets the custom block's default block state
|
||||
*
|
||||
* @return The default block state of the custom block.
|
||||
*/
|
||||
@NonNull CustomBlockState defaultBlockState();
|
||||
|
||||
/**
|
||||
* Gets a builder for a custom block state
|
||||
*
|
||||
* @return The builder for a custom block state.
|
||||
*/
|
||||
CustomBlockState.@NonNull Builder blockStateBuilder();
|
||||
|
||||
/**
|
||||
* Create a Builder for CustomBlockData
|
||||
*
|
||||
* @return A CustomBlockData Builder
|
||||
*/
|
||||
static CustomBlockData.Builder builder() {
|
||||
return GeyserApi.api().provider(CustomBlockData.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
Builder name(@NonNull String name);
|
||||
|
||||
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
|
||||
|
||||
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
|
||||
|
||||
Builder creativeGroup(@Nullable String creativeGroup);
|
||||
|
||||
Builder components(@NonNull CustomBlockComponents components);
|
||||
|
||||
Builder booleanProperty(@NonNull String propertyName);
|
||||
|
||||
Builder intProperty(@NonNull String propertyName, List<Integer> values);
|
||||
|
||||
Builder stringProperty(@NonNull String propertyName, List<String> values);
|
||||
|
||||
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
|
||||
|
||||
CustomBlockData build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block permutations, which contain custom
|
||||
* block components mapped to a Molang query that should return true or false
|
||||
*
|
||||
* @param components The components of the block
|
||||
* @param condition The Molang query that should return true or false
|
||||
*/
|
||||
public record CustomBlockPermutation(@NonNull CustomBlockComponents components, @NonNull String condition) {
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block state, which contains CustomBlockData
|
||||
* tied to defined properties and values
|
||||
*/
|
||||
public interface CustomBlockState {
|
||||
/**
|
||||
* Gets the custom block data associated with the state
|
||||
*
|
||||
* @return The custom block data for the state.
|
||||
*/
|
||||
@NonNull CustomBlockData block();
|
||||
|
||||
/**
|
||||
* Gets the name of the state
|
||||
*
|
||||
* @return The name of the state.
|
||||
*/
|
||||
@NonNull String name();
|
||||
|
||||
/**
|
||||
* Gets the given property for the state
|
||||
*
|
||||
* @param propertyName the property name
|
||||
* @return the boolean, int, or string property.
|
||||
*/
|
||||
@NonNull <T> T property(@NonNull String propertyName);
|
||||
|
||||
/**
|
||||
* Gets a map of the properties for the state
|
||||
*
|
||||
* @return The properties for the state.
|
||||
*/
|
||||
@NonNull Map<String, Object> properties();
|
||||
|
||||
interface Builder {
|
||||
Builder booleanProperty(@NonNull String propertyName, boolean value);
|
||||
|
||||
Builder intProperty(@NonNull String propertyName, int value);
|
||||
|
||||
Builder stringProperty(@NonNull String propertyName, @NonNull String value);
|
||||
|
||||
CustomBlockState build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a completely custom block that is not based on an existing vanilla Minecraft block.
|
||||
*/
|
||||
public interface NonVanillaCustomBlockData extends CustomBlockData {
|
||||
/**
|
||||
* Gets the namespace of the custom block
|
||||
*
|
||||
* @return The namespace of the custom block.
|
||||
*/
|
||||
@NonNull String namespace();
|
||||
|
||||
|
||||
/**
|
||||
* Create a Builder for NonVanillaCustomBlockData
|
||||
*
|
||||
* @return A NonVanillaCustomBlockData Builder
|
||||
*/
|
||||
static NonVanillaCustomBlockData.Builder builder() {
|
||||
return GeyserApi.api().provider(NonVanillaCustomBlockData.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder extends CustomBlockData.Builder {
|
||||
|
||||
Builder namespace(@NonNull String namespace);
|
||||
|
||||
@Override
|
||||
Builder name(@NonNull String name);
|
||||
|
||||
@Override
|
||||
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
|
||||
|
||||
@Override
|
||||
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
|
||||
|
||||
@Override
|
||||
Builder creativeGroup(@Nullable String creativeGroup);
|
||||
|
||||
@Override
|
||||
Builder components(@NonNull CustomBlockComponents components);
|
||||
|
||||
@Override
|
||||
Builder booleanProperty(@NonNull String propertyName);
|
||||
|
||||
@Override
|
||||
Builder intProperty(@NonNull String propertyName, List<Integer> values);
|
||||
|
||||
@Override
|
||||
Builder stringProperty(@NonNull String propertyName, List<String> values);
|
||||
|
||||
@Override
|
||||
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
|
||||
|
||||
@Override
|
||||
NonVanillaCustomBlockData build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This class is used to store a box component for the selection and
|
||||
* collision boxes of a custom block.
|
||||
*
|
||||
* @param originX The origin X of the box
|
||||
* @param originY The origin Y of the box
|
||||
* @param originZ The origin Z of the box
|
||||
* @param sizeX The size X of the box
|
||||
* @param sizeY The size Y of the box
|
||||
* @param sizeZ The size Z of the box
|
||||
*/
|
||||
public record BoxComponent(float originX, float originY, float originZ, float sizeX, float sizeY, float sizeZ) {
|
||||
private static final BoxComponent FULL_BOX = new BoxComponent(-8, 0, -8, 16, 16, 16);
|
||||
private static final BoxComponent EMPTY_BOX = new BoxComponent(0, 0, 0, 0, 0, 0);
|
||||
|
||||
/**
|
||||
* Gets a full box component
|
||||
*
|
||||
* @return A full box component
|
||||
*/
|
||||
public static BoxComponent fullBox() {
|
||||
return FULL_BOX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an empty box component
|
||||
*
|
||||
* @return An empty box component
|
||||
*/
|
||||
public static BoxComponent emptyBox() {
|
||||
return EMPTY_BOX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the box component is empty
|
||||
*
|
||||
* @return If the box component is empty.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return sizeX == 0 && sizeY == 0 && sizeZ == 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class is used to store components for a custom block or custom block permutation.
|
||||
*/
|
||||
public interface CustomBlockComponents {
|
||||
|
||||
/**
|
||||
* Gets the selection box component
|
||||
* Equivalent to "minecraft:selection_box"
|
||||
*
|
||||
* @return The selection box.
|
||||
*/
|
||||
@Nullable BoxComponent selectionBox();
|
||||
|
||||
/**
|
||||
* Gets the collision box component
|
||||
* Equivalent to "minecraft:collision_box"
|
||||
* @return The collision box.
|
||||
*/
|
||||
@Nullable BoxComponent collisionBox();
|
||||
|
||||
/**
|
||||
* Gets the display name component
|
||||
* Equivalent to "minecraft:display_name"
|
||||
*
|
||||
* @return The display name.
|
||||
*/
|
||||
@Nullable String displayName();
|
||||
|
||||
/**
|
||||
* Gets the geometry component
|
||||
* Equivalent to "minecraft:geometry"
|
||||
*
|
||||
* @return The geometry.
|
||||
*/
|
||||
@Nullable GeometryComponent geometry();
|
||||
|
||||
/**
|
||||
* Gets the material instances component
|
||||
* Equivalent to "minecraft:material_instances"
|
||||
*
|
||||
* @return The material instances.
|
||||
*/
|
||||
@NonNull Map<String, MaterialInstance> materialInstances();
|
||||
|
||||
/**
|
||||
* Gets the placement filter component
|
||||
* Equivalent to "minecraft:placement_filter"
|
||||
*
|
||||
* @return The placement filter.
|
||||
*/
|
||||
@Nullable List<PlacementConditions> placementFilter();
|
||||
|
||||
/**
|
||||
* Gets the destructible by mining component
|
||||
* Equivalent to "minecraft:destructible_by_mining"
|
||||
*
|
||||
* @return The destructible by mining value.
|
||||
*/
|
||||
@Nullable Float destructibleByMining();
|
||||
|
||||
/**
|
||||
* Gets the friction component
|
||||
* Equivalent to "minecraft:friction"
|
||||
*
|
||||
* @return The friction value.
|
||||
*/
|
||||
@Nullable Float friction();
|
||||
|
||||
/**
|
||||
* Gets the light emission component
|
||||
* Equivalent to "minecraft:light_emission"
|
||||
*
|
||||
* @return The light emission value.
|
||||
*/
|
||||
@Nullable Integer lightEmission();
|
||||
|
||||
/**
|
||||
* Gets the light dampening component
|
||||
* Equivalent to "minecraft:light_dampening"
|
||||
*
|
||||
* @return The light dampening value.
|
||||
*/
|
||||
@Nullable Integer lightDampening();
|
||||
|
||||
/**
|
||||
* Gets the transformation component
|
||||
* Equivalent to "minecraft:transformation"
|
||||
*
|
||||
* @return The transformation.
|
||||
*/
|
||||
@Nullable TransformationComponent transformation();
|
||||
|
||||
/**
|
||||
* 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"
|
||||
*
|
||||
* @return If the block should place only air.
|
||||
*/
|
||||
boolean placeAir();
|
||||
|
||||
/**
|
||||
* Gets the set of tags
|
||||
* Equivalent to "tag:some_tag"
|
||||
*
|
||||
* @return The set of tags.
|
||||
*/
|
||||
@NonNull Set<String> tags();
|
||||
|
||||
/**
|
||||
* Create a Builder for CustomBlockComponents
|
||||
*
|
||||
* @return A CustomBlockComponents Builder
|
||||
*/
|
||||
static CustomBlockComponents.Builder builder() {
|
||||
return GeyserApi.api().provider(CustomBlockComponents.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
Builder selectionBox(BoxComponent selectionBox);
|
||||
|
||||
Builder collisionBox(BoxComponent collisionBox);
|
||||
|
||||
Builder displayName(String displayName);
|
||||
|
||||
Builder geometry(GeometryComponent geometry);
|
||||
|
||||
Builder materialInstance(@NonNull String name, @NonNull MaterialInstance materialInstance);
|
||||
|
||||
Builder placementFilter(List<PlacementConditions> placementConditions);
|
||||
|
||||
Builder destructibleByMining(Float destructibleByMining);
|
||||
|
||||
Builder friction(Float friction);
|
||||
|
||||
Builder lightEmission(Integer lightEmission);
|
||||
|
||||
Builder lightDampening(Integer lightDampening);
|
||||
|
||||
Builder transformation(TransformationComponent transformation);
|
||||
|
||||
Builder unitCube(boolean unitCube);
|
||||
|
||||
Builder placeAir(boolean placeAir);
|
||||
|
||||
Builder tags(Set<String> tags);
|
||||
|
||||
CustomBlockComponents build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class is used to store data for a geometry component.
|
||||
*/
|
||||
public interface GeometryComponent {
|
||||
|
||||
/**
|
||||
* Gets the identifier of the geometry
|
||||
*
|
||||
* @return The identifier of the geometry.
|
||||
*/
|
||||
@NonNull String identifier();
|
||||
|
||||
/**
|
||||
* Gets the bone visibility of the geometry
|
||||
*
|
||||
* @return The bone visibility of the geometry.
|
||||
*/
|
||||
@Nullable Map<String, String> boneVisibility();
|
||||
|
||||
/**
|
||||
* Creates a builder for GeometryComponent
|
||||
*
|
||||
* @return a builder for GeometryComponent.
|
||||
*/
|
||||
static GeometryComponent.Builder builder() {
|
||||
return GeyserApi.api().provider(GeometryComponent.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
Builder identifier(@NonNull String identifier);
|
||||
|
||||
Builder boneVisibility(@Nullable Map<String, String> boneVisibility);
|
||||
|
||||
GeometryComponent build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
/**
|
||||
* This class is used to store data for a material instance.
|
||||
*/
|
||||
public interface MaterialInstance {
|
||||
/**
|
||||
* Gets the texture of the block
|
||||
*
|
||||
* @return The texture of the block.
|
||||
*/
|
||||
@NonNull String texture();
|
||||
|
||||
/**
|
||||
* Gets the render method of the block
|
||||
*
|
||||
* @return The render method of the block.
|
||||
*/
|
||||
@Nullable String renderMethod();
|
||||
|
||||
/**
|
||||
* Gets if the block should be dimmed on certain faces
|
||||
*
|
||||
* @return If the block should be dimmed on certain faces.
|
||||
*/
|
||||
@Nullable boolean faceDimming();
|
||||
|
||||
/**
|
||||
* Gets if the block should have ambient occlusion
|
||||
*
|
||||
* @return If the block should have ambient occlusion.
|
||||
*/
|
||||
@Nullable boolean ambientOcclusion();
|
||||
|
||||
/**
|
||||
* Creates a builder for MaterialInstance.
|
||||
*
|
||||
* @return a builder for MaterialInstance
|
||||
*/
|
||||
static MaterialInstance.Builder builder() {
|
||||
return GeyserApi.api().provider(MaterialInstance.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
Builder texture(@NonNull String texture);
|
||||
|
||||
Builder renderMethod(@Nullable String renderMethod);
|
||||
|
||||
Builder faceDimming(@Nullable boolean faceDimming);
|
||||
|
||||
Builder ambientOcclusion(@Nullable boolean ambientOcclusion);
|
||||
|
||||
MaterialInstance build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
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.
|
||||
*
|
||||
* @param allowedFaces The faces that the block can be placed on
|
||||
* @param blockFilters The block filters that control what blocks the block can be placed on
|
||||
*/
|
||||
public record PlacementConditions(@NonNull Set<Face> allowedFaces, @NonNull LinkedHashMap<String, BlockFilterType> blockFilters) {
|
||||
public enum Face {
|
||||
DOWN,
|
||||
UP,
|
||||
NORTH,
|
||||
SOUTH,
|
||||
WEST,
|
||||
EAST;
|
||||
}
|
||||
|
||||
public enum BlockFilterType {
|
||||
BLOCK,
|
||||
TAG
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This class is used to store the transformation component of a block
|
||||
*
|
||||
* @param rx The rotation on the x axis
|
||||
* @param ry The rotation on the y axis
|
||||
* @param rz The rotation on the z axis
|
||||
* @param sx The scale on the x axis
|
||||
* @param sy The scale on the y axis
|
||||
* @param sz The scale on the z axis
|
||||
* @param tx The translation on the x axis
|
||||
* @param ty The translation on the y axis
|
||||
* @param tz The translation on the z axis
|
||||
*/
|
||||
public record TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz, float tx, float ty, float tz) {
|
||||
|
||||
/**
|
||||
* Constructs a new TransformationComponent with the rotation values and assumes default scale and translation
|
||||
*
|
||||
* @param rx The rotation on the x axis
|
||||
* @param ry The rotation on the y axis
|
||||
* @param rz The rotation on the z axis
|
||||
*/
|
||||
public TransformationComponent(int rx, int ry, int rz) {
|
||||
this(rx, ry, rz, 1, 1, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new TransformationComponent with the rotation and scale values and assumes default translation
|
||||
*
|
||||
* @param rx The rotation on the x axis
|
||||
* @param ry The rotation on the y axis
|
||||
* @param rz The rotation on the z axis
|
||||
* @param sx The scale on the x axis
|
||||
* @param sy The scale on the y axis
|
||||
* @param sz The scale on the z axis
|
||||
*/
|
||||
public TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz) {
|
||||
this(rx, ry, rz, sx, sy, sz, 0, 0, 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.geysermc.geyser.api.block.custom.nonvanilla;
|
||||
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public record JavaBlockItem(@NonNull String identifier, @NonNegative int javaId, @NonNegative int stackSize) {
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package org.geysermc.geyser.api.block.custom.nonvanilla;
|
||||
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
public interface JavaBlockState {
|
||||
/**
|
||||
* Gets the identifier of the block state
|
||||
*
|
||||
* @return the identifier of the block state
|
||||
*/
|
||||
@NonNull String identifier();
|
||||
|
||||
/**
|
||||
* Gets the Java ID of the block state
|
||||
*
|
||||
* @return the Java ID of the block state
|
||||
*/
|
||||
@NonNegative int javaId();
|
||||
|
||||
/**
|
||||
* Gets the state group ID of the block state
|
||||
*
|
||||
* @return the state group ID of the block state
|
||||
*/
|
||||
@NonNegative int stateGroupId();
|
||||
|
||||
/**
|
||||
* Gets the block hardness of the block state
|
||||
*
|
||||
* @return the block hardness of the block state
|
||||
*/
|
||||
@NonNegative float blockHardness();
|
||||
|
||||
/**
|
||||
* Gets whether the block state is waterlogged
|
||||
*
|
||||
* @return whether the block state is waterlogged
|
||||
*/
|
||||
@NonNull boolean waterlogged();
|
||||
|
||||
/**
|
||||
* Gets the collision of the block state
|
||||
*
|
||||
* @return the collision of the block state
|
||||
*/
|
||||
@NonNull JavaBoundingBox[] collision();
|
||||
|
||||
/**
|
||||
* Gets whether the block state can be broken with hand
|
||||
*
|
||||
* @return whether the block state can be broken with hand
|
||||
*/
|
||||
@NonNull boolean canBreakWithHand();
|
||||
|
||||
/**
|
||||
* Gets the pick item of the block state
|
||||
*
|
||||
* @return the pick item of the block state
|
||||
*/
|
||||
@Nullable String pickItem();
|
||||
|
||||
/**
|
||||
* Gets the piston behavior of the block state
|
||||
*
|
||||
* @return the piston behavior of the block state
|
||||
*/
|
||||
@Nullable String pistonBehavior();
|
||||
|
||||
/**
|
||||
* Gets whether the block state has block entity
|
||||
*
|
||||
* @return whether the block state has block entity
|
||||
*/
|
||||
@Nullable boolean hasBlockEntity();
|
||||
|
||||
/**
|
||||
* Creates a new {@link JavaBlockState.Builder} instance
|
||||
*
|
||||
* @return a new {@link JavaBlockState.Builder} instance
|
||||
*/
|
||||
static JavaBlockState.Builder builder() {
|
||||
return GeyserApi.api().provider(JavaBlockState.Builder.class);
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
Builder identifier(@NonNull String identifier);
|
||||
|
||||
Builder javaId(@NonNegative int javaId);
|
||||
|
||||
Builder stateGroupId(@NonNegative int stateGroupId);
|
||||
|
||||
Builder blockHardness(@NonNegative float blockHardness);
|
||||
|
||||
Builder waterlogged(@NonNull boolean waterlogged);
|
||||
|
||||
Builder collision(@NonNull JavaBoundingBox[] collision);
|
||||
|
||||
Builder canBreakWithHand(@NonNull boolean canBreakWithHand);
|
||||
|
||||
Builder pickItem(@Nullable String pickItem);
|
||||
|
||||
Builder pistonBehavior(@Nullable String pistonBehavior);
|
||||
|
||||
Builder hasBlockEntity(@Nullable boolean hasBlockEntity);
|
||||
|
||||
JavaBlockState build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.geysermc.geyser.api.block.custom.nonvanilla;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public record JavaBoundingBox(@NonNull double middleX, @NonNull double middleY, @NonNull double middleZ, @NonNull double sizeX, @NonNull double sizeY, @NonNull double sizeZ) {
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.property;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class is used to store a property of a custom block of a generic type.
|
||||
*/
|
||||
public interface CustomBlockProperty<T> {
|
||||
/**
|
||||
* Gets the name of the property
|
||||
*
|
||||
* @return The name of the property.
|
||||
*/
|
||||
@NonNull String name();
|
||||
|
||||
/**
|
||||
* Gets the values of the property
|
||||
*
|
||||
* @return The values of the property.
|
||||
*/
|
||||
@NonNull List<T> values();
|
||||
|
||||
/**
|
||||
* Gets the type of the property
|
||||
*
|
||||
* @return The type of the property.
|
||||
*/
|
||||
@NonNull PropertyType type();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.property;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* This class is used to define a custom block property's type.
|
||||
*/
|
||||
public class PropertyType {
|
||||
private static final PropertyType BOOLEAN = new PropertyType(Boolean.class);
|
||||
private static final PropertyType INTEGER = new PropertyType(Integer.class);
|
||||
private static final PropertyType STRING = new PropertyType(String.class);
|
||||
|
||||
/**
|
||||
* Gets the property type for a boolean.
|
||||
*
|
||||
* @return The property type for a boolean.
|
||||
*/
|
||||
@NonNull public static PropertyType booleanProp() {
|
||||
return BOOLEAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the property type for an integer.
|
||||
*
|
||||
* @return The property type for an integer.
|
||||
*/
|
||||
@NonNull public static PropertyType integerProp() {
|
||||
return INTEGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the property type for a string.
|
||||
*
|
||||
* @return The property type for a string.
|
||||
*/
|
||||
@NonNull public static PropertyType stringProp() {
|
||||
return STRING;
|
||||
}
|
||||
|
||||
private final Class<?> typeClass;
|
||||
|
||||
/**
|
||||
* Gets the class of the property type
|
||||
*
|
||||
* @return The class of the property type.
|
||||
*/
|
||||
@NonNull public Class<?> typeClass() {
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
private PropertyType(Class<?> typeClass) {
|
||||
this.typeClass = typeClass;
|
||||
}
|
||||
}
|
|
@ -29,10 +29,12 @@ import org.checkerframework.checker.index.qual.NonNegative;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.api.connection.Connection;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
|
||||
import org.geysermc.geyser.api.command.CommandSource;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
|
@ -47,9 +49,50 @@ public interface GeyserConnection extends Connection, CommandSource {
|
|||
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
|
||||
|
||||
/**
|
||||
* Displays a player entity as emoting to this client.
|
||||
*
|
||||
* @param emoter the player entity emoting.
|
||||
* @param emoteId the emote ID to send to the client.
|
||||
* @param emoteId the emote ID to send to this client.
|
||||
*/
|
||||
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
|
||||
|
||||
/**
|
||||
* Shakes the client's camera.<br><br>
|
||||
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
|
||||
* will be layered on top of the existing intensity, with their own distinct durations.<br>
|
||||
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
|
||||
* switches to the new type. Otherwise, the existing shake is completely overridden.
|
||||
*
|
||||
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
|
||||
* @param duration the time in seconds that the shake will occur for
|
||||
* @param type the type of shake
|
||||
*/
|
||||
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
|
||||
|
||||
/**
|
||||
* Stops all camera shake of any type.
|
||||
*/
|
||||
void stopCameraShake();
|
||||
|
||||
/**
|
||||
* Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client.
|
||||
* <p>
|
||||
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
|
||||
*
|
||||
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
|
||||
*/
|
||||
void sendFog(String... fogNameSpaces);
|
||||
|
||||
/**
|
||||
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
|
||||
*
|
||||
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
|
||||
*/
|
||||
void removeFog(String... fogNameSpaces);
|
||||
|
||||
/**
|
||||
* Returns an immutable copy of all fog affects currently applied to this client.
|
||||
*/
|
||||
@NonNull
|
||||
Set<String> fogEffects();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.event.lifecycle;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockItem;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.event.Event;
|
||||
|
||||
/**
|
||||
* Called on Geyser's startup when looking for custom blocks. Custom blocks must be registered through this event.
|
||||
*
|
||||
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
|
||||
*/
|
||||
public abstract class GeyserDefineCustomBlocksEvent implements Event {
|
||||
/**
|
||||
* Registers the given {@link CustomBlockData} as a custom block
|
||||
*
|
||||
* @param customBlockData the custom block to register
|
||||
*/
|
||||
public abstract void register(@NonNull CustomBlockData customBlockData);
|
||||
|
||||
/**
|
||||
* Registers the given {@link CustomBlockState} as an override for the
|
||||
* given java state identifier
|
||||
* Java state identifiers are listed in
|
||||
* https://raw.githubusercontent.com/GeyserMC/mappings/master/blocks.json
|
||||
*
|
||||
* @param javaIdentifier the java state identifier to override
|
||||
* @param customBlockState the custom block state with which to override java state identifier
|
||||
*/
|
||||
public abstract void registerOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState);
|
||||
|
||||
/**
|
||||
* Registers the given {@link CustomBlockData} as an override for the
|
||||
* given java item identifier
|
||||
*
|
||||
* @param javaIdentifier the java item identifier to override
|
||||
* @param customBlockData the custom block data with which to override java item identifier
|
||||
*/
|
||||
public abstract void registerItemOverride(@NonNull String javaIdentifier, @NonNull CustomBlockData customBlockData);
|
||||
|
||||
/**
|
||||
* Registers the given {@link CustomBlockState} as an override for the
|
||||
* given {@link JavaBlockState}
|
||||
*
|
||||
* @param javaBlockState the java block state for the non-vanilla block
|
||||
* @param customBlockState the custom block state with which to override java state identifier
|
||||
*/
|
||||
public abstract void registerOverride(@NonNull JavaBlockState javaBlockState, @NonNull CustomBlockState customBlockState);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.geysermc.geyser.api.event.lifecycle;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.event.Event;
|
||||
|
||||
/**
|
||||
* Called on Geyser's startup when looking for custom skulls. Custom skulls must be registered through this event.
|
||||
*
|
||||
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
|
||||
*/
|
||||
public abstract class GeyserDefineCustomSkullsEvent implements Event {
|
||||
/**
|
||||
* The type of texture provided
|
||||
*/
|
||||
public enum SkullTextureType {
|
||||
USERNAME,
|
||||
UUID,
|
||||
PROFILE,
|
||||
SKIN_HASH
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given username, UUID, base64 encoded profile, or skin hash as a custom skull blocks
|
||||
* @param texture the username, UUID, base64 encoded profile, or skin hash
|
||||
* @param type the type of texture provided
|
||||
*/
|
||||
public abstract void register(@NonNull String texture, @NonNull SkullTextureType type);
|
||||
}
|
|
@ -67,4 +67,11 @@ public interface RemoteServer {
|
|||
*/
|
||||
@NonNull
|
||||
AuthType authType();
|
||||
|
||||
/**
|
||||
* Gets if we should attempt to resolve the SRV record for this server.
|
||||
*
|
||||
* @return if we should attempt to resolve the SRV record for this server
|
||||
*/
|
||||
boolean resolveSrv();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Represents the creative menu categories or tabs.
|
||||
*/
|
||||
public enum CreativeCategory {
|
||||
COMMANDS("commands", 1),
|
||||
CONSTRUCTION("construction", 2),
|
||||
EQUIPMENT("equipment", 3),
|
||||
ITEMS("items", 4),
|
||||
NATURE("nature", 5),
|
||||
NONE("none", 6);
|
||||
|
||||
private final String internalName;
|
||||
private final int id;
|
||||
|
||||
CreativeCategory(String internalName, int id) {
|
||||
this.internalName = internalName;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal name of the category.
|
||||
*
|
||||
* @return the name of the category
|
||||
*/
|
||||
@NonNull public String internalName() {
|
||||
return internalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the internal ID of the category.
|
||||
*
|
||||
* @return the ID of the category
|
||||
*/
|
||||
public int id() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -38,6 +38,10 @@ dependencies {
|
|||
}
|
||||
}
|
||||
|
||||
loom {
|
||||
mixin.defaultRefmapName.set("geyser-fabric-refmap.json")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://repo.opencollab.dev/maven-releases/")
|
||||
|
@ -117,7 +121,7 @@ modrinth {
|
|||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
|
||||
uploadFile.set(tasks.getByPath("remapModrinthJar"))
|
||||
gameVersions.addAll("1.20")
|
||||
gameVersions.addAll("1.20", "1.20.1")
|
||||
|
||||
loaders.add("fabric")
|
||||
failSilently.set(true)
|
||||
|
|
|
@ -38,7 +38,6 @@ import net.minecraft.commands.CommandSourceStack;
|
|||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
@ -212,7 +211,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
|||
return this.server.getServerVersion();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // IDEA thinks that ip cannot be null
|
||||
@NotNull
|
||||
@Override
|
||||
public String getServerBindAddress() {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"required": true,
|
||||
"package": "org.geysermc.geyser.platform.fabric.mixin",
|
||||
"compatibilityLevel": "JAVA_16",
|
||||
"refmap": "geyser-fabric-refmap.json",
|
||||
"client": [
|
||||
"client.IntegratedServerMixin"
|
||||
],
|
||||
|
|
|
@ -4,7 +4,9 @@ dependencies {
|
|||
isTransitive = false
|
||||
}
|
||||
|
||||
implementation(libs.adapters.spigot)
|
||||
implementation(variantOf(libs.adapters.spigot) {
|
||||
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
|
||||
})
|
||||
|
||||
implementation(libs.cloud.paper)
|
||||
implementation(libs.commodore)
|
||||
|
|
|
@ -163,13 +163,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
return;
|
||||
}
|
||||
|
||||
// Remove this in like a year
|
||||
if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) {
|
||||
geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", Constants.FLOODGATE_DOWNLOAD_LOCATION));
|
||||
this.getPluginLoader().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceConverter = new CommandSourceConverter<>(CommandSender.class, Bukkit::getPlayer, Bukkit::getConsoleSender);
|
||||
PaperCommandManager<GeyserCommandSource> cloud;
|
||||
try {
|
||||
|
@ -309,6 +302,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Avoid registering the same permission twice, e.g. for the extension help commands
|
||||
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
|
||||
continue;
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
|
||||
GeyserLocale.getLocaleStringLog(command.description()),
|
||||
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
|
||||
|
|
|
@ -8,7 +8,7 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("net.kyori", "indra-common", "3.0.1")
|
||||
implementation("net.kyori", "indra-common", "3.1.1")
|
||||
implementation("com.github.johnrengelman", "shadow", "7.1.3-SNAPSHOT")
|
||||
|
||||
// Within the gradle plugin classpath, there is a version conflict between loom and some other
|
||||
|
|
|
@ -34,4 +34,4 @@ tasks {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,12 +5,14 @@ plugins {
|
|||
}
|
||||
|
||||
allprojects {
|
||||
group = "org.geysermc.geyser"
|
||||
version = "2.1.1-SNAPSHOT"
|
||||
description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers."
|
||||
group = properties["group"] as String + "." + properties["id"] as String
|
||||
version = properties["version"] as String
|
||||
description = properties["description"] as String
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(16))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,3 +6,9 @@ dependencies {
|
|||
api(libs.cumulus)
|
||||
api(libs.gson)
|
||||
}
|
||||
|
||||
indra {
|
||||
javaVersions {
|
||||
target(8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,11 +36,15 @@ public final class Constants {
|
|||
|
||||
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
|
||||
|
||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://ci.geysermc.org";
|
||||
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
|
||||
public static final String UPDATE_PERMISSION = "geyser.update";
|
||||
|
||||
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
|
||||
|
||||
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
|
||||
|
||||
public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/";
|
||||
|
||||
static {
|
||||
URI wsUri = null;
|
||||
try {
|
||||
|
|
|
@ -110,7 +110,8 @@ public class GeyserCommandManager {
|
|||
|
||||
// Register help commands for all extensions with commands
|
||||
for (Map.Entry<Extension, Map<String, Command>> entry : this.extensionCommands.entrySet()) {
|
||||
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", entry.getKey().description().id(), entry.getValue()));
|
||||
String id = entry.getKey().description().id();
|
||||
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,8 @@ public class HelpCommand extends GeyserCommand {
|
|||
// todo: pagination
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", source.locale(), page, maxPage);
|
||||
String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
|
||||
String header = GeyserLocale.getPlayerLocaleString(translationKey, source.locale(), page, maxPage);
|
||||
source.sendMessage(header);
|
||||
|
||||
this.commands.stream()
|
||||
|
|
|
@ -29,6 +29,7 @@ import cloud.commandframework.Command;
|
|||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
|
@ -93,7 +94,7 @@ public class VersionCommand extends GeyserCommand {
|
|||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale()));
|
||||
} else {
|
||||
source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
|
||||
source.locale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/"));
|
||||
source.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION));
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("buildNumber missing");
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
|
||||
public class GeyserCustomSkullConfiguration {
|
||||
@JsonProperty("player-usernames")
|
||||
private List<String> playerUsernames;
|
||||
|
||||
@JsonProperty("player-uuids")
|
||||
private List<String> playerUUIDs;
|
||||
|
||||
@JsonProperty("player-profiles")
|
||||
private List<String> playerProfiles;
|
||||
|
||||
@JsonProperty("skin-hashes")
|
||||
private List<String> skinHashes;
|
||||
|
||||
public List<String> getPlayerUsernames() {
|
||||
return Objects.requireNonNullElse(playerUsernames, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> getPlayerUUIDs() {
|
||||
return Objects.requireNonNullElse(playerUUIDs, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> getPlayerProfiles() {
|
||||
return Objects.requireNonNullElse(playerProfiles, Collections.emptyList());
|
||||
}
|
||||
|
||||
public List<String> getPlayerSkinHashes() {
|
||||
return Objects.requireNonNullElse(skinHashes, Collections.emptyList());
|
||||
}
|
||||
}
|
|
@ -270,6 +270,11 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
|||
return authType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resolveSrv() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonProperty("allow-password-authentication")
|
||||
private boolean passwordAuthentication = true;
|
||||
|
|
|
@ -788,7 +788,7 @@ public final class EntityDefinitions {
|
|||
.build();
|
||||
FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase)
|
||||
.type(EntityType.FOX)
|
||||
.height(0.5f).width(1.25f)
|
||||
.height(0.7f).width(0.6f)
|
||||
.addTranslator(MetadataType.INT, FoxEntity::setFoxVariant)
|
||||
.addTranslator(MetadataType.BYTE, FoxEntity::setFoxFlags)
|
||||
.addTranslator(null) // Trusted player 1
|
||||
|
|
|
@ -66,7 +66,7 @@ public class BoatEntity extends Entity {
|
|||
private int variant;
|
||||
|
||||
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
|
||||
private final float ROWING_SPEED = 0.05f;
|
||||
private final float ROWING_SPEED = 0.1f;
|
||||
|
||||
public BoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
// Initial rotation is incorrect
|
||||
|
|
|
@ -41,7 +41,6 @@ import lombok.Setter;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
|
||||
|
@ -211,10 +210,10 @@ public class LivingEntity extends Entity {
|
|||
// If an entity has a banner on them, it will be in the helmet slot in Java but the chestplate spot in Bedrock
|
||||
// But don't overwrite the chestplate if it isn't empty
|
||||
ItemMapping banner = session.getItemMappings().getStoredItems().banner();
|
||||
if (ItemDefinition.AIR.equals(chestplate.getDefinition()) && helmet.getDefinition().equals(banner)) {
|
||||
if (ItemData.AIR.equals(chestplate) && helmet.getDefinition().equals(banner.getBedrockDefinition())) {
|
||||
chestplate = this.helmet;
|
||||
helmet = ItemData.AIR;
|
||||
} else if (chestplate.getDefinition().equals(banner)) {
|
||||
} else if (chestplate.getDefinition().equals(banner.getBedrockDefinition())) {
|
||||
// Prevent chestplate banners from showing erroneously
|
||||
chestplate = ItemData.AIR;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class AnvilContainer extends Container {
|
|||
|
||||
String originalName = ItemUtils.getCustomName(getInput().getNbt());
|
||||
|
||||
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale());
|
||||
String plainOriginalName = MessageTranslator.convertToPlainTextLenient(originalName, session.locale());
|
||||
String plainNewName = MessageTranslator.convertToPlainText(rename);
|
||||
if (!plainOriginalName.equals(plainNewName)) {
|
||||
// Strip out formatting since Java Edition does not allow it
|
||||
|
|
|
@ -77,15 +77,18 @@ public class BlockInventoryHolder extends InventoryHolder {
|
|||
// (This could be a virtual inventory that the player is opening)
|
||||
if (checkInteractionPosition(session)) {
|
||||
// Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
|
||||
// and the bedrock block is vanilla
|
||||
int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition());
|
||||
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.AIR).getJavaIdentifier().split("\\[");
|
||||
if (isValidBlock(javaBlockString)) {
|
||||
// We can safely use this block
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
|
||||
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId);
|
||||
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(javaBlockId)) {
|
||||
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.AIR).getJavaIdentifier().split("\\[");
|
||||
if (isValidBlock(javaBlockString)) {
|
||||
// We can safely use this block
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
|
||||
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId);
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +100,7 @@ public class BlockInventoryHolder extends InventoryHolder {
|
|||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(position);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(defaultJavaBlockState));
|
||||
blockPacket.setDefinition(session.getBlockMappings().getVanillaBedrockBlock(defaultJavaBlockState));
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
inventory.setHolderPosition(position);
|
||||
|
|
|
@ -45,11 +45,12 @@ public class StoredItemMappings {
|
|||
private final ItemMapping barrier;
|
||||
private final ItemMapping compass;
|
||||
private final ItemMapping crossbow;
|
||||
private final ItemMapping egg;
|
||||
private final ItemMapping glassBottle;
|
||||
private final ItemMapping milkBucket;
|
||||
private final ItemMapping powderSnowBucket;
|
||||
private final ItemMapping egg;
|
||||
private final ItemMapping shield;
|
||||
private final ItemMapping upgradeTemplate;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
|
||||
|
@ -59,11 +60,12 @@ public class StoredItemMappings {
|
|||
this.barrier = load(itemMappings, Items.BARRIER);
|
||||
this.compass = load(itemMappings, Items.COMPASS);
|
||||
this.crossbow = load(itemMappings, Items.CROSSBOW);
|
||||
this.egg = load(itemMappings, Items.EGG);
|
||||
this.glassBottle = load(itemMappings, Items.GLASS_BOTTLE);
|
||||
this.milkBucket = load(itemMappings, Items.MILK_BUCKET);
|
||||
this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET);
|
||||
this.egg = load(itemMappings, Items.EGG);
|
||||
this.shield = load(itemMappings, Items.SHIELD);
|
||||
this.upgradeTemplate = load(itemMappings, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE);
|
||||
this.wheat = load(itemMappings, Items.WHEAT);
|
||||
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
|
|||
|
||||
// Changing the item in the input slot resets the name field on Bedrock, but
|
||||
// does not result in a FilterTextPacket
|
||||
String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.locale());
|
||||
String originalName = MessageTranslator.convertToPlainTextLenient(ItemUtils.getCustomName(input.getNbt()), session.locale());
|
||||
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName);
|
||||
session.sendDownstreamPacket(renameItemPacket);
|
||||
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* 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.item.mappings.versions;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class MappingsReader_v1 extends MappingsReader {
|
||||
@Override
|
||||
public void readMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
this.readItemMappings(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonNode itemsNode = mappingsRoot.get("items");
|
||||
|
||||
if (itemsNode != null && itemsNode.isObject()) {
|
||||
itemsNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isArray()) {
|
||||
entry.getValue().forEach(data -> {
|
||||
try {
|
||||
CustomItemData customItemData = this.readItemMappingEntry(data);
|
||||
consumer.accept(entry.getKey(), customItemData);
|
||||
} catch (InvalidCustomMappingsFileException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error in custom mapping file: " + file.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private CustomItemOptions readItemCustomItemOptions(JsonNode node) {
|
||||
CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder();
|
||||
|
||||
JsonNode customModelData = node.get("custom_model_data");
|
||||
if (customModelData != null && customModelData.isInt()) {
|
||||
customItemOptions.customModelData(customModelData.asInt());
|
||||
}
|
||||
|
||||
JsonNode damagePredicate = node.get("damage_predicate");
|
||||
if (damagePredicate != null && damagePredicate.isInt()) {
|
||||
customItemOptions.damagePredicate(damagePredicate.asInt());
|
||||
}
|
||||
|
||||
JsonNode unbreakable = node.get("unbreakable");
|
||||
if (unbreakable != null && unbreakable.isBoolean()) {
|
||||
customItemOptions.unbreakable(unbreakable.asBoolean());
|
||||
}
|
||||
|
||||
JsonNode defaultItem = node.get("default");
|
||||
if (defaultItem != null && defaultItem.isBoolean()) {
|
||||
customItemOptions.defaultItem(defaultItem.asBoolean());
|
||||
}
|
||||
|
||||
return customItemOptions.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null || !node.isObject()) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid item mappings entry");
|
||||
}
|
||||
|
||||
JsonNode name = node.get("name");
|
||||
if (name == null || !name.isTextual() || name.asText().isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("An item entry has no name");
|
||||
}
|
||||
|
||||
CustomItemData.Builder customItemData = CustomItemData.builder()
|
||||
.name(name.asText())
|
||||
.customItemOptions(this.readItemCustomItemOptions(node));
|
||||
|
||||
//The next entries are optional
|
||||
if (node.has("display_name")) {
|
||||
customItemData.displayName(node.get("display_name").asText());
|
||||
}
|
||||
|
||||
if (node.has("icon")) {
|
||||
customItemData.icon(node.get("icon").asText());
|
||||
}
|
||||
|
||||
if (node.has("allow_offhand")) {
|
||||
customItemData.allowOffhand(node.get("allow_offhand").asBoolean());
|
||||
}
|
||||
|
||||
if (node.has("display_handheld")) {
|
||||
customItemData.displayHandheld(node.get("display_handheld").asBoolean());
|
||||
}
|
||||
|
||||
if (node.has("texture_size")) {
|
||||
customItemData.textureSize(node.get("texture_size").asInt());
|
||||
}
|
||||
|
||||
if (node.has("render_offsets")) {
|
||||
JsonNode tmpNode = node.get("render_offsets");
|
||||
|
||||
customItemData.renderOffsets(fromJsonNode(tmpNode));
|
||||
}
|
||||
|
||||
return customItemData.build();
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
||||
public class PlayerHeadItem extends Item {
|
||||
public PlayerHeadItem(String javaIdentifier, Builder builder) {
|
||||
|
@ -42,29 +43,35 @@ public class PlayerHeadItem extends Item {
|
|||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
Tag display = tag.get("display");
|
||||
if (!(display instanceof CompoundTag) || !((CompoundTag) display).contains("Name")) {
|
||||
Tag skullOwner = tag.get("SkullOwner");
|
||||
if (skullOwner != null) {
|
||||
CompoundTag displayTag;
|
||||
if (tag.get("display") instanceof CompoundTag existingDisplayTag) {
|
||||
displayTag = existingDisplayTag;
|
||||
} else {
|
||||
displayTag = new CompoundTag("display");
|
||||
tag.put(displayTag);
|
||||
}
|
||||
|
||||
if (displayTag.get("Name") instanceof StringTag nameTag) {
|
||||
// Custom names are always yellow and italic
|
||||
displayTag.put(new StringTag("Name", ChatColor.YELLOW + ChatColor.ITALIC + MessageTranslator.convertMessageLenient(nameTag.getValue(), session.locale())));
|
||||
} else {
|
||||
if (tag.contains("SkullOwner")) {
|
||||
StringTag name;
|
||||
if (skullOwner instanceof StringTag) {
|
||||
name = (StringTag) skullOwner;
|
||||
Tag skullOwner = tag.get("SkullOwner");
|
||||
if (skullOwner instanceof StringTag skullName) {
|
||||
name = skullName;
|
||||
} else {
|
||||
StringTag skullName;
|
||||
if (skullOwner instanceof CompoundTag && (skullName = ((CompoundTag) skullOwner).get("Name")) != null) {
|
||||
if (skullOwner instanceof CompoundTag && ((CompoundTag) skullOwner).get("Name") instanceof StringTag skullName) {
|
||||
name = skullName;
|
||||
} else {
|
||||
session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + tag);
|
||||
// No name found so default to "Player Head"
|
||||
displayTag.put(new StringTag("Name", ChatColor.RESET + ChatColor.YELLOW + MinecraftLocale.getLocaleString("block.minecraft.player_head", session.locale())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add correct name of player skull
|
||||
// TODO: It's always yellow, even with a custom name. Handle?
|
||||
String displayName = ChatColor.RESET + ChatColor.YELLOW + MinecraftLocale.getLocaleString("block.minecraft.player_head.named", session.locale()).replace("%s", name.getValue());
|
||||
if (!(display instanceof CompoundTag)) {
|
||||
tag.put(display = new CompoundTag("display"));
|
||||
}
|
||||
((CompoundTag) display).put(new StringTag("Name", displayName));
|
||||
displayTag.put(new StringTag("Name", displayName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ package org.geysermc.geyser.level;
|
|||
* @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's.
|
||||
*/
|
||||
public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) {
|
||||
public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true);
|
||||
public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false);
|
||||
public static BedrockDimension THE_END = new BedrockDimension(0, 256, true);
|
||||
public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true);
|
||||
public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false);
|
||||
public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* 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.level.block;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Value;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
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.GeometryComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
|
||||
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Value
|
||||
public class GeyserCustomBlockComponents implements CustomBlockComponents {
|
||||
BoxComponent selectionBox;
|
||||
BoxComponent collisionBox;
|
||||
String displayName;
|
||||
GeometryComponent geometry;
|
||||
Map<String, MaterialInstance> materialInstances;
|
||||
List<PlacementConditions> placementFilter;
|
||||
Float destructibleByMining;
|
||||
Float friction;
|
||||
Integer lightEmission;
|
||||
Integer lightDampening;
|
||||
TransformationComponent transformation;
|
||||
boolean unitCube;
|
||||
boolean placeAir;
|
||||
Set<String> tags;
|
||||
|
||||
private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) {
|
||||
this.selectionBox = builder.selectionBox;
|
||||
this.collisionBox = builder.collisionBox;
|
||||
this.displayName = builder.displayName;
|
||||
this.geometry = builder.geometry;
|
||||
if (builder.materialInstances.isEmpty()) {
|
||||
this.materialInstances = Object2ObjectMaps.emptyMap();
|
||||
} 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.transformation = builder.transformation;
|
||||
this.unitCube = builder.unitCube;
|
||||
this.placeAir = builder.placeAir;
|
||||
if (builder.tags.isEmpty()) {
|
||||
this.tags = Set.of();
|
||||
} else {
|
||||
this.tags = Set.copyOf(builder.tags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoxComponent selectionBox() {
|
||||
return selectionBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoxComponent collisionBox() {
|
||||
return collisionBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeometryComponent geometry() {
|
||||
return geometry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Map<String, MaterialInstance> materialInstances() {
|
||||
return materialInstances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlacementConditions> placementFilter() {
|
||||
return placementFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float destructibleByMining() {
|
||||
return destructibleByMining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float friction() {
|
||||
return friction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer lightEmission() {
|
||||
return lightEmission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer lightDampening() {
|
||||
return lightDampening;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformationComponent transformation() {
|
||||
return transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unitCube() {
|
||||
return unitCube;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeAir() {
|
||||
return placeAir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<String> tags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static class CustomBlockComponentsBuilder implements Builder {
|
||||
protected BoxComponent selectionBox;
|
||||
protected BoxComponent collisionBox;
|
||||
protected String displayName;
|
||||
protected GeometryComponent geometry;
|
||||
protected final Object2ObjectMap<String, MaterialInstance> materialInstances = new Object2ObjectOpenHashMap<>();
|
||||
protected List<PlacementConditions> placementFilter;
|
||||
protected Float destructibleByMining;
|
||||
protected Float friction;
|
||||
protected Integer lightEmission;
|
||||
protected Integer lightDampening;
|
||||
protected TransformationComponent transformation;
|
||||
protected boolean unitCube = false;
|
||||
protected boolean placeAir = false;
|
||||
protected final Set<String> tags = new HashSet<>();
|
||||
|
||||
private void validateBox(BoxComponent box) {
|
||||
if (box == null) {
|
||||
return;
|
||||
}
|
||||
if (box.sizeX() < 0 || box.sizeY() < 0 || box.sizeZ() < 0) {
|
||||
throw new IllegalArgumentException("Box size must be non-negative.");
|
||||
}
|
||||
float minX = box.originX() + 8;
|
||||
float minY = box.originY();
|
||||
float minZ = box.originZ() + 8;
|
||||
float maxX = minX + box.sizeX();
|
||||
float maxY = minY + box.sizeY();
|
||||
float maxZ = minZ + box.sizeZ();
|
||||
if (minX < 0 || minY < 0 || minZ < 0 || maxX > 16 || maxY > 16 || maxZ > 16) {
|
||||
throw new IllegalArgumentException("Box bounds must be within (0, 0, 0) and (16, 16, 16). Recieved: (" + minX + ", " + minY + ", " + minZ + ") to (" + maxX + ", " + maxY + ", " + maxZ + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder selectionBox(BoxComponent selectionBox) {
|
||||
validateBox(selectionBox);
|
||||
this.selectionBox = selectionBox;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder collisionBox(BoxComponent collisionBox) {
|
||||
validateBox(collisionBox);
|
||||
this.collisionBox = collisionBox;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder displayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder geometry(GeometryComponent geometry) {
|
||||
this.geometry = geometry;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder materialInstance(@NotNull String name, @NotNull MaterialInstance materialInstance) {
|
||||
this.materialInstances.put(name, materialInstance);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placementFilter(List<PlacementConditions> placementFilter) {
|
||||
this.placementFilter = placementFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder destructibleByMining(Float destructibleByMining) {
|
||||
if (destructibleByMining != null && destructibleByMining < 0) {
|
||||
throw new IllegalArgumentException("Destructible by mining must be non-negative");
|
||||
}
|
||||
this.destructibleByMining = destructibleByMining;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder friction(Float friction) {
|
||||
if (friction != null) {
|
||||
if (friction < 0 || friction > 1) {
|
||||
throw new IllegalArgumentException("Friction must be in the range 0-1");
|
||||
}
|
||||
}
|
||||
this.friction = friction;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder lightEmission(Integer lightEmission) {
|
||||
if (lightEmission != null) {
|
||||
if (lightEmission < 0 || lightEmission > 15) {
|
||||
throw new IllegalArgumentException("Light emission must be in the range 0-15");
|
||||
}
|
||||
}
|
||||
this.lightEmission = lightEmission;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder lightDampening(Integer lightDampening) {
|
||||
if (lightDampening != null) {
|
||||
if (lightDampening < 0 || lightDampening > 15) {
|
||||
throw new IllegalArgumentException("Light dampening must be in the range 0-15");
|
||||
}
|
||||
}
|
||||
this.lightDampening = lightDampening;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder transformation(TransformationComponent transformation) {
|
||||
if (transformation.rx() % 90 != 0 || transformation.ry() % 90 != 0 || transformation.rz() % 90 != 0) {
|
||||
throw new IllegalArgumentException("Rotation of transformation must be a multiple of 90 degrees.");
|
||||
}
|
||||
this.transformation = transformation;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder unitCube(boolean unitCube) {
|
||||
this.unitCube = unitCube;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeAir(boolean placeAir) {
|
||||
this.placeAir = placeAir;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder tags(Set<String> tags) {
|
||||
this.tags.addAll(tags);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomBlockComponents build() {
|
||||
return new GeyserCustomBlockComponents(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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.level.block;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
|
||||
import org.geysermc.geyser.api.block.custom.property.PropertyType;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserCustomBlockData implements CustomBlockData {
|
||||
private final String name;
|
||||
private final boolean includedInCreativeInventory;
|
||||
private final CreativeCategory creativeCategory;
|
||||
private final String creativeGroup;
|
||||
private final CustomBlockComponents components;
|
||||
private final Map<String, CustomBlockProperty<?>> properties;
|
||||
private final List<CustomBlockPermutation> permutations;
|
||||
|
||||
private final Map<String, Object> defaultProperties;
|
||||
|
||||
GeyserCustomBlockData(CustomBlockDataBuilder builder) {
|
||||
this.name = builder.name;
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("Name must be set");
|
||||
}
|
||||
|
||||
this.includedInCreativeInventory = builder.includedInCreativeInventory;
|
||||
this.creativeCategory = builder.creativeCategory;
|
||||
this.creativeGroup = builder.creativeGroup;
|
||||
|
||||
this.components = builder.components;
|
||||
|
||||
if (!builder.properties.isEmpty()) {
|
||||
this.properties = Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(builder.properties));
|
||||
Object2ObjectMap<String, Object> defaultProperties = new Object2ObjectOpenHashMap<>(this.properties.size());
|
||||
for (CustomBlockProperty<?> property : properties.values()) {
|
||||
if (property.values().size() > 16) {
|
||||
GeyserImpl.getInstance().getLogger().warning(property.name() + " contains more than 16 values, but BDS specifies it should not. This may break in future versions.");
|
||||
}
|
||||
if (property.values().stream().distinct().count() != property.values().size()) {
|
||||
throw new IllegalStateException(property.name() + " has duplicate values.");
|
||||
}
|
||||
if (property.values().isEmpty()) {
|
||||
throw new IllegalStateException(property.name() + " contains no values.");
|
||||
}
|
||||
defaultProperties.put(property.name(), property.values().get(0));
|
||||
}
|
||||
this.defaultProperties = Object2ObjectMaps.unmodifiable(defaultProperties);
|
||||
} else {
|
||||
this.properties = Object2ObjectMaps.emptyMap();
|
||||
this.defaultProperties = Object2ObjectMaps.emptyMap();
|
||||
}
|
||||
|
||||
if (!builder.permutations.isEmpty()) {
|
||||
this.permutations = List.of(builder.permutations.toArray(new CustomBlockPermutation[0]));
|
||||
} else {
|
||||
this.permutations = ObjectLists.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String identifier() {
|
||||
return Constants.GEYSER_CUSTOM_NAMESPACE + ":" + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includedInCreativeInventory() {
|
||||
return includedInCreativeInventory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable CreativeCategory creativeCategory() {
|
||||
return creativeCategory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String creativeGroup() {
|
||||
return creativeGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomBlockComponents components() {
|
||||
return components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Map<String, CustomBlockProperty<?>> properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<CustomBlockPermutation> permutations() {
|
||||
return permutations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull CustomBlockState defaultBlockState() {
|
||||
return new GeyserCustomBlockState(this, defaultProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomBlockState.@NotNull Builder blockStateBuilder() {
|
||||
return new GeyserCustomBlockState.CustomBlockStateBuilder(this);
|
||||
}
|
||||
|
||||
public static class CustomBlockDataBuilder implements Builder {
|
||||
private String name;
|
||||
private boolean includedInCreativeInventory;
|
||||
private CreativeCategory creativeCategory;
|
||||
private String creativeGroup;
|
||||
private CustomBlockComponents components;
|
||||
private final Object2ObjectMap<String, CustomBlockProperty<?>> properties = new Object2ObjectOpenHashMap<>();
|
||||
private List<CustomBlockPermutation> permutations = ObjectLists.emptyList();
|
||||
|
||||
@Override
|
||||
public Builder name(@NonNull String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder includedInCreativeInventory(boolean includedInCreativeInventory) {
|
||||
this.includedInCreativeInventory = includedInCreativeInventory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder creativeCategory(@Nullable CreativeCategory creativeCategory) {
|
||||
this.creativeCategory = creativeCategory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder creativeGroup(@Nullable String creativeGroup) {
|
||||
this.creativeGroup = creativeGroup;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder components(@NonNull CustomBlockComponents components) {
|
||||
this.components = components;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder booleanProperty(@NonNull String propertyName) {
|
||||
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, List.of((byte) 0, (byte) 1), PropertyType.booleanProp()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder intProperty(@NonNull String propertyName, List<Integer> values) {
|
||||
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, values, PropertyType.integerProp()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stringProperty(@NonNull String propertyName, List<String> values) {
|
||||
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, values, PropertyType.stringProp()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder permutations(@NonNull List<CustomBlockPermutation> permutations) {
|
||||
this.permutations = permutations;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomBlockData build() {
|
||||
return new GeyserCustomBlockData(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.level.block;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
|
||||
import org.geysermc.geyser.api.block.custom.property.PropertyType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A custom block property that can be used to store custom data for a block.
|
||||
*
|
||||
* @param <T> The type of the property
|
||||
* @param name The name of the property
|
||||
* @param values The values of the property
|
||||
* @param type The type of the property
|
||||
*/
|
||||
public record GeyserCustomBlockProperty<T>(@NonNull String name, @NonNull List<T> values,
|
||||
@NonNull PropertyType type) implements CustomBlockProperty<T> {
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.level.block;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Value
|
||||
public class GeyserCustomBlockState implements CustomBlockState {
|
||||
CustomBlockData block;
|
||||
Map<String, Object> properties;
|
||||
|
||||
@Override
|
||||
public @NonNull CustomBlockData block() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String name() {
|
||||
return block.name();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> @NonNull T property(String propertyName) {
|
||||
return (T) properties.get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Map<String, Object> properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class CustomBlockStateBuilder implements CustomBlockState.Builder {
|
||||
private final CustomBlockData blockData;
|
||||
private final Object2ObjectMap<String, Object> properties = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Override
|
||||
public Builder booleanProperty(@NonNull String propertyName, boolean value) {
|
||||
properties.put(propertyName, value ? (byte) 1 : (byte) 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder intProperty(@NonNull String propertyName, int value) {
|
||||
properties.put(propertyName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stringProperty(@NonNull String propertyName, @NonNull String value) {
|
||||
properties.put(propertyName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomBlockState build() {
|
||||
for (String propertyName : blockData.properties().keySet()) {
|
||||
if (!properties.containsKey(propertyName)) {
|
||||
throw new IllegalArgumentException("Missing property: " + propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : properties.entrySet()) {
|
||||
String propertyName = entry.getKey();
|
||||
Object propertyValue = entry.getValue();
|
||||
|
||||
CustomBlockProperty<?> property = blockData.properties().get(propertyName);
|
||||
if (property == null) {
|
||||
throw new IllegalArgumentException("Unknown property: " + propertyName);
|
||||
} else if (!property.values().contains(propertyValue)) {
|
||||
throw new IllegalArgumentException("Invalid value: " + propertyValue + " for property: " + propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
return new GeyserCustomBlockState(blockData, Object2ObjectMaps.unmodifiable(properties));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.level.block;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserGeometryComponent implements GeometryComponent {
|
||||
private final String identifier;
|
||||
private final Map<String, String> boneVisibility;
|
||||
|
||||
GeyserGeometryComponent(GeometryComponentBuilder builder) {
|
||||
this.identifier = builder.identifier;
|
||||
this.boneVisibility = builder.boneVisibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String identifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, String> boneVisibility() {
|
||||
return boneVisibility;
|
||||
}
|
||||
|
||||
public static class GeometryComponentBuilder implements Builder {
|
||||
private String identifier;
|
||||
private Map<String, String> boneVisibility;
|
||||
|
||||
@Override
|
||||
public GeometryComponent.Builder identifier(@NonNull String identifier) {
|
||||
this.identifier = identifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeometryComponent.Builder boneVisibility(@Nullable Map<String, String> boneVisibility) {
|
||||
this.boneVisibility = boneVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeometryComponent build() {
|
||||
return new GeyserGeometryComponent(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package org.geysermc.geyser.level.block;
|
||||
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBoundingBox;
|
||||
|
||||
public class GeyserJavaBlockState implements JavaBlockState {
|
||||
String identifier;
|
||||
int javaId;
|
||||
int stateGroupId;
|
||||
float blockHardness;
|
||||
boolean waterlogged;
|
||||
JavaBoundingBox[] collision;
|
||||
boolean canBreakWithHand;
|
||||
String pickItem;
|
||||
String pistonBehavior;
|
||||
boolean hasBlockEntity;
|
||||
|
||||
private GeyserJavaBlockState(JavaBlockStateBuilder builder) {
|
||||
this.identifier = builder.identifier;
|
||||
this.javaId = builder.javaId;
|
||||
this.stateGroupId = builder.stateGroupId;
|
||||
this.blockHardness = builder.blockHardness;
|
||||
this.waterlogged = builder.waterlogged;
|
||||
this.collision = builder.collision;
|
||||
this.canBreakWithHand = builder.canBreakWithHand;
|
||||
this.pickItem = builder.pickItem;
|
||||
this.pistonBehavior = builder.pistonBehavior;
|
||||
this.hasBlockEntity = builder.hasBlockEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String identifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNegative int javaId() {
|
||||
return javaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNegative int stateGroupId() {
|
||||
return stateGroupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNegative float blockHardness() {
|
||||
return blockHardness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull boolean waterlogged() {
|
||||
return waterlogged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull JavaBoundingBox[] collision() {
|
||||
return collision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull boolean canBreakWithHand() {
|
||||
return canBreakWithHand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String pickItem() {
|
||||
return pickItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String pistonBehavior() {
|
||||
return pistonBehavior;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable boolean hasBlockEntity() {
|
||||
return hasBlockEntity;
|
||||
}
|
||||
|
||||
public static class JavaBlockStateBuilder implements Builder {
|
||||
private String identifier;
|
||||
private int javaId;
|
||||
private int stateGroupId;
|
||||
private float blockHardness;
|
||||
private boolean waterlogged;
|
||||
private JavaBoundingBox[] collision;
|
||||
private boolean canBreakWithHand;
|
||||
private String pickItem;
|
||||
private String pistonBehavior;
|
||||
private boolean hasBlockEntity;
|
||||
|
||||
@Override
|
||||
public Builder identifier(@NonNull String identifier) {
|
||||
this.identifier = identifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder javaId(@NonNegative int javaId) {
|
||||
this.javaId = javaId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stateGroupId(@NonNegative int stateGroupId) {
|
||||
this.stateGroupId = stateGroupId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder blockHardness(@NonNegative float blockHardness) {
|
||||
this.blockHardness = blockHardness;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder waterlogged(@NonNull boolean waterlogged) {
|
||||
this.waterlogged = waterlogged;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder collision(@NonNull JavaBoundingBox[] collision) {
|
||||
this.collision = collision;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder canBreakWithHand(@NonNull boolean canBreakWithHand) {
|
||||
this.canBreakWithHand = canBreakWithHand;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder pickItem(@Nullable String pickItem) {
|
||||
this.pickItem = pickItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder pistonBehavior(@Nullable String pistonBehavior) {
|
||||
this.pistonBehavior = pistonBehavior;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder hasBlockEntity(@Nullable boolean hasBlockEntity) {
|
||||
this.hasBlockEntity = hasBlockEntity;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaBlockState build() {
|
||||
return new GeyserJavaBlockState(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.level.block;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserMaterialInstance implements MaterialInstance {
|
||||
private final String texture;
|
||||
private final String renderMethod;
|
||||
private final boolean faceDimming;
|
||||
private final boolean ambientOcclusion;
|
||||
|
||||
GeyserMaterialInstance(MaterialInstanceBuilder builder) {
|
||||
this.texture = builder.texture;
|
||||
this.renderMethod = builder.renderMethod;
|
||||
this.faceDimming = builder.faceDimming;
|
||||
this.ambientOcclusion = builder.ambientOcclusion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String texture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String renderMethod() {
|
||||
return renderMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable boolean faceDimming() {
|
||||
return faceDimming;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable boolean ambientOcclusion() {
|
||||
return ambientOcclusion;
|
||||
}
|
||||
|
||||
public static class MaterialInstanceBuilder implements Builder {
|
||||
private String texture;
|
||||
private String renderMethod;
|
||||
private boolean faceDimming;
|
||||
private boolean ambientOcclusion;
|
||||
|
||||
@Override
|
||||
public Builder texture(@NonNull String texture) {
|
||||
this.texture = texture;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder renderMethod(@Nullable String renderMethod) {
|
||||
this.renderMethod = renderMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder faceDimming(@Nullable boolean faceDimming) {
|
||||
this.faceDimming = faceDimming;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder ambientOcclusion(@Nullable boolean ambientOcclusion) {
|
||||
this.ambientOcclusion = ambientOcclusion;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialInstance build() {
|
||||
return new GeyserMaterialInstance(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.level.block;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData implements NonVanillaCustomBlockData {
|
||||
private final String namespace;
|
||||
|
||||
GeyserNonVanillaCustomBlockData(NonVanillaCustomBlockDataBuilder builder) {
|
||||
super(builder);
|
||||
|
||||
this.namespace = builder.namespace;
|
||||
if (namespace == null) {
|
||||
throw new IllegalStateException("Identifier must be set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String identifier() {
|
||||
return this.namespace + ":" + super.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String namespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
public static class NonVanillaCustomBlockDataBuilder extends CustomBlockDataBuilder implements NonVanillaCustomBlockData.Builder {
|
||||
private String namespace;
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder namespace(@NonNull String namespace) {
|
||||
this.namespace = namespace;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder name(@NonNull String name) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.name(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder includedInCreativeInventory(boolean includedInCreativeInventory) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.includedInCreativeInventory(includedInCreativeInventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder creativeCategory(@Nullable CreativeCategory creativeCategories) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.creativeCategory(creativeCategories);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder creativeGroup(@Nullable String creativeGroup) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.creativeGroup(creativeGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder components(@NonNull CustomBlockComponents components) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.components(components);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder booleanProperty(@NonNull String propertyName) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.booleanProperty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder intProperty(@NonNull String propertyName, List<Integer> values) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.intProperty(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder stringProperty(@NonNull String propertyName, List<String> values) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.stringProperty(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockDataBuilder permutations(@NonNull List<CustomBlockPermutation> permutations) {
|
||||
return (NonVanillaCustomBlockDataBuilder) super.permutations(permutations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonVanillaCustomBlockData build() {
|
||||
return new GeyserNonVanillaCustomBlockData(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,16 +30,19 @@ import org.cloudburstmc.protocol.common.util.Preconditions;
|
|||
|
||||
public class GeyserChunkSection {
|
||||
|
||||
private static final int CHUNK_SECTION_VERSION = 8;
|
||||
// As of at least 1.19.80
|
||||
private static final int CHUNK_SECTION_VERSION = 9;
|
||||
|
||||
private final BlockStorage[] storage;
|
||||
private final int sectionY;
|
||||
|
||||
public GeyserChunkSection(int airBlockId) {
|
||||
this(new BlockStorage[]{new BlockStorage(airBlockId), new BlockStorage(airBlockId)});
|
||||
public GeyserChunkSection(int airBlockId, int sectionY) {
|
||||
this(new BlockStorage[]{new BlockStorage(airBlockId), new BlockStorage(airBlockId)}, sectionY);
|
||||
}
|
||||
|
||||
public GeyserChunkSection(BlockStorage[] storage) {
|
||||
public GeyserChunkSection(BlockStorage[] storage, int sectionY) {
|
||||
this.storage = storage;
|
||||
this.sectionY = sectionY;
|
||||
}
|
||||
|
||||
public int getFullBlock(int x, int y, int z, int layer) {
|
||||
|
@ -57,6 +60,8 @@ public class GeyserChunkSection {
|
|||
public void writeToNetwork(ByteBuf buffer) {
|
||||
buffer.writeByte(CHUNK_SECTION_VERSION);
|
||||
buffer.writeByte(this.storage.length);
|
||||
// Required for chunk version 9+
|
||||
buffer.writeByte(this.sectionY);
|
||||
for (BlockStorage blockStorage : this.storage) {
|
||||
blockStorage.writeToNetwork(buffer);
|
||||
}
|
||||
|
@ -83,12 +88,12 @@ public class GeyserChunkSection {
|
|||
return true;
|
||||
}
|
||||
|
||||
public GeyserChunkSection copy() {
|
||||
public GeyserChunkSection copy(int sectionY) {
|
||||
BlockStorage[] storage = new BlockStorage[this.storage.length];
|
||||
for (int i = 0; i < storage.length; i++) {
|
||||
storage[i] = this.storage[i].copy();
|
||||
}
|
||||
return new GeyserChunkSection(storage);
|
||||
return new GeyserChunkSection(storage, sectionY);
|
||||
}
|
||||
|
||||
public static int blockPosition(int x, int y, int z) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.codec.PacketCodec;
|
|||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
|
@ -45,7 +46,7 @@ public final class GameProtocol {
|
|||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v589.CODEC;
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v594.CODEC;
|
||||
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
|
@ -59,9 +60,7 @@ public final class GameProtocol {
|
|||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v582.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.80/1.19.81")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v589.CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
}
|
||||
|
||||
|
@ -81,8 +80,8 @@ public final class GameProtocol {
|
|||
|
||||
/* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */
|
||||
|
||||
public static boolean isPre1_20(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() < Bedrock_v589.CODEC.getProtocolVersion();
|
||||
public static boolean isPre1_20_10(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() < Bedrock_v594.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -231,11 +231,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true));
|
||||
}
|
||||
|
||||
if (GameProtocol.isPre1_20(session)) {
|
||||
stackPacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
stackPacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
break;
|
||||
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* 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.pack;
|
||||
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class SkullResourcePackManager {
|
||||
|
||||
private static final long RESOURCE_PACK_VERSION = 8;
|
||||
|
||||
private static final Path SKULL_SKIN_CACHE_PATH = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache").resolve("player_skulls");
|
||||
|
||||
public static final Map<String, Path> SKULL_SKINS = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static Path createResourcePack() {
|
||||
Path cachePath = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("cache");
|
||||
try {
|
||||
Files.createDirectories(cachePath);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe("Unable to create directories for player skull resource pack!", e);
|
||||
return null;
|
||||
}
|
||||
cleanSkullSkinCache();
|
||||
|
||||
Path packPath = cachePath.resolve("player_skulls.mcpack");
|
||||
File packFile = packPath.toFile();
|
||||
if (BlockRegistries.CUSTOM_SKULLS.get().isEmpty() || !GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
packFile.delete(); // No need to keep resource pack
|
||||
return null;
|
||||
}
|
||||
if (packFile.exists() && canReusePack(packFile)) {
|
||||
GeyserImpl.getInstance().getLogger().info("Reusing cached player skull resource pack.");
|
||||
return packPath;
|
||||
}
|
||||
|
||||
// We need to create the resource pack from scratch
|
||||
GeyserImpl.getInstance().getLogger().info("Creating skull resource pack.");
|
||||
packFile.delete();
|
||||
try (ZipOutputStream zipOS = new ZipOutputStream(Files.newOutputStream(packPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE))) {
|
||||
addBaseResources(zipOS);
|
||||
addSkinTextures(zipOS);
|
||||
addAttachables(zipOS);
|
||||
GeyserImpl.getInstance().getLogger().info("Finished creating skull resource pack.");
|
||||
return packPath;
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe("Unable to create player skull resource pack!", e);
|
||||
GeyserImpl.getInstance().getLogger().severe("Bedrock players will see dirt blocks instead of custom skull blocks.");
|
||||
packFile.delete();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void cacheSkullSkin(String skinHash) throws IOException {
|
||||
String skinUrl = Constants.MINECRAFT_SKIN_SERVER_URL + skinHash;
|
||||
Path skinPath = SKULL_SKINS.get(skinHash);
|
||||
if (skinPath != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Files.createDirectories(SKULL_SKIN_CACHE_PATH);
|
||||
skinPath = SKULL_SKIN_CACHE_PATH.resolve(skinHash + ".png");
|
||||
if (Files.exists(skinPath)) {
|
||||
SKULL_SKINS.put(skinHash, skinPath);
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage image = SkinProvider.requestImage(skinUrl, null);
|
||||
// Resize skins to 48x16 to save on space and memory
|
||||
BufferedImage skullTexture = new BufferedImage(48, 16, image.getType());
|
||||
// Reorder skin parts to fit into the space
|
||||
// Right, Front, Left, Back, Top, Bottom - head
|
||||
// Right, Front, Left, Back, Top, Bottom - hat
|
||||
Graphics g = skullTexture.createGraphics();
|
||||
// Right, Front, Left, Back of the head
|
||||
g.drawImage(image, 0, 0, 32, 8, 0, 8, 32, 16, null);
|
||||
// Right, Front, Left, Back of the hat
|
||||
g.drawImage(image, 0, 8, 32, 16, 32, 8, 64, 16, null);
|
||||
// Top and bottom of the head
|
||||
g.drawImage(image, 32, 0, 48, 8, 8, 0, 24, 8, null);
|
||||
// Top and bottom of the hat
|
||||
g.drawImage(image, 32, 8, 48, 16, 40, 0, 56, 8, null);
|
||||
g.dispose();
|
||||
image.flush();
|
||||
|
||||
ImageIO.write(skullTexture, "png", skinPath.toFile());
|
||||
SKULL_SKINS.put(skinHash, skinPath);
|
||||
GeyserImpl.getInstance().getLogger().debug("Cached player skull to " + skinPath + " for " + skinHash);
|
||||
}
|
||||
|
||||
public static void cleanSkullSkinCache() {
|
||||
// No need to clean up if skin cache does not exist
|
||||
if (!Files.exists(SKULL_SKIN_CACHE_PATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> stream = Files.list(SKULL_SKIN_CACHE_PATH)) {
|
||||
int removeCount = 0;
|
||||
for (Path path : stream.toList()) {
|
||||
String skinHash = path.getFileName().toString();
|
||||
skinHash = skinHash.substring(0, skinHash.length() - ".png".length());
|
||||
if (!SKULL_SKINS.containsKey(skinHash) && path.toFile().delete()) {
|
||||
removeCount++;
|
||||
}
|
||||
}
|
||||
if (removeCount != 0) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Removed " + removeCount + " unnecessary skull skins.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Unable to clean up skull skin cache.");
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addBaseResources(ZipOutputStream zipOS) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GeyserImpl.getInstance().getBootstrap().getResource("bedrock/skull_resource_pack_files.txt")))) {
|
||||
List<String> lines = reader.lines().toList();
|
||||
for (String path : lines) {
|
||||
ZipEntry entry = new ZipEntry(path);
|
||||
|
||||
zipOS.putNextEntry(entry);
|
||||
String resourcePath = "bedrock/" + path;
|
||||
switch (path) {
|
||||
case "skull_resource_pack/manifest.json" ->
|
||||
fillTemplate(zipOS, resourcePath, SkullResourcePackManager::fillManifestJson);
|
||||
case "skull_resource_pack/textures/terrain_texture.json" ->
|
||||
fillTemplate(zipOS, resourcePath, SkullResourcePackManager::fillTerrainTextureJson);
|
||||
default -> zipOS.write(FileUtils.readAllBytes(resourcePath));
|
||||
}
|
||||
zipOS.closeEntry();
|
||||
}
|
||||
|
||||
addFloorGeometries(zipOS);
|
||||
|
||||
ZipEntry entry = new ZipEntry("skull_resource_pack/pack_icon.png");
|
||||
zipOS.putNextEntry(entry);
|
||||
zipOS.write(FileUtils.readAllBytes("icon.png"));
|
||||
zipOS.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFloorGeometries(ZipOutputStream zipOS) throws IOException {
|
||||
String template = FileUtils.readToString("bedrock/skull_resource_pack/models/blocks/player_skull_floor.geo.json");
|
||||
String[] quadrants = {"a", "b", "c", "d"};
|
||||
for (int i = 0; i < quadrants.length; i++) {
|
||||
String quadrant = quadrants[i];
|
||||
float yRotation = i * 22.5f;
|
||||
String contents = template
|
||||
.replace("${quadrant}", quadrant)
|
||||
.replace("${y_rotation}", String.valueOf(yRotation));
|
||||
|
||||
ZipEntry entry = new ZipEntry("skull_resource_pack/models/blocks/player_skull_floor_" + quadrant + ".geo.json");
|
||||
zipOS.putNextEntry(entry);
|
||||
zipOS.write(contents.getBytes(StandardCharsets.UTF_8));
|
||||
zipOS.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAttachables(ZipOutputStream zipOS) throws IOException {
|
||||
String template = FileUtils.readToString("bedrock/skull_resource_pack/attachables/template_attachable.json");
|
||||
for (CustomSkull skull : BlockRegistries.CUSTOM_SKULLS.get().values()) {
|
||||
ZipEntry entry = new ZipEntry("skull_resource_pack/attachables/" + truncateHash(skull.getSkinHash()) + ".json");
|
||||
zipOS.putNextEntry(entry);
|
||||
zipOS.write(fillAttachableJson(template, skull).getBytes(StandardCharsets.UTF_8));
|
||||
zipOS.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSkinTextures(ZipOutputStream zipOS) throws IOException {
|
||||
for (Path skinPath : SKULL_SKINS.values()) {
|
||||
ZipEntry entry = new ZipEntry("skull_resource_pack/textures/blocks/" + truncateHash(skinPath.getFileName().toString()) + ".png");
|
||||
zipOS.putNextEntry(entry);
|
||||
try (InputStream stream = Files.newInputStream(skinPath)) {
|
||||
stream.transferTo(zipOS);
|
||||
}
|
||||
zipOS.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillTemplate(ZipOutputStream zipOS, String path, UnaryOperator<String> filler) throws IOException {
|
||||
String template = FileUtils.readToString(path);
|
||||
String result = filler.apply(template);
|
||||
zipOS.write(result.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static String fillAttachableJson(String template, CustomSkull skull) {
|
||||
return template.replace("${identifier}", skull.getCustomBlockData().identifier())
|
||||
.replace("${texture}", truncateHash(skull.getSkinHash()));
|
||||
}
|
||||
|
||||
private static String fillManifestJson(String template) {
|
||||
Pair<UUID, UUID> uuids = generatePackUUIDs();
|
||||
return template.replace("${uuid1}", uuids.first().toString())
|
||||
.replace("${uuid2}", uuids.second().toString());
|
||||
}
|
||||
|
||||
private static String fillTerrainTextureJson(String template) {
|
||||
StringBuilder textures = new StringBuilder();
|
||||
for (String skinHash : SKULL_SKINS.keySet()) {
|
||||
String texture = String.format("\"geyser.%s_player_skin\":{\"textures\":\"textures/blocks/%s\"},\n", skinHash, truncateHash(skinHash));
|
||||
textures.append(texture);
|
||||
}
|
||||
if (textures.length() != 0) {
|
||||
// Remove trailing comma
|
||||
textures.delete(textures.length() - 2, textures.length());
|
||||
}
|
||||
return template.replace("${texture_data}", textures);
|
||||
}
|
||||
|
||||
private static Pair<UUID, UUID> generatePackUUIDs() {
|
||||
UUID uuid1 = UUID.randomUUID();
|
||||
UUID uuid2 = UUID.randomUUID();
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
md.update((byte) ((RESOURCE_PACK_VERSION >> (i * 8)) & 0xFF));
|
||||
}
|
||||
SKULL_SKINS.keySet().stream()
|
||||
.sorted()
|
||||
.map(hash -> hash.getBytes(StandardCharsets.UTF_8))
|
||||
.forEach(md::update);
|
||||
|
||||
ByteBuffer skinHashes = ByteBuffer.wrap(md.digest());
|
||||
uuid1 = new UUID(skinHashes.getLong(), skinHashes.getLong());
|
||||
uuid2 = new UUID(skinHashes.getLong(), skinHashes.getLong());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe("Unable to get SHA-256 Message Digest instance! Bedrock players will have to re-downloaded the player skull resource pack after each server restart.", e);
|
||||
}
|
||||
|
||||
return Pair.of(uuid1, uuid2);
|
||||
}
|
||||
|
||||
private static boolean canReusePack(File packFile) {
|
||||
Pair<UUID, UUID> uuids = generatePackUUIDs();
|
||||
try (ZipFile zipFile = new ZipFile(packFile)) {
|
||||
Optional<? extends ZipEntry> manifestEntry = zipFile.stream()
|
||||
.filter(entry -> entry.getName().contains("manifest.json"))
|
||||
.findFirst();
|
||||
if (manifestEntry.isPresent()) {
|
||||
GeyserResourcePackManifest manifest = FileUtils.loadJson(zipFile.getInputStream(manifestEntry.get()), GeyserResourcePackManifest.class);
|
||||
if (!uuids.first().equals(manifest.header().uuid())) {
|
||||
return false;
|
||||
}
|
||||
Optional<UUID> resourceUUID = manifest.modules().stream()
|
||||
.filter(module -> "resources".equals(module.type()))
|
||||
.findFirst()
|
||||
.map(ResourcePackManifest.Module::uuid);
|
||||
return resourceUUID.isPresent() && uuids.second().equals(resourceUUID.get());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Cached player skull resource pack was invalid! The pack will be recreated.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String truncateHash(String hash) {
|
||||
return hash.substring(0, Math.min(hash.length(), 32));
|
||||
}
|
||||
}
|
|
@ -25,17 +25,30 @@
|
|||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockItem;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.geyser.registry.loader.CollisionRegistryLoader;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
import org.geysermc.geyser.registry.populator.BlockRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.CustomSkullRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Holds all the block registries in Geyser.
|
||||
*/
|
||||
|
@ -57,6 +70,11 @@ public class BlockRegistries {
|
|||
*/
|
||||
public static final ArrayRegistry<BlockMapping> JAVA_BLOCKS = ArrayRegistry.create(RegistryLoaders.uninitialized());
|
||||
|
||||
/**
|
||||
* A mapped registry containing which holds block IDs to its {@link BlockCollision}.
|
||||
*/
|
||||
public static final IntMappedRegistry<BlockCollision> COLLISIONS;
|
||||
|
||||
/**
|
||||
* A mapped registry containing the Java identifiers to IDs.
|
||||
*/
|
||||
|
@ -83,11 +101,51 @@ public class BlockRegistries {
|
|||
*/
|
||||
public static final SimpleRegistry<BitSet> INTERACTIVE_MAY_BUILD = SimpleRegistry.create(RegistryLoaders.uninitialized());
|
||||
|
||||
/**
|
||||
* A registry containing all the custom blocks.
|
||||
*/
|
||||
public static final ArrayRegistry<CustomBlockData> CUSTOM_BLOCKS = ArrayRegistry.create(RegistryLoaders.empty(() -> new CustomBlockData[] {}));
|
||||
|
||||
/**
|
||||
* A registry which stores Java Ids and the custom block state it should be replaced with.
|
||||
*/
|
||||
public static final MappedRegistry<Integer, CustomBlockState, Int2ObjectMap<CustomBlockState>> CUSTOM_BLOCK_STATE_OVERRIDES = MappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A registry which stores non vanilla java blockstates and the custom block state it should be replaced with.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<JavaBlockState, CustomBlockState> NON_VANILLA_BLOCK_STATE_OVERRIDES = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A registry which stores clean Java Ids and the custom block it should be replaced with in the context of items.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<String, CustomBlockData> CUSTOM_BLOCK_ITEM_OVERRIDES = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A registry which stores Custom Block Data for extended collision boxes and the Java IDs of blocks that will have said extended collision boxes placed above them.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<CustomBlockData, Set<Integer>> EXTENDED_COLLISION_BOXES = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A registry which stores skin texture hashes to custom skull blocks.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<String, CustomSkull> CUSTOM_SKULLS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
|
||||
|
||||
static {
|
||||
BlockRegistryPopulator.populate();
|
||||
CustomSkullRegistryPopulator.populate();
|
||||
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.PRE_INIT);
|
||||
CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.DEFINITION);
|
||||
CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.NON_VANILLA_REGISTRATION);
|
||||
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_JAVA);
|
||||
COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new);
|
||||
CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.VANILLA_REGISTRATION);
|
||||
CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.CUSTOM_REGISTRATION);
|
||||
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK);
|
||||
BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.POST_INIT);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.data.game.level.event.LevelEvent;
|
|||
import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
|
@ -56,7 +55,6 @@ import org.geysermc.geyser.registry.type.EnchantmentData;
|
|||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.registry.type.ParticleMapping;
|
||||
import org.geysermc.geyser.registry.type.SoundMapping;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundInteractionTranslator;
|
||||
|
@ -68,6 +66,12 @@ import java.util.*;
|
|||
* Holds all the common registries in Geyser.
|
||||
*/
|
||||
public final class Registries {
|
||||
/**
|
||||
* A registry holding all the providers.
|
||||
* This has to be initialized first to allow extensions to access providers during other registry events.
|
||||
*/
|
||||
public static final SimpleMappedRegistry<Class<?>, ProviderSupplier> PROVIDERS = SimpleMappedRegistry.create(new IdentityHashMap<>(), ProviderRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A registry holding a CompoundTag of the known entity identifiers.
|
||||
*/
|
||||
|
@ -93,11 +97,6 @@ public final class Registries {
|
|||
*/
|
||||
public static final SimpleMappedRegistry<BlockEntityType, BlockEntityTranslator> BLOCK_ENTITIES = SimpleMappedRegistry.create("org.geysermc.geyser.translator.level.block.entity.BlockEntity", BlockEntityRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A mapped registry containing which holds block IDs to its {@link BlockCollision}.
|
||||
*/
|
||||
public static final IntMappedRegistry<BlockCollision> COLLISIONS = IntMappedRegistry.create(Pair.of("org.geysermc.geyser.translator.collision.CollisionRemapper", "mappings/collision.json"), CollisionRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A versioned registry which holds a {@link RecipeType} to a corresponding list of {@link RecipeData}.
|
||||
*/
|
||||
|
@ -144,11 +143,6 @@ public final class Registries {
|
|||
*/
|
||||
public static final VersionedRegistry<Set<PotionMixData>> POTION_MIXES;
|
||||
|
||||
/**
|
||||
* A registry holding all the
|
||||
*/
|
||||
public static final SimpleMappedRegistry<Class<?>, ProviderSupplier> PROVIDERS = SimpleMappedRegistry.create(new IdentityHashMap<>(), ProviderRegistryLoader::new);
|
||||
|
||||
/**
|
||||
* A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value.
|
||||
*/
|
||||
|
|
|
@ -79,6 +79,11 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String,
|
|||
Map<BlockCollision, BlockCollision> collisionInstances = new Object2ObjectOpenHashMap<>();
|
||||
for (int i = 0; i < blockMappings.length; i++) {
|
||||
BlockMapping blockMapping = blockMappings[i];
|
||||
if (blockMapping == null) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Missing block mapping for Java block " + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockCollision newCollision = instantiateCollision(blockMapping, annotationMap, collisionList);
|
||||
|
||||
if (newCollision != null) {
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.event.EventRegistrar;
|
||||
import org.geysermc.geyser.api.extension.Extension;
|
||||
|
@ -37,6 +43,12 @@ import org.geysermc.geyser.event.GeyserEventRegistrar;
|
|||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserJavaBlockState;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance;
|
||||
import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
||||
|
||||
|
@ -52,6 +64,14 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
|||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||
// misc
|
||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||
|
||||
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder());
|
||||
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder());
|
||||
providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.JavaBlockStateBuilder());
|
||||
providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.NonVanillaCustomBlockDataBuilder());
|
||||
providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.MaterialInstanceBuilder());
|
||||
providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.GeometryComponentBuilder());
|
||||
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
|||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
|
||||
import org.geysermc.geyser.pack.SkullResourcePackManager;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
@ -87,6 +88,12 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
resourcePacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
// Add custom skull pack
|
||||
Path skullResourcePack = SkullResourcePackManager.createResourcePack();
|
||||
if (skullResourcePack != null) {
|
||||
resourcePacks.add(skullResourcePack);
|
||||
}
|
||||
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
|
@ -146,7 +153,7 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<String, Reso
|
|||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
|
||||
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
|
||||
String contentKey = Files.exists(keyFile) ? Files.readString(keyFile, StandardCharsets.UTF_8) : "";
|
||||
|
||||
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -23,15 +23,16 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.mappings;
|
||||
package org.geysermc.geyser.registry.mappings;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.item.mappings.versions.MappingsReader;
|
||||
import org.geysermc.geyser.item.mappings.versions.MappingsReader_v1;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
|
||||
import org.geysermc.geyser.registry.mappings.versions.MappingsReader;
|
||||
import org.geysermc.geyser.registry.mappings.versions.MappingsReader_v1;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
@ -56,43 +57,88 @@ public class MappingsConfigReader {
|
|||
}
|
||||
}
|
||||
|
||||
public void loadMappingsFromJson(BiConsumer<String, CustomItemData> consumer) {
|
||||
Path customMappingsDirectory = this.customMappingsDirectory;
|
||||
if (!Files.exists(customMappingsDirectory)) {
|
||||
public boolean ensureMappingsDirectory(Path mappingsDirectory) {
|
||||
if (!Files.exists(mappingsDirectory)) {
|
||||
try {
|
||||
Files.createDirectories(customMappingsDirectory);
|
||||
Files.createDirectories(mappingsDirectory);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to create custom mappings directory", e);
|
||||
return;
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to create mappings directory", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void loadItemMappingsFromJson(BiConsumer<String, CustomItemData> consumer) {
|
||||
if (!ensureMappingsDirectory(this.customMappingsDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path[] mappingsFiles = this.getCustomMappingsFiles();
|
||||
for (Path mappingsFile : mappingsFiles) {
|
||||
this.readMappingsFromJson(mappingsFile, consumer);
|
||||
this.readItemMappingsFromJson(mappingsFile, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public void readMappingsFromJson(Path file, BiConsumer<String, CustomItemData> consumer) {
|
||||
public void loadBlockMappingsFromJson(BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
if (!ensureMappingsDirectory(this.customMappingsDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path[] mappingsFiles = this.getCustomMappingsFiles();
|
||||
for (Path mappingsFile : mappingsFiles) {
|
||||
this.readBlockMappingsFromJson(mappingsFile, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonNode getMappingsRoot(Path file) {
|
||||
JsonNode mappingsRoot;
|
||||
try {
|
||||
mappingsRoot = GeyserImpl.JSON_MAPPER.readTree(file.toFile());
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to read custom mapping file: " + file, e);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!mappingsRoot.has("format_version")) {
|
||||
GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " is missing the format version field!");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
int formatVersion = mappingsRoot.get("format_version").asInt();
|
||||
return mappingsRoot;
|
||||
}
|
||||
|
||||
public int getFormatVersion(JsonNode mappingsRoot, Path file) {
|
||||
int formatVersion = mappingsRoot.get("format_version").asInt();
|
||||
if (!this.mappingReaders.containsKey(formatVersion)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Mappings file " + file + " has an unknown format version: " + formatVersion);
|
||||
return -1;
|
||||
}
|
||||
return formatVersion;
|
||||
}
|
||||
|
||||
public void readItemMappingsFromJson(Path file, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonNode mappingsRoot = getMappingsRoot(file);
|
||||
|
||||
int formatVersion = getFormatVersion(mappingsRoot, file);
|
||||
|
||||
if (formatVersion < 0 || mappingsRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mappingReaders.get(formatVersion).readMappings(file, mappingsRoot, consumer);
|
||||
this.mappingReaders.get(formatVersion).readItemMappings(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
public void readBlockMappingsFromJson(Path file, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
JsonNode mappingsRoot = getMappingsRoot(file);
|
||||
|
||||
int formatVersion = getFormatVersion(mappingsRoot, file);
|
||||
|
||||
if (formatVersion < 0 || mappingsRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mappingReaders.get(formatVersion).readBlockMappings(file, mappingsRoot, consumer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.registry.mappings.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block components mapping, which contains custom
|
||||
* block components and a potenially null extended collision box
|
||||
*
|
||||
* @param components The components of the block
|
||||
* @param extendedCollisionBox The extended collision box of the block
|
||||
*/
|
||||
public record CustomBlockComponentsMapping(@NonNull CustomBlockComponents components, BoxComponent extendedCollisionBox) {
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.registry.mappings.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block mappings, which contain all of the
|
||||
* data required to register a custom block that overrides a group of java block
|
||||
* states.
|
||||
*
|
||||
* @param data The custom block data
|
||||
* @param states The custom block state mappings
|
||||
* @param javaIdentifier The java identifier of the block
|
||||
* @param overrideItem Whether or not the custom block should override the java item
|
||||
*/
|
||||
public record CustomBlockMapping(@NonNull CustomBlockData data, @NonNull Map<String, CustomBlockStateMapping> states, @NonNull String javaIdentifier, boolean overrideItem) {
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.registry.mappings.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block state builder mapping, which contains custom
|
||||
* block state builders and a potenially null extended collision box
|
||||
*
|
||||
* @param builder The builder of the block
|
||||
* @param extendedCollisionBox The extended collision box of the block
|
||||
*/
|
||||
public record CustomBlockStateBuilderMapping(@NonNull Function<CustomBlockState.Builder, CustomBlockState> builder, BoxComponent extendedCollisionBox) {
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.registry.mappings.util;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
|
||||
|
||||
/**
|
||||
* This class is used to store a custom block state mapping, which contains custom
|
||||
* block states and a potenially null extended collision box
|
||||
*
|
||||
* @param state The state of the block
|
||||
* @param extendedCollisionBox The extended collision box of the block
|
||||
*/
|
||||
public record CustomBlockStateMapping(@NonNull CustomBlockState state, BoxComponent extendedCollisionBox) {
|
||||
}
|
|
@ -23,20 +23,23 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.mappings.versions;
|
||||
package org.geysermc.geyser.registry.mappings.versions;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
||||
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public abstract class MappingsReader {
|
||||
public abstract void readMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer);
|
||||
public abstract void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer);
|
||||
public abstract void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer);
|
||||
|
||||
public abstract CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException;
|
||||
public abstract CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException;
|
||||
|
||||
protected CustomRenderOffsets fromJsonNode(JsonNode node) {
|
||||
if (node == null || !node.isObject()) {
|
|
@ -0,0 +1,713 @@
|
|||
/*
|
||||
* 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.registry.mappings.versions;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.github.steveice10.mc.protocol.data.game.Identifier;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.component.*;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockComponentsMapping;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockMapping;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockStateBuilderMapping;
|
||||
import org.geysermc.geyser.registry.mappings.util.CustomBlockStateMapping;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A class responsible for reading custom item and block mappings from a JSON file
|
||||
*/
|
||||
public class MappingsReader_v1 extends MappingsReader {
|
||||
@Override
|
||||
public void readItemMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
this.readItemMappingsV1(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read item block from a JSON node
|
||||
*
|
||||
* @param file The path to the file
|
||||
* @param mappingsRoot The {@link JsonNode} containing the mappings
|
||||
* @param consumer The consumer to accept the mappings
|
||||
* @see #readBlockMappingsV1(Path, JsonNode, BiConsumer)
|
||||
*/
|
||||
@Override
|
||||
public void readBlockMappings(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
this.readBlockMappingsV1(file, mappingsRoot, consumer);
|
||||
}
|
||||
|
||||
public void readItemMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomItemData> consumer) {
|
||||
JsonNode itemsNode = mappingsRoot.get("items");
|
||||
|
||||
if (itemsNode != null && itemsNode.isObject()) {
|
||||
itemsNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isArray()) {
|
||||
entry.getValue().forEach(data -> {
|
||||
try {
|
||||
CustomItemData customItemData = this.readItemMappingEntry(data);
|
||||
consumer.accept(entry.getKey(), customItemData);
|
||||
} catch (InvalidCustomMappingsFileException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error in registering items for custom mapping file: " + file.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read block mappings from a JSON node
|
||||
*
|
||||
* @param file The path to the file
|
||||
* @param mappingsRoot The {@link JsonNode} containing the mappings
|
||||
* @param consumer The consumer to accept the mappings
|
||||
* @see #readBlockMappings(Path, JsonNode, BiConsumer)
|
||||
*/
|
||||
public void readBlockMappingsV1(Path file, JsonNode mappingsRoot, BiConsumer<String, CustomBlockMapping> consumer) {
|
||||
JsonNode blocksNode = mappingsRoot.get("blocks");
|
||||
|
||||
if (blocksNode != null && blocksNode.isObject()) {
|
||||
blocksNode.fields().forEachRemaining(entry -> {
|
||||
if (entry.getValue().isObject()) {
|
||||
try {
|
||||
String identifier = Identifier.formalize(entry.getKey());
|
||||
CustomBlockMapping customBlockMapping = this.readBlockMappingEntry(identifier, entry.getValue());
|
||||
consumer.accept(identifier, customBlockMapping);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error in registering blocks for custom mapping file: " + file.toString());
|
||||
GeyserImpl.getInstance().getLogger().error("due to entry: " + entry, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private CustomItemOptions readItemCustomItemOptions(JsonNode node) {
|
||||
CustomItemOptions.Builder customItemOptions = CustomItemOptions.builder();
|
||||
|
||||
JsonNode customModelData = node.get("custom_model_data");
|
||||
if (customModelData != null && customModelData.isInt()) {
|
||||
customItemOptions.customModelData(customModelData.asInt());
|
||||
}
|
||||
|
||||
JsonNode damagePredicate = node.get("damage_predicate");
|
||||
if (damagePredicate != null && damagePredicate.isInt()) {
|
||||
customItemOptions.damagePredicate(damagePredicate.asInt());
|
||||
}
|
||||
|
||||
JsonNode unbreakable = node.get("unbreakable");
|
||||
if (unbreakable != null && unbreakable.isBoolean()) {
|
||||
customItemOptions.unbreakable(unbreakable.asBoolean());
|
||||
}
|
||||
|
||||
JsonNode defaultItem = node.get("default");
|
||||
if (defaultItem != null && defaultItem.isBoolean()) {
|
||||
customItemOptions.defaultItem(defaultItem.asBoolean());
|
||||
}
|
||||
|
||||
return customItemOptions.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomItemData readItemMappingEntry(JsonNode node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null || !node.isObject()) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid item mappings entry");
|
||||
}
|
||||
|
||||
JsonNode name = node.get("name");
|
||||
if (name == null || !name.isTextual() || name.asText().isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("An item entry has no name");
|
||||
}
|
||||
|
||||
CustomItemData.Builder customItemData = CustomItemData.builder()
|
||||
.name(name.asText())
|
||||
.customItemOptions(this.readItemCustomItemOptions(node));
|
||||
|
||||
//The next entries are optional
|
||||
if (node.has("display_name")) {
|
||||
customItemData.displayName(node.get("display_name").asText());
|
||||
}
|
||||
|
||||
if (node.has("icon")) {
|
||||
customItemData.icon(node.get("icon").asText());
|
||||
}
|
||||
|
||||
if (node.has("allow_offhand")) {
|
||||
customItemData.allowOffhand(node.get("allow_offhand").asBoolean());
|
||||
}
|
||||
|
||||
if (node.has("display_handheld")) {
|
||||
customItemData.displayHandheld(node.get("display_handheld").asBoolean());
|
||||
}
|
||||
|
||||
if (node.has("texture_size")) {
|
||||
customItemData.textureSize(node.get("texture_size").asInt());
|
||||
}
|
||||
|
||||
if (node.has("render_offsets")) {
|
||||
JsonNode tmpNode = node.get("render_offsets");
|
||||
|
||||
customItemData.renderOffsets(fromJsonNode(tmpNode));
|
||||
}
|
||||
|
||||
return customItemData.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a block mapping entry from a JSON node and Java identifier
|
||||
*
|
||||
* @param identifier The Java identifier of the block
|
||||
* @param node The {@link JsonNode} containing the block mapping entry
|
||||
* @return The {@link CustomBlockMapping} record to be read by {@link org.geysermc.geyser.registry.populator.CustomBlockRegistryPopulator}
|
||||
* @throws InvalidCustomMappingsFileException If the JSON node is invalid
|
||||
*/
|
||||
@Override
|
||||
public CustomBlockMapping readBlockMappingEntry(String identifier, JsonNode node) throws InvalidCustomMappingsFileException {
|
||||
if (node == null || !node.isObject()) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid block mappings entry:" + node);
|
||||
}
|
||||
|
||||
String name = node.get("name").asText();
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("A block entry has no name");
|
||||
}
|
||||
|
||||
boolean includedInCreativeInventory = node.has("included_in_creative_inventory") && node.get("included_in_creative_inventory").asBoolean();
|
||||
|
||||
CreativeCategory creativeCategory = CreativeCategory.NONE;
|
||||
if (node.has("creative_category")) {
|
||||
String categoryName = node.get("creative_category").asText();
|
||||
try {
|
||||
creativeCategory = CreativeCategory.valueOf(categoryName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidCustomMappingsFileException("Invalid creative category \"" + categoryName + "\" for block \"" + name + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
String creativeGroup = "";
|
||||
if (node.has("creative_group")) {
|
||||
creativeGroup = node.get("creative_group").asText();
|
||||
}
|
||||
|
||||
// If this is true, we will only register the states the user has specified rather than all the possible block states
|
||||
boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean();
|
||||
|
||||
// Create the data for the overall block
|
||||
CustomBlockData.Builder customBlockDataBuilder = new CustomBlockDataBuilder()
|
||||
.name(name)
|
||||
.includedInCreativeInventory(includedInCreativeInventory)
|
||||
.creativeCategory(creativeCategory)
|
||||
.creativeGroup(creativeGroup);
|
||||
|
||||
if (BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().containsKey(identifier)) {
|
||||
// There is only one Java block state to override
|
||||
CustomBlockComponentsMapping componentsMapping = createCustomBlockComponentsMapping(node, identifier, name);
|
||||
CustomBlockData blockData = customBlockDataBuilder
|
||||
.components(componentsMapping.components())
|
||||
.build();
|
||||
return new CustomBlockMapping(blockData, Map.of(identifier, new CustomBlockStateMapping(blockData.defaultBlockState(), componentsMapping.extendedCollisionBox())), identifier, !onlyOverrideStates);
|
||||
}
|
||||
|
||||
Map<String, CustomBlockComponentsMapping> componentsMap = new LinkedHashMap<>();
|
||||
|
||||
JsonNode stateOverrides = node.get("state_overrides");
|
||||
if (stateOverrides != null && stateOverrides.isObject()) {
|
||||
// Load components for specific Java block states
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = stateOverrides.fields();
|
||||
while (fields.hasNext()) {
|
||||
Map.Entry<String, JsonNode> overrideEntry = fields.next();
|
||||
String state = identifier + "[" + overrideEntry.getKey() + "]";
|
||||
if (!BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().containsKey(state)) {
|
||||
throw new InvalidCustomMappingsFileException("Unknown Java block state: " + state + " for state_overrides.");
|
||||
}
|
||||
componentsMap.put(state, createCustomBlockComponentsMapping(overrideEntry.getValue(), state, name));
|
||||
}
|
||||
}
|
||||
if (componentsMap.isEmpty() && onlyOverrideStates) {
|
||||
throw new InvalidCustomMappingsFileException("Block entry for " + identifier + " has only_override_states set to true, but has no state_overrides.");
|
||||
}
|
||||
|
||||
if (!onlyOverrideStates) {
|
||||
// Create components for any remaining Java block states
|
||||
BlockRegistries.JAVA_IDENTIFIER_TO_ID.get().keySet()
|
||||
.stream()
|
||||
.filter(s -> s.startsWith(identifier + "["))
|
||||
.filter(Predicate.not(componentsMap::containsKey))
|
||||
.forEach(state -> componentsMap.put(state, createCustomBlockComponentsMapping(null, state, name)));
|
||||
}
|
||||
|
||||
if (componentsMap.isEmpty()) {
|
||||
throw new InvalidCustomMappingsFileException("Unknown Java block: " + identifier);
|
||||
}
|
||||
|
||||
// We pass in the first state and just use the hitbox from that as the default
|
||||
// Each state will have its own so this is fine
|
||||
String firstState = componentsMap.keySet().iterator().next();
|
||||
CustomBlockComponentsMapping firstComponentsMapping = createCustomBlockComponentsMapping(node, firstState, name);
|
||||
customBlockDataBuilder.components(firstComponentsMapping.components());
|
||||
|
||||
return createCustomBlockMapping(customBlockDataBuilder, componentsMap, identifier, !onlyOverrideStates);
|
||||
}
|
||||
|
||||
private CustomBlockMapping createCustomBlockMapping(CustomBlockData.Builder customBlockDataBuilder, Map<String, CustomBlockComponentsMapping> componentsMap, String identifier, boolean overrideItem) {
|
||||
Map<String, LinkedHashSet<String>> valuesMap = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
List<CustomBlockPermutation> permutations = new ArrayList<>();
|
||||
Map<String, CustomBlockStateBuilderMapping> blockStateBuilders = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
// For each Java block state, extract the property values, create a CustomBlockPermutation,
|
||||
// and a CustomBlockState builder
|
||||
for (Map.Entry<String, CustomBlockComponentsMapping> entry : componentsMap.entrySet()) {
|
||||
String state = entry.getKey();
|
||||
String[] pairs = splitStateString(state);
|
||||
|
||||
String[] conditions = new String[pairs.length];
|
||||
Function<CustomBlockState.Builder, CustomBlockState.Builder> blockStateBuilder = Function.identity();
|
||||
|
||||
for (int i = 0; i < pairs.length; i++) {
|
||||
String[] parts = pairs[i].split("=");
|
||||
String property = parts[0];
|
||||
String value = parts[1];
|
||||
|
||||
valuesMap.computeIfAbsent(property, k -> new LinkedHashSet<>())
|
||||
.add(value);
|
||||
|
||||
conditions[i] = String.format("q.block_property('%s') == '%s'", property, value);
|
||||
blockStateBuilder = blockStateBuilder.andThen(builder -> builder.stringProperty(property, value));
|
||||
}
|
||||
|
||||
permutations.add(new CustomBlockPermutation(entry.getValue().components(), String.join(" && ", conditions)));
|
||||
blockStateBuilders.put(state, new CustomBlockStateBuilderMapping(blockStateBuilder.andThen(CustomBlockState.Builder::build), entry.getValue().extendedCollisionBox()));
|
||||
}
|
||||
|
||||
valuesMap.forEach((key, value) -> customBlockDataBuilder.stringProperty(key, new ArrayList<>(value)));
|
||||
|
||||
CustomBlockData customBlockData = customBlockDataBuilder
|
||||
.permutations(permutations)
|
||||
.build();
|
||||
// Build CustomBlockStates for each Java block state we wish to override
|
||||
Map<String, CustomBlockStateMapping> states = blockStateBuilders.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> new CustomBlockStateMapping(e.getValue().builder().apply(customBlockData.blockStateBuilder()), e.getValue().extendedCollisionBox())));
|
||||
|
||||
return new CustomBlockMapping(customBlockData, states, identifier, overrideItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CustomBlockComponents} object for the passed state override or base block node, Java block state identifier, and custom block name
|
||||
*
|
||||
* @param node the state override or base block {@link JsonNode}
|
||||
* @param stateKey the Java block state identifier
|
||||
* @param name the name of the custom block
|
||||
* @return the {@link CustomBlockComponents} object
|
||||
*/
|
||||
private CustomBlockComponentsMapping createCustomBlockComponentsMapping(JsonNode node, String stateKey, String name) {
|
||||
// This is needed to find the correct selection box for the given block
|
||||
int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1);
|
||||
BoxComponent boxComponent = createBoxComponent(id);
|
||||
BoxComponent extendedBoxComponent = createExtendedBoxComponent(id);
|
||||
CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder()
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(boxComponent);
|
||||
|
||||
if (node == null) {
|
||||
// No other components were defined
|
||||
return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent);
|
||||
}
|
||||
|
||||
BoxComponent selectionBox = createBoxComponent(node.get("selection_box"));
|
||||
if (selectionBox != null) {
|
||||
builder.selectionBox(selectionBox);
|
||||
}
|
||||
BoxComponent collisionBox = createBoxComponent(node.get("collision_box"));
|
||||
if (collisionBox != null) {
|
||||
builder.collisionBox(collisionBox);
|
||||
}
|
||||
BoxComponent extendedCollisionBox = createBoxComponent(node.get("extended_collision_box"));
|
||||
if (extendedCollisionBox != null) {
|
||||
extendedBoxComponent = extendedCollisionBox;
|
||||
}
|
||||
|
||||
|
||||
// We set this to max value by default so that we may dictate the correct destroy time ourselves
|
||||
float destructibleByMining = Float.MAX_VALUE;
|
||||
if (node.has("destructible_by_mining")) {
|
||||
destructibleByMining = node.get("destructible_by_mining").floatValue();
|
||||
}
|
||||
builder.destructibleByMining(destructibleByMining);
|
||||
|
||||
if (node.has("geometry")) {
|
||||
if (node.get("geometry").isTextual()) {
|
||||
builder.geometry(new GeometryComponentBuilder()
|
||||
.identifier(node.get("geometry").asText())
|
||||
.build());
|
||||
} else {
|
||||
JsonNode geometry = node.get("geometry");
|
||||
GeometryComponentBuilder geometryBuilder = new GeometryComponentBuilder();
|
||||
if (geometry.has("identifier")) {
|
||||
geometryBuilder.identifier(geometry.get("identifier").asText());
|
||||
}
|
||||
if (geometry.has("bone_visibility")) {
|
||||
JsonNode boneVisibility = geometry.get("bone_visibility");
|
||||
if (boneVisibility.isObject()) {
|
||||
Map<String, String> boneVisibilityMap = new Object2ObjectOpenHashMap<>();
|
||||
boneVisibility.fields().forEachRemaining(entry -> {
|
||||
boneVisibilityMap.put(entry.getKey(), entry.getValue().isBoolean() ? (entry.getValue().asBoolean() ? "1" : "0") : entry.getValue().asText());
|
||||
});
|
||||
geometryBuilder.boneVisibility(boneVisibilityMap);
|
||||
}
|
||||
}
|
||||
builder.geometry(geometryBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
String displayName = name;
|
||||
if (node.has("display_name")) {
|
||||
displayName = node.get("display_name").asText();
|
||||
}
|
||||
builder.displayName(displayName);
|
||||
|
||||
if (node.has("friction")) {
|
||||
builder.friction(node.get("friction").floatValue());
|
||||
}
|
||||
|
||||
if (node.has("light_emission")) {
|
||||
builder.lightEmission(node.get("light_emission").asInt());
|
||||
}
|
||||
|
||||
if (node.has("light_dampening")) {
|
||||
builder.lightDampening(node.get("light_dampening").asInt());
|
||||
}
|
||||
|
||||
boolean placeAir = true;
|
||||
if (node.has("place_air")) {
|
||||
placeAir = node.get("place_air").asBoolean();
|
||||
}
|
||||
builder.placeAir(placeAir);
|
||||
|
||||
if (node.has("transformation")) {
|
||||
JsonNode transformation = node.get("transformation");
|
||||
|
||||
int rotationX = 0;
|
||||
int rotationY = 0;
|
||||
int rotationZ = 0;
|
||||
float scaleX = 1;
|
||||
float scaleY = 1;
|
||||
float scaleZ = 1;
|
||||
float transformX = 0;
|
||||
float transformY = 0;
|
||||
float transformZ = 0;
|
||||
|
||||
if (transformation.has("rotation")) {
|
||||
JsonNode rotation = transformation.get("rotation");
|
||||
rotationX = rotation.get(0).asInt();
|
||||
rotationY = rotation.get(1).asInt();
|
||||
rotationZ = rotation.get(2).asInt();
|
||||
}
|
||||
if (transformation.has("scale")) {
|
||||
JsonNode scale = transformation.get("scale");
|
||||
scaleX = scale.get(0).floatValue();
|
||||
scaleY = scale.get(1).floatValue();
|
||||
scaleZ = scale.get(2).floatValue();
|
||||
}
|
||||
if (transformation.has("translation")) {
|
||||
JsonNode translation = transformation.get("translation");
|
||||
transformX = translation.get(0).floatValue();
|
||||
transformY = translation.get(1).floatValue();
|
||||
transformZ = translation.get(2).floatValue();
|
||||
}
|
||||
builder.transformation(new TransformationComponent(rotationX, rotationY, rotationZ, scaleX, scaleY, scaleZ, transformX, transformY, transformZ));
|
||||
}
|
||||
|
||||
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()) {
|
||||
materialInstances.fields().forEachRemaining(entry -> {
|
||||
String key = entry.getKey();
|
||||
JsonNode value = entry.getValue();
|
||||
if (value.isObject()) {
|
||||
MaterialInstance materialInstance = createMaterialInstanceComponent(value, name);
|
||||
builder.materialInstance(key, materialInstance);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
List<PlacementConditions> filter = createPlacementFilterComponent(conditions);
|
||||
builder.placementFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tags can be applied so that blocks will match return true when queried for the tag
|
||||
// Potentially useful for resource pack creators
|
||||
// Ideally we could programmatically extract the tags here https://wiki.bedrock.dev/blocks/block-tags.html
|
||||
// This would let us automatically apply the correct vanilla tags to blocks
|
||||
// However, its worth noting that vanilla tools do not currently honor these tags anyway
|
||||
if (node.get("tags") instanceof ArrayNode tags) {
|
||||
Set<String> tagsSet = new ObjectOpenHashSet<>();
|
||||
tags.forEach(tag -> tagsSet.add(tag.asText()));
|
||||
builder.tags(tagsSet);
|
||||
}
|
||||
|
||||
return new CustomBlockComponentsMapping(builder.build(), extendedBoxComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BoxComponent} based on a Java block's collision with provided bounds and offsets
|
||||
*
|
||||
* @param javaId the block's Java ID
|
||||
* @param heightTranslation the height translation of the box
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private BoxComponent createBoxComponent(int javaId, float heightTranslation) {
|
||||
// Some blocks (e.g. plants) have no collision box
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(javaId);
|
||||
if (blockCollision == null || blockCollision.getBoundingBoxes().length == 0) {
|
||||
return BoxComponent.emptyBox();
|
||||
}
|
||||
|
||||
float minX = 5;
|
||||
float minY = 5;
|
||||
float minZ = 5;
|
||||
float maxX = -5;
|
||||
float maxY = -5;
|
||||
float maxZ = -5;
|
||||
for (BoundingBox boundingBox : blockCollision.getBoundingBoxes()) {
|
||||
double offsetX = boundingBox.getSizeX() * 0.5;
|
||||
double offsetY = boundingBox.getSizeY() * 0.5;
|
||||
double offsetZ = boundingBox.getSizeZ() * 0.5;
|
||||
|
||||
minX = Math.min(minX, (float) (boundingBox.getMiddleX() - offsetX));
|
||||
minY = Math.min(minY, (float) (boundingBox.getMiddleY() - offsetY));
|
||||
minZ = Math.min(minZ, (float) (boundingBox.getMiddleZ() - offsetZ));
|
||||
|
||||
maxX = Math.max(maxX, (float) (boundingBox.getMiddleX() + offsetX));
|
||||
maxY = Math.max(maxY, (float) (boundingBox.getMiddleY() + offsetY));
|
||||
maxZ = Math.max(maxZ, (float) (boundingBox.getMiddleZ() + offsetZ));
|
||||
}
|
||||
minX = MathUtils.clamp(minX, 0, 1);
|
||||
minY = MathUtils.clamp(minY + heightTranslation, 0, 1);
|
||||
minZ = MathUtils.clamp(minZ, 0, 1);
|
||||
maxX = MathUtils.clamp(maxX, 0, 1);
|
||||
maxY = MathUtils.clamp(maxY + heightTranslation, 0, 1);
|
||||
maxZ = MathUtils.clamp(maxZ, 0, 1);
|
||||
|
||||
return new BoxComponent(
|
||||
16 * (1 - maxX) - 8, // For some odd reason X is mirrored on Bedrock
|
||||
16 * minY,
|
||||
16 * minZ - 8,
|
||||
16 * (maxX - minX),
|
||||
16 * (maxY - minY),
|
||||
16 * (maxZ - minZ)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BoxComponent} based on a Java block's collision
|
||||
*
|
||||
* @param javaId the block's Java ID
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private BoxComponent createBoxComponent(int javaId) {
|
||||
return createBoxComponent(javaId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link BoxComponent} for an extended collision box based on a Java block's collision
|
||||
*
|
||||
* @param javaId the block's Java ID
|
||||
* @return the {@link BoxComponent} or null if the block's collision box would not exceed 16 y units
|
||||
*/
|
||||
private BoxComponent createExtendedBoxComponent(int javaId) {
|
||||
BlockCollision blockCollision = BlockUtils.getCollision(javaId);
|
||||
if (blockCollision == null) {
|
||||
return null;
|
||||
}
|
||||
for (BoundingBox box : blockCollision.getBoundingBoxes()) {
|
||||
double maxY = 0.5 * box.getSizeY() + box.getMiddleY();
|
||||
if (maxY > 1) {
|
||||
return createBoxComponent(javaId, -1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BoxComponent} from a JSON Node
|
||||
*
|
||||
* @param node the JSON node
|
||||
* @return the {@link BoxComponent}
|
||||
*/
|
||||
private BoxComponent createBoxComponent(JsonNode node) {
|
||||
if (node != null && node.isObject()) {
|
||||
if (node.has("origin") && node.has("size")) {
|
||||
JsonNode origin = node.get("origin");
|
||||
float originX = origin.get(0).floatValue();
|
||||
float originY = origin.get(1).floatValue();
|
||||
float originZ = origin.get(2).floatValue();
|
||||
|
||||
JsonNode size = node.get("size");
|
||||
float sizeX = size.get(0).floatValue();
|
||||
float sizeY = size.get(1).floatValue();
|
||||
float sizeZ = size.get(2).floatValue();
|
||||
|
||||
return new BoxComponent(originX, originY, originZ, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link MaterialInstance} for the passed material instance node and custom block name
|
||||
* The name is used as a fallback if no texture is provided by the node
|
||||
*
|
||||
* @param node the material instance node
|
||||
* @param name the custom block name
|
||||
* @return the {@link MaterialInstance}
|
||||
*/
|
||||
private MaterialInstance createMaterialInstanceComponent(JsonNode node, String name) {
|
||||
// Set default values, and use what the user provides if they have provided something
|
||||
String texture = name;
|
||||
if (node.has("texture")) {
|
||||
texture = node.get("texture").asText();
|
||||
}
|
||||
|
||||
String renderMethod = "opaque";
|
||||
if (node.has("render_method")) {
|
||||
renderMethod = node.get("render_method").asText();
|
||||
}
|
||||
|
||||
boolean faceDimming = true;
|
||||
if (node.has("face_dimming")) {
|
||||
faceDimming = node.get("face_dimming").asBoolean();
|
||||
}
|
||||
|
||||
boolean ambientOcclusion = true;
|
||||
if (node.has("ambient_occlusion")) {
|
||||
ambientOcclusion = node.get("ambient_occlusion").asBoolean();
|
||||
}
|
||||
|
||||
return new MaterialInstanceBuilder()
|
||||
.texture(texture)
|
||||
.renderMethod(renderMethod)
|
||||
.faceDimming(faceDimming)
|
||||
.ambientOcclusion(ambientOcclusion)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of {@link PlacementConditions} for the passed conditions node
|
||||
*
|
||||
* @param node the conditions node
|
||||
* @return the list of {@link PlacementConditions}
|
||||
*/
|
||||
private List<PlacementConditions> createPlacementFilterComponent(JsonNode node) {
|
||||
List<PlacementConditions> conditions = new ArrayList<>();
|
||||
|
||||
// The structure of the placement filter component is the most complex of the current components
|
||||
// Each condition effectively separated into two arrays: one of allowed faces, and one of blocks/block Molang queries
|
||||
node.forEach(condition -> {
|
||||
Set<Face> faces = EnumSet.noneOf(Face.class);
|
||||
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<String, BlockFilterType> 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 PlacementConditions(faces, blockFilters));
|
||||
});
|
||||
|
||||
return conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the given java state identifier into an array of property=value pairs
|
||||
*
|
||||
* @param state the java state identifier
|
||||
* @return the array of property=value pairs
|
||||
*/
|
||||
private String[] splitStateString(String state) {
|
||||
int openBracketIndex = state.indexOf("[");
|
||||
|
||||
String states = state.substring(openBracketIndex + 1, state.length() - 1);
|
||||
return states.split(",");
|
||||
}
|
||||
|
||||
}
|
|
@ -30,12 +30,19 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Interner;
|
||||
import com.google.common.collect.Interners;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.cloudburstmc.nbt.*;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.PistonBehavior;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
|
@ -46,6 +53,7 @@ import org.geysermc.geyser.util.BlockUtils;
|
|||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
@ -54,67 +62,74 @@ import java.util.zip.GZIPInputStream;
|
|||
* Populates the block registries.
|
||||
*/
|
||||
public final class BlockRegistryPopulator {
|
||||
/**
|
||||
* The stage of population
|
||||
*/
|
||||
public enum Stage {
|
||||
PRE_INIT,
|
||||
INIT_JAVA,
|
||||
INIT_BEDROCK,
|
||||
POST_INIT;
|
||||
}
|
||||
|
||||
public static void populate(Stage stage) {
|
||||
switch (stage) {
|
||||
case PRE_INIT -> { nullifyBlocksNode(); }
|
||||
case INIT_JAVA -> { registerJavaBlocks(); }
|
||||
case INIT_BEDROCK -> { registerBedrockBlocks(); }
|
||||
case POST_INIT -> { nullifyBlocksNode(); }
|
||||
default -> { throw new IllegalArgumentException("Unknown stage: " + stage); }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the raw blocks JSON until it is no longer needed.
|
||||
*/
|
||||
private static JsonNode BLOCKS_JSON;
|
||||
private static int minCustomRuntimeID = -1;
|
||||
private static int maxCustomRuntimeID = -1;
|
||||
private static int javaBlocksSize = -1;
|
||||
|
||||
public static void populate() {
|
||||
registerJavaBlocks();
|
||||
registerBedrockBlocks();
|
||||
|
||||
private static void nullifyBlocksNode() {
|
||||
BLOCKS_JSON = null;
|
||||
}
|
||||
|
||||
private static void registerBedrockBlocks() {
|
||||
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
// We are using mappings that directly support 1.20, so this maps it back to 1.19.80
|
||||
BiFunction<String, NbtMapBuilder, String> legacyMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.endsWith("pumpkin")) {
|
||||
String direction = statesBuilder.remove("minecraft:cardinal_direction").toString();
|
||||
statesBuilder.putInt("direction", switch (direction) {
|
||||
case "north" -> 2;
|
||||
case "east" -> 3;
|
||||
case "west" -> 1;
|
||||
default -> 0; // south
|
||||
});
|
||||
} else if (bedrockIdentifier.endsWith("carpet") && !bedrockIdentifier.startsWith("minecraft:moss")) {
|
||||
String color = bedrockIdentifier.replace("minecraft:", "").replace("_carpet", "");
|
||||
if (color.equals("light_gray")) {
|
||||
color = "silver";
|
||||
// adapt 1.20 mappings to 1.20.10+
|
||||
BiFunction<String, NbtMapBuilder, String> concreteAndShulkerBoxMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.equals("minecraft:concrete")) {
|
||||
String color = (String) statesBuilder.remove("color");
|
||||
if (color.equals("silver")) {
|
||||
color = "light_gray";
|
||||
}
|
||||
statesBuilder.putString("color", color);
|
||||
return "minecraft:carpet";
|
||||
} else if (bedrockIdentifier.equals("minecraft:sniffer_egg")) {
|
||||
statesBuilder.remove("cracked_state");
|
||||
return "minecraft:dragon_egg";
|
||||
} else if (bedrockIdentifier.endsWith("coral")) {
|
||||
statesBuilder.putString("coral_color", "blue"); // all blue
|
||||
statesBuilder.putBoolean("dead_bit", bedrockIdentifier.startsWith("minecraft:dead"));
|
||||
return "minecraft:coral";
|
||||
} else if (bedrockIdentifier.endsWith("sculk_sensor")) {
|
||||
int phase = (int) statesBuilder.remove("sculk_sensor_phase");
|
||||
statesBuilder.putBoolean("powered_bit", phase != 0);
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_plant")) {
|
||||
statesBuilder.putString("double_plant_type", "sunflower");
|
||||
return "minecraft:double_plant";
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_crop")) {
|
||||
statesBuilder.remove("growth");
|
||||
if (((byte) statesBuilder.remove("upper_block_bit")) == 1){
|
||||
statesBuilder.putString("flower_type", "orchid");
|
||||
return "minecraft:red_flower"; // top
|
||||
}
|
||||
statesBuilder.putBoolean("update_bit", false);
|
||||
return "minecraft:flower_pot"; // bottom
|
||||
return "minecraft:" + color + "_concrete";
|
||||
}
|
||||
if (bedrockIdentifier.equals("minecraft:shulker_box")) {
|
||||
String color = (String) statesBuilder.remove("color");
|
||||
if (color.equals("silver")) {
|
||||
color = "light_gray";
|
||||
}
|
||||
return "minecraft:" + color + "_shulker_box";
|
||||
}
|
||||
if (bedrockIdentifier.equals("minecraft:observer")) {
|
||||
int direction = (int) statesBuilder.remove("facing_direction");
|
||||
statesBuilder.putString("minecraft:facing_direction", switch (direction) {
|
||||
case 0 -> "down";
|
||||
case 1 -> "up";
|
||||
case 2 -> "north";
|
||||
case 3 -> "south";
|
||||
case 4 -> "west";
|
||||
default -> "east";
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
|
||||
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), legacyMapper)
|
||||
.put(ObjectIntPair.of("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_20_10", Bedrock_v594.CODEC.getProtocolVersion()), concreteAndShulkerBoxMapper)
|
||||
.build();
|
||||
|
||||
// We can keep this strong as nothing should be garbage collected
|
||||
|
@ -122,36 +137,83 @@ public final class BlockRegistryPopulator {
|
|||
Interner<NbtMap> statesInterner = Interners.newStrongInterner();
|
||||
|
||||
for (Map.Entry<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : blockMappers.entrySet()) {
|
||||
NbtList<NbtMap> blocksTag;
|
||||
int protocolVersion = palette.getKey().valueInt();
|
||||
List<NbtMap> vanillaBlockStates;
|
||||
List<NbtMap> blockStates;
|
||||
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key()));
|
||||
NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) {
|
||||
NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) {
|
||||
NbtMap blockPalette = (NbtMap) nbtInputStream.readTag();
|
||||
blocksTag = (NbtList<NbtMap>) blockPalette.getList("blocks", NbtType.COMPOUND);
|
||||
|
||||
vanillaBlockStates = new ArrayList<>(blockPalette.getList("blocks", NbtType.COMPOUND));
|
||||
for (int i = 0; i < vanillaBlockStates.size(); i++) {
|
||||
NbtMapBuilder builder = vanillaBlockStates.get(i).toBuilder();
|
||||
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
||||
builder.remove("network_id"); // Added in 1.19.80 - ????
|
||||
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
||||
vanillaBlockStates.set(i, builder.build());
|
||||
}
|
||||
|
||||
blockStates = new ArrayList<>(vanillaBlockStates);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to get blocks from runtime block states", e);
|
||||
}
|
||||
int stateVersion = vanillaBlockStates.get(0).getInt("version");
|
||||
|
||||
List<BlockPropertyData> customBlockProperties = new ArrayList<>();
|
||||
List<NbtMap> customBlockStates = new ArrayList<>();
|
||||
List<CustomBlockState> customExtBlockStates = new ArrayList<>();
|
||||
int[] remappedVanillaIds = new int[0];
|
||||
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
||||
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
|
||||
customBlockProperties.add(CustomBlockRegistryPopulator.generateBlockPropertyData(customBlock, protocolVersion));
|
||||
CustomBlockRegistryPopulator.generateCustomBlockStates(customBlock, customBlockStates, customExtBlockStates, stateVersion);
|
||||
}
|
||||
blockStates.addAll(customBlockStates);
|
||||
GeyserImpl.getInstance().getLogger().debug("Added " + customBlockStates.size() + " custom block states to v" + protocolVersion + " palette.");
|
||||
|
||||
// The palette is sorted by the FNV1 64-bit hash of the name
|
||||
blockStates.sort((a, b) -> Long.compareUnsigned(fnv164(a.getString("name")), fnv164(b.getString("name"))));
|
||||
}
|
||||
|
||||
// New since 1.16.100 - find the block runtime ID by the order given to us in the block palette,
|
||||
// as we no longer send a block palette
|
||||
Object2ObjectMap<NbtMap, GeyserBedrockBlock> blockStateOrderedMap = new Object2ObjectOpenHashMap<>(blocksTag.size());
|
||||
GeyserBedrockBlock[] bedrockRuntimeMap = new GeyserBedrockBlock[blocksTag.size()];
|
||||
|
||||
int stateVersion = -1;
|
||||
for (int i = 0; i < blocksTag.size(); i++) {
|
||||
NbtMapBuilder builder = blocksTag.get(i).toBuilder();
|
||||
builder.remove("name_hash"); // Quick workaround - was added in 1.19.20
|
||||
builder.remove("network_id"); // Added in 1.19.80 - ????
|
||||
builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states")));
|
||||
NbtMap tag = builder.build();
|
||||
Object2ObjectMap<NbtMap, GeyserBedrockBlock> blockStateOrderedMap = new Object2ObjectOpenHashMap<>(blockStates.size());
|
||||
GeyserBedrockBlock[] bedrockRuntimeMap = new GeyserBedrockBlock[blockStates.size()];
|
||||
for (int i = 0; i < blockStates.size(); i++) {
|
||||
NbtMap tag = blockStates.get(i);
|
||||
if (blockStateOrderedMap.containsKey(tag)) {
|
||||
throw new AssertionError("Duplicate block states in Bedrock palette: " + tag);
|
||||
}
|
||||
GeyserBedrockBlock block = new GeyserBedrockBlock(i, tag);
|
||||
blockStateOrderedMap.put(tag, block);
|
||||
bedrockRuntimeMap[i] = block;
|
||||
if (stateVersion == -1) {
|
||||
stateVersion = tag.getInt("version");
|
||||
}
|
||||
|
||||
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions = Object2ObjectMaps.emptyMap();
|
||||
Int2ObjectMap<GeyserBedrockBlock> extendedCollisionBoxes = new Int2ObjectOpenHashMap<>();
|
||||
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
||||
customBlockStateDefinitions = new Object2ObjectOpenHashMap<>(customExtBlockStates.size());
|
||||
for (int i = 0; i < customExtBlockStates.size(); i++) {
|
||||
NbtMap tag = customBlockStates.get(i);
|
||||
CustomBlockState blockState = customExtBlockStates.get(i);
|
||||
GeyserBedrockBlock bedrockBlock = blockStateOrderedMap.get(tag);
|
||||
customBlockStateDefinitions.put(blockState, bedrockBlock);
|
||||
|
||||
Set<Integer> extendedCollisionjavaIds = BlockRegistries.EXTENDED_COLLISION_BOXES.getOrDefault(blockState.block(), null);
|
||||
if (extendedCollisionjavaIds != null) {
|
||||
for (int javaId : extendedCollisionjavaIds) {
|
||||
extendedCollisionBoxes.put(javaId, bedrockBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remappedVanillaIds = new int[vanillaBlockStates.size()];
|
||||
for (int i = 0; i < vanillaBlockStates.size(); i++) {
|
||||
GeyserBedrockBlock bedrockBlock = blockStateOrderedMap.get(vanillaBlockStates.get(i));
|
||||
remappedVanillaIds[i] = bedrockBlock != null ? bedrockBlock.getRuntimeId() : -1;
|
||||
}
|
||||
}
|
||||
|
||||
int javaRuntimeId = -1;
|
||||
|
||||
GeyserBedrockBlock airDefinition = null;
|
||||
|
@ -162,7 +224,8 @@ public final class BlockRegistryPopulator {
|
|||
|
||||
BiFunction<String, NbtMapBuilder, String> stateMapper = blockMappers.getOrDefault(palette.getKey(), emptyMapper);
|
||||
|
||||
GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[BLOCKS_JSON.size()];
|
||||
GeyserBedrockBlock[] javaToBedrockBlocks = new GeyserBedrockBlock[javaBlocksSize];
|
||||
GeyserBedrockBlock[] javaToVanillaBedrockBlocks = new GeyserBedrockBlock[javaBlocksSize];
|
||||
|
||||
Map<String, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
|
||||
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
|
||||
|
@ -174,11 +237,22 @@ public final class BlockRegistryPopulator {
|
|||
javaRuntimeId++;
|
||||
Map.Entry<String, JsonNode> entry = blocksIterator.next();
|
||||
String javaId = entry.getKey();
|
||||
GeyserBedrockBlock vanillaBedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
|
||||
GeyserBedrockBlock bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
if (bedrockDefinition == null) {
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition on version "
|
||||
+ palette.getKey().key() + "! Built NBT tag: \n" + buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
GeyserBedrockBlock bedrockDefinition;
|
||||
CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(javaRuntimeId);
|
||||
if (blockStateOverride == null) {
|
||||
bedrockDefinition = vanillaBedrockDefinition;
|
||||
if (bedrockDefinition == null) {
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built NBT tag: \n" +
|
||||
palette.getKey().key() + buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
}
|
||||
} else {
|
||||
bedrockDefinition = customBlockStateDefinitions.get(blockStateOverride);
|
||||
if (bedrockDefinition == null) {
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Custom block override: \n" +
|
||||
blockStateOverride);
|
||||
}
|
||||
}
|
||||
|
||||
switch (javaId) {
|
||||
|
@ -204,9 +278,10 @@ public final class BlockRegistryPopulator {
|
|||
|
||||
// Get the tag needed for non-empty flower pots
|
||||
if (entry.getValue().get("pottable") != null) {
|
||||
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockDefinition.getRuntimeId()));
|
||||
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blockStates.get(bedrockDefinition.getRuntimeId()));
|
||||
}
|
||||
|
||||
javaToVanillaBedrockBlocks[javaRuntimeId] = vanillaBedrockDefinition;
|
||||
javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition;
|
||||
}
|
||||
|
||||
|
@ -231,6 +306,33 @@ public final class BlockRegistryPopulator {
|
|||
}
|
||||
builder.bedrockMovingBlock(movingBlockDefinition);
|
||||
|
||||
Map<JavaBlockState, CustomBlockState> nonVanillaStateOverrides = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get();
|
||||
if (nonVanillaStateOverrides.size() > 0) {
|
||||
// First ensure all non vanilla runtime IDs at minimum are air in case they aren't consecutive
|
||||
Arrays.fill(javaToVanillaBedrockBlocks, minCustomRuntimeID, javaToVanillaBedrockBlocks.length, airDefinition);
|
||||
Arrays.fill(javaToBedrockBlocks, minCustomRuntimeID, javaToBedrockBlocks.length, airDefinition);
|
||||
|
||||
for (Map.Entry<JavaBlockState, CustomBlockState> entry : nonVanillaStateOverrides.entrySet()) {
|
||||
GeyserBedrockBlock bedrockDefinition = customBlockStateDefinitions.get(entry.getValue());
|
||||
if (bedrockDefinition == null) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Unable to find custom block for " + entry.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
JavaBlockState javaState = entry.getKey();
|
||||
int stateRuntimeId = javaState.javaId();
|
||||
|
||||
boolean waterlogged = javaState.waterlogged();
|
||||
|
||||
if (waterlogged) {
|
||||
BlockRegistries.WATERLOGGED.register(set -> set.set(stateRuntimeId));
|
||||
}
|
||||
|
||||
javaToVanillaBedrockBlocks[stateRuntimeId] = bedrockDefinition; // TODO: Check this?
|
||||
javaToBedrockBlocks[stateRuntimeId] = bedrockDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop around again to find all item frame runtime IDs
|
||||
Object2ObjectMaps.fastForEach(blockStateOrderedMap, entry -> {
|
||||
String name = entry.getKey().getString("name");
|
||||
|
@ -242,9 +344,15 @@ public final class BlockRegistryPopulator {
|
|||
BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion)
|
||||
.bedrockRuntimeMap(bedrockRuntimeMap)
|
||||
.javaToBedrockBlocks(javaToBedrockBlocks)
|
||||
.javaToVanillaBedrockBlocks(javaToVanillaBedrockBlocks)
|
||||
.stateDefinitionMap(blockStateOrderedMap)
|
||||
.itemFrames(itemFrames)
|
||||
.flowerPotBlocks(flowerPotBlocks)
|
||||
.jigsawStates(jigsawDefinitions)
|
||||
.remappedVanillaIds(remappedVanillaIds)
|
||||
.blockProperties(customBlockProperties)
|
||||
.customBlockStateDefinitions(customBlockStateDefinitions)
|
||||
.extendedCollisionBoxes(extendedCollisionBoxes)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +365,20 @@ public final class BlockRegistryPopulator {
|
|||
throw new AssertionError("Unable to load Java block mappings", e);
|
||||
}
|
||||
|
||||
BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[blocksJson.size()]); // Set array size to number of blockstates
|
||||
javaBlocksSize = blocksJson.size();
|
||||
|
||||
if (BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().size() > 0) {
|
||||
minCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).get().javaId();
|
||||
maxCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().max(Comparator.comparing(JavaBlockState::javaId)).get().javaId();
|
||||
|
||||
if (minCustomRuntimeID < blocksJson.size()) {
|
||||
throw new RuntimeException("Non vanilla custom block state overrides runtime ID must start after the last vanilla block state (" + javaBlocksSize + ")");
|
||||
}
|
||||
|
||||
javaBlocksSize = maxCustomRuntimeID + 1; // Runtime ids start at 0, so we need to add 1
|
||||
}
|
||||
|
||||
BlockRegistries.JAVA_BLOCKS.set(new BlockMapping[javaBlocksSize]); // Set array size to number of blockstates
|
||||
|
||||
Deque<String> cleanIdentifiers = new ArrayDeque<>();
|
||||
|
||||
|
@ -395,6 +516,46 @@ public final class BlockRegistryPopulator {
|
|||
}
|
||||
BlockStateValues.JAVA_WATER_ID = waterRuntimeId;
|
||||
|
||||
if (BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().size() > 0) {
|
||||
Set<Integer> usedNonVanillaRuntimeIDs = new HashSet<>();
|
||||
|
||||
for (JavaBlockState javaBlockState : BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet()) {
|
||||
if (!usedNonVanillaRuntimeIDs.add(javaBlockState.javaId())) {
|
||||
throw new RuntimeException("Duplicate runtime ID " + javaBlockState.javaId() + " for non vanilla Java block state " + javaBlockState.identifier());
|
||||
}
|
||||
|
||||
CustomBlockState customBlockState = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().get(javaBlockState);
|
||||
|
||||
String javaId = javaBlockState.identifier();
|
||||
int stateRuntimeId = javaBlockState.javaId();
|
||||
BlockMapping blockMapping = BlockMapping.builder()
|
||||
.canBreakWithHand(javaBlockState.canBreakWithHand())
|
||||
.pickItem(javaBlockState.pickItem())
|
||||
.isNonVanilla(true)
|
||||
.javaIdentifier(javaId)
|
||||
.javaBlockId(javaBlockState.stateGroupId())
|
||||
.hardness(javaBlockState.blockHardness())
|
||||
.pistonBehavior(javaBlockState.pistonBehavior() == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(javaBlockState.pistonBehavior()))
|
||||
.isBlockEntity(javaBlockState.hasBlockEntity())
|
||||
.build();
|
||||
|
||||
String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier());
|
||||
String bedrockIdentifier = customBlockState.block().identifier();
|
||||
|
||||
if (!cleanJavaIdentifier.equals(cleanIdentifiers.peekLast())) {
|
||||
uniqueJavaId++;
|
||||
cleanIdentifiers.add(cleanJavaIdentifier.intern());
|
||||
}
|
||||
|
||||
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId);
|
||||
BlockRegistries.JAVA_BLOCKS.register(stateRuntimeId, blockMapping);
|
||||
|
||||
// Keeping this here since this is currently unchanged between versions
|
||||
// It's possible to only have this store differences in names, but the key set of all Java names is used in sending command suggestions
|
||||
BlockRegistries.JAVA_TO_BEDROCK_IDENTIFIERS.register(cleanJavaIdentifier.intern(), bedrockIdentifier.intern());
|
||||
}
|
||||
}
|
||||
|
||||
BlockRegistries.CLEAN_JAVA_IDENTIFIERS.set(cleanIdentifiers.toArray(new String[0]));
|
||||
|
||||
BLOCKS_JSON = blocksJson;
|
||||
|
@ -448,4 +609,22 @@ public final class BlockRegistryPopulator {
|
|||
tagBuilder.put("states", statesBuilder.build());
|
||||
return tagBuilder.build();
|
||||
}
|
||||
|
||||
private static final long FNV1_64_OFFSET_BASIS = 0xcbf29ce484222325L;
|
||||
private static final long FNV1_64_PRIME = 1099511628211L;
|
||||
|
||||
/**
|
||||
* Hashes a string using the FNV-1a 64-bit algorithm.
|
||||
*
|
||||
* @param str The string to hash
|
||||
* @return The hashed string
|
||||
*/
|
||||
private static long fnv164(String str) {
|
||||
long hash = FNV1_64_OFFSET_BASIS;
|
||||
for (byte b : str.getBytes(StandardCharsets.UTF_8)) {
|
||||
hash *= FNV1_64_PRIME;
|
||||
hash ^= b;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@
|
|||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtUtils;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
|
@ -34,6 +37,7 @@ import org.geysermc.geyser.GeyserBootstrap;
|
|||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
import org.geysermc.geyser.registry.type.GeyserBedrockBlock;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -99,12 +103,32 @@ public class CreativeItemRegistryPopulator {
|
|||
count = countNode.asInt();
|
||||
}
|
||||
|
||||
GeyserBedrockBlock blockDefinition = null;
|
||||
JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId");
|
||||
JsonNode blockStateNode;
|
||||
if (blockRuntimeIdNode != null) {
|
||||
bedrockBlockRuntimeId = blockRuntimeIdNode.asInt();
|
||||
if (bedrockBlockRuntimeId == 0 && !identifier.equals("minecraft:blue_candle")) { // FIXME
|
||||
bedrockBlockRuntimeId = -1;
|
||||
}
|
||||
|
||||
blockDefinition = bedrockBlockRuntimeId == -1 ? null : blockMappings.getDefinition(bedrockBlockRuntimeId);
|
||||
} else if ((blockStateNode = itemNode.get("block_state_b64")) != null) {
|
||||
byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText());
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
try {
|
||||
NbtMap stateTag = (NbtMap) NbtUtils.createReaderLE(bais).readTag();
|
||||
|
||||
// We remove these from the state definition map in
|
||||
// BlockMappings, so we need to remove it from here
|
||||
NbtMapBuilder builder = stateTag.toBuilder();
|
||||
builder.remove("name_hash");
|
||||
builder.remove("network_id");
|
||||
|
||||
blockDefinition = blockMappings.getDefinition(builder.build());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode nbtNode = itemNode.get("nbt_b64");
|
||||
|
@ -129,6 +153,6 @@ public class CreativeItemRegistryPopulator {
|
|||
.damage(damage)
|
||||
.count(count)
|
||||
.tag(tag)
|
||||
.blockDefinition(bedrockBlockRuntimeId == -1 ? null : blockMappings.getDefinition(bedrockBlockRuntimeId));
|
||||
.blockDefinition(blockDefinition);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,480 @@
|
|||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594;
|
||||
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
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.PlacementConditions;
|
||||
import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
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;
|
||||
import org.geysermc.geyser.api.util.CreativeCategory;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockState;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomBlockRegistryPopulator {
|
||||
/**
|
||||
* The stage of population
|
||||
*/
|
||||
public enum Stage {
|
||||
DEFINITION,
|
||||
VANILLA_REGISTRATION,
|
||||
NON_VANILLA_REGISTRATION,
|
||||
CUSTOM_REGISTRATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the custom block registries by stage
|
||||
*
|
||||
* @param stage the stage to populate
|
||||
*/
|
||||
public static void populate(Stage stage) {
|
||||
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case DEFINITION -> { populateBedrock(); }
|
||||
case VANILLA_REGISTRATION -> { populateVanilla(); }
|
||||
case NON_VANILLA_REGISTRATION -> { populateNonVanilla(); }
|
||||
case CUSTOM_REGISTRATION -> { registration(); }
|
||||
default -> { throw new IllegalArgumentException("Unknown stage: " + stage); }
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<CustomBlockData> customBlocks;
|
||||
private static Set<String> customBlockNames;
|
||||
private static Int2ObjectMap<CustomBlockState> blockStateOverrides;
|
||||
private static Map<String, CustomBlockData> customBlockItemOverrides;
|
||||
private static Map<JavaBlockState, CustomBlockState> nonVanillaBlockStateOverrides;
|
||||
|
||||
/**
|
||||
* Initializes custom blocks defined by API
|
||||
*/
|
||||
private static void populateBedrock() {
|
||||
customBlocks = new ObjectOpenHashSet<>();
|
||||
customBlockNames = new ObjectOpenHashSet<>();
|
||||
blockStateOverrides = new Int2ObjectOpenHashMap<>();
|
||||
customBlockItemOverrides = new HashMap<>();
|
||||
nonVanillaBlockStateOverrides = new HashMap<>();
|
||||
|
||||
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() {
|
||||
@Override
|
||||
public void register(@NonNull CustomBlockData customBlockData) {
|
||||
if (customBlockData.name().length() == 0) {
|
||||
throw new IllegalArgumentException("Custom block name must have at least 1 character.");
|
||||
}
|
||||
if (!customBlockNames.add(customBlockData.name())) {
|
||||
throw new IllegalArgumentException("Another custom block was already registered under the name: " + customBlockData.name());
|
||||
}
|
||||
if (Character.isDigit(customBlockData.name().charAt(0))) {
|
||||
throw new IllegalArgumentException("Custom block can not start with a digit. Name: " + customBlockData.name());
|
||||
}
|
||||
customBlocks.add(customBlockData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState) {
|
||||
int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(javaIdentifier, -1);
|
||||
if (id == -1) {
|
||||
throw new IllegalArgumentException("Unknown Java block state. Identifier: " + javaIdentifier);
|
||||
}
|
||||
if (!customBlocks.contains(customBlockState.block())) {
|
||||
throw new IllegalArgumentException("Custom block is unregistered. Name: " + customBlockState.name());
|
||||
}
|
||||
CustomBlockState oldBlockState = blockStateOverrides.put(id, customBlockState);
|
||||
if (oldBlockState != null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Duplicate block state override for Java Identifier: " +
|
||||
javaIdentifier + " Old override: " + oldBlockState.name() + " New override: " + customBlockState.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerItemOverride(@NonNull String javaIdentifier, @NonNull CustomBlockData customBlockData) {
|
||||
if (!customBlocks.contains(customBlockData)) {
|
||||
throw new IllegalArgumentException("Custom block is unregistered. Name: " + customBlockData.name());
|
||||
}
|
||||
customBlockItemOverrides.put(javaIdentifier, customBlockData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOverride(@NonNull JavaBlockState javaBlockState, @NonNull CustomBlockState customBlockState) {
|
||||
if (!customBlocks.contains(customBlockState.block())) {
|
||||
throw new IllegalArgumentException("Custom block is unregistered. Name: " + customBlockState.name());
|
||||
}
|
||||
nonVanillaBlockStateOverrides.put(javaBlockState, customBlockState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all vanilla custom blocks and skulls defined by API and mappings
|
||||
*/
|
||||
private static void populateVanilla() {
|
||||
for (CustomSkull customSkull : BlockRegistries.CUSTOM_SKULLS.get().values()) {
|
||||
customBlocks.add(customSkull.getCustomBlockData());
|
||||
}
|
||||
|
||||
Map<CustomBlockData, Set<Integer>> extendedCollisionBoxes = new HashMap<>();
|
||||
Map<BoxComponent, CustomBlockData> extendedCollisionBoxSet = new HashMap<>();
|
||||
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
||||
mappingsConfigReader.loadBlockMappingsFromJson((key, block) -> {
|
||||
customBlocks.add(block.data());
|
||||
if (block.overrideItem()) {
|
||||
customBlockItemOverrides.put(block.javaIdentifier(), block.data());
|
||||
}
|
||||
block.states().forEach((javaIdentifier, customBlockState) -> {
|
||||
int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(javaIdentifier, -1);
|
||||
blockStateOverrides.put(id, customBlockState.state());
|
||||
BoxComponent extendedCollisionBox = customBlockState.extendedCollisionBox();
|
||||
if (extendedCollisionBox != null) {
|
||||
CustomBlockData extendedCollisionBlock = extendedCollisionBoxSet.computeIfAbsent(extendedCollisionBox, box -> {
|
||||
CustomBlockData collisionBlock = createExtendedCollisionBlock(box, extendedCollisionBoxSet.size());
|
||||
customBlocks.add(collisionBlock);
|
||||
return collisionBlock;
|
||||
});
|
||||
extendedCollisionBoxes.computeIfAbsent(extendedCollisionBlock, k -> new HashSet<>())
|
||||
.add(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.set(blockStateOverrides);
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + blockStateOverrides.size() + " custom block overrides.");
|
||||
|
||||
BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.set(customBlockItemOverrides);
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + customBlockItemOverrides.size() + " custom block item overrides.");
|
||||
|
||||
BlockRegistries.EXTENDED_COLLISION_BOXES.set(extendedCollisionBoxes);
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + extendedCollisionBoxes.size() + " custom block extended collision boxes.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all non-vanilla custom blocks defined by API
|
||||
*/
|
||||
private static void populateNonVanilla() {
|
||||
BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.set(nonVanillaBlockStateOverrides);
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + nonVanillaBlockStateOverrides.size() + " non-vanilla block overrides.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all bedrock custom blocks defined in previous stages
|
||||
*/
|
||||
private static void registration() {
|
||||
BlockRegistries.CUSTOM_BLOCKS.set(customBlocks.toArray(new CustomBlockData[0]));
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + customBlocks.size() + " custom blocks.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and appends all custom block states to the provided list of custom block states
|
||||
* Appends the custom block states to the provided list of NBT maps
|
||||
*
|
||||
* @param customBlock the custom block data to generate states for
|
||||
* @param blockStates the list of NBT maps to append the custom block states to
|
||||
* @param customExtBlockStates the list of custom block states to append the custom block states to
|
||||
* @param stateVersion the state version to use for the custom block states
|
||||
*/
|
||||
static void generateCustomBlockStates(CustomBlockData customBlock, List<NbtMap> blockStates, List<CustomBlockState> customExtBlockStates, int stateVersion) {
|
||||
int totalPermutations = 1;
|
||||
for (CustomBlockProperty<?> property : customBlock.properties().values()) {
|
||||
totalPermutations *= property.values().size();
|
||||
}
|
||||
|
||||
for (int i = 0; i < totalPermutations; i++) {
|
||||
NbtMapBuilder statesBuilder = NbtMap.builder();
|
||||
int permIndex = i;
|
||||
for (CustomBlockProperty<?> property : customBlock.properties().values()) {
|
||||
statesBuilder.put(property.name(), property.values().get(permIndex % property.values().size()));
|
||||
permIndex /= property.values().size();
|
||||
}
|
||||
NbtMap states = statesBuilder.build();
|
||||
|
||||
blockStates.add(NbtMap.builder()
|
||||
.putString("name", customBlock.identifier())
|
||||
.putInt("version", stateVersion)
|
||||
.putCompound("states", states)
|
||||
.build());
|
||||
customExtBlockStates.add(new GeyserCustomBlockState(customBlock, states));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and returns the block property data for the provided custom block
|
||||
*
|
||||
* @param customBlock the custom block to generate block property data for
|
||||
* @param protocolVersion the protocol version to use for the block property data
|
||||
* @return the block property data for the provided custom block
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static BlockPropertyData generateBlockPropertyData(CustomBlockData customBlock, int protocolVersion) {
|
||||
List<NbtMap> permutations = new ArrayList<>();
|
||||
for (CustomBlockPermutation permutation : customBlock.permutations()) {
|
||||
permutations.add(NbtMap.builder()
|
||||
.putCompound("components", CustomBlockRegistryPopulator.convertComponents(permutation.components(), protocolVersion))
|
||||
.putString("condition", permutation.condition())
|
||||
.build());
|
||||
}
|
||||
|
||||
// The order that properties are defined influences the order that block states are generated
|
||||
List<NbtMap> properties = new ArrayList<>();
|
||||
for (CustomBlockProperty<?> property : customBlock.properties().values()) {
|
||||
NbtMapBuilder propertyBuilder = NbtMap.builder()
|
||||
.putString("name", property.name());
|
||||
if (property.type() == PropertyType.booleanProp()) {
|
||||
propertyBuilder.putList("enum", NbtType.BYTE, List.of((byte) 0, (byte) 1));
|
||||
} else if (property.type() == PropertyType.integerProp()) {
|
||||
propertyBuilder.putList("enum", NbtType.INT, (List<Integer>) property.values());
|
||||
} else if (property.type() == PropertyType.stringProp()) {
|
||||
propertyBuilder.putList("enum", NbtType.STRING, (List<String>) property.values());
|
||||
}
|
||||
properties.add(propertyBuilder.build());
|
||||
}
|
||||
|
||||
CreativeCategory creativeCategory = customBlock.creativeCategory() != null ? customBlock.creativeCategory() : CreativeCategory.NONE;
|
||||
String creativeGroup = customBlock.creativeGroup() != null ? customBlock.creativeGroup() : "";
|
||||
NbtMap propertyTag = NbtMap.builder()
|
||||
.putCompound("components", CustomBlockRegistryPopulator.convertComponents(customBlock.components(), protocolVersion))
|
||||
// this is required or the client will crash
|
||||
// in the future, this can be used to replace items in the creative inventory
|
||||
// this would require us to map https://wiki.bedrock.dev/documentation/creative-categories.html#for-blocks programatically
|
||||
.putCompound("menu_category", NbtMap.builder()
|
||||
.putString("category", creativeCategory.internalName())
|
||||
.putString("group", creativeGroup)
|
||||
.putBoolean("is_hidden_in_commands", false)
|
||||
.build())
|
||||
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
|
||||
.putInt("molangVersion", 1)
|
||||
.putList("permutations", NbtType.COMPOUND, permutations)
|
||||
.putList("properties", NbtType.COMPOUND, properties)
|
||||
.build();
|
||||
return new BlockPropertyData(customBlock.identifier(), propertyTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided custom block components to an {@link NbtMap} to be sent to the client in the StartGame packet
|
||||
*
|
||||
* @param components the custom block components to convert
|
||||
* @param protocolVersion the protocol version to use for the conversion
|
||||
* @return the NBT representation of the provided custom block components
|
||||
*/
|
||||
private static NbtMap convertComponents(CustomBlockComponents components, int protocolVersion) {
|
||||
if (components == null) {
|
||||
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()));
|
||||
}
|
||||
|
||||
if (components.collisionBox() != null) {
|
||||
builder.putCompound("minecraft:collision_box", convertBox(components.collisionBox()));
|
||||
}
|
||||
|
||||
if (components.geometry() != null) {
|
||||
NbtMapBuilder geometryBuilder = NbtMap.builder();
|
||||
if (protocolVersion >= Bedrock_v594.CODEC.getProtocolVersion()) {
|
||||
geometryBuilder.putString("identifier", components.geometry().identifier());
|
||||
if (components.geometry().boneVisibility() != null) {
|
||||
NbtMapBuilder boneVisibilityBuilder = NbtMap.builder();
|
||||
components.geometry().boneVisibility().entrySet().forEach(
|
||||
entry -> boneVisibilityBuilder.putString(entry.getKey(), entry.getValue()));
|
||||
geometryBuilder.putCompound("bone_visibility", boneVisibilityBuilder.build());
|
||||
}
|
||||
} else {
|
||||
geometryBuilder.putString("value", components.geometry().identifier());
|
||||
}
|
||||
builder.putCompound("minecraft:geometry", geometryBuilder.build());
|
||||
}
|
||||
|
||||
if (!components.materialInstances().isEmpty()) {
|
||||
NbtMapBuilder materialsBuilder = NbtMap.builder();
|
||||
for (Map.Entry<String, MaterialInstance> entry : components.materialInstances().entrySet()) {
|
||||
MaterialInstance materialInstance = entry.getValue();
|
||||
materialsBuilder.putCompound(entry.getKey(), NbtMap.builder()
|
||||
.putString("texture", materialInstance.texture())
|
||||
.putString("render_method", materialInstance.renderMethod())
|
||||
.putBoolean("face_dimming", materialInstance.faceDimming())
|
||||
.putBoolean("ambient_occlusion", materialInstance.faceDimming())
|
||||
.build());
|
||||
}
|
||||
|
||||
builder.putCompound("minecraft:material_instances", NbtMap.builder()
|
||||
// we could read these, but there is no functional reason to use them at the moment
|
||||
// they only allow you to make aliases for material instances
|
||||
// but you could already just define the same instance twice if this was really needed
|
||||
.putCompound("mappings", NbtMap.EMPTY)
|
||||
.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())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (components.friction() != null) {
|
||||
builder.putCompound("minecraft:friction", NbtMap.builder()
|
||||
.putFloat("value", components.friction())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (components.lightEmission() != null) {
|
||||
builder.putCompound("minecraft:light_emission", NbtMap.builder()
|
||||
.putByte("emission", components.lightEmission().byteValue())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (components.lightDampening() != null) {
|
||||
builder.putCompound("minecraft:light_dampening", NbtMap.builder()
|
||||
.putByte("lightLevel", components.lightDampening().byteValue())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (components.transformation() != null) {
|
||||
builder.putCompound("minecraft:transformation", NbtMap.builder()
|
||||
.putInt("RX", MathUtils.unwrapDegreesToInt(components.transformation().rx()) / 90)
|
||||
.putInt("RY", MathUtils.unwrapDegreesToInt(components.transformation().ry()) / 90)
|
||||
.putInt("RZ", MathUtils.unwrapDegreesToInt(components.transformation().rz()) / 90)
|
||||
.putFloat("SX", components.transformation().sx())
|
||||
.putFloat("SY", components.transformation().sy())
|
||||
.putFloat("SZ", components.transformation().sz())
|
||||
.putFloat("TX", components.transformation().tx())
|
||||
.putFloat("TY", components.transformation().ty())
|
||||
.putFloat("TZ", components.transformation().tz())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (components.unitCube()) {
|
||||
builder.putCompound("minecraft:unit_cube", NbtMap.EMPTY);
|
||||
}
|
||||
|
||||
// place_air is not an actual component
|
||||
// We just apply a dummy event to prevent the client from trying to place a block
|
||||
// This mitigates the issue with the client sometimes double placing blocks
|
||||
if (components.placeAir()) {
|
||||
builder.putCompound("minecraft:on_player_placing", NbtMap.builder()
|
||||
.putString("triggerType", "geyser:place_event")
|
||||
.build());
|
||||
}
|
||||
|
||||
if (!components.tags().isEmpty()) {
|
||||
components.tags().forEach(tag -> builder.putCompound("tag:" + tag, NbtMap.EMPTY));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided box component to an {@link NbtMap}
|
||||
*
|
||||
* @param boxComponent the box component to convert
|
||||
* @return the NBT representation of the provided box component
|
||||
*/
|
||||
private static NbtMap convertBox(BoxComponent boxComponent) {
|
||||
return NbtMap.builder()
|
||||
.putBoolean("enabled", !boxComponent.isEmpty())
|
||||
.putList("origin", NbtType.FLOAT, boxComponent.originX(), boxComponent.originY(), boxComponent.originZ())
|
||||
.putList("size", NbtType.FLOAT, boxComponent.sizeX(), boxComponent.sizeY(), boxComponent.sizeZ())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the provided placement filter to a list of {@link NbtMap}
|
||||
*
|
||||
* @param placementFilter the placement filter to convert
|
||||
* @return the NBT representation of the provided placement filter
|
||||
*/
|
||||
private static List<NbtMap> convertPlacementFilter(List<PlacementConditions> placementFilter) {
|
||||
List<NbtMap> conditions = new ArrayList<>();
|
||||
placementFilter.forEach((condition) -> {
|
||||
NbtMapBuilder conditionBuilder = NbtMap.builder();
|
||||
|
||||
// allowed_faces on the network is represented by 6 bits for the 6 possible faces
|
||||
// the enum has the proper values for that face only, so we just bitwise OR them together
|
||||
byte allowedFaces = 0;
|
||||
for (Face face : condition.allowedFaces()) { allowedFaces |= (1 << face.ordinal()); }
|
||||
conditionBuilder.putByte("allowed_faces", allowedFaces);
|
||||
|
||||
// block_filters is a list of either blocks or queries for block tags
|
||||
// if these match the block the player is trying to place on, the placement is allowed by the client
|
||||
List <NbtMap> blockFilters = new ArrayList<>();
|
||||
condition.blockFilters().forEach((value, type) -> {
|
||||
NbtMapBuilder blockFilterBuilder = NbtMap.builder();
|
||||
switch (type) {
|
||||
case BLOCK -> blockFilterBuilder.putString("name", value);
|
||||
// meaning of this version is unknown, but it's required for tags to work and should probably be checked periodically
|
||||
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;
|
||||
}
|
||||
|
||||
private static CustomBlockData createExtendedCollisionBlock(BoxComponent boxComponent, int extendedCollisionBlock) {
|
||||
CustomBlockData customBlockData = new CustomBlockDataBuilder()
|
||||
.name("extended_collision_" + extendedCollisionBlock)
|
||||
.components(
|
||||
new CustomBlockComponentsBuilder()
|
||||
.collisionBox(boxComponent)
|
||||
.selectionBox(BoxComponent.emptyBox())
|
||||
.materialInstance("*", new MaterialInstanceBuilder()
|
||||
.texture("glass")
|
||||
.renderMethod("alpha_test")
|
||||
.faceDimming(false)
|
||||
.ambientOcclusion(false)
|
||||
.build())
|
||||
.lightDampening(0)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.identifier("geometry.invisible")
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
return customBlockData;
|
||||
}
|
||||
}
|
|
@ -41,10 +41,9 @@ import org.geysermc.geyser.api.util.TriState;
|
|||
import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl;
|
||||
import org.geysermc.geyser.item.GeyserCustomMappingData;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.components.ToolBreakSpeedsUtils;
|
||||
import org.geysermc.geyser.item.components.WearableSlot;
|
||||
import org.geysermc.geyser.item.mappings.MappingsConfigReader;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
|
||||
import org.geysermc.geyser.registry.type.GeyserMappingItem;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
||||
|
@ -56,7 +55,7 @@ public class CustomItemRegistryPopulator {
|
|||
public static void populate(Map<String, GeyserMappingItem> items, Multimap<String, CustomItemData> customItems, List<NonVanillaCustomItemData> nonVanillaCustomItems) {
|
||||
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
||||
// Load custom items from mappings files
|
||||
mappingsConfigReader.loadMappingsFromJson((key, item) -> {
|
||||
mappingsConfigReader.loadItemMappingsFromJson((key, item) -> {
|
||||
if (CustomItemRegistryPopulator.initialCheck(key, item, items)) {
|
||||
customItems.get(key).add(item);
|
||||
}
|
||||
|
@ -294,34 +293,45 @@ public class CustomItemRegistryPopulator {
|
|||
boolean canDestroyInCreative = true;
|
||||
float miningSpeed = 1.0f;
|
||||
|
||||
if (toolType.equals("shears")) {
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShearsDigger(15));
|
||||
} else {
|
||||
int toolSpeed = ToolBreakSpeedsUtils.toolTierToSpeed(toolTier);
|
||||
switch (toolType) {
|
||||
case "sword" -> {
|
||||
miningSpeed = 1.5f;
|
||||
canDestroyInCreative = false;
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getSwordDigger(toolSpeed));
|
||||
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
|
||||
}
|
||||
case "pickaxe" -> {
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getPickaxeDigger(toolSpeed, toolTier));
|
||||
setItemTag(componentBuilder, "pickaxe");
|
||||
}
|
||||
case "axe" -> {
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getAxeDigger(toolSpeed));
|
||||
setItemTag(componentBuilder, "axe");
|
||||
}
|
||||
case "shovel" -> {
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getShovelDigger(toolSpeed));
|
||||
setItemTag(componentBuilder, "shovel");
|
||||
}
|
||||
case "hoe" -> {
|
||||
componentBuilder.putCompound("minecraft:digger", ToolBreakSpeedsUtils.getHoeDigger(toolSpeed));
|
||||
setItemTag(componentBuilder, "hoe");
|
||||
}
|
||||
}
|
||||
// This means client side the tool can never destroy a block
|
||||
// This works because the molang '1' for tags will be true for all blocks and the speed will be 0
|
||||
// We want this since we calculate break speed server side in BedrockActionTranslator
|
||||
List<NbtMap> speed = new ArrayList<>(List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("block", NbtMap.builder()
|
||||
.putString("tags", "1")
|
||||
.build())
|
||||
.putCompound("on_dig", NbtMap.builder()
|
||||
.putCompound("condition", NbtMap.builder()
|
||||
.putString("expression", "")
|
||||
.putInt("version", -1)
|
||||
.build())
|
||||
.putString("event", "tool_durability")
|
||||
.putString("target", "self")
|
||||
.build())
|
||||
.putInt("speed", 0)
|
||||
.build()
|
||||
));
|
||||
|
||||
componentBuilder.putCompound("minecraft:digger",
|
||||
NbtMap.builder()
|
||||
.putList("destroy_speeds", NbtType.COMPOUND, speed)
|
||||
.putCompound("on_dig", NbtMap.builder()
|
||||
.putCompound("condition", NbtMap.builder()
|
||||
.putString("expression", "")
|
||||
.putInt("version", -1)
|
||||
.build())
|
||||
.putString("event", "tool_durability")
|
||||
.putString("target", "self")
|
||||
.build())
|
||||
.putBoolean("use_efficiency", true)
|
||||
.build()
|
||||
);
|
||||
|
||||
if (toolType.equals("sword")) {
|
||||
miningSpeed = 1.5f;
|
||||
canDestroyInCreative = false;
|
||||
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("hand_equipped", true);
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* 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.registry.populator;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomSkullsEvent;
|
||||
import org.geysermc.geyser.configuration.GeyserCustomSkullConfiguration;
|
||||
import org.geysermc.geyser.pack.SkullResourcePackManager;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.skin.SkinManager;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CustomSkullRegistryPopulator {
|
||||
|
||||
public static void populate() {
|
||||
SkullResourcePackManager.SKULL_SKINS.clear(); // Remove skins after reloading
|
||||
BlockRegistries.CUSTOM_SKULLS.set(Object2ObjectMaps.emptyMap());
|
||||
|
||||
if (!GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserCustomSkullConfiguration skullConfig;
|
||||
try {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
Path skullConfigPath = bootstrap.getConfigFolder().resolve("custom-skulls.yml");
|
||||
File skullConfigFile = FileUtils.fileOrCopiedFromResource(skullConfigPath.toFile(), "custom-skulls.yml", Function.identity(), bootstrap);
|
||||
skullConfig = FileUtils.loadConfig(skullConfigFile, GeyserCustomSkullConfiguration.class);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
BlockRegistries.CUSTOM_SKULLS.set(new Object2ObjectOpenHashMap<>());
|
||||
|
||||
List<String> profiles = new ArrayList<>(skullConfig.getPlayerProfiles());
|
||||
List<String> usernames = new ArrayList<>(skullConfig.getPlayerUsernames());
|
||||
List<String> uuids = new ArrayList<>(skullConfig.getPlayerUUIDs());
|
||||
List<String> skinHashes = new ArrayList<>(skullConfig.getPlayerSkinHashes());
|
||||
|
||||
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomSkullsEvent() {
|
||||
@Override
|
||||
public void register(@NonNull String texture, @NonNull SkullTextureType type) {
|
||||
switch (type) {
|
||||
case USERNAME -> usernames.add(texture);
|
||||
case UUID -> uuids.add(texture);
|
||||
case PROFILE -> profiles.add(texture);
|
||||
case SKIN_HASH -> skinHashes.add(texture);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
usernames.forEach((username) -> {
|
||||
String profile = getProfileFromUsername(username);
|
||||
if (profile != null) {
|
||||
String skinHash = getSkinHash(profile);
|
||||
if (skinHash != null) {
|
||||
skinHashes.add(skinHash);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uuids.forEach((uuid) -> {
|
||||
String profile = getProfileFromUuid(uuid);
|
||||
if (profile != null) {
|
||||
String skinHash = getSkinHash(profile);
|
||||
if (skinHash != null) {
|
||||
skinHashes.add(skinHash);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
profiles.forEach((profile) -> {
|
||||
String skinHash = getSkinHash(profile);
|
||||
if (skinHash != null) {
|
||||
skinHashes.add(skinHash);
|
||||
}
|
||||
});
|
||||
|
||||
skinHashes.forEach((skinHash) -> {
|
||||
if (!skinHash.matches("^[a-fA-F0-9]+$")) {
|
||||
GeyserImpl.getInstance().getLogger().error("Skin hash " + skinHash + " does not match required format ^[a-fA-F0-9]{64}$ and will not be added as a custom block.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SkullResourcePackManager.cacheSkullSkin(skinHash);
|
||||
BlockRegistries.CUSTOM_SKULLS.register(skinHash, new CustomSkull(skinHash));
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to cache skin for skull texture " + skinHash + " This skull will not be added as a custom block.", e);
|
||||
}
|
||||
});
|
||||
|
||||
GeyserImpl.getInstance().getLogger().info("Registered " + BlockRegistries.CUSTOM_SKULLS.get().size() + " custom skulls as custom blocks.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the skin hash from a base64 encoded profile
|
||||
* @param profile the base64 encoded profile
|
||||
* @return the skin hash or null if the profile is invalid
|
||||
*/
|
||||
private static String getSkinHash(String profile) {
|
||||
try {
|
||||
SkinManager.GameProfileData profileData = SkinManager.GameProfileData.loadFromJson(profile);
|
||||
if (profileData == null) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Skull texture " + profile + " contained no skins and will not be added as a custom block.");
|
||||
return null;
|
||||
}
|
||||
String skinUrl = profileData.skinUrl();
|
||||
return skinUrl.substring(skinUrl.lastIndexOf("/") + 1);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Skull texture " + profile + " is invalid and will not be added as a custom block.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base64 encoded profile from a player's username
|
||||
* @param username the player username
|
||||
* @return the base64 encoded profile or null if the request failed
|
||||
*/
|
||||
private static String getProfileFromUsername(String username) {
|
||||
try {
|
||||
return SkinProvider.requestTexturesFromUsername(username).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + username + " This skull will not be added as a custom block.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base64 encoded profile from a player's UUID
|
||||
* @param uuid the player UUID
|
||||
* @return the base64 encoded profile or null if the request failed
|
||||
*/
|
||||
private static String getProfileFromUuid(String uuid) {
|
||||
try {
|
||||
String uuidDigits = uuid.replace("-", "");
|
||||
if (uuidDigits.length() != 32) {
|
||||
GeyserImpl.getInstance().getLogger().error("Invalid skull uuid " + uuid + " This skull will not be added as a custom block.");
|
||||
return null;
|
||||
}
|
||||
return SkinProvider.requestTexturesFromUUID(uuid).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Unable to request skull textures for " + uuid + " This skull will not be added as a custom block.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,29 +34,34 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
||||
import org.geysermc.geyser.item.GeyserCustomMappingData;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.type.*;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
@ -82,26 +87,19 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
public static void populate() {
|
||||
Map<Item, String> legacyJavaOnly = new HashMap<>();
|
||||
legacyJavaOnly.put(Items.MUSIC_DISC_RELIC, "minecraft:music_disc_wait");
|
||||
legacyJavaOnly.put(Items.PITCHER_PLANT, "minecraft:chorus_flower");
|
||||
legacyJavaOnly.put(Items.PITCHER_POD, "minecraft:beetroot");
|
||||
legacyJavaOnly.put(Items.SNIFFER_EGG, "minecraft:sniffer_spawn_egg"); // the BlockItem of the sniffer egg block
|
||||
|
||||
List<PaletteVersion> paletteVersions = new ArrayList<>(2);
|
||||
paletteVersions.add(new PaletteVersion("1_19_80", Bedrock_v582.CODEC.getProtocolVersion(), legacyJavaOnly, (item, mapping) -> {
|
||||
paletteVersions.add(new PaletteVersion("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()));
|
||||
paletteVersions.add(new PaletteVersion("1_20_10", Bedrock_v594.CODEC.getProtocolVersion(), Collections.emptyMap(), (item, mapping) -> {
|
||||
// Forward-map 1.20 mappings to 1.20.10
|
||||
// 1.20.10+ received parity for concrete and shulker boxes
|
||||
String id = item.javaIdentifier();
|
||||
if (id.endsWith("pottery_sherd")) {
|
||||
return mapping.withBedrockIdentifier(id.replace("sherd", "shard"));
|
||||
} else if (id.endsWith("carpet") && !id.startsWith("minecraft:moss")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:carpet");
|
||||
} else if (id.endsWith("coral")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:coral");
|
||||
if (id.endsWith("_concrete") || id.endsWith("_shulker_box")) {
|
||||
// the first underscore in "_shulker_box" accounts for ignoring "minecraft:shulker_box"
|
||||
// which is mapped to "minecraft:undyed_shulker_box"
|
||||
return mapping.withBedrockIdentifier(id);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}));
|
||||
paletteVersions.add(new PaletteVersion("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()));
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
|
@ -162,6 +160,8 @@ public class ItemRegistryPopulator {
|
|||
Object2ObjectMap<String, BlockDefinition> bedrockBlockIdOverrides = new Object2ObjectOpenHashMap<>();
|
||||
Object2IntMap<String> blacklistedIdentifiers = new Object2IntOpenHashMap<>();
|
||||
|
||||
Object2ObjectMap<CustomBlockData, ItemDefinition> customBlockItemDefinitions = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
List<ItemDefinition> buckets = new ObjectArrayList<>();
|
||||
List<ItemData> carpets = new ObjectArrayList<>();
|
||||
|
||||
|
@ -235,15 +235,29 @@ public class ItemRegistryPopulator {
|
|||
|
||||
BlockDefinition bedrockBlock = null;
|
||||
Integer firstBlockRuntimeId = entry.getValue().getFirstBlockRuntimeId();
|
||||
BlockDefinition customBlockItemOverride = null;
|
||||
if (firstBlockRuntimeId != null) {
|
||||
BlockDefinition blockOverride = bedrockBlockIdOverrides.get(bedrockIdentifier);
|
||||
if (blockOverride != null) {
|
||||
|
||||
// We'll do this here for custom blocks we want in the creative inventory so we can piggyback off the existing logic to find these
|
||||
// blocks in creativeItems
|
||||
CustomBlockData customBlockData = BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.getOrDefault(javaItem.javaIdentifier(), null);
|
||||
if (customBlockData != null) {
|
||||
// this block has a custom item override and thus we should use its runtime ID for the ItemMapping
|
||||
if (customBlockData.includedInCreativeInventory()) {
|
||||
CustomBlockState customBlockState = customBlockData.defaultBlockState();
|
||||
customBlockItemOverride = blockMappings.getCustomBlockStateDefinitions().getOrDefault(customBlockState, null);
|
||||
}
|
||||
}
|
||||
|
||||
// If it' s a custom block we can't do this because we need to make sure we find the creative item
|
||||
if (blockOverride != null && customBlockItemOverride == null) {
|
||||
// Straight from BDS is our best chance of getting an item that doesn't run into issues
|
||||
bedrockBlock = blockOverride;
|
||||
} else {
|
||||
// Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
|
||||
int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, -1);
|
||||
if (aValidBedrockBlockId == -1) {
|
||||
int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1);
|
||||
if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) {
|
||||
// Fallback
|
||||
bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId);
|
||||
} else {
|
||||
|
@ -259,7 +273,7 @@ public class ItemRegistryPopulator {
|
|||
// and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents
|
||||
Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId();
|
||||
for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) {
|
||||
GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getBedrockBlock(i);
|
||||
GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getVanillaBedrockBlock(i);
|
||||
NbtMap blockTag = bedrockBlockRuntimeId.getState();
|
||||
String bedrockName = blockTag.getString("name");
|
||||
if (!bedrockName.equals(correctBedrockIdentifier)) {
|
||||
|
@ -325,6 +339,12 @@ public class ItemRegistryPopulator {
|
|||
|
||||
// Because we have replaced the Bedrock block ID, we also need to replace the creative contents block runtime ID
|
||||
// That way, creative items work correctly for these blocks
|
||||
|
||||
// Set our custom block override now if there is one
|
||||
if (customBlockItemOverride != null) {
|
||||
bedrockBlock = customBlockItemOverride;
|
||||
}
|
||||
|
||||
for (int j = 0; j < creativeItems.size(); j++) {
|
||||
ItemData itemData = creativeItems.get(j);
|
||||
if (itemData.getDefinition().equals(definition)) {
|
||||
|
@ -333,16 +353,35 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
NbtMap states = ((GeyserBedrockBlock) itemData.getBlockDefinition()).getState().getCompound("states");
|
||||
|
||||
boolean valid = true;
|
||||
for (Map.Entry<String, Object> nbtEntry : requiredBlockStates.entrySet()) {
|
||||
if (!states.get(nbtEntry.getKey()).equals(nbtEntry.getValue())) {
|
||||
if (states.getOrDefault(nbtEntry.getKey(), null) == null || !states.get(nbtEntry.getKey()).equals(nbtEntry.getValue())) {
|
||||
// A required block state doesn't match - this one is not valid
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
creativeItems.set(j, itemData.toBuilder().blockDefinition(bedrockBlock).build());
|
||||
if (customBlockItemOverride != null && customBlockData != null) {
|
||||
// Assuming this is a valid custom block override we'll just register it now while we have the creative item
|
||||
int customProtocolId = nextFreeBedrockId++;
|
||||
mappingItem.setBedrockData(customProtocolId);
|
||||
bedrockIdentifier = customBlockData.identifier();
|
||||
definition = new SimpleItemDefinition(bedrockIdentifier, customProtocolId, true);
|
||||
registry.put(customProtocolId, definition);
|
||||
customBlockItemDefinitions.put(customBlockData, definition);
|
||||
customIdMappings.put(customProtocolId, bedrockIdentifier);
|
||||
|
||||
creativeItems.set(j, itemData.toBuilder()
|
||||
.definition(definition)
|
||||
.blockDefinition(bedrockBlock)
|
||||
.netId(itemData.getNetId())
|
||||
.count(1)
|
||||
.build());
|
||||
} else {
|
||||
creativeItems.set(j, itemData.toBuilder().blockDefinition(bedrockBlock).build());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -383,10 +422,10 @@ public class ItemRegistryPopulator {
|
|||
for (CustomItemData customItem : customItemsToLoad) {
|
||||
int customProtocolId = nextFreeBedrockId++;
|
||||
|
||||
String customItemName = "geyser_custom:" + customItem.name();
|
||||
String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem.identifier() : Constants.GEYSER_CUSTOM_NAMESPACE + ":" + customItem.name();
|
||||
if (!registeredItemNames.add(customItemName)) {
|
||||
if (firstMappingsPass) {
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItem.name() + "' already exists and was registered again! Skipping...");
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item name '" + customItemName + "' already exists and was registered again! Skipping...");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -502,6 +541,41 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
}
|
||||
|
||||
// Register the item forms of custom blocks
|
||||
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
|
||||
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
|
||||
// We might've registered it already with the vanilla blocks so check first
|
||||
if (customBlockItemDefinitions.containsKey(customBlock)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Non-vanilla custom blocks will be handled in the item
|
||||
// registry, so we don't need to do anything here.
|
||||
if (customBlock instanceof NonVanillaCustomBlockData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int customProtocolId = nextFreeBedrockId++;
|
||||
String identifier = customBlock.identifier();
|
||||
|
||||
final ItemDefinition definition = new SimpleItemDefinition(identifier, customProtocolId, true);
|
||||
registry.put(customProtocolId, definition);
|
||||
customBlockItemDefinitions.put(customBlock, definition);
|
||||
customIdMappings.put(customProtocolId, identifier);
|
||||
|
||||
GeyserBedrockBlock bedrockBlock = blockMappings.getCustomBlockStateDefinitions().getOrDefault(customBlock.defaultBlockState(), null);
|
||||
|
||||
if (bedrockBlock != null && customBlock.includedInCreativeInventory()) {
|
||||
creativeItems.add(ItemData.builder()
|
||||
.definition(definition)
|
||||
.blockDefinition(bedrockBlock)
|
||||
.netId(creativeNetId.incrementAndGet())
|
||||
.count(1)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemMappings itemMappings = ItemMappings.builder()
|
||||
.items(mappings.toArray(new ItemMapping[0]))
|
||||
.creativeItems(creativeItems.toArray(new ItemData[0]))
|
||||
|
@ -513,6 +587,7 @@ public class ItemRegistryPopulator {
|
|||
.componentItemData(componentItemData)
|
||||
.lodestoneCompass(lodestoneEntry)
|
||||
.customIdMappings(customIdMappings)
|
||||
.customBlockItemDefinitions(customBlockItemDefinitions)
|
||||
.build();
|
||||
|
||||
Registries.ITEMS.register(palette.protocolVersion(), itemMappings);
|
||||
|
|
|
@ -56,6 +56,7 @@ public class BlockMapping {
|
|||
@Nonnull
|
||||
PistonBehavior pistonBehavior;
|
||||
boolean isBlockEntity;
|
||||
boolean isNonVanilla;
|
||||
|
||||
/**
|
||||
* @return the identifier without the additional block states
|
||||
|
|
|
@ -25,12 +25,17 @@
|
|||
|
||||
package org.geysermc.geyser.registry.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.common.DefinitionRegistry;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -44,8 +49,11 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
|||
int blockStateVersion;
|
||||
|
||||
GeyserBedrockBlock[] javaToBedrockBlocks;
|
||||
GeyserBedrockBlock[] javaToVanillaBedrockBlocks;
|
||||
|
||||
Map<NbtMap, GeyserBedrockBlock> stateDefinitionMap;
|
||||
GeyserBedrockBlock[] bedrockRuntimeMap;
|
||||
int[] remappedVanillaIds;
|
||||
|
||||
BlockDefinition commandBlock;
|
||||
|
||||
|
@ -54,6 +62,10 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
|||
|
||||
Set<BlockDefinition> jigsawStates;
|
||||
|
||||
List<BlockPropertyData> blockProperties;
|
||||
Object2ObjectMap<CustomBlockState, GeyserBedrockBlock> customBlockStateDefinitions;
|
||||
Int2ObjectMap<GeyserBedrockBlock> extendedCollisionBoxes;
|
||||
|
||||
public int getBedrockBlockId(int javaState) {
|
||||
return getBedrockBlock(javaState).getRuntimeId();
|
||||
}
|
||||
|
@ -65,6 +77,13 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
|||
return this.javaToBedrockBlocks[javaState];
|
||||
}
|
||||
|
||||
public GeyserBedrockBlock getVanillaBedrockBlock(int javaState) {
|
||||
if (javaState < 0 || javaState >= this.javaToVanillaBedrockBlocks.length) {
|
||||
return bedrockAir;
|
||||
}
|
||||
return this.javaToVanillaBedrockBlocks[javaState];
|
||||
}
|
||||
|
||||
public BlockDefinition getItemFrame(NbtMap tag) {
|
||||
return this.itemFrames.get(tag);
|
||||
}
|
||||
|
@ -85,6 +104,14 @@ public class BlockMappings implements DefinitionRegistry<GeyserBedrockBlock> {
|
|||
return bedrockRuntimeMap[bedrockId];
|
||||
}
|
||||
|
||||
public GeyserBedrockBlock getDefinition(NbtMap tag) {
|
||||
if (tag == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.stateDefinitionMap.get(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(GeyserBedrockBlock bedrockBlock) {
|
||||
return getDefinition(bedrockBlock.getRuntimeId()) == bedrockBlock;
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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.registry.type;
|
||||
|
||||
import lombok.Data;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
|
||||
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.TransformationComponent;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
|
||||
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
|
||||
import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder;
|
||||
import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@Data
|
||||
public class CustomSkull {
|
||||
private final String skinHash;
|
||||
|
||||
private CustomBlockData customBlockData;
|
||||
|
||||
private static final String BITS_A_PROPERTY = "geyser_skull:bits_a";
|
||||
private static final String BITS_B_PROPERTY = "geyser_skull:bits_b";
|
||||
|
||||
private static final int[] ROTATIONS = {0, -90, 180, 90};
|
||||
|
||||
private static final BoxComponent FLOOR_BOX = new BoxComponent(
|
||||
-4, 0, -4,
|
||||
8, 8, 8
|
||||
);
|
||||
|
||||
private static final BoxComponent WALL_BOX = new BoxComponent(
|
||||
-4, 4, 0,
|
||||
8, 8, 8
|
||||
);
|
||||
|
||||
public CustomSkull(String skinHash) {
|
||||
this.skinHash = skinHash;
|
||||
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
.destructibleByMining(1.5f)
|
||||
.materialInstance("*", new MaterialInstanceBuilder()
|
||||
.texture("geyser." + skinHash + "_player_skin")
|
||||
.renderMethod("alpha_test")
|
||||
.faceDimming(true)
|
||||
.ambientOcclusion(true)
|
||||
.build())
|
||||
.lightDampening(0)
|
||||
.placeAir(true)
|
||||
.build();
|
||||
|
||||
List<CustomBlockPermutation> permutations = new ArrayList<>();
|
||||
addDefaultPermutation(permutations);
|
||||
addFloorPermutations(permutations);
|
||||
addWallPermutations(permutations);
|
||||
|
||||
customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder()
|
||||
.name("player_skull_" + skinHash)
|
||||
.components(components)
|
||||
.intProperty(BITS_A_PROPERTY, IntStream.rangeClosed(0, 6).boxed().toList()) // This gives us exactly 21 block states
|
||||
.intProperty(BITS_B_PROPERTY, IntStream.rangeClosed(0, 2).boxed().toList())
|
||||
.permutations(permutations)
|
||||
.build();
|
||||
}
|
||||
|
||||
public CustomBlockState getWallBlockState(int wallDirection) {
|
||||
wallDirection = switch (wallDirection) {
|
||||
case 0 -> 2; // South
|
||||
case 90 -> 3; // West
|
||||
case 180 -> 0; // North
|
||||
case 270 -> 1; // East
|
||||
default -> throw new IllegalArgumentException("Unknown skull wall direction: " + wallDirection);
|
||||
};
|
||||
|
||||
return customBlockData.blockStateBuilder()
|
||||
.intProperty(BITS_A_PROPERTY, wallDirection + 1)
|
||||
.intProperty(BITS_B_PROPERTY, 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
public CustomBlockState getFloorBlockState(int floorRotation) {
|
||||
return customBlockData.blockStateBuilder()
|
||||
.intProperty(BITS_A_PROPERTY, (5 + floorRotation) % 7)
|
||||
.intProperty(BITS_B_PROPERTY, (5 + floorRotation) / 7)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void addDefaultPermutation(List<CustomBlockPermutation> permutations) {
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.identifier("geometry.geyser.player_skull_hand")
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, 180, 0))
|
||||
.build();
|
||||
|
||||
String condition = String.format("query.block_property('%s') == 0 && query.block_property('%s') == 0", BITS_A_PROPERTY, BITS_B_PROPERTY);
|
||||
permutations.add(new CustomBlockPermutation(components, condition));
|
||||
}
|
||||
|
||||
private void addFloorPermutations(List<CustomBlockPermutation> permutations) {
|
||||
String[] quadrantNames = {"a", "b", "c", "d"};
|
||||
|
||||
for (int quadrant = 0; quadrant < 4; quadrant++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int floorRotation = 4 * quadrant + i;
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
.selectionBox(FLOOR_BOX)
|
||||
.collisionBox(FLOOR_BOX)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.identifier("geometry.geyser.player_skull_floor_" + quadrantNames[i])
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, ROTATIONS[quadrant], 0))
|
||||
.build();
|
||||
|
||||
int bitsA = (5 + floorRotation) % 7;
|
||||
int bitsB = (5 + floorRotation) / 7;
|
||||
String condition = String.format("query.block_property('%s') == %d && query.block_property('%s') == %d", BITS_A_PROPERTY, bitsA, BITS_B_PROPERTY, bitsB);
|
||||
permutations.add(new CustomBlockPermutation(components, condition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addWallPermutations(List<CustomBlockPermutation> permutations) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()
|
||||
.selectionBox(WALL_BOX)
|
||||
.collisionBox(WALL_BOX)
|
||||
.geometry(new GeometryComponentBuilder()
|
||||
.identifier("geometry.geyser.player_skull_wall")
|
||||
.build())
|
||||
.transformation(new TransformationComponent(0, ROTATIONS[i], 0))
|
||||
.build();
|
||||
|
||||
String condition = String.format("query.block_property('%s') == %d && query.block_property('%s') == %d", BITS_A_PROPERTY, i + 1, BITS_B_PROPERTY, 0);
|
||||
permutations.add(new CustomBlockPermutation(components, condition));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import lombok.AllArgsConstructor;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.With;
|
||||
|
||||
|
@ -39,6 +40,7 @@ import lombok.With;
|
|||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@Setter
|
||||
@With
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
|
|
|
@ -27,19 +27,22 @@ package org.geysermc.geyser.registry.type;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.common.DefinitionRegistry;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.item.type.PotionItem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -71,13 +74,15 @@ public class ItemMappings implements DefinitionRegistry<ItemDefinition> {
|
|||
List<ComponentItemData> componentItemData;
|
||||
Int2ObjectMap<String> customIdMappings;
|
||||
|
||||
Object2ObjectMap<CustomBlockData, ItemDefinition> customBlockItemDefinitions;
|
||||
|
||||
/**
|
||||
* Gets an {@link ItemMapping} from the given {@link ItemStack}.
|
||||
*
|
||||
* @param itemStack the itemstack
|
||||
* @return an item entry from the given java edition identifier
|
||||
*/
|
||||
@Nonnull
|
||||
@NonNull
|
||||
public ItemMapping getMapping(ItemStack itemStack) {
|
||||
return this.getMapping(itemStack.getId());
|
||||
}
|
||||
|
@ -89,11 +94,12 @@ public class ItemMappings implements DefinitionRegistry<ItemDefinition> {
|
|||
* @param javaId the id
|
||||
* @return an item entry from the given java edition identifier
|
||||
*/
|
||||
@Nonnull
|
||||
@NonNull
|
||||
public ItemMapping getMapping(int javaId) {
|
||||
return javaId >= 0 && javaId < this.items.length ? this.items[javaId] : ItemMapping.AIR;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ItemMapping getMapping(Item javaItem) {
|
||||
return getMapping(javaItem.javaIdentifier());
|
||||
}
|
||||
|
@ -105,6 +111,7 @@ public class ItemMappings implements DefinitionRegistry<ItemDefinition> {
|
|||
* @param javaIdentifier the block state identifier
|
||||
* @return an item entry from the given java edition identifier
|
||||
*/
|
||||
@Nullable
|
||||
public ItemMapping getMapping(String javaIdentifier) {
|
||||
return this.cachedJavaMappings.computeIfAbsent(javaIdentifier, key -> {
|
||||
for (ItemMapping mapping : this.items) {
|
||||
|
@ -122,6 +129,7 @@ public class ItemMappings implements DefinitionRegistry<ItemDefinition> {
|
|||
* @param data the item data
|
||||
* @return an item entry from the given item data
|
||||
*/
|
||||
@NonNull
|
||||
public ItemMapping getMapping(ItemData data) {
|
||||
ItemDefinition definition = data.getDefinition();
|
||||
if (ItemDefinition.AIR.equals(definition)) {
|
||||
|
@ -158,11 +166,22 @@ public class ItemMappings implements DefinitionRegistry<ItemDefinition> {
|
|||
return ItemMapping.AIR;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ItemDefinition getDefinition(int bedrockId) {
|
||||
return this.itemDefinitions.get(bedrockId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ItemDefinition getDefinition(String bedrockIdentifier) {
|
||||
for (ItemDefinition itemDefinition : this.itemDefinitions.values()) {
|
||||
if (itemDefinition.getIdentifier().equals(bedrockIdentifier)) {
|
||||
return itemDefinition;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(ItemDefinition definition) {
|
||||
return getDefinition(definition.getRuntimeId()) == definition;
|
||||
|
|
|
@ -52,6 +52,7 @@ import static org.geysermc.geyser.scoreboard.UpdateType.*;
|
|||
|
||||
public final class Scoreboard {
|
||||
private static final boolean SHOW_SCOREBOARD_LOGS = Boolean.parseBoolean(System.getProperty("Geyser.ShowScoreboardLogs", "true"));
|
||||
private static final boolean ADD_TEAM_SUGGESTIONS = Boolean.parseBoolean(System.getProperty("Geyser.AddTeamSuggestions", "true"));
|
||||
|
||||
private final GeyserSession session;
|
||||
private final GeyserLogger logger;
|
||||
|
@ -150,8 +151,9 @@ public final class Scoreboard {
|
|||
teams.put(teamName, team);
|
||||
|
||||
// Update command parameters - is safe to send even if the command enum doesn't exist on the client (as of 1.19.51)
|
||||
session.addCommandEnum("Geyser_Teams", team.getId());
|
||||
|
||||
if (ADD_TEAM_SUGGESTIONS) {
|
||||
session.addCommandEnum("Geyser_Teams", team.getId());
|
||||
}
|
||||
return team;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ import com.github.steveice10.packetlib.event.session.SessionAdapter;
|
|||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
||||
import com.github.steveice10.packetlib.tcp.TcpSession;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.EventLoop;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
|
@ -99,6 +98,7 @@ import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
|||
import org.geysermc.api.util.BedrockPlatform;
|
||||
import org.geysermc.api.util.InputMode;
|
||||
import org.geysermc.api.util.UiProfile;
|
||||
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
|
@ -114,6 +114,7 @@ import org.geysermc.geyser.api.network.AuthType;
|
|||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
|
@ -155,6 +156,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -178,7 +180,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
* Used for Floodgate skin uploading
|
||||
*/
|
||||
@Setter
|
||||
private List<SignedJWT> certChainData;
|
||||
private List<String> certChainData;
|
||||
|
||||
@NotNull
|
||||
@Setter
|
||||
|
@ -423,6 +425,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private boolean emulatePost1_18Logic = true;
|
||||
|
||||
/**
|
||||
* Whether to emulate pre-1.20 smithing table behavior.
|
||||
* Adapts ViaVersion's furnace UI to one Bedrock can use.
|
||||
* See {@link org.geysermc.geyser.translator.inventory.OldSmithingTableTranslator}.
|
||||
*/
|
||||
@Setter
|
||||
private boolean oldSmithingTable = false;
|
||||
|
||||
/**
|
||||
* The current attack speed of the player. Used for sending proper cooldown timings.
|
||||
* Setting a default fixes cooldowns not showing up on a fresh world.
|
||||
|
@ -454,6 +464,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private long lastInteractionTime;
|
||||
|
||||
/**
|
||||
* Stores when the player started to break a block. Used to allow correct break time for custom blocks.
|
||||
*/
|
||||
@Setter
|
||||
private long blockBreakStartTime;
|
||||
|
||||
/**
|
||||
* Stores whether the player intended to place a bucket.
|
||||
*/
|
||||
|
@ -534,7 +550,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private boolean waitingForStatistics = false;
|
||||
|
||||
private final Set<String> fogNameSpaces = new HashSet<>();
|
||||
/**
|
||||
* All fog effects that are currently applied to the client.
|
||||
*/
|
||||
private final Set<String> appliedFog = new HashSet<>();
|
||||
|
||||
private final Set<UUID> emotes;
|
||||
|
||||
|
@ -561,6 +580,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private ScheduledFuture<?> mountVehicleScheduledFuture = null;
|
||||
|
||||
/**
|
||||
* A cache of IDs from ClientboundKeepAlivePackets that have been sent to the Bedrock client, but haven't been returned to the server.
|
||||
* Only used if {@link GeyserConfiguration#isForwardPlayerPing()} is enabled.
|
||||
*/
|
||||
private final Queue<Long> keepAliveCache = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private MinecraftProtocol protocol;
|
||||
|
||||
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) {
|
||||
|
@ -910,7 +935,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
} else {
|
||||
downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), this.protocol);
|
||||
this.downstream = new DownstreamSession(downstream);
|
||||
disableSrvResolving();
|
||||
|
||||
boolean resolveSrv = false;
|
||||
try {
|
||||
resolveSrv = this.remoteServer.resolveSrv();
|
||||
} catch (AbstractMethodError | NoSuchMethodError ignored) {
|
||||
// Ignore if the method doesn't exist
|
||||
// This will happen with extensions using old APIs
|
||||
}
|
||||
this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, resolveSrv);
|
||||
}
|
||||
|
||||
if (geyser.getConfig().getRemote().isUseProxyProtocol()) {
|
||||
|
@ -1394,13 +1427,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
sendDownstreamPacket(swapHandsPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be overwritten for GeyserConnect.
|
||||
*/
|
||||
protected void disableSrvResolving() {
|
||||
this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return playerEntity.getUsername();
|
||||
|
@ -1561,6 +1587,17 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
startGamePacket.setItemDefinitions(this.itemMappings.getItemDefinitions().values().stream().toList()); // TODO
|
||||
// startGamePacket.setBlockPalette(this.blockMappings.getBedrockBlockPalette());
|
||||
|
||||
// Needed for custom block mappings and custom skulls system
|
||||
startGamePacket.getBlockProperties().addAll(this.blockMappings.getBlockProperties());
|
||||
|
||||
// See https://learn.microsoft.com/en-us/minecraft/creator/documents/experimentalfeaturestoggle for info on each experiment
|
||||
// data_driven_items (Holiday Creator Features) is needed for blocks and items
|
||||
startGamePacket.getExperiments().add(new ExperimentData("data_driven_items", true));
|
||||
// Needed for block properties for states
|
||||
startGamePacket.getExperiments().add(new ExperimentData("upcoming_creator_features", true));
|
||||
// Needed for certain molang queries used in blocks and items
|
||||
startGamePacket.getExperiments().add(new ExperimentData("experimental_molang_features", true));
|
||||
|
||||
startGamePacket.setVanillaVersion("*");
|
||||
startGamePacket.setInventoriesServerAuthoritative(true);
|
||||
startGamePacket.setServerEngine(""); // Do we want to fill this in?
|
||||
|
@ -1574,11 +1611,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
startGamePacket.setRewindHistorySize(0);
|
||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||
|
||||
if (GameProtocol.isPre1_20(this)) {
|
||||
startGamePacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
startGamePacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
upstream.sendPacket(startGamePacket);
|
||||
}
|
||||
|
||||
|
@ -1835,38 +1867,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the following fog IDs, as well as the cached ones, to the client.
|
||||
*
|
||||
* Fog IDs can be found here:
|
||||
* https://wiki.bedrock.dev/documentation/fog-ids.html
|
||||
*
|
||||
* @param fogNameSpaces the fog ids to add
|
||||
*/
|
||||
public void sendFog(String... fogNameSpaces) {
|
||||
this.fogNameSpaces.addAll(Arrays.asList(fogNameSpaces));
|
||||
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.fogNameSpaces);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the following fog IDs from the client and the cache.
|
||||
*
|
||||
* @param fogNameSpaces the fog ids to remove
|
||||
*/
|
||||
public void removeFog(String... fogNameSpaces) {
|
||||
if (fogNameSpaces.length == 0) {
|
||||
this.fogNameSpaces.clear();
|
||||
} else {
|
||||
this.fogNameSpaces.removeAll(Arrays.asList(fogNameSpaces));
|
||||
}
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.fogNameSpaces);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
public boolean canUseCommandBlocks() {
|
||||
return instabuild && opPermissionLevel >= 2;
|
||||
}
|
||||
|
@ -1978,6 +1978,54 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) {
|
||||
CameraShakePacket packet = new CameraShakePacket();
|
||||
packet.setIntensity(intensity);
|
||||
packet.setDuration(duration);
|
||||
packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL);
|
||||
packet.setShakeAction(CameraShakeAction.ADD);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopCameraShake() {
|
||||
CameraShakePacket packet = new CameraShakePacket();
|
||||
// CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null
|
||||
packet.setShakeType(CameraShakeType.POSITIONAL);
|
||||
packet.setShakeAction(CameraShakeAction.STOP);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFog(String... fogNameSpaces) {
|
||||
Collections.addAll(this.appliedFog, fogNameSpaces);
|
||||
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.appliedFog);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFog(String... fogNameSpaces) {
|
||||
if (fogNameSpaces.length == 0) {
|
||||
this.appliedFog.clear();
|
||||
} else {
|
||||
for (String id : fogNameSpaces) {
|
||||
this.appliedFog.remove(id);
|
||||
}
|
||||
}
|
||||
PlayerFogPacket packet = new PlayerFogPacket();
|
||||
packet.getFogStack().addAll(this.appliedFog);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Set<String> fogEffects() {
|
||||
// Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME)
|
||||
return Set.copyOf(this.appliedFog);
|
||||
}
|
||||
|
||||
public void addCommandEnum(String name, String enums) {
|
||||
softEnumPacket(name, SoftEnumUpdateType.ADD, enums);
|
||||
}
|
||||
|
@ -1987,6 +2035,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
}
|
||||
|
||||
private void softEnumPacket(String name, SoftEnumUpdateType type, String enums) {
|
||||
// There is no need to send command enums if command suggestions are disabled
|
||||
if (!this.geyser.getConfig().isCommandSuggestions()) {
|
||||
return;
|
||||
}
|
||||
UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket();
|
||||
packet.setType(type);
|
||||
packet.setSoftEnum(new CommandEnumData(name, Collections.singletonMap(enums, Collections.emptySet()), true));
|
||||
|
|
|
@ -42,6 +42,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
@RequiredArgsConstructor
|
||||
public class FormCache {
|
||||
|
||||
/**
|
||||
* The magnitude of this doesn't actually matter, but it must be negative so that
|
||||
* BedrockNetworkStackLatencyTranslator can detect the hack.
|
||||
*/
|
||||
private static final long MAGIC_FORM_IMAGE_HACK_TIMESTAMP = -1234567890L;
|
||||
|
||||
private final FormDefinitions formDefinitions = FormDefinitions.instance();
|
||||
private final AtomicInteger formIdCounter = new AtomicInteger(0);
|
||||
private final Int2ObjectMap<Form> forms = new Int2ObjectOpenHashMap<>();
|
||||
|
@ -73,7 +80,7 @@ public class FormCache {
|
|||
if (form instanceof SimpleForm) {
|
||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||
latencyPacket.setFromServer(true);
|
||||
latencyPacket.setTimestamp(-System.currentTimeMillis());
|
||||
latencyPacket.setTimestamp(MAGIC_FORM_IMAGE_HACK_TIMESTAMP);
|
||||
session.scheduleInEventLoop(
|
||||
() -> session.sendUpstreamPacket(latencyPacket),
|
||||
500, TimeUnit.MILLISECONDS
|
||||
|
|
|
@ -31,9 +31,17 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.skin.SkinManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class SkullCache {
|
||||
|
@ -71,21 +79,44 @@ public class SkullCache {
|
|||
this.skullRenderDistanceSquared = distance * distance;
|
||||
}
|
||||
|
||||
public void putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) {
|
||||
public Skull putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) {
|
||||
Skull skull = skulls.computeIfAbsent(position, Skull::new);
|
||||
skull.uuid = uuid;
|
||||
skull.texturesProperty = texturesProperty;
|
||||
if (!texturesProperty.equals(skull.texturesProperty)) {
|
||||
skull.texturesProperty = texturesProperty;
|
||||
skull.skinHash = null;
|
||||
try {
|
||||
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty);
|
||||
if (gameProfileData != null && gameProfileData.skinUrl() != null) {
|
||||
String skinUrl = gameProfileData.skinUrl();
|
||||
skull.skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1);
|
||||
} else {
|
||||
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty);
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
skull.blockState = blockState;
|
||||
skull.blockDefinition = translateCustomSkull(skull.skinHash, blockState);
|
||||
|
||||
if (skull.blockDefinition != null) {
|
||||
reassignSkullEntity(skull);
|
||||
return skull;
|
||||
}
|
||||
|
||||
if (skull.entity != null) {
|
||||
skull.entity.updateSkull(skull);
|
||||
} else {
|
||||
if (!cullingEnabled) {
|
||||
assignSkullEntity(skull);
|
||||
return;
|
||||
return skull;
|
||||
}
|
||||
if (lastPlayerPosition == null) {
|
||||
return;
|
||||
return skull;
|
||||
}
|
||||
skull.distanceSquared = position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ());
|
||||
if (skull.distanceSquared < skullRenderDistanceSquared) {
|
||||
|
@ -105,24 +136,24 @@ public class SkullCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
return skull;
|
||||
}
|
||||
|
||||
public void removeSkull(Vector3i position) {
|
||||
Skull skull = skulls.remove(position);
|
||||
if (skull != null) {
|
||||
boolean hadEntity = skull.entity != null;
|
||||
freeSkullEntity(skull);
|
||||
|
||||
if (cullingEnabled) {
|
||||
inRangeSkulls.remove(skull);
|
||||
if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) {
|
||||
// Reassign entity to the closest skull without an entity
|
||||
assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1));
|
||||
}
|
||||
}
|
||||
reassignSkullEntity(skull);
|
||||
}
|
||||
}
|
||||
|
||||
public Skull updateSkull(Vector3i position, int blockState) {
|
||||
Skull skull = skulls.get(position);
|
||||
if (skull != null) {
|
||||
putSkull(position, skull.uuid, skull.texturesProperty, blockState);
|
||||
}
|
||||
return skull;
|
||||
}
|
||||
|
||||
public void updateVisibleSkulls() {
|
||||
if (cullingEnabled) {
|
||||
// No need to recheck skull visibility for small movements
|
||||
|
@ -133,6 +164,10 @@ public class SkullCache {
|
|||
|
||||
inRangeSkulls.clear();
|
||||
for (Skull skull : skulls.values()) {
|
||||
if (skull.blockDefinition != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
skull.distanceSquared = skull.position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ());
|
||||
if (skull.distanceSquared > skullRenderDistanceSquared) {
|
||||
freeSkullEntity(skull);
|
||||
|
@ -191,6 +226,19 @@ public class SkullCache {
|
|||
}
|
||||
}
|
||||
|
||||
private void reassignSkullEntity(Skull skull) {
|
||||
boolean hadEntity = skull.entity != null;
|
||||
freeSkullEntity(skull);
|
||||
|
||||
if (cullingEnabled) {
|
||||
inRangeSkulls.remove(skull);
|
||||
if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) {
|
||||
// Reassign entity to the closest skull without an entity
|
||||
assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
skulls.clear();
|
||||
inRangeSkulls.clear();
|
||||
|
@ -199,12 +247,33 @@ public class SkullCache {
|
|||
lastPlayerPosition = null;
|
||||
}
|
||||
|
||||
private BlockDefinition translateCustomSkull(String skinHash, int blockState) {
|
||||
CustomSkull customSkull = BlockRegistries.CUSTOM_SKULLS.get(skinHash);
|
||||
if (customSkull != null) {
|
||||
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
|
||||
CustomBlockState customBlockState;
|
||||
if (floorRotation == -1) {
|
||||
// Wall skull
|
||||
int wallDirection = BlockStateValues.getSkullWallDirections().get(blockState);
|
||||
customBlockState = customSkull.getWallBlockState(wallDirection);
|
||||
} else {
|
||||
customBlockState = customSkull.getFloorBlockState(floorRotation);
|
||||
}
|
||||
|
||||
return session.getBlockMappings().getCustomBlockStateDefinitions().get(customBlockState);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
public static class Skull {
|
||||
private UUID uuid;
|
||||
private String texturesProperty;
|
||||
private String skinHash;
|
||||
|
||||
private int blockState;
|
||||
private BlockDefinition blockDefinition;
|
||||
private SkullPlayerEntity entity;
|
||||
|
||||
private final Vector3i position;
|
||||
|
|
|
@ -30,7 +30,6 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||
import org.geysermc.floodgate.util.WebsocketEventType;
|
||||
|
@ -191,14 +190,14 @@ public final class FloodgateSkinUploader {
|
|||
};
|
||||
}
|
||||
|
||||
public void uploadSkin(List<SignedJWT> chainData, String clientData) {
|
||||
public void uploadSkin(List<String> chainData, String clientData) {
|
||||
if (chainData == null || clientData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectNode node = JACKSON.createObjectNode();
|
||||
ArrayNode chainDataNode = JACKSON.createArrayNode();
|
||||
chainData.forEach(jwt -> chainDataNode.add(jwt.serialize()));
|
||||
chainData.forEach(chainDataNode::add);
|
||||
node.set("chain_data", chainDataNode);
|
||||
node.put("client_data", clientData);
|
||||
|
||||
|
|
|
@ -277,8 +277,15 @@ public class SkinManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException {
|
||||
JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
|
||||
public static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException {
|
||||
JsonNode skinObject;
|
||||
try {
|
||||
skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
|
||||
} catch (IllegalArgumentException e) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Invalid base64 encoded skin entry: " + encodedJson);
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonNode textures = skinObject.get("textures");
|
||||
|
||||
if (textures == null) {
|
||||
|
|
|
@ -460,7 +460,7 @@ public class SkinProvider {
|
|||
|
||||
private static Skin supplySkin(UUID uuid, String textureUrl) {
|
||||
try {
|
||||
byte[] skin = requestImage(textureUrl, null);
|
||||
byte[] skin = requestImageData(textureUrl, null);
|
||||
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false);
|
||||
} catch (Exception ignored) {} // just ignore I guess
|
||||
|
||||
|
@ -470,7 +470,7 @@ public class SkinProvider {
|
|||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||
byte[] cape = EMPTY_CAPE.capeData();
|
||||
try {
|
||||
cape = requestImage(capeUrl, provider);
|
||||
cape = requestImageData(capeUrl, provider);
|
||||
} catch (Exception ignored) {
|
||||
} // just ignore I guess
|
||||
|
||||
|
@ -527,7 +527,7 @@ public class SkinProvider {
|
|||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception {
|
||||
public static BufferedImage requestImage(String imageUrl, CapeProvider provider) throws IOException {
|
||||
BufferedImage image = null;
|
||||
|
||||
// First see if we have a cached file. We also update the modification stamp so we know when the file was last used
|
||||
|
@ -587,6 +587,11 @@ public class SkinProvider {
|
|||
// TODO remove alpha channel
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private static byte[] requestImageData(String imageUrl, CapeProvider provider) throws Exception {
|
||||
BufferedImage image = requestImage(imageUrl, provider);
|
||||
byte[] data = bufferedImageToImageData(image);
|
||||
image.flush();
|
||||
return data;
|
||||
|
|
|
@ -28,7 +28,7 @@ package org.geysermc.geyser.text;
|
|||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer;
|
||||
import net.kyori.adventure.text.serializer.json.LegacyHoverEventSerializer;
|
||||
import net.kyori.adventure.util.Codec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -40,9 +40,9 @@ public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSe
|
|||
private final HoverEvent.ShowItem dummyShowItem;
|
||||
|
||||
public DummyLegacyHoverEventSerializer() {
|
||||
dummyShowEntity = HoverEvent.ShowEntity.of(Key.key("geysermc", "dummyshowitem"),
|
||||
dummyShowEntity = HoverEvent.ShowEntity.showEntity(Key.key("geysermc", "dummyshowitem"),
|
||||
UUID.nameUUIDFromBytes("entitiesareprettyneat".getBytes(StandardCharsets.UTF_8)));
|
||||
dummyShowItem = HoverEvent.ShowItem.of(Key.key("geysermc", "dummyshowentity"), 0);
|
||||
dummyShowItem = HoverEvent.ShowItem.showItem(Key.key("geysermc", "dummyshowentity"), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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.translator.inventory;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.DropAction;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.ItemStackRequestAction;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.PlaceAction;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.SwapAction;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.TakeAction;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
|
||||
import org.geysermc.geyser.inventory.BedrockContainerSlot;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Translator for smithing tables for pre-1.20 servers.
|
||||
* This adapts ViaVersion's furnace ui to the 1.20+ smithing table; with the addition of a fake smithing template so Bedrock clients can use it.
|
||||
*/
|
||||
public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator {
|
||||
|
||||
public static final OldSmithingTableTranslator INSTANCE = new OldSmithingTableTranslator();
|
||||
|
||||
private static final IntFunction<ItemData> UPGRADE_TEMPLATE = InventoryUtils.getUpgradeTemplate();
|
||||
|
||||
private OldSmithingTableTranslator() {
|
||||
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
|
||||
return switch (slotInfoData.getContainer()) {
|
||||
case SMITHING_TABLE_INPUT -> 0;
|
||||
case SMITHING_TABLE_MATERIAL -> 1;
|
||||
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 2;
|
||||
default -> super.bedrockSlotToJava(slotInfoData);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
|
||||
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
|
||||
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
|
||||
default -> super.javaSlotToBedrockContainer(slot);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int javaSlotToBedrock(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> 51;
|
||||
case 1 -> 52;
|
||||
case 2 -> 50;
|
||||
default -> super.javaSlotToBedrock(slot);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
for (var action: request.getActions()) {
|
||||
switch (action.getType()) {
|
||||
case DROP -> {
|
||||
if (isInvalidAction(((DropAction) action).getSource())) {
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
}
|
||||
case TAKE -> {
|
||||
if (isInvalidAction(((TakeAction) action).getSource()) ||
|
||||
isInvalidAction(((TakeAction) action).getDestination())) {
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
}
|
||||
case SWAP -> {
|
||||
if (isInvalidAction(((SwapAction) action).getSource()) ||
|
||||
isInvalidAction(((SwapAction) action).getDestination())) {
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
}
|
||||
case PLACE -> {
|
||||
if (isInvalidAction(((PlaceAction) action).getSource()) ||
|
||||
isInvalidAction(((PlaceAction) action).getDestination())) {
|
||||
return rejectRequest(request, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Allow everything else that doesn't involve the fake template
|
||||
return super.translateRequest(session, inventory, request);
|
||||
}
|
||||
|
||||
private boolean isInvalidAction(ItemStackRequestSlotData slotData) {
|
||||
return slotData.getContainer().equals(ContainerSlotType.SMITHING_TABLE_TEMPLATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
super.openInventory(session, inventory);
|
||||
|
||||
// pre-1.20 server has no concept of templates, but we are working with a 1.20 client
|
||||
// put a fake netherite upgrade template in the template slot otherwise the client doesn't recognize a valid recipe
|
||||
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||
slotPacket.setContainerId(ContainerId.UI);
|
||||
slotPacket.setSlot(53);
|
||||
slotPacket.setItem(UPGRADE_TEMPLATE.apply(session.getUpstream().getProtocolVersion()));
|
||||
session.sendUpstreamPacket(slotPacket);
|
||||
}
|
||||
}
|
|
@ -57,30 +57,32 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
|
|||
// See BlockInventoryHolder - same concept there except we're also dealing with a specific block state
|
||||
if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) {
|
||||
int javaBlockId = session.getGeyser().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition());
|
||||
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.AIR).getJavaIdentifier().split("\\[");
|
||||
if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest"))
|
||||
&& !javaBlockString[1].contains("type=single")) {
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
|
||||
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(javaBlockId)) {
|
||||
String[] javaBlockString = BlockRegistries.JAVA_BLOCKS.getOrDefault(javaBlockId, BlockMapping.AIR).getJavaIdentifier().split("\\[");
|
||||
if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest"))
|
||||
&& !javaBlockString[1].contains("type=single")) {
|
||||
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
|
||||
((Container) inventory).setUsingRealBlock(true, javaBlockString[0]);
|
||||
|
||||
NbtMapBuilder tag = NbtMap.builder()
|
||||
.putString("id", "Chest")
|
||||
.putInt("x", session.getLastInteractionBlockPosition().getX())
|
||||
.putInt("y", session.getLastInteractionBlockPosition().getY())
|
||||
.putInt("z", session.getLastInteractionBlockPosition().getZ())
|
||||
.putString("CustomName", inventory.getTitle())
|
||||
.putString("id", "Chest");
|
||||
NbtMapBuilder tag = NbtMap.builder()
|
||||
.putString("id", "Chest")
|
||||
.putInt("x", session.getLastInteractionBlockPosition().getX())
|
||||
.putInt("y", session.getLastInteractionBlockPosition().getY())
|
||||
.putInt("z", session.getLastInteractionBlockPosition().getZ())
|
||||
.putString("CustomName", inventory.getTitle())
|
||||
.putString("id", "Chest");
|
||||
|
||||
DoubleChestValue chestValue = BlockStateValues.getDoubleChestValues().get(javaBlockId);
|
||||
DoubleChestBlockEntityTranslator.translateChestValue(tag, chestValue,
|
||||
session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ());
|
||||
DoubleChestValue chestValue = BlockStateValues.getDoubleChestValues().get(javaBlockId);
|
||||
DoubleChestBlockEntityTranslator.translateChestValue(tag, chestValue,
|
||||
session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ());
|
||||
|
||||
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag.build());
|
||||
dataPacket.setBlockPosition(session.getLastInteractionBlockPosition());
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag.build());
|
||||
dataPacket.setBlockPosition(session.getLastInteractionBlockPosition());
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +92,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
|
|||
}
|
||||
|
||||
Vector3i pairPosition = position.add(Vector3i.UNIT_X);
|
||||
BlockDefinition definition = session.getBlockMappings().getBedrockBlock(defaultJavaBlockState);
|
||||
BlockDefinition definition = session.getBlockMappings().getVanillaBedrockBlock(defaultJavaBlockState);
|
||||
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
|
|
|
@ -28,9 +28,23 @@ package org.geysermc.geyser.translator.inventory.item;
|
|||
import com.github.steveice10.mc.protocol.data.game.Identifier;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.attribute.ModifierOperation;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.DoubleTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.FloatTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ShortTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockData;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
|
@ -41,12 +55,15 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.CustomSkull;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.skin.SkinManager;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
@ -143,9 +160,21 @@ public final class ItemTranslator {
|
|||
|
||||
ItemData.Builder builder = javaItem.translateToBedrock(itemStack, bedrockItem, session.getItemMappings());
|
||||
if (bedrockItem.isBlock()) {
|
||||
builder.blockDefinition(bedrockItem.getBedrockBlockDefinition());
|
||||
CustomBlockData customBlockData = BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.getOrDefault(
|
||||
bedrockItem.getJavaItem().javaIdentifier(), null);
|
||||
if (customBlockData != null) {
|
||||
translateCustomBlock(customBlockData, session, builder);
|
||||
} else {
|
||||
builder.blockDefinition(bedrockItem.getBedrockBlockDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
if (bedrockItem.getJavaItem().equals(Items.PLAYER_HEAD)) {
|
||||
translatePlayerHead(session, nbt, builder);
|
||||
}
|
||||
|
||||
translateCustomItem(nbt, builder, bedrockItem);
|
||||
|
||||
if (nbt != null) {
|
||||
// Translate the canDestroy and canPlaceOn Java NBT
|
||||
ListTag canDestroy = nbt.get("CanDestroy");
|
||||
|
@ -362,10 +391,24 @@ public final class ItemTranslator {
|
|||
|
||||
ItemMapping mapping = itemStack.asItem().toBedrockDefinition(itemStack.getNbt(), session.getItemMappings());
|
||||
|
||||
ItemDefinition itemDefinition = mapping.getBedrockDefinition();
|
||||
CustomBlockData customBlockData = BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.getOrDefault(
|
||||
mapping.getJavaItem().javaIdentifier(), null);
|
||||
if (customBlockData != null) {
|
||||
itemDefinition = session.getItemMappings().getCustomBlockItemDefinitions().get(customBlockData);
|
||||
}
|
||||
|
||||
if (mapping.getJavaItem().equals(Items.PLAYER_HEAD)) {
|
||||
CustomSkull customSkull = getCustomSkull(session, itemStack.getNbt());
|
||||
if (customSkull != null) {
|
||||
itemDefinition = session.getItemMappings().getCustomBlockItemDefinitions().get(customSkull.getCustomBlockData());
|
||||
}
|
||||
}
|
||||
|
||||
ItemDefinition definition = CustomItemTranslator.getCustomItem(itemStack.getNbt(), mapping);
|
||||
if (definition == null) {
|
||||
// No custom item
|
||||
return mapping.getBedrockDefinition();
|
||||
return itemDefinition;
|
||||
} else {
|
||||
return definition;
|
||||
}
|
||||
|
@ -553,6 +596,46 @@ public final class ItemTranslator {
|
|||
ItemDefinition definition = CustomItemTranslator.getCustomItem(nbt, mapping);
|
||||
if (definition != null) {
|
||||
builder.definition(definition);
|
||||
builder.blockDefinition(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a custom block override
|
||||
*/
|
||||
private static void translateCustomBlock(CustomBlockData customBlockData, GeyserSession session, ItemData.Builder builder) {
|
||||
ItemDefinition itemDefinition = session.getItemMappings().getCustomBlockItemDefinitions().get(customBlockData);
|
||||
BlockDefinition blockDefinition = session.getBlockMappings().getCustomBlockStateDefinitions().get(customBlockData.defaultBlockState());
|
||||
builder.definition(itemDefinition);
|
||||
builder.blockDefinition(blockDefinition);
|
||||
}
|
||||
|
||||
private static CustomSkull getCustomSkull(GeyserSession session, CompoundTag nbt) {
|
||||
if (nbt != null && nbt.contains("SkullOwner")) {
|
||||
if (!(nbt.get("SkullOwner") instanceof CompoundTag skullOwner)) {
|
||||
// It's a username give up d:
|
||||
return null;
|
||||
}
|
||||
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(skullOwner);
|
||||
if (data == null) {
|
||||
session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt);
|
||||
return null;
|
||||
}
|
||||
|
||||
String skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1);
|
||||
return BlockRegistries.CUSTOM_SKULLS.get(skinHash);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void translatePlayerHead(GeyserSession session, CompoundTag nbt, ItemData.Builder builder) {
|
||||
CustomSkull customSkull = getCustomSkull(session, nbt);
|
||||
if (customSkull != null) {
|
||||
CustomBlockData customBlockData = customSkull.getCustomBlockData();
|
||||
ItemDefinition itemDefinition = session.getItemMappings().getCustomBlockItemDefinitions().get(customBlockData);
|
||||
BlockDefinition blockDefinition = session.getBlockMappings().getCustomBlockStateDefinitions().get(customBlockData.defaultBlockState());
|
||||
builder.definition(itemDefinition);
|
||||
builder.blockDefinition(blockDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,11 +39,12 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
|||
public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
ListTag items = tag.get("Items");
|
||||
int i = 1;
|
||||
for (Tag itemTag : items.getValue()) {
|
||||
builder.put("Item" + i, getItem((CompoundTag) itemTag));
|
||||
i++;
|
||||
if (tag.get("Items") instanceof ListTag items) {
|
||||
int i = 1;
|
||||
for (Tag itemTag : items.getValue()) {
|
||||
builder.put("Item" + i, getItem((CompoundTag) itemTag));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator imp
|
|||
// Java and Bedrock values
|
||||
builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
|
||||
builder.put("auto", ((ByteTag) tag.get("auto")).getValue());
|
||||
builder.put("CustomName", MessageTranslator.convertMessage(((StringTag) tag.get("CustomName")).getValue()));
|
||||
builder.put("CustomName", MessageTranslator.convertJsonMessage(((StringTag) tag.get("CustomName")).getValue()));
|
||||
builder.put("powered", ((ByteTag) tag.get("powered")).getValue());
|
||||
builder.put("Command", ((StringTag) tag.get("Command")).getValue());
|
||||
builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.geysermc.geyser.level.physics.Axis;
|
|||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.translator.collision.BlockCollision;
|
||||
|
@ -95,7 +95,7 @@ public class PistonBlockEntity {
|
|||
|
||||
static {
|
||||
// Create a ~1 x ~0.5 x ~1 bounding box above the honey block
|
||||
BlockCollision blockCollision = Registries.COLLISIONS.get(BlockStateValues.JAVA_HONEY_BLOCK_ID);
|
||||
BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(BlockStateValues.JAVA_HONEY_BLOCK_ID);
|
||||
if (blockCollision == null) {
|
||||
throw new RuntimeException("Failed to find honey block collision");
|
||||
}
|
||||
|
@ -485,7 +485,7 @@ public class PistonBlockEntity {
|
|||
pistonCache.displacePlayer(movement.mul(delta));
|
||||
} else {
|
||||
// Move the player out of collision
|
||||
BlockCollision blockCollision = Registries.COLLISIONS.get(javaId);
|
||||
BlockCollision blockCollision = BlockRegistries.COLLISIONS.get(javaId);
|
||||
if (blockCollision != null) {
|
||||
Vector3d extend = movement.mul(Math.min(1 - blockMovement, 0.5));
|
||||
Direction movementDirection = orientation;
|
||||
|
|
|
@ -30,10 +30,14 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
|||
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.skin.SkinProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -41,6 +45,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.SKULL)
|
||||
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
|
||||
|
@ -91,21 +96,55 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
|
|||
return CompletableFuture.completedFuture(texture.getValue());
|
||||
}
|
||||
|
||||
public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) {
|
||||
Vector3i blockPosition = Vector3i.from(posX, posY, posZ);
|
||||
public static BlockDefinition translateSkull(GeyserSession session, CompoundTag tag, Vector3i blockPosition, int blockState) {
|
||||
CompoundTag owner = tag.get("SkullOwner");
|
||||
if (owner == null) {
|
||||
session.getSkullCache().removeSkull(blockPosition);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
UUID uuid = getUUID(owner);
|
||||
|
||||
CompletableFuture<String> texturesFuture = getTextures(owner, uuid);
|
||||
if (texturesFuture.isDone()) {
|
||||
try {
|
||||
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, uuid, texturesFuture.get(), blockState);
|
||||
return skull.getBlockDefinition();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
session.getGeyser().getLogger().debug("Failed to acquire textures for custom skull: " + blockPosition + " " + tag);
|
||||
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
UUID uuid = getUUID(owner);
|
||||
getTextures(owner, uuid).whenComplete((texturesProperty, throwable) -> {
|
||||
// SkullOwner contained a username, so we have to wait for it to be retrieved
|
||||
texturesFuture.whenComplete((texturesProperty, throwable) -> {
|
||||
if (texturesProperty == null) {
|
||||
session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag);
|
||||
return;
|
||||
}
|
||||
if (session.getEventLoop().inEventLoop()) {
|
||||
session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState);
|
||||
putSkull(session, blockPosition, uuid, texturesProperty, blockState);
|
||||
} else {
|
||||
session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState));
|
||||
session.executeInEventLoop(() -> putSkull(session, blockPosition, uuid, texturesProperty, blockState));
|
||||
}
|
||||
});
|
||||
|
||||
// We don't have the textures yet, so we can't determine if a custom block was defined for this skull
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void putSkull(GeyserSession session, Vector3i blockPosition, UUID uuid, String texturesProperty, int blockState) {
|
||||
SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState);
|
||||
if (skull.getBlockDefinition() != null) {
|
||||
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
||||
updateBlockPacket.setDataLayer(0);
|
||||
updateBlockPacket.setBlockPosition(blockPosition);
|
||||
updateBlockPacket.setDefinition(skull.getBlockDefinition());
|
||||
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
|
||||
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
|
||||
session.sendUpstreamPacket(updateBlockPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.cloudburstmc.math.vector.Vector3d;
|
|||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
|
@ -66,6 +67,7 @@ import org.geysermc.geyser.item.type.SpawnEggItem;
|
|||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.skin.FakeHeadProvider;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
||||
|
@ -181,6 +183,27 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
}
|
||||
}
|
||||
|
||||
// Check if this is a double placement due to an extended collision block
|
||||
if (!session.getBlockMappings().getExtendedCollisionBoxes().isEmpty()) {
|
||||
Vector3i belowBlockPos = null;
|
||||
switch (packet.getBlockFace()) {
|
||||
case 1 -> belowBlockPos = blockPos.add(0, -2, 0);
|
||||
case 2 -> belowBlockPos = blockPos.add(0, -1, 1);
|
||||
case 3 -> belowBlockPos = blockPos.add(0, -1, -1);
|
||||
case 4 -> belowBlockPos = blockPos.add(1, -1, 0);
|
||||
case 5 -> belowBlockPos = blockPos.add(-1, -1, 0);
|
||||
}
|
||||
|
||||
if (belowBlockPos != null) {
|
||||
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, belowBlockPos);
|
||||
BlockDefinition extendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
|
||||
if (extendedCollisionDefinition != null && (System.currentTimeMillis() - session.getLastInteractionTime()) < 200) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to make sure the client isn't spamming interaction
|
||||
// Based on Nukkit 1.0, with changes to ensure holding down still works
|
||||
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
|
||||
|
@ -265,6 +288,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
restoreCorrectBlock(session, blockPos, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Block place checks end - client is good to go
|
||||
*/
|
||||
|
@ -524,10 +548,20 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
*/
|
||||
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
|
||||
int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos);
|
||||
BlockDefinition bedrockBlock = session.getBlockMappings().getBedrockBlock(javaBlockState);
|
||||
|
||||
if (BlockStateValues.getSkullVariant(javaBlockState) == 3) {
|
||||
// The changed block was a player skull so check if a custom block was defined for this skull
|
||||
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(blockPos);
|
||||
if (skull != null && skull.getBlockDefinition() != null) {
|
||||
bedrockBlock = skull.getBlockDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
||||
updateBlockPacket.setDataLayer(0);
|
||||
updateBlockPacket.setBlockPosition(blockPos);
|
||||
updateBlockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(javaBlockState));
|
||||
updateBlockPacket.setDefinition(bedrockBlock);
|
||||
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(updateBlockPacket);
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
|||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
||||
/**
|
||||
* Sent by the client when moving a horse.
|
||||
* Sent by the client when moving a horse or boat.
|
||||
*/
|
||||
@Translator(packet = MoveEntityAbsolutePacket.class)
|
||||
public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEntityAbsolutePacket> {
|
||||
|
@ -64,7 +64,7 @@ public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEn
|
|||
}
|
||||
|
||||
float y = packet.getPosition().getY();
|
||||
if (ridingEntity instanceof BoatEntity) {
|
||||
if (ridingEntity instanceof BoatEntity && !ridingEntity.isOnGround()) {
|
||||
// Remove the offset to prevents boats from looking like they're floating in water
|
||||
y -= EntityDefinitions.BOAT.offset();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundKe
|
|||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.floodgate.util.DeviceOs;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
|
@ -46,37 +45,43 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
|||
|
||||
@Override
|
||||
public void translate(GeyserSession session, NetworkStackLatencyPacket packet) {
|
||||
long pingId;
|
||||
// so apparently, as of 1.16.200
|
||||
// PS4 divides the network stack latency timestamp FOR US!!!
|
||||
// WTF
|
||||
if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
|
||||
pingId = packet.getTimestamp();
|
||||
} else {
|
||||
pingId = packet.getTimestamp() / 1000;
|
||||
}
|
||||
|
||||
// negative timestamps are used as hack to fix the url image loading bug
|
||||
if (packet.getTimestamp() > 0) {
|
||||
if (packet.getTimestamp() >= 0) {
|
||||
if (session.getGeyser().getConfig().isForwardPlayerPing()) {
|
||||
ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(pingId);
|
||||
// use our cached value because
|
||||
// a) bedrock can be inaccurate with the value returned
|
||||
// b) playstation replies with a different magnitude than other platforms
|
||||
// c) 1.20.10 and later reply with a different magnitude
|
||||
Long keepAliveId = session.getKeepAliveCache().poll();
|
||||
if (keepAliveId == null) {
|
||||
session.getGeyser().getLogger().debug("Received a latency packet that we don't have a KeepAlive for: " + packet);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerboundKeepAlivePacket keepAlivePacket = new ServerboundKeepAlivePacket(keepAliveId);
|
||||
session.sendDownstreamPacket(keepAlivePacket);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack to fix the url image loading bug
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
session.scheduleInEventLoop(() -> {
|
||||
// Hack to fix the url image loading bug
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
|
||||
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
|
||||
if (attribute != null) {
|
||||
attributesPacket.setAttributes(Collections.singletonList(attribute));
|
||||
} else {
|
||||
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
|
||||
}
|
||||
AttributeData attribute = session.getPlayerEntity().getAttributes().get(GeyserAttributeType.EXPERIENCE_LEVEL);
|
||||
if (attribute != null) {
|
||||
attributesPacket.setAttributes(Collections.singletonList(attribute));
|
||||
} else {
|
||||
attributesPacket.setAttributes(Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0)));
|
||||
}
|
||||
|
||||
session.scheduleInEventLoop(() -> session.sendUpstreamPacket(attributesPacket),
|
||||
500, TimeUnit.MILLISECONDS);
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecuteInEventLoop() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue