Super cursed custom skulls custom block

This commit is contained in:
davchoo 2022-06-19 03:59:07 -04:00
parent f9fd7cb831
commit 0eb7218582
No known key found for this signature in database
GPG key ID: A0168C8E45799B7D
33 changed files with 1681 additions and 33 deletions

View file

@ -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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
import java.util.List;
import java.util.Map;
/**
* This is used to store data for a custom block.
*/
public interface CustomBlockData {
@NonNull String name();
CustomBlockComponents components();
@NonNull Map<String, CustomBlockProperty<?>> properties();
@NonNull List<CustomBlockPermutation> permutations();
CustomBlockState.Builder blockStateBuilder();
interface Builder {
Builder name(@NonNull String name);
Builder components(@NonNull CustomBlockComponents components);
Builder booleanProperty(@NonNull String propertyName, List<Boolean> values);
Builder intProperty(@NonNull String propertyName, List<Integer> values);
Builder stringProperty(@NonNull String propertyName, List<String> values);
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
CustomBlockData build();
}
}

View file

@ -0,0 +1,43 @@
/*
* 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;
public interface CustomBlockPermutation {
@NonNull String condition();
CustomBlockComponents components();
interface Builder {
Builder condition(@NonNull String condition);
Builder components(CustomBlockComponents components);
CustomBlockPermutation build();
}
}

View file

@ -0,0 +1,48 @@
/*
* 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;
public interface CustomBlockState {
@NonNull String name();
@NonNull <T> T property(String propertyName);
@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();
}
}

View file

@ -0,0 +1,30 @@
/*
* 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;
public record CollisionComponent(float originX, float originY, float originZ,
float sizeX, float sizeY, float sizeZ) {
}

View file

@ -0,0 +1,70 @@
/*
* 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.Map;
public interface CustomBlockComponents {
CollisionComponent aimCollision();
CollisionComponent entityCollision();
String geometry();
Map<String, MaterialInstance> materialInstances();
Float destroyTime();
Float friction();
Float lightEmission();
Integer lightFilter();
RotationComponent rotation();
interface Builder {
Builder aimCollision(CollisionComponent aimCollision);
Builder entityCollision(CollisionComponent entityCollision);
Builder geometry(String geometry);
Builder materialInstances(Map<String, MaterialInstance> materialInstances);
Builder destroyTime(Float destroyTime);
Builder friction(Float friction);
Builder lightEmission(Float lightEmission);
Builder lightFilter(Integer lightFilter);
Builder rotation(RotationComponent rotation);
CustomBlockComponents build();
}
}

View file

@ -0,0 +1,32 @@
/*
* 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;
public record MaterialInstance(@NonNull String texture, @NonNull RenderMethod renderMethod, boolean faceDimming, boolean ambientOcclusion) {
}

View file

@ -0,0 +1,32 @@
/*
* 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;
public enum RenderMethod {
BLEND,
OPAQUE,
ALPHA_TEST
}

View file

@ -0,0 +1,29 @@
/*
* 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;
public record RotationComponent(float x, float y, float z) {
}

View file

@ -0,0 +1,38 @@
/*
* 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;
public interface CustomBlockProperty<T> {
@NonNull String name();
@NonNull List<T> values();
@NonNull PropertyType<T> type();
}

View file

@ -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.api.block.custom.property;
public class PropertyType<T> {
public static final PropertyType<Boolean> BOOLEAN = new PropertyType<>(Boolean.class);
public static final PropertyType<Integer> INTEGER = new PropertyType<>(Integer.class);
public static final PropertyType<String> STRING = new PropertyType<>(String.class);
private final Class<?> typeClass;
private PropertyType(Class<?> typeClass) {
this.typeClass = typeClass;
}
public Class<?> getTypeClass() {
return typeClass;
}
}

View file

@ -0,0 +1,48 @@
/*
* 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;
import lombok.RequiredArgsConstructor;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
@RequiredArgsConstructor
public class GeyserConvertSkullInventoryEvent implements Event {
private final String skinHash;
private CustomBlockData replacementBlock;
public void replaceWithBlock(CustomBlockData customBlockData) {
this.replacementBlock = customBlockData;
}
public String skinHash() {
return skinHash;
}
public CustomBlockData replacementBlock() {
return replacementBlock;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.event.Event;
public abstract class GeyserDefineCustomBlocksEvent implements Event {
public abstract boolean registerCustomBlock(@NonNull CustomBlockData customBlockData);
}

View file

@ -0,0 +1,118 @@
/*
* 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.world;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.event.Cancellable;
import org.geysermc.geyser.api.event.Event;
public class GeyserConvertSkullEvent implements Event, Cancellable {
private final int x;
private final int y;
private final int z;
private final boolean onFloor;
private final WallDirection wallDirection;
private final int floorDirection;
private final String skinHash;
private boolean cancelled;
private CustomBlockState newBlockState;
public GeyserConvertSkullEvent(int x, int y, int z, boolean onFloor, WallDirection wallDirection, int floorDirection, String skinHash) {
this.x = x;
this.y = y;
this.z = z;
this.onFloor = onFloor;
this.wallDirection = wallDirection;
this.floorDirection = floorDirection;
if (onFloor && (wallDirection != WallDirection.INVALID || floorDirection == -1)) {
throw new IllegalArgumentException("Skull can't be on the floor and wall at the same time");
} else if (!onFloor && (wallDirection == WallDirection.INVALID || floorDirection != -1)) {
throw new IllegalArgumentException("Skull can't be on the floor and wall at the same time");
}
this.skinHash = skinHash;
}
public int x() {
return x;
}
public int y() {
return y;
}
public int z() {
return z;
}
public boolean onFloor() {
return onFloor;
}
public WallDirection wallDirection() {
return wallDirection;
}
public int floorDirection() {
return floorDirection;
}
public String skinHash() {
return skinHash;
}
public void replaceWithBlock(CustomBlockState blockState) {
this.newBlockState = blockState;
}
public CustomBlockState getNewBlockState() {
return newBlockState;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public enum WallDirection {
NORTH,
EAST,
SOUTH,
WEST,
INVALID
}
}

View file

@ -0,0 +1,173 @@
/*
* 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.geysermc.geyser.api.block.custom.component.CollisionComponent;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
import org.geysermc.geyser.api.block.custom.component.RotationComponent;
import java.util.Map;
public class GeyserCustomBlockComponents implements CustomBlockComponents {
private final CollisionComponent aimCollision;
private final CollisionComponent entityCollision;
private final String geometry;
private final Map<String, MaterialInstance> materialInstances;
private final Float destroyTime;
private final Float friction;
private final Float lightEmission;
private final Integer lightFilter;
private final RotationComponent rotation;
public GeyserCustomBlockComponents(BuilderImpl builder) {
this.aimCollision = builder.aimCollision;
this.entityCollision = builder.entityCollision;
this.geometry = builder.geometry;
this.materialInstances = builder.materialInstances;
this.destroyTime = builder.destroyTime;
this.friction = builder.friction;
this.lightEmission = builder.lightEmission;
this.lightFilter = builder.lightFilter;
this.rotation = builder.rotation;
}
@Override
public CollisionComponent aimCollision() {
return aimCollision;
}
@Override
public CollisionComponent entityCollision() {
return entityCollision;
}
@Override
public String geometry() {
return geometry;
}
@Override
public Map<String, MaterialInstance> materialInstances() {
return materialInstances;
}
@Override
public Float destroyTime() {
return destroyTime;
}
@Override
public Float friction() {
return friction;
}
@Override
public Float lightEmission() {
return lightEmission;
}
@Override
public Integer lightFilter() {
return lightFilter;
}
@Override
public RotationComponent rotation() {
return rotation;
}
public static class BuilderImpl implements Builder {
protected CollisionComponent aimCollision;
protected CollisionComponent entityCollision;
protected String geometry;
protected Map<String, MaterialInstance> materialInstances;
protected Float destroyTime;
protected Float friction;
protected Float lightEmission;
protected Integer lightFilter;
protected RotationComponent rotation;
@Override
public Builder aimCollision(CollisionComponent aimCollision) {
this.aimCollision = aimCollision;
return this;
}
@Override
public Builder entityCollision(CollisionComponent entityCollision) {
this.entityCollision = entityCollision;
return this;
}
@Override
public Builder geometry(String geometry) {
this.geometry = geometry;
return this;
}
@Override
public Builder materialInstances(Map<String, MaterialInstance> materialInstances) {
this.materialInstances = materialInstances;
return this;
}
@Override
public Builder destroyTime(Float destroyTime) {
this.destroyTime = destroyTime;
return this;
}
@Override
public Builder friction(Float friction) {
this.friction = friction;
return this;
}
@Override
public Builder lightEmission(Float lightEmission) {
this.lightEmission = lightEmission;
return this;
}
@Override
public Builder lightFilter(Integer lightFilter) {
this.lightFilter = lightFilter;
return this;
}
@Override
public Builder rotation(RotationComponent rotation) {
this.rotation = rotation;
return this;
}
@Override
public CustomBlockComponents build() {
return new GeyserCustomBlockComponents(this);
}
}
}

View file

@ -0,0 +1,137 @@
/*
* 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.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectLists;
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.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 java.util.List;
import java.util.Map;
@Value
public class GeyserCustomBlockData implements CustomBlockData {
String name;
CustomBlockComponents components;
Map<String, CustomBlockProperty<?>> properties;
List<CustomBlockPermutation> permutations;
@Override
public @NonNull String name() {
return name;
}
@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 CustomBlockState.Builder blockStateBuilder() {
return new GeyserCustomBlockState.BuilderImpl(this);
}
public static class BuilderImpl implements Builder {
private String name;
private CustomBlockComponents components;
private Map<String, CustomBlockProperty<?>> properties = new Object2ObjectOpenHashMap<>();
private List<CustomBlockPermutation> permutations;
@Override
public Builder name(@NonNull String name) {
this.name = name;
return this;
}
@Override
public Builder components(@NonNull CustomBlockComponents components) {
this.components = components;
return this;
}
@Override
public Builder booleanProperty(@NonNull String propertyName, List<Boolean> values) {
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, values, PropertyType.BOOLEAN));
return this;
}
@Override
public Builder intProperty(@NonNull String propertyName, List<Integer> values) {
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, values, PropertyType.INTEGER));
return this;
}
@Override
public Builder stringProperty(@NonNull String propertyName, List<String> values) {
this.properties.put(propertyName, new GeyserCustomBlockProperty<>(propertyName, values, PropertyType.STRING));
return this;
}
@Override
public Builder permutations(@NonNull List<CustomBlockPermutation> permutations) {
this.permutations = permutations;
return this;
}
@Override
public CustomBlockData build() {
if (name == null) {
throw new IllegalArgumentException("Name must be set");
}
if (properties == null) {
properties = Object2ObjectMaps.emptyMap();
} else {
for (CustomBlockProperty<?> property : properties.values()) {
if (property.values().isEmpty() || property.values().size() > 16) {
throw new IllegalArgumentException(property.name() + " must contain 1 to 16 values.");
}
}
}
if (permutations == null) {
permutations = ObjectLists.emptyList();
}
return new GeyserCustomBlockData(name, components, properties, permutations);
}
}
}

View file

@ -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.level.block;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
public class GeyserCustomBlockPermutation implements CustomBlockPermutation {
private final String condition;
private final CustomBlockComponents components;
public GeyserCustomBlockPermutation(String condition, CustomBlockComponents components) {
this.condition = condition;
this.components = components;
}
@Override
public @NonNull String condition() {
return condition;
}
@Override
public CustomBlockComponents components() {
return components;
}
public static class BuilderImpl implements Builder {
private String condition;
private CustomBlockComponents components;
@Override
public Builder condition(@NonNull String condition) {
this.condition = condition;
return this;
}
@Override
public Builder components(CustomBlockComponents components) {
this.components = components;
return this;
}
@Override
public CustomBlockPermutation build() {
if (condition == null) {
throw new IllegalArgumentException("Condition must be set");
}
return new GeyserCustomBlockPermutation(condition, components);
}
}
}

View file

@ -0,0 +1,60 @@
/*
* 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;
public class GeyserCustomBlockProperty<T> implements CustomBlockProperty<T> {
private final String name;
private final List<T> values;
private final PropertyType<T> type;
public GeyserCustomBlockProperty(String name, List<T> values, PropertyType<T> type) {
this.name = name;
this.values = values;
this.type = type;
}
@Override
public @NonNull String name() {
return name;
}
@Override
public @NonNull List<T> values(){
return values;
}
@Override
public @NonNull PropertyType<T> type() {
return type;
}
}

View file

@ -0,0 +1,107 @@
/*
* 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 {
String name;
Map<String, Object> properties;
@Override
public @NonNull String name() {
return 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 BuilderImpl 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);
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 for property: " + propertyName + " Value: " + propertyValue);
}
}
return new GeyserCustomBlockState(blockData.name(), Object2ObjectMaps.unmodifiable(properties));
}
}
}

View file

@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.registry.loader.RegistryLoaders;
import org.geysermc.geyser.registry.populator.BlockRegistryPopulator; import org.geysermc.geyser.registry.populator.BlockRegistryPopulator;
import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.BlockMapping;
@ -72,6 +73,11 @@ public class BlockRegistries {
*/ */
public static final SimpleRegistry<IntSet> WATERLOGGED = SimpleRegistry.create(RegistryLoaders.empty(IntOpenHashSet::new)); public static final SimpleRegistry<IntSet> WATERLOGGED = SimpleRegistry.create(RegistryLoaders.empty(IntOpenHashSet::new));
/**
* A registry containing all the custom blocks.
*/
public static final ArrayRegistry<CustomBlockData> CUSTOM_BLOCKS = ArrayRegistry.create(RegistryLoaders.empty(() -> new CustomBlockData[] {}));
static { static {
BlockRegistryPopulator.populate(); BlockRegistryPopulator.populate();
} }

