mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Custom entity properties API (#5788)
* Custom entity properties API * Fix build fail * Resolve comments * Entity property registration improvements * oops * Add boolean and enum property sync API * default value + no packet if value unchanged * Don't send packet if no properties were updated * small refactor * the refactor, part two * Move updateProperties to GeyserEntity * Don't inherit properties from parent * temp * type-safe property updating * Address review * call the GeyserDefineEntityPropertiesEvent once, require specifying entity identifier instead of calling the event once for every entity type * Migrate to identifiers (from custom items v2, thanks eclipse), remove duplicate logic * fix test * Merge 1.21.9, update copper golem entity property usage * fixup javadocs --------- Co-authored-by: onebeastchris <github@onechris.mozmail.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntityPropertiesEvent;
|
||||
|
||||
/**
|
||||
* Collects property changes to be applied as a single, batched update to an entity.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li>Passing {@code null} as a value resets the property to its default.</li>
|
||||
* <li>Numeric properties must be within declared ranges; enum properties must use an allowed value.</li>
|
||||
* <li>Multiple updates to the same property within a single batch will result in the last value being applied.</li>
|
||||
* <li>The updater is short-lived and should not be retained outside the batching callback.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <pre>{@code
|
||||
* entity.updatePropertiesBatched(updater -> {
|
||||
* updater.update(SOME_FLOAT_PROPERTY, 0.15f);
|
||||
* updater.update(SOME_BOOLEAN_PROPERTY, true);
|
||||
* updater.update(SOME_INT_PROPERTY, null); // reset to default
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BatchPropertyUpdater {
|
||||
|
||||
/**
|
||||
* Queues an update for the given property within the current batch.
|
||||
* <p>
|
||||
* If {@code value} is {@code null}, the property will be reset to its default value
|
||||
* as declared when the property was registered during the {@link GeyserDefineEntityPropertiesEvent}.
|
||||
*
|
||||
* @param property a {@link GeyserEntityProperty} registered for the target entity type
|
||||
* @param value the new value, or {@code null} to reset to the default
|
||||
* @param <T> the property's value type
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
<T> void update(@NonNull GeyserEntityProperty<T> property, @Nullable T value);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
/**
|
||||
* Represents a property that can be attached to an entity.
|
||||
* <p>
|
||||
* Entity properties are used to describe metadata about an entity, such as
|
||||
* integers, floats, booleans, or enums.
|
||||
* @see <a href="https://learn.microsoft.com/en-us/minecraft/creator/documents/introductiontoentityproperties?view=minecraft-bedrock-stable#number-of-entity-properties-per-entity-type">
|
||||
* Official documentation for info</a>
|
||||
*
|
||||
* @param <T> the type of value stored by this property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserEntityProperty<T> {
|
||||
|
||||
/**
|
||||
* Gets the unique name of this property.
|
||||
* Custom properties cannot use the vanilla namespace
|
||||
* to avoid collisions with vanilla entity properties.
|
||||
*
|
||||
* @return the property identifier
|
||||
* @since 2.9.0
|
||||
*/
|
||||
@NonNull
|
||||
Identifier identifier();
|
||||
|
||||
/**
|
||||
* Gets the default value of this property which
|
||||
* is set upon spawning entities.
|
||||
*
|
||||
* @return the default value of this property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
@NonNull
|
||||
T defaultValue();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property.type;
|
||||
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
|
||||
/**
|
||||
* Represents a boolean entity property.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserBooleanEntityProperty extends GeyserEntityProperty<Boolean> {
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property.type;
|
||||
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
|
||||
/**
|
||||
* Represents a Java enum-backed enum property.
|
||||
* There are a few key limitations:
|
||||
* <ul>
|
||||
* <li>There cannot be more than 16 values</li>
|
||||
* <li>Enum names cannot be longer than 32 chars, must start with a letter, and may contain numbers and underscores</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <E> the enum type
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserEnumEntityProperty<E extends Enum<E>> extends GeyserEntityProperty<E> {
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property.type;
|
||||
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntityPropertiesEvent;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
/**
|
||||
* Represents a float-backed entity property with inclusive bounds.
|
||||
* Values associated with this property must be always within the {@code [min(), max()]} bounds.
|
||||
*
|
||||
* @see GeyserDefineEntityPropertiesEvent#registerFloatProperty(Identifier, Identifier, float, float, Float)
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserFloatEntityProperty extends GeyserEntityProperty<Float> {
|
||||
|
||||
/**
|
||||
* @return the inclusive lower bound for this property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
float min();
|
||||
|
||||
/**
|
||||
* @return the inclusive upper bound for this property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
float max();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property.type;
|
||||
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntityPropertiesEvent;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
/**
|
||||
* Represents an int-backed entity property with inclusive bounds.
|
||||
* There are a few key limitations:
|
||||
* <ul>
|
||||
* <li>Values must be always within the {@code [min(), max()]} bounds</li>
|
||||
* <li>Molang evaluation uses floats under the hood; very large integers can lose precision.
|
||||
* Prefer keeping values in a practical range to avoid rounding issues.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see GeyserDefineEntityPropertiesEvent#registerIntegerProperty(Identifier, Identifier, int, int, Integer)
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserIntEntityProperty extends GeyserEntityProperty<Integer> {
|
||||
|
||||
/**
|
||||
* @return the inclusive lower bound for this property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
int min();
|
||||
|
||||
/**
|
||||
* @return the inclusive upper bound for this property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
int max();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.property.type;
|
||||
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a string-backed enum property.
|
||||
* There are a few key limitations:
|
||||
* <ul>
|
||||
* <li>There cannot be more than 16 values</li>
|
||||
* <li>The values' names cannot be longer than 32 chars, must start with a letter, and may contain numbers and underscores</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserStringEnumProperty extends GeyserEntityProperty<String> {
|
||||
|
||||
/**
|
||||
* @return an unmodifiable list of all registered values
|
||||
* @since 2.9.0
|
||||
*/
|
||||
List<String> values();
|
||||
}
|
||||
@@ -26,9 +26,17 @@
|
||||
package org.geysermc.geyser.api.entity.type;
|
||||
|
||||
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.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.entity.property.BatchPropertyUpdater;
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntityPropertiesEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a unique instance of an entity. Each {@link org.geysermc.geyser.api.connection.GeyserConnection}
|
||||
* Represents a unique instance of an entity. Each {@link GeyserConnection}
|
||||
* have their own sets of entities - no two instances will share the same GeyserEntity instance.
|
||||
*/
|
||||
public interface GeyserEntity {
|
||||
@@ -37,4 +45,24 @@ public interface GeyserEntity {
|
||||
*/
|
||||
@NonNegative
|
||||
int javaId();
|
||||
|
||||
/**
|
||||
* Updates an entity property with a new value.
|
||||
* If the new value is null, the property is reset to the default value.
|
||||
*
|
||||
* @param property a {@link GeyserEntityProperty} registered for this type in the {@link GeyserDefineEntityPropertiesEvent}
|
||||
* @param value the new property value
|
||||
* @param <T> the type of the value
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default <T> void updateProperty(@NonNull GeyserEntityProperty<T> property, @Nullable T value) {
|
||||
this.updatePropertiesBatched(consumer -> consumer.update(property, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates multiple properties with just one update packet.
|
||||
* @see BatchPropertyUpdater
|
||||
* @since 2.9.0
|
||||
*/
|
||||
void updatePropertiesBatched(Consumer<BatchPropertyUpdater> consumer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.event.Event;
|
||||
import org.geysermc.geyser.api.entity.EntityData;
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserBooleanEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserEnumEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserFloatEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserIntEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserStringEnumProperty;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Lifecycle event fired during Geyser's startup to allow custom entity properties
|
||||
* to be registered for a specific entity type.
|
||||
* <p>
|
||||
* Listeners can add new properties for any entity by passing the target entity's
|
||||
* identifier (e.g., {@code Identifier.of("player")}) to the registration methods.
|
||||
* The returned {@link GeyserEntityProperty} is used to identify the properties and to
|
||||
* update the value of a specific entity instance.
|
||||
*
|
||||
* <h2>Example usage</h2>
|
||||
* <pre>{@code
|
||||
* public void onDefine(GeyserDefineEntityPropertiesEvent event) {
|
||||
* Identifier player = Identifier.of("player");
|
||||
* GeyserFloatEntityProperty ANIMATION_SPEED =
|
||||
* event.registerFloatProperty(player, Identifier.of("my_group:animation_speed"), 0.0f, 1.0f, 0.1f);
|
||||
* GeyserBooleanEntityProperty SHOW_SHORTS =
|
||||
* event.registerBooleanProperty(player, Identifier.of("my_group:show_shorts"), false);
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* Retrieving entity instances is possible with the {@link EntityData#entityByJavaId(int)} method, or
|
||||
* {@link EntityData#playerEntity()} for the connection player entity.
|
||||
* To update the value of a property on a specific entity, use {@link GeyserEntity#updateProperty(GeyserEntityProperty, Object)},
|
||||
* or {@link GeyserEntity#updatePropertiesBatched(Consumer)} to update multiple properties efficiently at once.
|
||||
*
|
||||
* <p><b>Notes:</b>
|
||||
* <ul>
|
||||
* <li>Default values must fall within the provided bounds.</li>
|
||||
* <li>There cannot be more than 32 properties registered per entity type in total</li>
|
||||
* <li>{@link #properties(Identifier)} returns properties registered for the given entity
|
||||
* (including those added earlier in the same callback), including vanilla properties.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface GeyserDefineEntityPropertiesEvent extends Event {
|
||||
|
||||
/**
|
||||
* Returns an <em>unmodifiable</em> view of all properties that have been registered
|
||||
* so far for the given entity type. This includes entity properties used for vanilla gameplay,
|
||||
* such as those used for creaking animations.
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @return an unmodifiable collection of registered properties
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
Collection<GeyserEntityProperty<?>> properties(@NonNull Identifier entityType);
|
||||
|
||||
/**
|
||||
* Registers a {@code float}-backed entity property.
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param min the minimum allowed value (inclusive)
|
||||
* @param max the maximum allowed value (inclusive)
|
||||
* @param defaultValue the default value assigned initially on entity spawn - if null, it will be the minimum value
|
||||
* @return the created float property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
GeyserFloatEntityProperty registerFloatProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, float min, float max, @Nullable Float defaultValue);
|
||||
|
||||
/**
|
||||
* Registers a {@code float}-backed entity property with a default value set to the minimum value.
|
||||
* @see #registerFloatProperty(Identifier, Identifier, float, float, Float)
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param min the minimum allowed value (inclusive)
|
||||
* @param max the maximum allowed value (inclusive)
|
||||
* @return the created float property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default GeyserFloatEntityProperty registerFloatProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, float min, float max) {
|
||||
return registerFloatProperty(entityType, propertyIdentifier, min, max, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an {@code int}-backed entity property.
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param min the minimum allowed value (inclusive)
|
||||
* @param max the maximum allowed value (inclusive)
|
||||
* @param defaultValue the default value assigned initially on entity spawn - if null, it will be the minimum value
|
||||
* @return the created int property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
GeyserIntEntityProperty registerIntegerProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, int min, int max, @Nullable Integer defaultValue);
|
||||
|
||||
/**
|
||||
* Registers an {@code int}-backed entity property with a default value set to the minimum value.
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param min the minimum allowed value (inclusive)
|
||||
* @param max the maximum allowed value (inclusive)
|
||||
* @return the created int property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default GeyserIntEntityProperty registerIntegerProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, int min, int max) {
|
||||
return registerIntegerProperty(entityType, propertyIdentifier, min, max, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@code boolean}-backed entity property.
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param defaultValue the default boolean value
|
||||
* @return the created boolean property handle
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
GeyserBooleanEntityProperty registerBooleanProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, boolean defaultValue);
|
||||
|
||||
/**
|
||||
* Registers a {@code boolean}-backed entity property with a default of {@code false}.
|
||||
* @see #registerBooleanProperty(Identifier, Identifier, boolean)
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @return the created boolean property
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default GeyserBooleanEntityProperty registerBooleanProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier) {
|
||||
return registerBooleanProperty(entityType, propertyIdentifier, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a typed {@linkplain Enum enum}-backed entity property.
|
||||
* <p>
|
||||
* The enum constants define the allowed values. If {@code defaultValue} is {@code null},
|
||||
* the first enum value is set as the default.
|
||||
* @see GeyserEnumEntityProperty for further limitations
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param enumClass the enum class that defines allowed values
|
||||
* @param defaultValue the default enum value, or {@code null} for the first enum value to be the default
|
||||
* @param <E> the enum type
|
||||
* @return the created enum property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
<E extends Enum<E>> GeyserEnumEntityProperty<E> registerEnumProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, @NonNull Class<E> enumClass, @Nullable E defaultValue);
|
||||
|
||||
/**
|
||||
* Registers a typed {@linkplain Enum enum}-backed entity property with the first value set as the default.
|
||||
* @see #registerEnumProperty(Identifier, Identifier, Class, Enum)
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param enumClass the enum class that defines allowed values
|
||||
* @param <E> the enum type
|
||||
* @return the created enum property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default <E extends Enum<E>> GeyserEnumEntityProperty<E> registerEnumProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, @NonNull Class<E> enumClass) {
|
||||
return registerEnumProperty(entityType, propertyIdentifier, enumClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a string-backed "enum-like" entity property where the set of allowed values
|
||||
* is defined by the provided list. If {@code defaultValue} is {@code null}, the first value is used as the default
|
||||
* on entity spawn. The default must be one of the values in {@code values}.
|
||||
* @see GeyserStringEnumProperty
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param values the allowed string values
|
||||
* @param defaultValue the default string value, or {@code null} for the first value to be used
|
||||
* @return the created string-enum property
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
GeyserStringEnumProperty registerEnumProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, @NonNull List<String> values, @Nullable String defaultValue);
|
||||
|
||||
/**
|
||||
* Registers a string-backed "enum-like" entity property with the first value as the default.
|
||||
* @see #registerEnumProperty(Identifier, Identifier, List, String)
|
||||
*
|
||||
* @param entityType the Java edition entity type identifier
|
||||
* @param propertyIdentifier the unique property identifier
|
||||
* @param values the allowed string values
|
||||
* @return the created string-enum property handle
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default GeyserStringEnumProperty registerEnumProperty(@NonNull Identifier entityType, @NonNull Identifier propertyIdentifier, @NonNull List<String> values) {
|
||||
return registerEnumProperty(entityType, propertyIdentifier, values, null);
|
||||
}
|
||||
}
|
||||
116
api/src/main/java/org/geysermc/geyser/api/util/Identifier.java
Normal file
116
api/src/main/java/org/geysermc/geyser/api/util/Identifier.java
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 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;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
/**
|
||||
* An identifying object for representing unique objects.
|
||||
* This identifier consists of two parts:
|
||||
* <ul>
|
||||
* <li>
|
||||
* a namespace, which is usually a name identifying your work
|
||||
* </li>
|
||||
* <li>
|
||||
* a path, which holds a value.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* Examples of identifiers:
|
||||
* <ul>
|
||||
* <li>{@code minecraft:fox}</li>
|
||||
* <li>{@code geysermc:one_fun_example}</li>
|
||||
* </ul>
|
||||
*
|
||||
* If this identifier is referencing anything not in the
|
||||
* vanilla Minecraft game, the namespace cannot be "minecraft".
|
||||
* Further, paths cannot contain colons ({@code :}).
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public interface Identifier {
|
||||
|
||||
/**
|
||||
* The namespace for Minecraft.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
String DEFAULT_NAMESPACE = "minecraft";
|
||||
|
||||
/**
|
||||
* Attempts to create a new identifier from a namespace and path.
|
||||
*
|
||||
* @return the identifier for this namespace and path
|
||||
* @throws IllegalArgumentException if either namespace or path are invalid.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
static Identifier of(@NonNull String namespace, @NonNull String path) {
|
||||
return GeyserApi.api().provider(Identifier.class, namespace, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to create a new identifier from a string representation.
|
||||
*
|
||||
* @return the identifier for this namespace and path
|
||||
* @throws IllegalArgumentException if either the namespace or path are invalid
|
||||
* @since 2.9.0
|
||||
*/
|
||||
static Identifier of(String identifier) {
|
||||
String[] split = identifier.split(":");
|
||||
String namespace;
|
||||
String path;
|
||||
if (split.length == 1) {
|
||||
namespace = DEFAULT_NAMESPACE;
|
||||
path = split[0];
|
||||
} else if (split.length == 2) {
|
||||
namespace = split[0];
|
||||
path = split[1];
|
||||
} else {
|
||||
throw new IllegalArgumentException("':' in identifier path: " + identifier);
|
||||
}
|
||||
return of(namespace, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the namespace of this identifier.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
String namespace();
|
||||
|
||||
/**
|
||||
* @return the path of this identifier.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
String path();
|
||||
|
||||
/**
|
||||
* Checks whether this identifier is using the "minecraft" namespace.
|
||||
* @since 2.9.0
|
||||
*/
|
||||
default boolean vanilla() {
|
||||
return namespace().equals(DEFAULT_NAMESPACE);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import lombok.experimental.Accessors;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.entity.factory.EntityFactory;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
|
||||
import org.geysermc.geyser.entity.properties.type.PropertyType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.translator.entity.EntityMetadataTranslator;
|
||||
@@ -54,7 +55,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
|
||||
|
||||
public static <T extends Entity> Builder<T> inherited(EntityFactory<T> factory, EntityDefinition<? super T> parent) {
|
||||
return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, parent.registeredProperties, new ObjectArrayList<>(parent.translators));
|
||||
return new Builder<>(factory, parent.entityType, parent.identifier, parent.width, parent.height, parent.offset, new ObjectArrayList<>(parent.translators));
|
||||
}
|
||||
|
||||
public static <T extends Entity> Builder<T> builder(EntityFactory<T> factory) {
|
||||
@@ -89,7 +90,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
private float width;
|
||||
private float height;
|
||||
private float offset = 0.00001f;
|
||||
private GeyserEntityProperties registeredProperties;
|
||||
private GeyserEntityProperties.Builder propertiesBuilder;
|
||||
private final List<EntityMetadataTranslator<? super T, ?, ?>> translators;
|
||||
|
||||
private Builder(EntityFactory<T> factory) {
|
||||
@@ -97,14 +98,13 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
translators = new ObjectArrayList<>();
|
||||
}
|
||||
|
||||
public Builder(EntityFactory<T> factory, EntityType type, String identifier, float width, float height, float offset, GeyserEntityProperties registeredProperties, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
|
||||
public Builder(EntityFactory<T> factory, EntityType type, String identifier, float width, float height, float offset, List<EntityMetadataTranslator<? super T, ?, ?>> translators) {
|
||||
this.factory = factory;
|
||||
this.type = type;
|
||||
this.identifier = identifier;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.offset = offset;
|
||||
this.registeredProperties = registeredProperties;
|
||||
this.translators = translators;
|
||||
}
|
||||
|
||||
@@ -131,8 +131,11 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> properties(GeyserEntityProperties registeredProperties) {
|
||||
this.registeredProperties = registeredProperties;
|
||||
public Builder<T> property(PropertyType<?, ?> propertyType) {
|
||||
if (this.propertiesBuilder == null) {
|
||||
this.propertiesBuilder = new GeyserEntityProperties.Builder(this.identifier);
|
||||
}
|
||||
propertiesBuilder.add(propertyType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -158,13 +161,11 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
|
||||
if (identifier == null && type != null) {
|
||||
identifier = "minecraft:" + type.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
GeyserEntityProperties registeredProperties = propertiesBuilder == null ? null : propertiesBuilder.build();
|
||||
EntityDefinition<T> definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, registeredProperties, translators);
|
||||
if (register && definition.entityType() != null) {
|
||||
Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition);
|
||||
Registries.JAVA_ENTITY_IDENTIFIERS.get().putIfAbsent("minecraft:" + type.name().toLowerCase(Locale.ROOT), definition);
|
||||
if (definition.registeredProperties() != null) {
|
||||
Registries.BEDROCK_ENTITY_PROPERTIES.get().add(definition.registeredProperties().toNbtMap(identifier));
|
||||
}
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,23 @@
|
||||
|
||||
package org.geysermc.geyser.entity;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserFloatEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserStringEnumProperty;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineEntityPropertiesEvent;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
import org.geysermc.geyser.entity.factory.EntityFactory;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.FloatProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.IntProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.PropertyType;
|
||||
import org.geysermc.geyser.entity.properties.type.StringEnumProperty;
|
||||
import org.geysermc.geyser.entity.type.AbstractArrowEntity;
|
||||
import org.geysermc.geyser.entity.type.AbstractWindChargeEntity;
|
||||
import org.geysermc.geyser.entity.type.AreaEffectCloudEntity;
|
||||
@@ -37,8 +50,6 @@ import org.geysermc.geyser.entity.type.BoatEntity;
|
||||
import org.geysermc.geyser.entity.type.ChestBoatEntity;
|
||||
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
|
||||
import org.geysermc.geyser.entity.type.DisplayBaseEntity;
|
||||
import org.geysermc.geyser.entity.type.HangingEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableEggEntity;
|
||||
import org.geysermc.geyser.entity.type.EnderCrystalEntity;
|
||||
import org.geysermc.geyser.entity.type.EnderEyeEntity;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
@@ -49,6 +60,7 @@ import org.geysermc.geyser.entity.type.FireballEntity;
|
||||
import org.geysermc.geyser.entity.type.FireworkEntity;
|
||||
import org.geysermc.geyser.entity.type.FishingHookEntity;
|
||||
import org.geysermc.geyser.entity.type.FurnaceMinecartEntity;
|
||||
import org.geysermc.geyser.entity.type.HangingEntity;
|
||||
import org.geysermc.geyser.entity.type.InteractionEntity;
|
||||
import org.geysermc.geyser.entity.type.ItemEntity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
@@ -60,6 +72,7 @@ import org.geysermc.geyser.entity.type.PaintingEntity;
|
||||
import org.geysermc.geyser.entity.type.SpawnerMinecartEntity;
|
||||
import org.geysermc.geyser.entity.type.TNTEntity;
|
||||
import org.geysermc.geyser.entity.type.TextDisplayEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableEggEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableItemEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrownPotionEntity;
|
||||
@@ -83,17 +96,14 @@ import org.geysermc.geyser.entity.type.living.TadpoleEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AxolotlEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.BeeEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.ChickenEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.CowEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.FoxEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.FrogEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.GoatEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.HappyGhastEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.HoglinEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.MooshroomEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.OcelotEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PandaEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.PigEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PolarBearEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.PufferFishEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.RabbitEntity;
|
||||
@@ -102,6 +112,10 @@ import org.geysermc.geyser.entity.type.living.animal.SnifferEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.StriderEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.TropicalFishEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.TurtleEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.ChickenEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.CowEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.PigEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.TemperatureVariantAnimal;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.ChestedHorseEntity;
|
||||
@@ -158,6 +172,10 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.Boolea
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class EntityDefinitions {
|
||||
public static final EntityDefinition<BoatEntity> ACACIA_BOAT;
|
||||
public static final EntityDefinition<ChestBoatEntity> ACACIA_CHEST_BOAT;
|
||||
@@ -469,7 +487,7 @@ public final class EntityDefinitions {
|
||||
EGG = EntityDefinition.inherited(ThrowableEggEntity::new, throwableItemBase)
|
||||
.type(EntityType.EGG)
|
||||
.heightAndWidth(0.25f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.property(TemperatureVariantAnimal.TEMPERATE_VARIANT_PROPERTY)
|
||||
.build();
|
||||
ENDER_PEARL = EntityDefinition.inherited(ThrowableItemEntity::new, throwableItemBase)
|
||||
.type(EntityType.ENDER_PEARL)
|
||||
@@ -715,7 +733,9 @@ public final class EntityDefinitions {
|
||||
.height(0.49f).width(0.98f)
|
||||
.addTranslator(MetadataTypes.WEATHERING_COPPER_STATE, CopperGolemEntity::setWeatheringState)
|
||||
.addTranslator(MetadataTypes.COPPER_GOLEM_STATE, CopperGolemEntity::setGolemState)
|
||||
.properties(VanillaEntityProperties.COPPER_GOLEM)
|
||||
.property(CopperGolemEntity.CHEST_INTERACTION_PROPERTY)
|
||||
.property(CopperGolemEntity.HAS_FLOWER_PROPERTY)
|
||||
.property(CopperGolemEntity.OXIDATION_LEVEL_STATE_ENUM_PROPERTY)
|
||||
.build();
|
||||
CREAKING = EntityDefinition.inherited(CreakingEntity::new, mobEntityBase)
|
||||
.type(EntityType.CREAKING)
|
||||
@@ -724,7 +744,8 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setActive)
|
||||
.addTranslator(MetadataTypes.BOOLEAN, CreakingEntity::setIsTearingDown)
|
||||
.addTranslator(MetadataTypes.OPTIONAL_BLOCK_POS, CreakingEntity::setHomePos)
|
||||
.properties(VanillaEntityProperties.CREAKING)
|
||||
.property(CreakingEntity.STATE_PROPERTY)
|
||||
.property(CreakingEntity.SWAYING_TICKS_PROPERTY)
|
||||
.build();
|
||||
CREEPER = EntityDefinition.inherited(CreeperEntity::new, mobEntityBase)
|
||||
.type(EntityType.CREEPER)
|
||||
@@ -977,7 +998,7 @@ public final class EntityDefinitions {
|
||||
ARMADILLO = EntityDefinition.inherited(ArmadilloEntity::new, ageableEntityBase)
|
||||
.type(EntityType.ARMADILLO)
|
||||
.height(0.65f).width(0.7f)
|
||||
.properties(VanillaEntityProperties.ARMADILLO)
|
||||
.property(ArmadilloEntity.STATE_PROPERTY)
|
||||
.addTranslator(MetadataTypes.ARMADILLO_STATE, ArmadilloEntity::setArmadilloState)
|
||||
.build();
|
||||
AXOLOTL = EntityDefinition.inherited(AxolotlEntity::new, ageableEntityBase)
|
||||
@@ -990,20 +1011,20 @@ public final class EntityDefinitions {
|
||||
BEE = EntityDefinition.inherited(BeeEntity::new, ageableEntityBase)
|
||||
.type(EntityType.BEE)
|
||||
.heightAndWidth(0.6f)
|
||||
.properties(VanillaEntityProperties.BEE)
|
||||
.property(BeeEntity.NECTAR_PROPERTY)
|
||||
.addTranslator(MetadataTypes.BYTE, BeeEntity::setBeeFlags)
|
||||
.addTranslator(MetadataTypes.INT, BeeEntity::setAngerTime)
|
||||
.build();
|
||||
CHICKEN = EntityDefinition.inherited(ChickenEntity::new, ageableEntityBase)
|
||||
.type(EntityType.CHICKEN)
|
||||
.height(0.7f).width(0.4f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.property(TemperatureVariantAnimal.TEMPERATE_VARIANT_PROPERTY)
|
||||
.addTranslator(MetadataTypes.CHICKEN_VARIANT, ChickenEntity::setVariant)
|
||||
.build();
|
||||
COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase)
|
||||
.type(EntityType.COW)
|
||||
.height(1.4f).width(0.9f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.property(TemperatureVariantAnimal.TEMPERATE_VARIANT_PROPERTY)
|
||||
.addTranslator(MetadataTypes.COW_VARIANT, CowEntity::setVariant)
|
||||
.build();
|
||||
FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase)
|
||||
@@ -1023,7 +1044,7 @@ public final class EntityDefinitions {
|
||||
HAPPY_GHAST = EntityDefinition.inherited(HappyGhastEntity::new, ageableEntityBase)
|
||||
.type(EntityType.HAPPY_GHAST)
|
||||
.heightAndWidth(4f)
|
||||
.properties(VanillaEntityProperties.HAPPY_GHAST)
|
||||
.property(HappyGhastEntity.CAN_MOVE_PROPERTY)
|
||||
.addTranslator(null) // Is leash holder
|
||||
.addTranslator(MetadataTypes.BOOLEAN, HappyGhastEntity::setStaysStill)
|
||||
.build();
|
||||
@@ -1062,7 +1083,7 @@ public final class EntityDefinitions {
|
||||
PIG = EntityDefinition.inherited(PigEntity::new, ageableEntityBase)
|
||||
.type(EntityType.PIG)
|
||||
.heightAndWidth(0.9f)
|
||||
.properties(VanillaEntityProperties.CLIMATE_VARIANT)
|
||||
.property(TemperatureVariantAnimal.TEMPERATE_VARIANT_PROPERTY)
|
||||
.addTranslator(MetadataTypes.INT, PigEntity::setBoost)
|
||||
.addTranslator(MetadataTypes.PIG_VARIANT, PigEntity::setVariant)
|
||||
.build();
|
||||
@@ -1208,7 +1229,7 @@ public final class EntityDefinitions {
|
||||
WOLF = EntityDefinition.inherited(WolfEntity::new, tameableEntityBase)
|
||||
.type(EntityType.WOLF)
|
||||
.height(0.85f).width(0.6f)
|
||||
.properties(VanillaEntityProperties.WOLF_SOUND_VARIANT)
|
||||
.property(WolfEntity.SOUND_VARIANT)
|
||||
// "Begging" on wiki.vg, "Interested" in Nukkit - the tilt of the head
|
||||
.addTranslator(MetadataTypes.BOOLEAN, (wolfEntity, entityMetadata) -> wolfEntity.setFlag(EntityFlag.INTERESTED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
|
||||
.addTranslator(MetadataTypes.INT, WolfEntity::setCollarColor)
|
||||
@@ -1242,7 +1263,95 @@ public final class EntityDefinitions {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// no-op
|
||||
// entities would be initialized before this event is called
|
||||
GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineEntityPropertiesEvent() {
|
||||
@Override
|
||||
public GeyserFloatEntityProperty registerFloatProperty(@NonNull Identifier identifier, @NonNull Identifier propertyId, float min, float max, @Nullable Float defaultValue) {
|
||||
Objects.requireNonNull(identifier);
|
||||
Objects.requireNonNull(propertyId);
|
||||
if (propertyId.vanilla()) {
|
||||
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
|
||||
}
|
||||
FloatProperty property = new FloatProperty(propertyId, min, max, defaultValue);
|
||||
registerProperty(identifier, property);
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntProperty registerIntegerProperty(@NonNull Identifier identifier, @NonNull Identifier propertyId, int min, int max, @Nullable Integer defaultValue) {
|
||||
Objects.requireNonNull(identifier);
|
||||
Objects.requireNonNull(propertyId);
|
||||
if (propertyId.vanilla()) {
|
||||
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
|
||||
}
|
||||
IntProperty property = new IntProperty(propertyId, min, max, defaultValue);
|
||||
registerProperty(identifier, property);
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanProperty registerBooleanProperty(@NonNull Identifier identifier, @NonNull Identifier propertyId, boolean defaultValue) {
|
||||
Objects.requireNonNull(identifier);
|
||||
Objects.requireNonNull(propertyId);
|
||||
if (propertyId.vanilla()) {
|
||||
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
|
||||
}
|
||||
BooleanProperty property = new BooleanProperty(propertyId, defaultValue);
|
||||
registerProperty(identifier, property);
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Enum<E>> EnumProperty<E> registerEnumProperty(@NonNull Identifier identifier, @NonNull Identifier propertyId, @NonNull Class<E> enumClass, @Nullable E defaultValue) {
|
||||
Objects.requireNonNull(identifier);
|
||||
Objects.requireNonNull(propertyId);
|
||||
Objects.requireNonNull(enumClass);
|
||||
if (propertyId.vanilla()) {
|
||||
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
|
||||
}
|
||||
EnumProperty<E> property = new EnumProperty<>(propertyId, enumClass, defaultValue == null ? enumClass.getEnumConstants()[0] : defaultValue);
|
||||
registerProperty(identifier, property);
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserStringEnumProperty registerEnumProperty(@NonNull Identifier identifier, @NonNull Identifier propertyId, @NonNull List<String> values, @Nullable String defaultValue) {
|
||||
Objects.requireNonNull(identifier);
|
||||
Objects.requireNonNull(propertyId);
|
||||
Objects.requireNonNull(values);
|
||||
if (propertyId.vanilla()) {
|
||||
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
|
||||
}
|
||||
StringEnumProperty property = new StringEnumProperty(propertyId, values, defaultValue);
|
||||
registerProperty(identifier, property);
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GeyserEntityProperty<?>> properties(@NonNull Identifier identifier) {
|
||||
Objects.requireNonNull(identifier);
|
||||
var definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(identifier.toString());
|
||||
if (definition == null) {
|
||||
throw new IllegalArgumentException("Unknown entity type: " + identifier);
|
||||
}
|
||||
return List.copyOf(definition.registeredProperties().getProperties());
|
||||
}
|
||||
});
|
||||
|
||||
for (var definition : Registries.ENTITY_DEFINITIONS.get().values()) {
|
||||
if (definition.registeredProperties() != null) {
|
||||
Registries.BEDROCK_ENTITY_PROPERTIES.get().add(definition.registeredProperties().toNbtMap(definition.identifier()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void registerProperty(Identifier entityType, PropertyType<T, ?> property) {
|
||||
var definition = Registries.JAVA_ENTITY_IDENTIFIERS.get(entityType.toString());
|
||||
if (definition == null) {
|
||||
throw new IllegalArgumentException("Unknown entity type: " + entityType);
|
||||
}
|
||||
|
||||
definition.registeredProperties().add(entityType.toString(), property);
|
||||
}
|
||||
|
||||
private EntityDefinitions() {
|
||||
|
||||
@@ -44,7 +44,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
public class GeyserEntityData implements EntityData {
|
||||
|
||||
private final GeyserSession session;
|
||||
|
||||
private final Set<UUID> movementLockOwners = new HashSet<>();
|
||||
|
||||
public GeyserEntityData(GeyserSession session) {
|
||||
|
||||
@@ -31,36 +31,38 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.FloatProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.IntProperty;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.PropertyType;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class GeyserEntityProperties {
|
||||
private final ObjectArrayList<PropertyType> properties;
|
||||
|
||||
private final static Pattern ENTITY_PROPERTY_PATTERN = Pattern.compile("^[a-z0-9_.:-]*:[a-z0-9_.:-]*$");
|
||||
|
||||
private final ObjectArrayList<PropertyType<?, ?>> properties;
|
||||
private final Object2IntMap<String> propertyIndices;
|
||||
|
||||
private GeyserEntityProperties(ObjectArrayList<PropertyType> properties,
|
||||
Object2IntMap<String> propertyIndices) {
|
||||
this.properties = properties;
|
||||
this.propertyIndices = propertyIndices;
|
||||
private GeyserEntityProperties() {
|
||||
this.properties = new ObjectArrayList<>();
|
||||
this.propertyIndices = new Object2IntOpenHashMap<>();
|
||||
}
|
||||
|
||||
public NbtMap toNbtMap(String entityType) {
|
||||
NbtMapBuilder mapBuilder = NbtMap.builder();
|
||||
List<NbtMap> nbtProperties = new ArrayList<>();
|
||||
|
||||
for (PropertyType property : properties) {
|
||||
for (PropertyType<?, ?> property : properties) {
|
||||
nbtProperties.add(property.nbtMap());
|
||||
}
|
||||
mapBuilder.putList("properties", NbtType.COMPOUND, nbtProperties);
|
||||
@@ -68,7 +70,30 @@ public class GeyserEntityProperties {
|
||||
return mapBuilder.putString("type", entityType).build();
|
||||
}
|
||||
|
||||
public @NonNull List<PropertyType> getProperties() {
|
||||
public <T> void add(String entityType, @NonNull PropertyType<T, ? extends EntityProperty> property) {
|
||||
if (!Registries.BEDROCK_ENTITY_PROPERTIES.get().isEmpty()) {
|
||||
throw new IllegalStateException("Cannot add properties outside the GeyserDefineEntityProperties event!");
|
||||
}
|
||||
|
||||
if (this.properties.size() > 32) {
|
||||
throw new IllegalArgumentException("Cannot register more than 32 properties for entity type " + entityType);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(property, "property cannot be null!");
|
||||
String name = property.identifier().toString();
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
} else if (!ENTITY_PROPERTY_PATTERN.matcher(name).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot register property with name " + name + " because property name is invalid! Must match: " + ENTITY_PROPERTY_PATTERN.pattern()
|
||||
);
|
||||
}
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
}
|
||||
|
||||
public @NonNull List<PropertyType<?, ?>> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -77,89 +102,24 @@ public class GeyserEntityProperties {
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final ObjectArrayList<PropertyType> properties = new ObjectArrayList<>();
|
||||
private final Object2IntMap<String> propertyIndices = new Object2IntOpenHashMap<>();
|
||||
private GeyserEntityProperties properties;
|
||||
private final String identifier;
|
||||
|
||||
public Builder addInt(@NonNull String name, int min, int max) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
public Builder(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public <T> Builder add(@NonNull PropertyType<T, ? extends EntityProperty> property) {
|
||||
Objects.requireNonNull(property, "property cannot be null!");
|
||||
if (properties == null) {
|
||||
properties = new GeyserEntityProperties();
|
||||
}
|
||||
PropertyType property = new IntProperty(name, min, max);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
properties.add(identifier, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addInt(@NonNull String name) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
PropertyType property = new IntProperty(name, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addFloat(@NonNull String name, float min, float max) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
PropertyType property = new FloatProperty(name, min, max);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addFloat(@NonNull String name) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
PropertyType property = new FloatProperty(name, Float.MIN_NORMAL, Float.MAX_VALUE);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addBoolean(@NonNull String name) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
PropertyType property = new BooleanProperty(name);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addEnum(@NonNull String name, List<String> values) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
PropertyType property = new EnumProperty(name, values);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addEnum(@NonNull String name, String... values) {
|
||||
if (propertyIndices.containsKey(name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property with name " + name + " already exists on builder!");
|
||||
}
|
||||
List<String> valuesList = Arrays.asList(values); // Convert array to list
|
||||
PropertyType property = new EnumProperty(name, valuesList);
|
||||
this.properties.add(property);
|
||||
propertyIndices.put(name, properties.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GeyserEntityProperties build() {
|
||||
return new GeyserEntityProperties(properties, propertyIndices);
|
||||
public @Nullable GeyserEntityProperties build() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.entity.properties;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityProperty;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.FloatEntityProperty;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.PropertyType;
|
||||
|
||||
import java.util.List;
|
||||
@@ -36,34 +38,29 @@ import java.util.List;
|
||||
public class GeyserEntityPropertyManager {
|
||||
|
||||
private final GeyserEntityProperties properties;
|
||||
|
||||
private final ObjectArrayList<IntEntityProperty> intEntityProperties = new ObjectArrayList<>();
|
||||
private final ObjectArrayList<FloatEntityProperty> floatEntityProperties = new ObjectArrayList<>();
|
||||
private final Object2ObjectMap<String, IntEntityProperty> intEntityProperties = new Object2ObjectArrayMap<>();
|
||||
private final Object2ObjectMap<String, FloatEntityProperty> floatEntityProperties = new Object2ObjectArrayMap<>();
|
||||
|
||||
public GeyserEntityPropertyManager(GeyserEntityProperties properties) {
|
||||
this.properties = properties;
|
||||
for (PropertyType<?, ?> property : properties.getProperties()) {
|
||||
String name = property.identifier().toString();
|
||||
int index = properties.getPropertyIndex(name);
|
||||
addProperty(name, property.defaultValue(index));
|
||||
}
|
||||
}
|
||||
|
||||
public void add(String propertyName, int value) {
|
||||
int index = properties.getPropertyIndex(propertyName);
|
||||
intEntityProperties.add(new IntEntityProperty(index, value));
|
||||
public <T> void addProperty(PropertyType<T, ? extends EntityProperty> propertyType, T value) {
|
||||
int index = properties.getPropertyIndex(propertyType.identifier().toString());
|
||||
this.addProperty(propertyType.identifier().toString(), propertyType.createValue(index, value));
|
||||
}
|
||||
|
||||
public void add(String propertyName, boolean value) {
|
||||
int index = properties.getPropertyIndex(propertyName);
|
||||
intEntityProperties.add(new IntEntityProperty(index, value ? 1 : 0));
|
||||
}
|
||||
|
||||
public void add(String propertyName, String value) {
|
||||
int index = properties.getPropertyIndex(propertyName);
|
||||
PropertyType property = properties.getProperties().get(index);
|
||||
int enumIndex = ((EnumProperty) property).getIndex(value);
|
||||
intEntityProperties.add(new IntEntityProperty(index, enumIndex));
|
||||
}
|
||||
|
||||
public void add(String propertyName, float value) {
|
||||
int index = properties.getPropertyIndex(propertyName);
|
||||
floatEntityProperties.add(new FloatEntityProperty(index, value));
|
||||
private void addProperty(String propertyName, EntityProperty entityProperty) {
|
||||
if (entityProperty instanceof FloatEntityProperty floatEntityProperty) {
|
||||
floatEntityProperties.put(propertyName, floatEntityProperty);
|
||||
} else if (entityProperty instanceof IntEntityProperty intEntityProperty) {
|
||||
intEntityProperties.put(propertyName, intEntityProperty);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasFloatProperties() {
|
||||
@@ -78,21 +75,17 @@ public class GeyserEntityPropertyManager {
|
||||
return hasFloatProperties() || hasIntProperties();
|
||||
}
|
||||
|
||||
public ObjectArrayList<IntEntityProperty> intProperties() {
|
||||
return this.intEntityProperties;
|
||||
}
|
||||
|
||||
public void applyIntProperties(List<IntEntityProperty> properties) {
|
||||
properties.addAll(intEntityProperties);
|
||||
properties.addAll(intEntityProperties.values());
|
||||
intEntityProperties.clear();
|
||||
}
|
||||
|
||||
public ObjectArrayList<FloatEntityProperty> floatProperties() {
|
||||
return this.floatEntityProperties;
|
||||
}
|
||||
|
||||
public void applyFloatProperties(List<FloatEntityProperty> properties) {
|
||||
properties.addAll(floatEntityProperties);
|
||||
properties.addAll(floatEntityProperties.values());
|
||||
floatEntityProperties.clear();
|
||||
}
|
||||
|
||||
public NbtMap toNbtMap(String entityType) {
|
||||
return this.properties.toNbtMap(entityType);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.properties;
|
||||
|
||||
import org.geysermc.geyser.entity.type.living.CopperGolemEntity;
|
||||
import org.geysermc.geyser.entity.type.living.monster.CreakingEntity;
|
||||
|
||||
public class VanillaEntityProperties {
|
||||
|
||||
public static final String CLIMATE_VARIANT_ID = "minecraft:climate_variant";
|
||||
|
||||
public static final GeyserEntityProperties ARMADILLO = new GeyserEntityProperties.Builder()
|
||||
.addEnum("minecraft:armadillo_state",
|
||||
"unrolled",
|
||||
"rolled_up",
|
||||
"rolled_up_peeking",
|
||||
"rolled_up_relaxing",
|
||||
"rolled_up_unrolling")
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties BEE = new GeyserEntityProperties.Builder()
|
||||
.addBoolean("minecraft:has_nectar")
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties CLIMATE_VARIANT = new GeyserEntityProperties.Builder()
|
||||
.addEnum(CLIMATE_VARIANT_ID,
|
||||
"temperate",
|
||||
"warm",
|
||||
"cold")
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties CREAKING = new GeyserEntityProperties.Builder()
|
||||
.addEnum(CreakingEntity.CREAKING_STATE,
|
||||
"neutral",
|
||||
"hostile_observed",
|
||||
"hostile_unobserved",
|
||||
"twitching",
|
||||
"crumbling")
|
||||
.addInt(CreakingEntity.CREAKING_SWAYING_TICKS, 0, 6)
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties HAPPY_GHAST = new GeyserEntityProperties.Builder()
|
||||
.addBoolean("minecraft:can_move")
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties WOLF_SOUND_VARIANT = new GeyserEntityProperties.Builder()
|
||||
.addEnum("minecraft:sound_variant",
|
||||
"default",
|
||||
"big",
|
||||
"cute",
|
||||
"grumpy",
|
||||
"mad",
|
||||
"puglin",
|
||||
"sad")
|
||||
.build();
|
||||
|
||||
public static final GeyserEntityProperties COPPER_GOLEM = new GeyserEntityProperties.Builder()
|
||||
.addEnum(CopperGolemEntity.CHEST_INTERACTION,
|
||||
"none",
|
||||
"take",
|
||||
"take_fail",
|
||||
"put",
|
||||
"put_fail")
|
||||
.addBoolean(CopperGolemEntity.HAS_FLOWER)
|
||||
.addEnum(CopperGolemEntity.OXIDIZATION_LEVEL,
|
||||
"unoxidized",
|
||||
"exposed",
|
||||
"weathered",
|
||||
"oxidized")
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.entity.properties.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public interface AbstractEnumProperty<T> extends PropertyType<T, IntEntityProperty> {
|
||||
|
||||
Pattern VALUE_VALIDATION_REGEX = Pattern.compile("^[A-Za-z][A-Za-z0-9_]{0,31}$");
|
||||
|
||||
@Override
|
||||
default NbtMap nbtMap() {
|
||||
return NbtMap.builder()
|
||||
.putString("name", identifier().toString())
|
||||
.putList("enum", NbtType.STRING, allBedrockValues())
|
||||
.putInt("type", 3)
|
||||
.build();
|
||||
}
|
||||
|
||||
default void validateAllValues(Identifier name, List<String> values) {
|
||||
if (values.size() > 16) {
|
||||
throw new IllegalArgumentException("Cannot register enum property with name " + name + " because it has more than 16 values!");
|
||||
}
|
||||
|
||||
for (String value : values) {
|
||||
if (!VALUE_VALIDATION_REGEX.matcher(value).matches()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot register enum property with name " + name + " and value " + value +
|
||||
" because enum values can only contain alphanumeric characters and underscores."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<String> allBedrockValues();
|
||||
|
||||
@Override
|
||||
default IntEntityProperty defaultValue(int index) {
|
||||
return new IntEntityProperty(index, defaultIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
default IntEntityProperty createValue(int index, @Nullable T value) {
|
||||
if (value == null) {
|
||||
return defaultValue(index);
|
||||
}
|
||||
|
||||
int valueIndex = indexOf(value);
|
||||
if (valueIndex == -1) {
|
||||
throw new IllegalArgumentException("Enum value " + value + " is not a valid enum value!");
|
||||
}
|
||||
return new IntEntityProperty(index, valueIndex);
|
||||
}
|
||||
|
||||
int indexOf(T value);
|
||||
|
||||
int defaultIndex();
|
||||
}
|
||||
@@ -26,19 +26,33 @@
|
||||
package org.geysermc.geyser.entity.properties.type;
|
||||
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserBooleanEntityProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
public class BooleanProperty implements PropertyType {
|
||||
private final String name;
|
||||
|
||||
public BooleanProperty(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public record BooleanProperty(
|
||||
Identifier identifier,
|
||||
Boolean defaultValue
|
||||
) implements PropertyType<Boolean, IntEntityProperty>, GeyserBooleanEntityProperty {
|
||||
|
||||
@Override
|
||||
public NbtMap nbtMap() {
|
||||
return NbtMap.builder()
|
||||
.putString("name", name)
|
||||
.putString("name", identifier.toString())
|
||||
.putInt("type", 2)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntEntityProperty defaultValue(int index) {
|
||||
return createValue(index, defaultValue != null && defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntEntityProperty createValue(int index, Boolean value) {
|
||||
if (value == null) {
|
||||
return defaultValue(index);
|
||||
}
|
||||
return new IntEntityProperty(index, value ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2025 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
|
||||
@@ -25,37 +25,40 @@
|
||||
|
||||
package org.geysermc.geyser.entity.properties.type;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserEnumEntityProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class EnumProperty implements PropertyType {
|
||||
private final String name;
|
||||
private final List<String> values;
|
||||
private final Object2IntMap<String> valueIndexMap;
|
||||
public record EnumProperty<E extends Enum<E>>(
|
||||
Identifier identifier,
|
||||
Class<E> enumClass,
|
||||
E defaultValue
|
||||
) implements AbstractEnumProperty<E>, GeyserEnumEntityProperty<E> {
|
||||
|
||||
public EnumProperty(String name, List<String> values) {
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.valueIndexMap = new Object2IntOpenHashMap<>(values.size());
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
valueIndexMap.put(values.get(i), i);
|
||||
}
|
||||
public EnumProperty {
|
||||
validateAllValues(identifier, Arrays.stream(enumClass.getEnumConstants()).map(value -> value.name().toLowerCase(Locale.ROOT)).toList());
|
||||
}
|
||||
|
||||
public List<E> values() {
|
||||
return List.of(enumClass.getEnumConstants());
|
||||
}
|
||||
|
||||
public List<String> allBedrockValues() {
|
||||
return values().stream().map(
|
||||
value -> value.name().toLowerCase(Locale.ROOT)
|
||||
).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtMap nbtMap() {
|
||||
return NbtMap.builder()
|
||||
.putString("name", name)
|
||||
.putList("enum", NbtType.STRING, values)
|
||||
.putInt("type", 3)
|
||||
.build();
|
||||
public int indexOf(E value) {
|
||||
return value.ordinal();
|
||||
}
|
||||
|
||||
public int getIndex(String value) {
|
||||
return valueIndexMap.getOrDefault(value, -1);
|
||||
@Override
|
||||
public int defaultIndex() {
|
||||
return defaultValue.ordinal();
|
||||
}
|
||||
}
|
||||
@@ -26,25 +26,48 @@
|
||||
package org.geysermc.geyser.entity.properties.type;
|
||||
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.FloatEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserFloatEntityProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
public class FloatProperty implements PropertyType {
|
||||
private final String name;
|
||||
private final float max;
|
||||
private final float min;
|
||||
public record FloatProperty(
|
||||
Identifier identifier,
|
||||
float max,
|
||||
float min,
|
||||
Float defaultValue
|
||||
) implements PropertyType<Float, FloatEntityProperty>, GeyserFloatEntityProperty {
|
||||
|
||||
public FloatProperty(String name, float min, float max) {
|
||||
this.name = name;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
public FloatProperty {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException("Cannot create float entity property (%s) with a minimum value (%s) greater than maximum (%s)!"
|
||||
.formatted(identifier, min, max));
|
||||
}
|
||||
if (defaultValue < min || defaultValue > max) {
|
||||
throw new IllegalArgumentException("Cannot create float entity property (%s) with a default value (%s) outside of the range (%s - %s)!"
|
||||
.formatted(identifier, defaultValue, min, max));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtMap nbtMap() {
|
||||
return NbtMap.builder()
|
||||
.putString("name", name)
|
||||
.putString("name", identifier.toString())
|
||||
.putFloat("max", max)
|
||||
.putFloat("min", min)
|
||||
.putInt("type", 1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatEntityProperty defaultValue(int index) {
|
||||
return createValue(index, defaultValue == null ? min : defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatEntityProperty createValue(int index, Float value) {
|
||||
if (value == null) {
|
||||
return defaultValue(index);
|
||||
}
|
||||
return new FloatEntityProperty(index, value);
|
||||
}
|
||||
}
|
||||
@@ -26,25 +26,53 @@
|
||||
package org.geysermc.geyser.entity.properties.type;
|
||||
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserIntEntityProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
public class IntProperty implements PropertyType {
|
||||
private final String name;
|
||||
private final int max;
|
||||
private final int min;
|
||||
public record IntProperty(
|
||||
Identifier identifier,
|
||||
int max,
|
||||
int min,
|
||||
Integer defaultValue
|
||||
) implements PropertyType<Integer, IntEntityProperty>, GeyserIntEntityProperty {
|
||||
|
||||
public IntProperty(String name, int min, int max) {
|
||||
this.name = name;
|
||||
this.max = max;
|
||||
this.min = min;
|
||||
public IntProperty {
|
||||
if (min > max) {
|
||||
throw new IllegalArgumentException("Cannot create int entity property (%s) with a minimum value (%s) greater than maximum (%s)!"
|
||||
.formatted(identifier, min, max));
|
||||
}
|
||||
if (defaultValue < min || defaultValue > max) {
|
||||
throw new IllegalArgumentException("Cannot create int entity property (%s) with a default value (%s) outside of the range (%s - %s)!"
|
||||
.formatted(identifier, defaultValue, min, max));
|
||||
}
|
||||
if (min < -1000000 || max > 1000000) {
|
||||
// https://learn.microsoft.com/en-us/minecraft/creator/documents/introductiontoentityproperties?view=minecraft-bedrock-stable#a-note-on-large-integer-entity-property-values
|
||||
GeyserImpl.getInstance().getLogger().warning("Using int entity properties with min / max values larger than +- 1 million is not recommended!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NbtMap nbtMap() {
|
||||
return NbtMap.builder()
|
||||
.putString("name", name)
|
||||
.putString("name", identifier.toString())
|
||||
.putInt("max", max)
|
||||
.putInt("min", min)
|
||||
.putInt("type", 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntEntityProperty defaultValue(int index) {
|
||||
return createValue(index, defaultValue == null ? min : defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntEntityProperty createValue(int index, Integer value) {
|
||||
if (value == null) {
|
||||
return defaultValue(index);
|
||||
}
|
||||
return new IntEntityProperty(index, value);
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,20 @@
|
||||
|
||||
package org.geysermc.geyser.entity.properties.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityProperty;
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
|
||||
|
||||
public interface PropertyType {
|
||||
public interface PropertyType<Type, NetworkRepresentation extends EntityProperty> extends GeyserEntityProperty<Type> {
|
||||
NbtMap nbtMap();
|
||||
|
||||
NetworkRepresentation defaultValue(int index);
|
||||
|
||||
NetworkRepresentation createValue(int index, @Nullable Type value);
|
||||
|
||||
default void apply(GeyserEntityPropertyManager manager, Type value) {
|
||||
manager.addProperty(this, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2024 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.entity.properties.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.entity.property.type.GeyserStringEnumProperty;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record StringEnumProperty(
|
||||
Identifier identifier,
|
||||
List<String> values,
|
||||
int defaultIndex
|
||||
) implements AbstractEnumProperty<String>, GeyserStringEnumProperty {
|
||||
|
||||
public StringEnumProperty {
|
||||
if (defaultIndex < 0) {
|
||||
throw new IllegalArgumentException("Unable to find default value for enum property with name " + identifier);
|
||||
}
|
||||
validateAllValues(identifier, values);
|
||||
}
|
||||
|
||||
public StringEnumProperty(Identifier name, List<String> values, @Nullable String defaultValue) {
|
||||
this(name, values, defaultValue == null ? 0 : values.indexOf(defaultValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> allBedrockValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(String value) {
|
||||
return values.indexOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String defaultValue() {
|
||||
return values.get(defaultIndex);
|
||||
}
|
||||
}
|
||||
@@ -29,22 +29,28 @@ import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector2f;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityProperty;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.geyser.api.entity.property.BatchPropertyUpdater;
|
||||
import org.geysermc.geyser.api.entity.property.GeyserEntityProperty;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityProperties;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
|
||||
import org.geysermc.geyser.entity.properties.type.PropertyType;
|
||||
import org.geysermc.geyser.entity.type.living.MobEntity;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
@@ -71,6 +77,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -201,6 +208,10 @@ public class Entity implements GeyserEntity {
|
||||
addEntityPacket.setBodyRotation(yaw); // TODO: This should be bodyYaw
|
||||
addEntityPacket.getMetadata().putFlags(flags);
|
||||
dirtyMetadata.apply(addEntityPacket.getMetadata());
|
||||
if (propertyManager != null) {
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
propertyManager.applyFloatProperties(addEntityPacket.getProperties().getFloatProperties());
|
||||
}
|
||||
addAdditionalSpawnData(addEntityPacket);
|
||||
|
||||
valid = true;
|
||||
@@ -752,4 +763,42 @@ public class Entity implements GeyserEntity {
|
||||
packet.setData(data);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePropertiesBatched(Consumer<BatchPropertyUpdater> consumer) {
|
||||
if (this.propertyManager != null) {
|
||||
Objects.requireNonNull(consumer);
|
||||
GeyserEntityProperties propertyDefinitions = definition.registeredProperties();
|
||||
consumer.accept(new BatchPropertyUpdater() {
|
||||
@Override
|
||||
public <T> void update(@NonNull GeyserEntityProperty<T> property, @Nullable T value) {
|
||||
Objects.requireNonNull(property, "property must not be null!");
|
||||
if (!(property instanceof PropertyType<T, ? extends EntityProperty> propertyType)) {
|
||||
throw new IllegalArgumentException("Invalid property implementation! Got: " + property.getClass().getSimpleName());
|
||||
}
|
||||
int index = propertyDefinitions.getPropertyIndex(property.identifier().toString());
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("No property with the name " + property.identifier() + " has been registered.");
|
||||
}
|
||||
|
||||
var expectedProperty = propertyDefinitions.getProperties().get(index);
|
||||
if (!expectedProperty.equals(propertyType)) {
|
||||
throw new IllegalArgumentException("The supplied property was not registered with this entity type!");
|
||||
}
|
||||
|
||||
propertyType.apply(propertyManager, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (propertyManager.hasProperties()) {
|
||||
SetEntityDataPacket packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(getGeyserId());
|
||||
propertyManager.applyFloatProperties(packet.getProperties().getFloatProperties());
|
||||
propertyManager.applyIntProperties(packet.getProperties().getIntProperties());
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Given entity has no registered properties!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ package org.geysermc.geyser.entity.type;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.type.living.animal.farm.TemperatureVariantAnimal;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
@@ -41,7 +39,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetad
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@@ -54,31 +51,25 @@ public class ThrowableEggEntity extends ThrowableItemEntity {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(EntityMetadata<ItemStack, ?> entityMetadata) {
|
||||
GeyserItemStack stack = GeyserItemStack.from(entityMetadata.getValue());
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, getVariantOrFallback(session, stack));
|
||||
TemperatureVariantAnimal.TEMPERATE_VARIANT_PROPERTY.apply(propertyManager, getVariantOrFallback(session, stack));
|
||||
updateBedrockEntityProperties();
|
||||
this.itemStack = stack;
|
||||
}
|
||||
|
||||
private static String getVariantOrFallback(GeyserSession session, GeyserItemStack stack) {
|
||||
private static TemperatureVariantAnimal.BuiltInVariant getVariantOrFallback(GeyserSession session, GeyserItemStack stack) {
|
||||
Holder<Key> holder = stack.getComponent(DataComponentTypes.CHICKEN_VARIANT);
|
||||
if (holder != null) {
|
||||
Key chickenVariant = holder.getOrCompute(id -> JavaRegistries.CHICKEN_VARIANT.key(session, id));
|
||||
for (var variant : TemperatureVariantAnimal.BuiltInVariant.values()) {
|
||||
if (chickenVariant.asMinimalString().equalsIgnoreCase(variant.name())) {
|
||||
return chickenVariant.asMinimalString().toLowerCase(Locale.ROOT);
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TemperatureVariantAnimal.BuiltInVariant.TEMPERATE.toBedrock();
|
||||
return TemperatureVariantAnimal.BuiltInVariant.TEMPERATE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ package org.geysermc.geyser.entity.type.living;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@@ -45,21 +47,42 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CopperGolemEntity extends GolemEntity {
|
||||
public static final String CHEST_INTERACTION = "minecraft:chest_interaction";
|
||||
public static final String HAS_FLOWER = "has_flower";
|
||||
public static final String OXIDIZATION_LEVEL = "minecraft:oxidation_level";
|
||||
public static final BooleanProperty HAS_FLOWER_PROPERTY = new BooleanProperty(
|
||||
IdentifierImpl.of("has_flower"),
|
||||
false
|
||||
);
|
||||
|
||||
public static final EnumProperty<ChestInteractionState> CHEST_INTERACTION_PROPERTY = new EnumProperty<>(
|
||||
IdentifierImpl.of("chest_interaction"),
|
||||
ChestInteractionState.class,
|
||||
ChestInteractionState.NONE
|
||||
);
|
||||
|
||||
public static final EnumProperty<OxidationLevelState> OXIDATION_LEVEL_STATE_ENUM_PROPERTY = new EnumProperty<>(
|
||||
IdentifierImpl.of("oxidation_level"),
|
||||
OxidationLevelState.class,
|
||||
OxidationLevelState.UNOXIDIZED
|
||||
);
|
||||
|
||||
public enum ChestInteractionState {
|
||||
NONE,
|
||||
TAKE,
|
||||
TAKE_FAIL,
|
||||
PUT,
|
||||
PUT_FAIL
|
||||
}
|
||||
|
||||
public enum OxidationLevelState {
|
||||
UNOXIDIZED,
|
||||
EXPOSED,
|
||||
WEATHERED,
|
||||
OXIDIZED
|
||||
}
|
||||
|
||||
public CopperGolemEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(CHEST_INTERACTION, "none");
|
||||
propertyManager.add(HAS_FLOWER, false);
|
||||
propertyManager.add(OXIDIZATION_LEVEL, "unoxidized");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull InteractiveTag testMobInteraction(@NonNull Hand hand, @NonNull GeyserItemStack itemInHand) {
|
||||
if (itemInHand.isEmpty() && !getMainHandItem().isEmpty()) {
|
||||
@@ -95,29 +118,29 @@ public class CopperGolemEntity extends GolemEntity {
|
||||
super.setSaddle(stack);
|
||||
|
||||
// Equipment on Java, entity property on bedrock
|
||||
propertyManager.add(HAS_FLOWER, stack.is(Items.POPPY));
|
||||
HAS_FLOWER_PROPERTY.apply(propertyManager, stack.is(Items.POPPY));
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public void setWeatheringState(EntityMetadata<WeatheringCopperState, ? extends MetadataType<WeatheringCopperState>> metadata) {
|
||||
WeatheringCopperState state = metadata.getValue();
|
||||
propertyManager.add(OXIDIZATION_LEVEL, switch (state) {
|
||||
case UNAFFECTED -> "unoxidized";
|
||||
case EXPOSED -> "exposed";
|
||||
case WEATHERED -> "weathered";
|
||||
case OXIDIZED -> "oxidized";
|
||||
OXIDATION_LEVEL_STATE_ENUM_PROPERTY.apply(propertyManager, switch (state) {
|
||||
case UNAFFECTED -> OxidationLevelState.UNOXIDIZED;
|
||||
case EXPOSED -> OxidationLevelState.EXPOSED;
|
||||
case WEATHERED -> OxidationLevelState.WEATHERED;
|
||||
case OXIDIZED -> OxidationLevelState.OXIDIZED;
|
||||
});
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public void setGolemState(EntityMetadata<CopperGolemState, ? extends MetadataType<CopperGolemState>> metadata) {
|
||||
CopperGolemState state = metadata.getValue();
|
||||
propertyManager.add(CHEST_INTERACTION, switch (state) {
|
||||
case IDLE -> "none";
|
||||
case GETTING_ITEM -> "take";
|
||||
case GETTING_NO_ITEM -> "take_fail";
|
||||
case DROPPING_ITEM -> "put";
|
||||
case DROPPING_NO_ITEM -> "put_fail";
|
||||
CHEST_INTERACTION_PROPERTY.apply(propertyManager, switch (state) {
|
||||
case IDLE -> ChestInteractionState.NONE;
|
||||
case GETTING_ITEM -> ChestInteractionState.TAKE;
|
||||
case GETTING_NO_ITEM -> ChestInteractionState.TAKE_FAIL;
|
||||
case DROPPING_ITEM -> ChestInteractionState.PUT;
|
||||
case DROPPING_NO_ITEM -> ChestInteractionState.PUT_FAIL;
|
||||
});
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ package org.geysermc.geyser.entity.type.living.animal;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
@@ -39,6 +41,13 @@ import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ArmadilloEntity extends AnimalEntity {
|
||||
|
||||
public static final EnumProperty<State> STATE_PROPERTY = new EnumProperty<>(
|
||||
IdentifierImpl.of("armadillo_state"),
|
||||
State.class,
|
||||
State.UNROLLED
|
||||
);
|
||||
|
||||
private ArmadilloState armadilloState = ArmadilloState.IDLE;
|
||||
|
||||
public ArmadilloEntity(GeyserSession session, int entityId, long geyserId, UUID uuid,
|
||||
@@ -50,10 +59,10 @@ public class ArmadilloEntity extends AnimalEntity {
|
||||
armadilloState = entityMetadata.getValue();
|
||||
|
||||
switch (armadilloState) {
|
||||
case IDLE -> propertyManager.add("minecraft:armadillo_state", "unrolled");
|
||||
case ROLLING -> propertyManager.add("minecraft:armadillo_state", "rolled_up");
|
||||
case SCARED -> propertyManager.add("minecraft:armadillo_state", "rolled_up_relaxing");
|
||||
case UNROLLING -> propertyManager.add("minecraft:armadillo_state", "rolled_up_unrolling");
|
||||
case IDLE -> STATE_PROPERTY.apply(propertyManager, State.UNROLLED);
|
||||
case ROLLING -> STATE_PROPERTY.apply(propertyManager, State.ROLLED_UP);
|
||||
case SCARED -> STATE_PROPERTY.apply(propertyManager, State.ROLLED_UP_RELAXING);
|
||||
case UNROLLING -> STATE_PROPERTY.apply(propertyManager, State.ROLLED_UP_UNROLLING);
|
||||
}
|
||||
|
||||
updateBedrockEntityProperties();
|
||||
@@ -62,13 +71,13 @@ public class ArmadilloEntity extends AnimalEntity {
|
||||
public void onPeeking() {
|
||||
// Technically we should wait if not currently scared
|
||||
if (armadilloState == ArmadilloState.SCARED) {
|
||||
propertyManager.add("minecraft:armadillo_state", "rolled_up_peeking");
|
||||
STATE_PROPERTY.apply(propertyManager, State.ROLLED_UP_PEEKING);
|
||||
updateBedrockEntityProperties();
|
||||
|
||||
// Needed for consecutive peeks
|
||||
session.scheduleInEventLoop(() -> {
|
||||
if (armadilloState == ArmadilloState.SCARED) {
|
||||
propertyManager.add("minecraft:armadillo_state", "rolled_up_relaxing");
|
||||
STATE_PROPERTY.apply(propertyManager, State.ROLLED_UP_RELAXING);
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
}, 250, TimeUnit.MILLISECONDS);
|
||||
@@ -80,4 +89,12 @@ public class ArmadilloEntity extends AnimalEntity {
|
||||
protected Tag<Item> getFoodTag() {
|
||||
return ItemTag.ARMADILLO_FOOD;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
UNROLLED,
|
||||
ROLLED_UP,
|
||||
ROLLED_UP_PEEKING,
|
||||
ROLLED_UP_RELAXING,
|
||||
ROLLED_UP_UNROLLING
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
@@ -43,6 +45,11 @@ import java.util.UUID;
|
||||
|
||||
public class BeeEntity extends AnimalEntity {
|
||||
|
||||
public static final BooleanProperty NECTAR_PROPERTY = new BooleanProperty(
|
||||
IdentifierImpl.of("has_nectar"),
|
||||
false
|
||||
);
|
||||
|
||||
public BeeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
@@ -60,7 +67,7 @@ public class BeeEntity extends AnimalEntity {
|
||||
// If the bee has stung
|
||||
dirtyMetadata.put(EntityDataTypes.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0);
|
||||
// If the bee has nectar or not
|
||||
propertyManager.add("minecraft:has_nectar", (xd & 0x08) == 0x08);
|
||||
NECTAR_PROPERTY.apply(propertyManager, (xd & 0x08) == 0x08);
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,13 @@ import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.BooleanProperty;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.entity.vehicle.HappyGhastVehicleComponent;
|
||||
import org.geysermc.geyser.entity.vehicle.VehicleComponent;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
@@ -61,6 +63,11 @@ public class HappyGhastEntity extends AnimalEntity implements ClientVehicle {
|
||||
public static final float[] X_OFFSETS = {0.0F, -1.7F, 0.0F, 1.7F};
|
||||
public static final float[] Z_OFFSETS = {1.7F, 0.0F, -1.7F, 0.0F};
|
||||
|
||||
public static final BooleanProperty CAN_MOVE_PROPERTY = new BooleanProperty(
|
||||
IdentifierImpl.of("can_move"),
|
||||
true
|
||||
);
|
||||
|
||||
private final HappyGhastVehicleComponent vehicleComponent = new HappyGhastVehicleComponent(this, 0.0f);
|
||||
private boolean staysStill;
|
||||
|
||||
@@ -80,8 +87,6 @@ public class HappyGhastEntity extends AnimalEntity implements ClientVehicle {
|
||||
|
||||
setFlag(EntityFlag.WASD_AIR_CONTROLLED, true);
|
||||
setFlag(EntityFlag.DOES_SERVER_AUTH_ONLY_DISMOUNT, true);
|
||||
|
||||
propertyManager.add("minecraft:can_move", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +116,7 @@ public class HappyGhastEntity extends AnimalEntity implements ClientVehicle {
|
||||
|
||||
public void setStaysStill(BooleanEntityMetadata entityMetadata) {
|
||||
staysStill = entityMetadata.getPrimitiveValue();
|
||||
propertyManager.add("minecraft:can_move", !entityMetadata.getPrimitiveValue());
|
||||
CAN_MOVE_PROPERTY.apply(propertyManager, !entityMetadata.getPrimitiveValue());
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,19 +26,24 @@
|
||||
package org.geysermc.geyser.entity.type.living.animal.farm;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.VanillaEntityProperties;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.VariantHolder;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.RegistryCache;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class TemperatureVariantAnimal extends AnimalEntity implements VariantHolder<TemperatureVariantAnimal.BuiltInVariant> {
|
||||
|
||||
public static final EnumProperty<BuiltInVariant> TEMPERATE_VARIANT_PROPERTY = new EnumProperty<>(
|
||||
IdentifierImpl.of("climate_variant"),
|
||||
BuiltInVariant.class,
|
||||
BuiltInVariant.TEMPERATE
|
||||
);
|
||||
|
||||
public static final RegistryCache.RegistryReader<BuiltInVariant> VARIANT_READER = VariantHolder.reader(BuiltInVariant.class, BuiltInVariant.TEMPERATE);
|
||||
|
||||
public TemperatureVariantAnimal(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition,
|
||||
@@ -46,25 +51,15 @@ public abstract class TemperatureVariantAnimal extends AnimalEntity implements V
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, "temperate");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBedrockVariant(BuiltInVariant variant) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, variant.toBedrock());
|
||||
TEMPERATE_VARIANT_PROPERTY.apply(propertyManager, variant);
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public enum BuiltInVariant implements VariantHolder.BuiltIn {
|
||||
COLD,
|
||||
TEMPERATE,
|
||||
WARM;
|
||||
|
||||
public String toBedrock() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
WARM,
|
||||
COLD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,11 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.StringEnumProperty;
|
||||
import org.geysermc.geyser.entity.type.living.animal.VariantIntHolder;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.enchantment.EnchantmentComponent;
|
||||
@@ -56,9 +57,25 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponen
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WolfEntity extends TameableEntity implements VariantIntHolder {
|
||||
|
||||
public static final StringEnumProperty SOUND_VARIANT = new StringEnumProperty(
|
||||
IdentifierImpl.of("sound_variant"),
|
||||
List.of(
|
||||
"default",
|
||||
"big",
|
||||
"cute",
|
||||
"grumpy",
|
||||
"mad",
|
||||
"puglin",
|
||||
"sad"
|
||||
),
|
||||
null
|
||||
);
|
||||
|
||||
private byte collarColor = 14; // Red - default
|
||||
private HolderSet repairableItems = null;
|
||||
private boolean isCurseOfBinding = false;
|
||||
@@ -67,12 +84,6 @@ public class WolfEntity extends TameableEntity implements VariantIntHolder {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add("minecraft:sound_variant", "default");
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTameableFlags(ByteEntityMetadata entityMetadata) {
|
||||
super.setTameableFlags(entityMetadata);
|
||||
|
||||
@@ -30,9 +30,11 @@ import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.properties.type.EnumProperty;
|
||||
import org.geysermc.geyser.entity.properties.type.IntProperty;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataType;
|
||||
@@ -41,8 +43,23 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreakingEntity extends MonsterEntity {
|
||||
public static final String CREAKING_STATE = "minecraft:creaking_state";
|
||||
public static final String CREAKING_SWAYING_TICKS = "minecraft:creaking_swaying_ticks";
|
||||
|
||||
public static final EnumProperty<CreakingState> STATE_PROPERTY = new EnumProperty<>(
|
||||
IdentifierImpl.of("creaking_state"),
|
||||
CreakingState.class,
|
||||
CreakingState.NEUTRAL
|
||||
);
|
||||
|
||||
// also, the creaking seems to have this minecraft:creaking_swaying_ticks thingy
|
||||
// which i guess is responsible for some animation?
|
||||
// it's sent over the network, all 6 "stages" 50ms in between of each other.
|
||||
// no clue what it's used for tbh, so i'm not gonna bother implementing it
|
||||
// - chris
|
||||
// update: this still holds true, even a refactor later :(
|
||||
public static final IntProperty SWAYING_TICKS_PROPERTY = new IntProperty(
|
||||
IdentifierImpl.of("creaking_swaying_ticks"),
|
||||
6, 0, 0
|
||||
);
|
||||
|
||||
private Vector3i homePosition;
|
||||
|
||||
@@ -56,33 +73,21 @@ public class CreakingEntity extends MonsterEntity {
|
||||
setFlag(EntityFlag.FIRE_IMMUNE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSpawnData(AddEntityPacket addEntityPacket) {
|
||||
propertyManager.add(CREAKING_STATE, "neutral");
|
||||
// also, the creaking seems to have this minecraft:creaking_swaying_ticks thingy
|
||||
// which i guess is responsible for some animation?
|
||||
// it's sent over the network, all 6 "stages" 50ms in between of each other.
|
||||
// no clue what it's used for tbh, so i'm not gonna bother implementing it
|
||||
// - chris
|
||||
propertyManager.add(CREAKING_SWAYING_TICKS, 0);
|
||||
propertyManager.applyIntProperties(addEntityPacket.getProperties().getIntProperties());
|
||||
}
|
||||
|
||||
public void setCanMove(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
|
||||
setFlag(EntityFlag.BODY_ROTATION_BLOCKED, !booleanEntityMetadata.getValue());
|
||||
propertyManager.add(CREAKING_STATE, booleanEntityMetadata.getValue() ? "hostile_unobserved" : "hostile_observed");
|
||||
STATE_PROPERTY.apply(propertyManager, booleanEntityMetadata.getValue() ? CreakingState.HOSTILE_UNOBSERVED : CreakingState.HOSTILE_OBSERVED);
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public void setActive(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
|
||||
if (!booleanEntityMetadata.getValue()) {
|
||||
propertyManager.add(CREAKING_STATE, "neutral");
|
||||
STATE_PROPERTY.apply(propertyManager, CreakingState.NEUTRAL);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsTearingDown(EntityMetadata<Boolean,? extends MetadataType<Boolean>> booleanEntityMetadata) {
|
||||
if (booleanEntityMetadata.getValue()) {
|
||||
propertyManager.add(CREAKING_STATE, "crumbling");
|
||||
STATE_PROPERTY.apply(propertyManager, CreakingState.CRUMBLING);
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
}
|
||||
@@ -115,4 +120,12 @@ public class CreakingEntity extends MonsterEntity {
|
||||
session.sendUpstreamPacket(levelEventGenericPacket);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CreakingState {
|
||||
NEUTRAL,
|
||||
HOSTILE_OBSERVED,
|
||||
HOSTILE_UNOBSERVED,
|
||||
TWITCHING,
|
||||
CRUMBLING
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.impl;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
import org.geysermc.geyser.util.MinecraftKey;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record IdentifierImpl(Key identifier) implements Identifier {
|
||||
|
||||
public static IdentifierImpl of(String namespace, String value) throws IllegalArgumentException {
|
||||
Objects.requireNonNull(namespace, "namespace cannot be null!");
|
||||
Objects.requireNonNull(value, "value cannot be null!");
|
||||
try {
|
||||
return new IdentifierImpl(MinecraftKey.key(namespace, value));
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalArgumentException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME using the identifier interface from the API breaks tests
|
||||
public static IdentifierImpl of(String value) {
|
||||
return of(Identifier.DEFAULT_NAMESPACE, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String namespace() {
|
||||
return identifier.namespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String path() {
|
||||
return identifier.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier.toString();
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,10 @@ import org.geysermc.geyser.api.pack.UrlPackCodec;
|
||||
import org.geysermc.geyser.api.pack.option.PriorityOption;
|
||||
import org.geysermc.geyser.api.pack.option.SubpackOption;
|
||||
import org.geysermc.geyser.api.pack.option.UrlFallbackOption;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraFade;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraPosition;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
@@ -74,6 +76,9 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||
// misc
|
||||
providers.put(Identifier.class, args -> IdentifierImpl.of((String) args[0], (String) args[1]));
|
||||
|
||||
// commands
|
||||
providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0]));
|
||||
|
||||
|
||||
@@ -1855,6 +1855,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
// It does *not* mean we can dictate the break speed server-sided :(
|
||||
startGamePacket.setServerAuthoritativeBlockBreaking(true);
|
||||
|
||||
if (playerEntity.getPropertyManager() != null) {
|
||||
startGamePacket.setPlayerPropertyData(playerEntity.getPropertyManager().toNbtMap("minecraft:player"));
|
||||
}
|
||||
|
||||
startGamePacket.setServerId("");
|
||||
startGamePacket.setWorldId("");
|
||||
startGamePacket.setScenarioId("");
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
package org.geysermc.geyser.util;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.api.util.Identifier;
|
||||
import org.geysermc.geyser.impl.IdentifierImpl;
|
||||
import org.intellij.lang.annotations.Subst;
|
||||
|
||||
public final class MinecraftKey {
|
||||
@@ -36,4 +39,25 @@ public final class MinecraftKey {
|
||||
public static Key key(@Subst("empty") String s) {
|
||||
return Key.key(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* To prevent constant warnings from invalid regex.
|
||||
*/
|
||||
public static Key key(@Subst("empty") String namespace, @Subst("empty") String value) {
|
||||
return Key.key(namespace, value);
|
||||
}
|
||||
|
||||
public static @Nullable Key identifierToKey(@Nullable Identifier identifier) {
|
||||
if (identifier == null) {
|
||||
return null;
|
||||
}
|
||||
return identifier instanceof IdentifierImpl impl ? impl.identifier() : key(identifier.namespace(), identifier.path());
|
||||
}
|
||||
|
||||
public static @Nullable Identifier keyToIdentifier(@Nullable Key key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return new IdentifierImpl(key);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user