View file

@ -28,15 +28,24 @@ package org.geysermc.geyser.registry.populator;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.nukkitx.nbt.*; import com.nukkitx.nbt.*;
import com.nukkitx.protocol.bedrock.data.BlockPropertyData;
import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.*;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.checkerframework.checker.nullness.qual.NonNull;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import org.geysermc.geyser.GeyserImpl; 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.CollisionComponent;
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.property.CustomBlockProperty;
import org.geysermc.geyser.api.block.custom.property.PropertyType;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.level.block.GeyserCustomBlockState;
import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.level.physics.PistonBehavior;
import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.BlockMapping;
@ -45,10 +54,8 @@ import org.geysermc.geyser.util.BlockUtils;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayDeque; import java.nio.charset.StandardCharsets;
import java.util.Deque; import java.util.*;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -71,38 +78,172 @@ public class BlockRegistryPopulator {
*/ */
private static JsonNode BLOCKS_JSON; private static JsonNode BLOCKS_JSON;
public static final String CUSTOM_PREFIX = "";
public static void populate() { public static void populate() {
registerJavaBlocks(); registerJavaBlocks();
registerCustomBedrockBlocks();
registerBedrockBlocks(); registerBedrockBlocks();
BLOCKS_JSON = null; BLOCKS_JSON = null;
} }
private static void registerCustomBedrockBlocks() {
List<CustomBlockData> customBlocks = new ArrayList<>();
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() {
@Override
public boolean registerCustomBlock(@NonNull CustomBlockData customBlockData) {
customBlocks.add(customBlockData);
return true;
}
});
BlockRegistries.CUSTOM_BLOCKS.set(customBlocks.toArray(new CustomBlockData[0]));
GeyserImpl.getInstance().getLogger().debug("Registered " + customBlocks.size() + " custom blocks.");
}
private static void generateCustomBlockStates(List<NbtMap> blockStates, List<CustomBlockState> customExtBlockStates, CustomBlockData customBlock, int stateVersion) {
if (customBlock.properties().isEmpty()) {
blockStates.add(NbtMap.builder()
.putString("name", CUSTOM_PREFIX + customBlock.name())
.putInt("version", stateVersion)
.putCompound("states", NbtMap.EMPTY)
.build());
}
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();
}
String name = CUSTOM_PREFIX + customBlock.name();
NbtMap states = statesBuilder.build();
blockStates.add(NbtMap.builder()
.putString("name", name)
.putInt("version", stateVersion)
.putCompound("states", states)
.build());
customExtBlockStates.add(new GeyserCustomBlockState(name, states));
}
}
@SuppressWarnings("unchecked")
private static BlockPropertyData generateBlockPropertyData(CustomBlockData customBlock) {
List<NbtMap> permutations = new ArrayList<>();
for (CustomBlockPermutation permutation : customBlock.permutations()) {
permutations.add(NbtMap.builder()
.putCompound("components", convertComponents(permutation.components()))
.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.BOOLEAN) {
List<Byte> values = new ArrayList<>();
for (boolean value : (List<Boolean>) property.values()) {
values.add((byte) (value ? 1 : 0));
}
propertyBuilder.putList("enum", NbtType.BYTE, values);
} else if (property.type() == PropertyType.INTEGER) {
propertyBuilder.putList("enum", NbtType.INT, (List<Integer>) property.values());
} else if (property.type() == PropertyType.STRING) {
propertyBuilder.putList("enum", NbtType.STRING, (List<String>) property.values());
}
properties.add(propertyBuilder.build());
}
NbtMap propertyTag = NbtMap.builder()
.putCompound("components", convertComponents(customBlock.components()))
.putInt("molangVersion", 0)
.putList("permutations", NbtType.COMPOUND, permutations)
.putList("properties", NbtType.COMPOUND, properties)
.build();
return new BlockPropertyData(CUSTOM_PREFIX + customBlock.name(), propertyTag);
}
private static final long FNV1_64_OFFSET_BASIS = 0xcbf29ce484222325L;
private static final long FNV1_64_PRIME = 1099511628211L;
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;
}
private static void registerBedrockBlocks() { private static void registerBedrockBlocks() {
for (Map.Entry<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : BLOCK_MAPPERS.entrySet()) { for (Map.Entry<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : BLOCK_MAPPERS.entrySet()) {
NbtList<NbtMap> blocksTag; NbtList<NbtMap> blocksTag;
List<NbtMap> blockStates;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); 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(); NbtMap blockPalette = (NbtMap) nbtInputStream.readTag();
blocksTag = (NbtList<NbtMap>) blockPalette.getList("blocks", NbtType.COMPOUND); blocksTag = (NbtList<NbtMap>) blockPalette.getList("blocks", NbtType.COMPOUND);
blockStates = new ArrayList<>(blocksTag);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError("Unable to get blocks from runtime block states", e); throw new AssertionError("Unable to get blocks from runtime block states", e);
} }
int stateVersion = blocksTag.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(generateBlockPropertyData(customBlock));
generateCustomBlockStates(customBlockStates, customExtBlockStates, customBlock, stateVersion);
}
blockStates.addAll(customBlockStates);
GeyserImpl.getInstance().getLogger().debug("Added " + customBlockStates.size() + " custom block states to v" + palette.getKey().valueInt() + " palette.");
if (palette.getKey().valueInt() > 486) {
// Version 1.18.30 and above sort the palette by the FNV1 64 bit hash of the name
blockStates.sort((a, b) -> Long.compareUnsigned(fnv164(a.getString("name")), fnv164(b.getString("name"))));
} else {
// Version below 1.18.30 sort the palette alphabetically by name
blockStates.sort(Comparator.comparing(state -> state.getString("name")));
}
}
// New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // 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 // as we no longer send a block palette
Object2IntMap<NbtMap> blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); Object2IntMap<NbtMap> blockStateOrderedMap = new Object2IntOpenHashMap<>(blockStates.size());
for (int i = 0; i < blockStates.size(); i++) {
int stateVersion = -1; NbtMap tag = blockStates.get(i);
for (int i = 0; i < blocksTag.size(); i++) {
NbtMap tag = blocksTag.get(i);
if (blockStateOrderedMap.containsKey(tag)) { if (blockStateOrderedMap.containsKey(tag)) {
throw new AssertionError("Duplicate block states in Bedrock palette: " + tag); throw new AssertionError("Duplicate block states in Bedrock palette: " + tag);
} }
blockStateOrderedMap.put(tag, i); blockStateOrderedMap.put(tag, i);
if (stateVersion == -1) { }
stateVersion = tag.getInt("version");
Object2IntMap<CustomBlockState> customBlockStateIds = Object2IntMaps.emptyMap();
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
customBlockStateIds = new Object2IntOpenHashMap<>(customExtBlockStates.size());
for (int i = 0; i < customExtBlockStates.size(); i++) {
NbtMap tag = customBlockStates.get(i);
CustomBlockState blockState = customExtBlockStates.get(i);
customBlockStateIds.put(blockState, blockStateOrderedMap.getInt(tag));
}
remappedVanillaIds = new int[blocksTag.size()];
for (int i = 0; i < blocksTag.size(); i++) {
remappedVanillaIds[i] = blockStateOrderedMap.getInt(blocksTag.get(i));
} }
} }
int airRuntimeId = -1; int airRuntimeId = -1;
int commandBlockRuntimeId = -1; int commandBlockRuntimeId = -1;
int javaRuntimeId = -1; int javaRuntimeId = -1;
@ -154,7 +295,7 @@ public class BlockRegistryPopulator {
// Get the tag needed for non-empty flower pots // Get the tag needed for non-empty flower pots
if (entry.getValue().get("pottable") != null) { if (entry.getValue().get("pottable") != null) {
flowerPotBlocks.put(cleanJavaIdentifier.intern(), blocksTag.get(bedrockRuntimeId)); flowerPotBlocks.put(cleanJavaIdentifier.intern(), blockStates.get(bedrockRuntimeId));
} }
javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId; javaToBedrockBlocks[javaRuntimeId] = bedrockRuntimeId;
@ -187,17 +328,92 @@ public class BlockRegistryPopulator {
itemFrames.put(entry.getKey(), entry.getIntValue()); itemFrames.put(entry.getKey(), entry.getIntValue());
} }
} }
builder.bedrockBlockStates(blocksTag); builder.bedrockBlockStates(new NbtList<>(NbtType.COMPOUND, blockStates));
BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion)
.javaToBedrockBlocks(javaToBedrockBlocks) .javaToBedrockBlocks(javaToBedrockBlocks)
.itemFrames(itemFrames) .itemFrames(itemFrames)
.flowerPotBlocks(flowerPotBlocks) .flowerPotBlocks(flowerPotBlocks)
.jigsawStateIds(jigsawStateIds) .jigsawStateIds(jigsawStateIds)
.remappedVanillaIds(remappedVanillaIds)
.blockProperties(customBlockProperties)
.customBlockStateIds(customBlockStateIds)
.build()); .build());
} }
} }
private static NbtMap convertComponents(CustomBlockComponents components) {
if (components == null) {
return NbtMap.EMPTY;
}
NbtMapBuilder builder = NbtMap.builder();
if (components.aimCollision() != null) {
CollisionComponent collisionComponent = components.aimCollision();
builder.putCompound("minecraft:aim_collision", NbtMap.builder()
.putBoolean("enabled", true)
.putList("origin", NbtType.FLOAT, collisionComponent.originX(), collisionComponent.originY(), collisionComponent.originZ())
.putList("size", NbtType.FLOAT, collisionComponent.sizeX(), collisionComponent.sizeY(), collisionComponent.sizeZ())
.build());
}
if (components.entityCollision() != null) {
CollisionComponent collisionComponent = components.entityCollision();
builder.putCompound("minecraft:block_collision", NbtMap.builder()
.putBoolean("enabled", true)
.putList("origin", NbtType.FLOAT, collisionComponent.originX(), collisionComponent.originY(), collisionComponent.originZ())
.putList("size", NbtType.FLOAT, collisionComponent.sizeX(), collisionComponent.sizeY(), collisionComponent.sizeZ())
.build());
}
if (components.geometry() != null) {
builder.putCompound("minecraft:geometry", NbtMap.builder()
.putString("value", components.geometry())
.build());
}
if (components.materialInstances() != null) {
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().toString().toLowerCase(Locale.ROOT))
.putBoolean("face_dimming", materialInstance.faceDimming())
.putBoolean("ambient_occlusion", materialInstance.faceDimming())
.build());
}
builder.putCompound("minecraft:material_instances", NbtMap.builder()
.putCompound("mappings", NbtMap.EMPTY)
.putCompound("materials", materialsBuilder.build())
.build());
}
if (components.destroyTime() != null) {
builder.putCompound("minecraft:destroy_time", NbtMap.builder()
.putFloat("value", components.destroyTime())
.build());
}
if (components.friction() != null) {
builder.putCompound("minecraft:friction", NbtMap.builder()
.putFloat("value", components.friction())
.build());
}
if (components.lightEmission() != null) {
builder.putCompound("minecraft:block_light_emission", NbtMap.builder()
.putFloat("value", components.lightEmission())
.build());
}
if (components.lightFilter() != null) {
builder.putCompound("minecraft:block_light_filter", NbtMap.builder()
.putByte("value", components.lightFilter().byteValue())
.build());
}
if (components.rotation() != null) {
builder.putCompound("minecraft:rotation", NbtMap.builder()
.putFloat("x", components.rotation().x())
.putFloat("y", components.rotation().y())
.putFloat("z", components.rotation().z())
.build());
}
return builder.build();
}
private static void registerJavaBlocks() { private static void registerJavaBlocks() {
JsonNode blocksJson; JsonNode blocksJson;
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/blocks.json")) { try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/blocks.json")) {

View file

@ -46,6 +46,7 @@ import it.unimi.dsi.fastutil.objects.*;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent;
import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.CustomItemOptions;
@ -198,6 +199,8 @@ public class ItemRegistryPopulator {
// Temporary mapping to create stored items // Temporary mapping to create stored items
Map<String, ItemMapping> identifierToMapping = new Object2ObjectOpenHashMap<>(); Map<String, ItemMapping> identifierToMapping = new Object2ObjectOpenHashMap<>();
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion());
int netId = 1; int netId = 1;
List<ItemData> creativeItems = new ArrayList<>(); List<ItemData> creativeItems = new ArrayList<>();
for (JsonNode itemNode : creativeItemEntries) { for (JsonNode itemNode : creativeItemEntries) {
@ -216,6 +219,10 @@ public class ItemRegistryPopulator {
JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId"); JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId");
if (blockRuntimeIdNode != null) { if (blockRuntimeIdNode != null) {
blockRuntimeId = blockRuntimeIdNode.asInt(); blockRuntimeId = blockRuntimeIdNode.asInt();
if (blockMappings.getRemappedVanillaIds().length != 0) {
blockRuntimeId = blockMappings.getRemappedVanillaIds()[blockRuntimeId];
}
} }
JsonNode nbtNode = itemNode.get("nbt_b64"); JsonNode nbtNode = itemNode.get("nbt_b64");
if (nbtNode != null) { if (nbtNode != null) {
@ -270,8 +277,6 @@ public class ItemRegistryPopulator {
} }
} }
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion());
int itemIndex = 0; int itemIndex = 0;
int javaFurnaceMinecartId = 0; int javaFurnaceMinecartId = 0;
@ -631,6 +636,19 @@ public class ItemRegistryPopulator {
} }
} }
// Register the item forms of custom blocks
Object2IntMap<CustomBlockData> customBlockItemIds = Object2IntMaps.emptyMap();
if (BlockRegistries.CUSTOM_BLOCKS.get().length != 0) {
customBlockItemIds = new Object2IntOpenHashMap<>();
for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) {
int customProtocolId = nextFreeBedrockId++;
String identifier = BlockRegistryPopulator.CUSTOM_PREFIX + customBlock.name();
entries.put(identifier, new StartGamePacket.ItemEntry(identifier, (short) customProtocolId));
customBlockItemIds.put(customBlock, customProtocolId);
}
}
ItemMappings itemMappings = ItemMappings.builder() ItemMappings itemMappings = ItemMappings.builder()
.items(mappings.toArray(new ItemMapping[0])) .items(mappings.toArray(new ItemMapping[0]))
.creativeItems(creativeItems.toArray(new ItemData[0])) .creativeItems(creativeItems.toArray(new ItemData[0]))
@ -645,6 +663,7 @@ public class ItemRegistryPopulator {
.componentItemData(componentItemData) .componentItemData(componentItemData)
.lodestoneCompass(lodestoneEntry) .lodestoneCompass(lodestoneEntry)
.customIdMappings(customIdMappings) .customIdMappings(customIdMappings)
.customBlockItemIds(customBlockItemIds)
.build(); .build();
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings); Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);

View file

@ -27,6 +27,9 @@ package org.geysermc.geyser.registry.provider;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockPermutation;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemData;
@ -37,6 +40,9 @@ import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemData;
import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserCustomItemOptions;
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; 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.GeyserCustomBlockPermutation;
import org.geysermc.geyser.registry.ProviderRegistries; import org.geysermc.geyser.registry.ProviderRegistries;
import org.geysermc.geyser.registry.SimpleMappedRegistry; import org.geysermc.geyser.registry.SimpleMappedRegistry;
@ -52,6 +58,11 @@ public class GeyserBuilderProvider extends AbstractProvider implements BuilderPr
@Override @Override
public void registerProviders(Map<Class<?>, ProviderSupplier> providers) { public void registerProviders(Map<Class<?>, ProviderSupplier> providers) {
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class<? extends CommandSource>) args[0])); providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Class<? extends CommandSource>) args[0]));
providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.BuilderImpl());
providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.BuilderImpl());
providers.put(CustomBlockPermutation.Builder.class, args -> new GeyserCustomBlockPermutation.BuilderImpl());
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder()); providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder()); providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());

View file

@ -27,11 +27,14 @@ package org.geysermc.geyser.registry.type;
import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtList;
import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.data.BlockPropertyData;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import lombok.Builder; import lombok.Builder;
import lombok.Value; import lombok.Value;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import java.util.List;
import java.util.Map; import java.util.Map;
@Builder @Builder
@ -46,6 +49,7 @@ public class BlockMappings {
int[] javaToBedrockBlocks; int[] javaToBedrockBlocks;
NbtList<NbtMap> bedrockBlockStates; NbtList<NbtMap> bedrockBlockStates;
int[] remappedVanillaIds;
int commandBlockRuntimeId; int commandBlockRuntimeId;
@ -54,6 +58,9 @@ public class BlockMappings {
IntSet jigsawStateIds; IntSet jigsawStateIds;
List<BlockPropertyData> blockProperties;
Object2IntMap<CustomBlockState> customBlockStateIds;
public int getBedrockBlockId(int state) { public int getBedrockBlockId(int state) {
if (state >= this.javaToBedrockBlocks.length) { if (state >= this.javaToBedrockBlocks.length) {
return bedrockAirId; return bedrockAirId;

View file

@ -31,9 +31,11 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import lombok.Builder; import lombok.Builder;
import lombok.Value; import lombok.Value;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.inventory.item.StoredItemMappings;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -70,6 +72,8 @@ public class ItemMappings {
List<ComponentItemData> componentItemData; List<ComponentItemData> componentItemData;
Int2ObjectMap<String> customIdMappings; Int2ObjectMap<String> customIdMappings;
Object2IntMap<CustomBlockData> customBlockItemIds;
/** /**
* Gets an {@link ItemMapping} from the given {@link ItemStack}. * Gets an {@link ItemMapping} from the given {@link ItemStack}.
* *

View file

@ -1467,6 +1467,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.setMultiplayerCorrelationId(""); startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(this.itemMappings.getItemEntries()); startGamePacket.setItemEntries(this.itemMappings.getItemEntries());
startGamePacket.getBlockProperties().addAll(this.blockMappings.getBlockProperties());
startGamePacket.getExperiments().add(new ExperimentData("vanilla_experiments", true));
startGamePacket.getExperiments().add(new ExperimentData("data_driven_items", true));
startGamePacket.getExperiments().add(new ExperimentData("upcoming_creator_features", true));
startGamePacket.getExperiments().add(new ExperimentData("experimental_molang_features", true));
startGamePacket.setVanillaVersion("*"); startGamePacket.setVanillaVersion("*");
startGamePacket.setInventoriesServerAuthoritative(true); startGamePacket.setInventoriesServerAuthoritative(true);

View file

@ -27,12 +27,14 @@ package org.geysermc.geyser.session.cache;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
import java.util.*; import java.util.*;
@ -75,6 +77,17 @@ public class SkullCache {
Skull skull = skulls.computeIfAbsent(position, Skull::new); Skull skull = skulls.computeIfAbsent(position, Skull::new);
skull.texturesProperty = texturesProperty; skull.texturesProperty = texturesProperty;
skull.blockState = blockState; skull.blockState = blockState;
if (skull.customRuntimeId != -1) {
skull.customRuntimeId = -1;
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(blockState));
updateBlockPacket.setDataLayer(0);
session.sendUpstreamPacket(updateBlockPacket);
}
if (skull.entity != null) { if (skull.entity != null) {
skull.entity.updateSkull(skull); skull.entity.updateSkull(skull);
@ -106,6 +119,24 @@ public class SkullCache {
} }
} }
public void putSkull(Vector3i position, String texturesProperty, int blockState, int customRuntimeId) {
Skull skull = skulls.computeIfAbsent(position, Skull::new);
skull.texturesProperty = texturesProperty;
skull.blockState = blockState;
skull.customRuntimeId = customRuntimeId;
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 removeSkull(Vector3i position) { public void removeSkull(Vector3i position) {
Skull skull = skulls.remove(position); Skull skull = skulls.remove(position);
if (skull != null) { if (skull != null) {
@ -122,6 +153,20 @@ public class SkullCache {
} }
} }
public int updateSkull(Vector3i position, int blockState) {
Skull skull = skulls.remove(position);
if (skull != null) {
int customRuntimeId = SkullBlockEntityTranslator.translateCustomSkull(session, position, skull.texturesProperty, blockState);
if (customRuntimeId != -1) {
putSkull(position, skull.texturesProperty, blockState, customRuntimeId);
} else {
putSkull(position, skull.texturesProperty, blockState);
}
return customRuntimeId;
}
return -1;
}
public void updateVisibleSkulls() { public void updateVisibleSkulls() {
if (cullingEnabled) { if (cullingEnabled) {
// No need to recheck skull visibility for small movements // No need to recheck skull visibility for small movements
@ -132,6 +177,10 @@ public class SkullCache {
inRangeSkulls.clear(); inRangeSkulls.clear();
for (Skull skull : skulls.values()) { for (Skull skull : skulls.values()) {
if (skull.customRuntimeId != -1) {
continue;
}
skull.distanceSquared = skull.position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); skull.distanceSquared = skull.position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ());
if (skull.distanceSquared > skullRenderDistanceSquared) { if (skull.distanceSquared > skullRenderDistanceSquared) {
freeSkullEntity(skull); freeSkullEntity(skull);
@ -203,6 +252,7 @@ public class SkullCache {
public static class Skull { public static class Skull {
private String texturesProperty; private String texturesProperty;
private int blockState; private int blockState;
private int customRuntimeId = -1;
private SkullPlayerEntity entity; private SkullPlayerEntity entity;
private final Vector3i position; private final Vector3i position;

View file

@ -254,7 +254,7 @@ public class SkinManager {
} }
} }
private static GameProfileData loadFromJson(String encodedJson) throws IOException { public static GameProfileData loadFromJson(String encodedJson) throws IOException {
JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
JsonNode textures = skinObject.get("textures"); JsonNode textures = skinObject.get("textures");

View file

@ -38,6 +38,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.event.GeyserConvertSkullInventoryEvent;
import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
@ -45,11 +47,13 @@ import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.FileUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -175,6 +179,10 @@ public abstract class ItemTranslator {
translateCustomItem(nbt, builder, bedrockItem); translateCustomItem(nbt, builder, bedrockItem);
if (bedrockItem == session.getItemMappings().getStoredItems().playerHead()) {
translatePlayerHead(session, nbt, builder, bedrockItem);
}
if (nbt != null) { if (nbt != null) {
// Translate the canDestroy and canPlaceOn Java NBT // Translate the canDestroy and canPlaceOn Java NBT
ListTag canDestroy = nbt.get("CanDestroy"); ListTag canDestroy = nbt.get("CanDestroy");
@ -542,6 +550,50 @@ public abstract class ItemTranslator {
builder.id(bedrockId); builder.id(bedrockId);
} }
} }
private static void translatePlayerHead(GeyserSession session, CompoundTag nbt, ItemData.Builder builder, ItemMapping mapping) {
if (nbt != null && nbt.contains("SkullOwner")) {
String skinHash = null;
Tag skullOwner = nbt.get("SkullOwner");
if (skullOwner instanceof StringTag) {
// It's a username give up d:
return;
} else if (skullOwner instanceof CompoundTag) {
Tag properties = ((CompoundTag) skullOwner).get("Properties");
if (properties instanceof CompoundTag) {
Tag textures = ((CompoundTag) properties).get("textures");
if (textures instanceof ListTag) {
List<Tag> textureTags = ((ListTag) textures).getValue();
if (!textureTags.isEmpty()) {
Tag skinTag = textureTags.get(0);
if (skinTag instanceof CompoundTag) {
Tag valueTag = ((CompoundTag) skinTag).get("Value");
String encodedJson = (String) valueTag.getValue();
try {
SkinManager.GameProfileData data = SkinManager.GameProfileData.loadFromJson(encodedJson);
skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1);
} catch (IOException e) {
session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt + " " + e.getMessage());
return;
}
}
}
}
}
}
if (skinHash == null) {
session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt);
}
GeyserConvertSkullInventoryEvent skullInventoryEvent = new GeyserConvertSkullInventoryEvent(skinHash);
GeyserImpl.getInstance().getEventBus().fire(skullInventoryEvent);
CustomBlockData replacementBlock = skullInventoryEvent.replacementBlock();
if (replacementBlock != null) {
int bedrockId = session.getItemMappings().getCustomBlockItemIds().getInt(replacementBlock);
builder.id(bedrockId);
}
}
}
private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) { private static int getCustomItem(CompoundTag nbt, ItemMapping mapping) {
if (nbt == null) { if (nbt == null) {

View file

@ -31,12 +31,17 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtMapBuilder;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.world.GeyserConvertSkullEvent;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.skin.SkinProvider;
import java.io.IOException;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@BlockEntity(type = BlockEntityType.SKULL) @BlockEntity(type = BlockEntityType.SKULL)
public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
@ -69,18 +74,65 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { public static int translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) {
Vector3i blockPosition = Vector3i.from(posX, posY, posZ); try {
getTextures(tag).whenComplete((texturesProperty, throwable) -> { String texturesProperty = getTextures(tag).get();
Vector3i blockPosition = Vector3i.from(posX, posY, posZ);
if (texturesProperty == null) { if (texturesProperty == null) {
session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag);
return; return -1;
} }
if (session.getEventLoop().inEventLoop()) { int runtimeId = translateCustomSkull(session, blockPosition, texturesProperty, blockState);
if (runtimeId == -1) {
session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState);
} else { } else {
session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState)); session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState, runtimeId);
} }
}); return runtimeId;
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
public static int translateCustomSkull(GeyserSession session, Vector3i blockPosition, String texturesProperty, int blockState) {
try {
SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty);
if (gameProfileData == null || gameProfileData.skinUrl() == null) {
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + blockPosition + " Textures: " + texturesProperty);
return -1;
}
String skinUrl = gameProfileData.skinUrl();
String skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1);
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
GeyserConvertSkullEvent.WallDirection wallDirection = GeyserConvertSkullEvent.WallDirection.INVALID;
boolean onFloor = true;
if (floorRotation == -1) {
// Wall skull
onFloor = false;
int wallRotation = BlockStateValues.getSkullWallDirections().get(blockState);
wallDirection = switch (wallRotation) {
case 0 -> GeyserConvertSkullEvent.WallDirection.SOUTH;
case 90 -> GeyserConvertSkullEvent.WallDirection.WEST;
case 180 -> GeyserConvertSkullEvent.WallDirection.NORTH;
case 270 -> GeyserConvertSkullEvent.WallDirection.EAST;
default -> GeyserConvertSkullEvent.WallDirection.INVALID;
};
}
GeyserConvertSkullEvent event = new GeyserConvertSkullEvent(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), onFloor, wallDirection, floorRotation, skinHash);
GeyserImpl.getInstance().getEventBus().fire(event);
if (event.getNewBlockState() != null) {
return session.getBlockMappings().getCustomBlockStateIds().getOrDefault(event.getNewBlockState(), -1);
}
if (event.isCancelled()) {
return -1;
}
} catch (IOException e) {
session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + blockPosition + " Textures: " + texturesProperty + " Message: " + e.getMessage());
}
return -1;
} }
} }

View file

@ -505,10 +505,18 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
*/ */
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) { private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos); int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos);
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaBlockState);
if (BlockStateValues.getSkullVariant(javaBlockState) == 3) {
int customRuntimeId = session.getSkullCache().updateSkull(blockPos, javaBlockState);
if (customRuntimeId != -1) {
bedrockId = customRuntimeId;
}
}
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0); updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(blockPos); updateBlockPacket.setBlockPosition(blockPos);
updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(javaBlockState)); updateBlockPacket.setRuntimeId(bedrockId);
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateBlockPacket); session.sendUpstreamPacket(updateBlockPacket);

View file

@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.Clientb
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator; import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
@ -63,8 +64,23 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(type, position.getX(), position.getY(), position.getZ(), BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(type, position.getX(), position.getY(), position.getZ(),
packet.getNbt(), blockState), packet.getPosition()); packet.getNbt(), blockState), packet.getPosition());
// Check for custom skulls. // Check for custom skulls.
boolean customSkull = false;
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt() != null && packet.getNbt().contains("SkullOwner")) { if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt() != null && packet.getNbt().contains("SkullOwner")) {
SkullBlockEntityTranslator.translateSkull(session, packet.getNbt(), position.getX(), position.getY(), position.getZ(), blockState); int runtimeId = SkullBlockEntityTranslator.translateSkull(session, packet.getNbt(), position.getX(), position.getY(), position.getZ(), blockState);
if (runtimeId != -1) {
customSkull = true;
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
updateBlockPacket.setRuntimeId(runtimeId);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
session.sendUpstreamPacket(updateBlockPacket);
}
}
if (!customSkull) {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(type, position.getX(), position.getY(), position.getZ(),
packet.getNbt(), blockState), packet.getPosition());
} }
// If block entity is command block, OP permission level is appropriate, player is in creative mode and the NBT is not empty // If block entity is command block, OP permission level is appropriate, player is in creative mode and the NBT is not empty

View file

@ -46,6 +46,7 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntImmutableList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists; import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -273,7 +274,18 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
// Check for custom skulls // Check for custom skulls
if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.contains("SkullOwner")) { if (session.getPreferencesCache().showCustomSkulls() && type == BlockEntityType.SKULL && tag != null && tag.contains("SkullOwner")) {
SkullBlockEntityTranslator.translateSkull(session, tag, x + chunkBlockX, y, z + chunkBlockZ, blockState); int runtimeId = SkullBlockEntityTranslator.translateSkull(session, tag, x + chunkBlockX, y, z + chunkBlockZ, blockState);
if (runtimeId != -1) {
int bedrockSectionY = (y >> 4) - (bedrockDimension.minY() >> 4);
GeyserChunkSection bedrockSection = sections[bedrockSectionY];
IntList palette = bedrockSection.getBlockStorageArray()[0].getPalette();
if (palette instanceof IntImmutableList || palette instanceof IntLists.Singleton) {
// TODO there has to be a better way to expand the palette .-.
bedrockSection = bedrockSection.copy();
sections[bedrockSectionY] = bedrockSection;
}
bedrockSection.setFullBlock(x, y & 0xF, z, 0, runtimeId);
}
} }
} }

View file

@ -135,16 +135,21 @@ public class ChunkUtils {
// Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now // Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now
} }
if (BlockStateValues.getSkullVariant(blockState) == -1) { int blockId = session.getBlockMappings().getBedrockBlockId(blockState);
int skullVariant = BlockStateValues.getSkullVariant(blockState);
if (skullVariant == -1) {
// Skull is gone // Skull is gone
session.getSkullCache().removeSkull(position); session.getSkullCache().removeSkull(position);
} else if (skullVariant == 3) {
int customRuntimeId = session.getSkullCache().updateSkull(position, blockState);
if (customRuntimeId != -1) {
blockId = customRuntimeId;
}
} }
// Prevent moving_piston from being placed // Prevent moving_piston from being placed
// It's used for extending piston heads, but it isn't needed on Bedrock and causes pistons to flicker // It's used for extending piston heads, but it isn't needed on Bedrock and causes pistons to flicker
if (!BlockStateValues.isMovingPiston(blockState)) { if (!BlockStateValues.isMovingPiston(blockState)) {
int blockId = session.getBlockMappings().getBedrockBlockId(blockState);
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0); updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position); updateBlockPacket.setBlockPosition(position);