From f555dc0a928bf5d2e46859b726d688542158408f Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 31 Jan 2024 11:21:06 +0100 Subject: [PATCH] Feature: Camera/Input locking API (#4332) Adds API methods to control player cameras - including fancy transitions, color fades, or simple input locks. --- api/build.gradle.kts | 1 + .../geyser/api/bedrock/camera/CameraData.java | 148 +++++++++++ .../api/bedrock/camera/CameraEaseType.java | 77 ++++++ .../geyser/api/bedrock/camera/CameraFade.java | 94 +++++++ .../api/bedrock/camera/CameraPerspective.java | 48 ++++ .../api/bedrock/camera/CameraPosition.java | 150 +++++++++++ .../api/bedrock/camera/CameraShake.java | 3 + .../api/connection/GeyserConnection.java | 39 ++- .../geyser/api/entity/EntityData.java | 84 +++++++ .../type/player/GeyserPlayerEntity.java | 8 + .../geyser/entity/GeyserEntityData.java | 99 ++++++++ .../entity/type/player/PlayerEntity.java | 17 +- .../geyser/impl/camera/CameraDefinitions.java | 96 +++++++ .../geyser/impl/camera/GeyserCameraData.java | 235 ++++++++++++++++++ .../geyser/impl/camera/GeyserCameraFade.java | 104 ++++++++ .../impl/camera/GeyserCameraPosition.java | 131 ++++++++++ .../geyser/item/GeyserCustomItemData.java | 2 +- .../geyser/item/GeyserCustomItemOptions.java | 2 +- .../item/GeyserNonVanillaCustomItemData.java | 38 +-- .../block/GeyserCustomBlockComponents.java | 4 +- .../level/block/GeyserCustomBlockData.java | 6 +- .../level/block/GeyserCustomBlockState.java | 2 +- .../level/block/GeyserGeometryComponent.java | 8 +- .../level/block/GeyserJavaBlockState.java | 4 +- .../level/block/GeyserMaterialInstance.java | 4 +- .../GeyserNonVanillaCustomBlockData.java | 42 ++-- .../geyser/network/LoggingPacketHandler.java | 10 + .../loader/ProviderRegistryLoader.java | 26 +- .../mappings/versions/MappingsReader_v1.java | 34 ++- .../CustomBlockRegistryPopulator.java | 16 +- .../geyser/registry/type/CustomSkull.java | 22 +- .../geyser/session/GeyserSession.java | 108 ++++---- .../protocol/java/JavaLoginTranslator.java | 2 +- .../geysermc/geyser/util/DimensionUtils.java | 4 +- gradle.properties | 2 +- gradle/libs.versions.toml | 2 + 36 files changed, 1523 insertions(+), 149 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java create mode 100644 core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java create mode 100644 core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java create mode 100644 core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java create mode 100644 core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java create mode 100644 core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java diff --git a/api/build.gradle.kts b/api/build.gradle.kts index c0ed242b6..bd54a9ce4 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -4,4 +4,5 @@ plugins { dependencies { api(libs.base.api) + api(libs.math) } \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java new file mode 100644 index 000000000..2f715fa1e --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraData.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.connection.GeyserConnection; + +import java.util.Set; +import java.util.UUID; + +/** + * This interface holds all the methods that relate to a client's camera. + * Can be accessed through {@link GeyserConnection#camera()}. + */ +public interface CameraData { + + /** + * Sends a camera fade instruction to the client. + * If an existing camera fade is already in progress, the current fade will be prolonged. + * Can be built using {@link CameraFade.Builder}. + * To stop a fade early, use {@link #clearCameraInstructions()}. + * + * @param fade the camera fade instruction to send + */ + void sendCameraFade(@NonNull CameraFade fade); + + /** + * Sends a camera position instruction to the client. + * If an existing camera movement is already in progress, + * the final camera position will be the one of the latest instruction, and + * the (optional) camera fade will be added on top of the existing fade. + * Can be built using {@link CameraPosition.Builder}. + * To stop reset the camera position/stop ongoing instructions, use {@link #clearCameraInstructions()}. + * + * @param position the camera position instruction to send + */ + void sendCameraPosition(@NonNull CameraPosition position); + + /** + * Stops all sent camera instructions (fades, movements, and perspective locks). + * This will not stop any camera shakes/input locks/fog effects, use the respective methods for those. + */ + void clearCameraInstructions(); + + /** + * Forces a {@link CameraPerspective} on the client. This will prevent the client + * from changing their camera perspective until it is unlocked via {@link #clearCameraInstructions()}. + *

+ * Note: You cannot force a client into a free camera perspective with this method. + * To do that, send a {@link CameraPosition} via {@link #sendCameraPosition(CameraPosition)} - it requires a set position + * instead of being relative to the player. + * + * @param perspective the {@link CameraPerspective} to force + */ + void forceCameraPerspective(@NonNull CameraPerspective perspective); + + /** + * Gets the client's current {@link CameraPerspective}, if one is currently forced. + * This will return {@code null} if the client is not currently forced into a perspective. + * If a perspective is forced, the client will not be able to change their camera perspective until it is unlocked. + * + * @return the forced perspective, or {@code null} if none is forced + */ + @Nullable CameraPerspective forcedCameraPerspective(); + + /** + * Shakes the client's camera. + *

+ * If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity + * will be layered on top of the existing intensity, with their own distinct durations.
+ * If the existing shake type is different and the new intensity/duration are not positive, the existing shake only + * switches to the new type. Otherwise, the existing shake is completely overridden. + * + * @param intensity the intensity of the shake. The client has a maximum total intensity of 4. + * @param duration the time in seconds that the shake will occur for + * @param type the type of shake + */ + void shakeCamera(float intensity, float duration, @NonNull CameraShake type); + + /** + * Stops all camera shakes of any type. + */ + void stopCameraShake(); + + /** + * Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client. + *

+ * Fog IDs can be found here + * + * @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent. + */ + void sendFog(String... fogNameSpaces); + + /** + * Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client. + * + * @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed. + */ + void removeFog(String... fogNameSpaces); + + /** + * Returns an immutable copy of all fog affects currently applied to this client. + */ + @NonNull + Set fogEffects(); + + /** + * (Un)locks the client's camera, so that they cannot look around. + * To ensure the camera is only unlocked when all locks are released, you must supply + * a UUID when using method, and use the same UUID to unlock the camera. + * + * @param lock whether to lock the camera + * @param owner the owner of the lock, represented with a UUID + * @return if the camera is locked after this method call + */ + boolean lockCamera(boolean lock, @NonNull UUID owner); + + /** + * Returns whether the client's camera is locked. + * + * @return whether the camera is currently locked + */ + boolean isCameraLocked(); +} \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java new file mode 100644 index 000000000..64c313ec1 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraEaseType.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +/** + * These are all the easing types that can be used when sending a {@link CameraPosition} instruction. + * When using these, the client won't teleport to the new camera position, but instead transition to it. + *

+ * See https://easings.net/ for more information. + */ +public enum CameraEaseType { + LINEAR("linear"), + SPRING("spring"), + EASE_IN_SINE("in_sine"), + EASE_OUT_SINE("out_sine"), + EASE_IN_OUT_SINE("in_out_sine"), + EASE_IN_QUAD("in_quad"), + EASE_OUT_QUAD("out_quad"), + EASE_IN_OUT_QUAD("in_out_quad"), + EASE_IN_CUBIC("in_cubic"), + EASE_OUT_CUBIC("out_cubic"), + EASE_IN_OUT_CUBIC("in_out_cubic"), + EASE_IN_QUART("in_quart"), + EASE_OUT_QUART("out_quart"), + EASE_IN_OUT_QUART("in_out_quart"), + EASE_IN_QUINT("in_quint"), + EASE_OUT_QUINT("out_quint"), + EASE_IN_OUT_QUINT("in_out_quint"), + EASE_IN_EXPO("in_expo"), + EASE_OUT_EXPO("out_expo"), + EASE_IN_OUT_EXPO("in_out_expo"), + EASE_IN_CIRC("in_circ"), + EASE_OUT_CIRC("out_circ"), + EASE_IN_OUT_CIRC("in_out_circ"), + EASE_IN_BACK("in_back"), + EASE_OUT_BACK("out_back"), + EASE_IN_OUT_BACK("in_out_back"), + EASE_IN_ELASTIC("in_elastic"), + EASE_OUT_ELASTIC("out_elastic"), + EASE_IN_OUT_ELASTIC("in_out_elastic"), + EASE_IN_BOUNCE("in_bounce"), + EASE_OUT_BOUNCE("out_bounce"), + EASE_IN_OUT_BOUNCE("in_out_bounce"); + + private final String id; + + CameraEaseType(String id) { + this.id = id; + } + + public String id() { + return this.id; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java new file mode 100644 index 000000000..38baa73bb --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraFade.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; +import org.geysermc.geyser.api.GeyserApi; + +import java.awt.Color; + +/** + * Represents a coloured fade overlay on the camera. + *

+ * Can be sent with {@link CameraData#sendCameraFade(CameraFade)}, or with a {@link CameraPosition} instruction. + */ +public interface CameraFade { + + /** + * Gets the color overlay of the camera. + * Bedrock uses an RGB color system. + * + * @return the color of the fade + */ + @NonNull Color color(); + + /** + * Gets the seconds it takes to fade in. + * All fade times combined must take at least 0.5 seconds, and at most 30 seconds. + * + * @return the seconds it takes to fade in + */ + float fadeInSeconds(); + + /** + * Gets the seconds the overlay is held. + * All fade times combined must take at least 0.5 seconds, and at most 30 seconds. + * + * @return the seconds the overlay is held + */ + float fadeHoldSeconds(); + + /** + * Gets the seconds it takes to fade out. + * All fade times combined must take at least 0.5 seconds, and at most 30 seconds. + * + * @return the seconds it takes to fade out + */ + float fadeOutSeconds(); + + /** + * Creates a Builder for CameraFade + * + * @return a CameraFade Builder + */ + static CameraFade.Builder builder() { + return GeyserApi.api().provider(CameraFade.Builder.class); + } + + interface Builder { + + Builder color(@NonNull Color color); + + Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds); + + Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds); + + Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds); + + CameraFade build(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java new file mode 100644 index 000000000..4167f2a34 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPerspective.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +/** + * Represents a camera perspective that a player's camera can take. + * All perspectives except for {@link #FREE} are locked to the player's head, + * and are therefore relative to the player's position and rotation. + */ +public enum CameraPerspective { + FIRST_PERSON("minecraft:first_person"), + FREE("minecraft:free"), + THIRD_PERSON("minecraft:third_person"), + THIRD_PERSON_FRONT("minecraft:third_person_front"); + + private final String id; + + CameraPerspective(String id) { + this.id = id; + } + + public String id() { + return this.id; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java new file mode 100644 index 000000000..6d42d499e --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraPosition.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; +import org.cloudburstmc.math.vector.Vector3f; +import org.geysermc.geyser.api.GeyserApi; + +/** + * This interface represents a camera position instruction. Can be built with the {@link #builder()}. + *

+ * Any camera position instruction pins the client camera to a specific position and rotation. + * You can set {@link CameraEaseType} to ensure a smooth transition that will last {@link #easeSeconds()} seconds. + * A {@link CameraFade} can also be sent, which will transition the player to a coloured transition during the transition. + *

+ * Use {@link CameraData#sendCameraPosition(CameraPosition)} to send such an instruction to any connection. + */ +public interface CameraPosition { + + /** + * Gets the camera's position. + * + * @return camera position vector + */ + @NonNull Vector3f position(); + + /** + * Gets the {@link CameraEaseType} of the camera. + * If not set, there is no easing. + * + * @return camera ease type + */ + @Nullable CameraEaseType easeType(); + + /** + * Gets the {@link CameraFade} to be sent along the camera position instruction. + * If set, they will run at once. + * + * @return camera fade, or null if not present + */ + @Nullable CameraFade cameraFade(); + + /** + * Gets the easing duration of the camera, in seconds. + * Is only used if a {@link CameraEaseType} is set. + * + * @return camera easing duration in seconds + */ + float easeSeconds(); + + /** + * Gets the x-axis rotation of the camera. + * To prevent the camera from being upside down, Bedrock limits the range to -90 to 90. + * Will be overridden if {@link #facingPosition()} is set. + * + * @return camera x-axis rotation + */ + @IntRange(from = -90, to = 90) int rotationX(); + + /** + * Gets the y-axis rotation of the camera. + * Will be overridden if {@link #facingPosition()} is set. + * + * @return camera y-axis rotation + */ + int rotationY(); + + /** + * Gets the position that the camera is facing. + * Can be used instead of manually setting rotation values. + *

+ * If set, the rotation values set via {@link #rotationX()} and {@link #rotationY()} will be ignored. + * + * @return Camera's facing position + */ + @Nullable Vector3f facingPosition(); + + /** + * Controls whether player effects, such as night vision or blindness, should be rendered on the camera. + * Defaults to false. + * + * @return whether player effects should be rendered + */ + boolean renderPlayerEffects(); + + /** + * Controls whether the player position should be used for directional audio. + * If false, the camera position will be used instead. + * + * @return whether the players position should be used for directional audio + */ + boolean playerPositionForAudio(); + + /** + * Creates a Builder for CameraPosition + * + * @return a CameraPosition Builder + */ + static CameraPosition.Builder builder() { + return GeyserApi.api().provider(CameraPosition.Builder.class); + } + + interface Builder { + + Builder cameraFade(@Nullable CameraFade cameraFade); + + Builder renderPlayerEffects(boolean renderPlayerEffects); + + Builder playerPositionForAudio(boolean playerPositionForAudio); + + Builder easeType(@Nullable CameraEaseType easeType); + + Builder easeSeconds(float easeSeconds); + + Builder position(@NonNull Vector3f position); + + Builder rotationX(@IntRange(from = -90, to = 90) int rotationX); + + Builder rotationY(int rotationY); + + Builder facingPosition(@Nullable Vector3f facingPosition); + + CameraPosition build(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java index 67b56b1f5..304969edb 100644 --- a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.api.bedrock.camera; +/** + * Represents a camera shake instruction. Can be sent in {@link CameraData#shakeCamera(float, float, CameraShake)} + */ public enum CameraShake { POSITIONAL, ROTATIONAL diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index 7094812a0..9bda4f903 100644 --- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -29,8 +29,10 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.connection.Connection; +import org.geysermc.geyser.api.bedrock.camera.CameraData; import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; @@ -41,10 +43,29 @@ import java.util.concurrent.CompletableFuture; * Represents a player connection used in Geyser. */ public interface GeyserConnection extends Connection, CommandSource { + + /** + * Exposes the {@link CameraData} for this connection. + * It allows you to send fogs, camera shakes, force camera perspectives, and more. + * + * @return the CameraData for this connection. + */ + @NonNull CameraData camera(); + + /** + * Exposes the {@link EntityData} for this connection. + * It allows you to get entities by their Java entity ID, show emotes, and get the player entity. + * + * @return the EntityData for this connection. + */ + @NonNull EntityData entities(); + /** * @param javaId the Java entity ID to look up. * @return a {@link GeyserEntity} if present in this connection's entity tracker. + * @deprecated Use {@link EntityData#entityByJavaId(int)} instead */ + @Deprecated @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId); @@ -53,11 +74,14 @@ public interface GeyserConnection extends Connection, CommandSource { * * @param emoter the player entity emoting. * @param emoteId the emote ID to send to this client. + * @deprecated use {@link EntityData#showEmote(GeyserPlayerEntity, String)} instead */ + @Deprecated void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId); /** - * Shakes the client's camera.

+ * Shakes the client's camera. + *

* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity * will be layered on top of the existing intensity, with their own distinct durations.
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only @@ -66,12 +90,18 @@ public interface GeyserConnection extends Connection, CommandSource { * @param intensity the intensity of the shake. The client has a maximum total intensity of 4. * @param duration the time in seconds that the shake will occur for * @param type the type of shake + * + * @deprecated Use {@link CameraData#shakeCamera(float, float, CameraShake)} instead. */ + @Deprecated void shakeCamera(float intensity, float duration, @NonNull CameraShake type); /** * Stops all camera shake of any type. + * + * @deprecated Use {@link CameraData#stopCameraShake()} instead. */ + @Deprecated void stopCameraShake(); /** @@ -80,19 +110,26 @@ public interface GeyserConnection extends Connection, CommandSource { * Fog IDs can be found here * * @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent. + * @deprecated Use {@link CameraData#sendFog(String...)} instead. */ + @Deprecated void sendFog(String... fogNameSpaces); /** * Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client. * * @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed. + * @deprecated Use {@link CameraData#removeFog(String...)} instead. */ + @Deprecated void removeFog(String... fogNameSpaces); /** * Returns an immutable copy of all fog affects currently applied to this client. + * + * @deprecated Use {@link CameraData#fogEffects()} instead. */ + @Deprecated @NonNull Set fogEffects(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java new file mode 100644 index 000000000..90b3fc821 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/entity/EntityData.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.entity; + +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.type.GeyserEntity; +import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * This class holds all the methods that relate to entities. + * Can be accessed through {@link GeyserConnection#entities()}. + */ +public interface EntityData { + + /** + * Returns a {@link GeyserEntity} to e.g. make them play an emote. + * + * @param javaId the Java entity ID to look up + * @return a {@link GeyserEntity} if present in this connection's entity tracker + */ + @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId); + + /** + * Displays a player entity as emoting to this client. + * + * @param emoter the player entity emoting + * @param emoteId the emote ID to send to this client + */ + void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId); + + /** + * Gets the {@link GeyserPlayerEntity} of this connection. + * + * @return the {@link GeyserPlayerEntity} of this connection + */ + @NonNull GeyserPlayerEntity playerEntity(); + + /** + * (Un)locks the client's movement inputs, so that they cannot move. + * To ensure that movement is only unlocked when all locks are released, you must supply + * a UUID with this method, and use the same UUID to unlock the camera. + * + * @param lock whether to lock the movement + * @param owner the owner of the lock + * @return if the movement is locked after this method call + */ + boolean lockMovement(boolean lock, @NonNull UUID owner); + + /** + * Returns whether the client's movement is currently locked. + * + * @return whether the movement is locked + */ + boolean isMovementLocked(); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java index da2e28609..bba4dbf3e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java +++ b/api/src/main/java/org/geysermc/geyser/api/entity/type/player/GeyserPlayerEntity.java @@ -25,7 +25,15 @@ package org.geysermc.geyser.api.entity.type.player; +import org.cloudburstmc.math.vector.Vector3f; import org.geysermc.geyser.api.entity.type.GeyserEntity; public interface GeyserPlayerEntity extends GeyserEntity { + + /** + * Gets the position of the player. + * + * @return the position of the player. + */ + Vector3f position(); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java new file mode 100644 index 000000000..c9ef7a2dd --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.entity; + +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.protocol.bedrock.packet.EmotePacket; +import org.geysermc.geyser.api.entity.EntityData; +import org.geysermc.geyser.api.entity.type.GeyserEntity; +import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; +import org.geysermc.geyser.entity.type.Entity; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class GeyserEntityData implements EntityData { + + private final GeyserSession session; + + private final Set movementLockOwners = new HashSet<>(); + + public GeyserEntityData(GeyserSession session) { + this.session = session; + } + + @Override + public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) { + CompletableFuture future = new CompletableFuture<>(); + session.ensureInEventLoop(() -> future.complete(session.getEntityCache().getEntityByJavaId(javaId))); + return future; + } + + @Override + public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) { + Objects.requireNonNull(emoter, "emoter must not be null!"); + Entity entity = (Entity) emoter; + if (entity.getSession() != session) { + throw new IllegalStateException("Given entity must be from this session!"); + } + + EmotePacket packet = new EmotePacket(); + packet.setRuntimeEntityId(entity.getGeyserId()); + packet.setXuid(""); + packet.setPlatformId(""); // BDS sends empty + packet.setEmoteId(emoteId); + session.sendUpstreamPacket(packet); + } + + @Override + public @NonNull GeyserPlayerEntity playerEntity() { + return session.getPlayerEntity(); + } + + @Override + public boolean lockMovement(boolean lock, @NonNull UUID owner) { + Objects.requireNonNull(owner, "owner must not be null!"); + if (lock) { + movementLockOwners.add(owner); + } else { + movementLockOwners.remove(owner); + } + + session.lockInputs(session.camera().isCameraLocked(), isMovementLocked()); + return isMovementLocked(); + } + + @Override + public boolean isMovementLocked() { + return !movementLockOwners.isEmpty(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 7504db1b1..37e408502 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -39,12 +39,20 @@ import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; -import org.cloudburstmc.protocol.bedrock.data.*; +import org.cloudburstmc.protocol.bedrock.data.Ability; +import org.cloudburstmc.protocol.bedrock.data.AbilityLayer; +import org.cloudburstmc.protocol.bedrock.data.AttributeData; +import org.cloudburstmc.protocol.bedrock.data.GameType; +import org.cloudburstmc.protocol.bedrock.data.PlayerPermission; import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; -import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket; +import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; @@ -433,4 +441,9 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { public UUID getTabListUuid() { return getUuid(); } + + @Override + public Vector3f position() { + return this.position.clone(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java new file mode 100644 index 000000000..80564bdf3 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/CameraDefinitions.java @@ -0,0 +1,96 @@ +/* + * 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.impl.camera; + +import org.cloudburstmc.protocol.bedrock.data.camera.CameraAudioListener; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraPreset; +import org.cloudburstmc.protocol.common.DefinitionRegistry; +import org.cloudburstmc.protocol.common.NamedDefinition; +import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry; +import org.cloudburstmc.protocol.common.util.OptionalBoolean; +import org.geysermc.geyser.api.bedrock.camera.CameraPerspective; + +import java.util.List; + +public class CameraDefinitions { + + public static final DefinitionRegistry CAMERA_DEFINITIONS; + + public static final List CAMERA_PRESETS; + + static { + CAMERA_PRESETS = List.of( + new CameraPreset(CameraPerspective.FIRST_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), + new CameraPreset(CameraPerspective.FREE.id(), "", null, null, null, null, OptionalBoolean.empty()), + new CameraPreset(CameraPerspective.THIRD_PERSON.id(), "", null, null, null, null, OptionalBoolean.empty()), + new CameraPreset(CameraPerspective.THIRD_PERSON_FRONT.id(), "", null, null, null, null, OptionalBoolean.empty()), + new CameraPreset("geyser:free_audio", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(false)), + new CameraPreset("geyser:free_effects", "minecraft:free", null, null, null, CameraAudioListener.CAMERA, OptionalBoolean.of(true)), + new CameraPreset("geyser:free_audio_effects", "minecraft:free", null, null, null, CameraAudioListener.PLAYER, OptionalBoolean.of(true))); + + SimpleDefinitionRegistry.Builder builder = SimpleDefinitionRegistry.builder(); + for (int i = 0; i < CAMERA_PRESETS.size(); i++) { + builder.add(CameraDefinition.of(CAMERA_PRESETS.get(i).getIdentifier(), i)); + } + CAMERA_DEFINITIONS = builder.build(); + } + + public static NamedDefinition getById(int id) { + return CAMERA_DEFINITIONS.getDefinition(id); + } + + public static NamedDefinition getByFunctionality(boolean audio, boolean effects) { + if (!audio && !effects) { + return getById(1); // FREE + } + if (audio) { + if (effects) { + return getById(6); // FREE_AUDIO_EFFECTS + } else { + return getById(4); // FREE_AUDIO + } + } else { + return getById(5); // FREE_EFFECTS + } + } + + public record CameraDefinition(String identifier, int runtimeId) implements NamedDefinition { + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public int getRuntimeId() { + return runtimeId; + } + + public static CameraDefinition of(String identifier, int runtimeId) { + return new CameraDefinition(identifier, runtimeId); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java new file mode 100644 index 000000000..28c881eba --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java @@ -0,0 +1,235 @@ +/* + * 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.impl.camera; + +import lombok.Getter; +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.CameraShakeAction; +import org.cloudburstmc.protocol.bedrock.data.CameraShakeType; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraEase; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraFadeInstruction; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraSetInstruction; +import org.cloudburstmc.protocol.bedrock.packet.CameraInstructionPacket; +import org.cloudburstmc.protocol.bedrock.packet.CameraShakePacket; +import org.cloudburstmc.protocol.bedrock.packet.PlayerFogPacket; +import org.geysermc.geyser.api.bedrock.camera.CameraEaseType; +import org.geysermc.geyser.api.bedrock.camera.CameraData; +import org.geysermc.geyser.api.bedrock.camera.CameraFade; +import org.geysermc.geyser.api.bedrock.camera.CameraPerspective; +import org.geysermc.geyser.api.bedrock.camera.CameraPosition; +import org.geysermc.geyser.api.bedrock.camera.CameraShake; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +public class GeyserCameraData implements CameraData { + + private final GeyserSession session; + + @Getter + private CameraPerspective cameraPerspective; + + /** + * All fog effects that are currently applied to the client. + */ + private final Set appliedFog = new HashSet<>(); + + private final Set cameraLockOwners = new HashSet<>(); + + public GeyserCameraData(GeyserSession session) { + this.session = session; + } + + @Override + public void clearCameraInstructions() { + this.cameraPerspective = null; + CameraInstructionPacket packet = new CameraInstructionPacket(); + packet.setClear(true); + session.sendUpstreamPacket(packet); + } + + @Override + public void forceCameraPerspective(@NonNull CameraPerspective perspective) { + Objects.requireNonNull(perspective, "perspective cannot be null!"); + + if (perspective == cameraPerspective) { + return; // nothing to do + } + + this.cameraPerspective = perspective; + CameraInstructionPacket packet = new CameraInstructionPacket(); + CameraSetInstruction setInstruction = new CameraSetInstruction(); + + if (perspective == CameraPerspective.FREE) { + throw new IllegalArgumentException("Cannot force a stationary camera (CameraPerspective#FREE) on the player!" + + "Send a CameraPosition with an exact position instead"); + } + + setInstruction.setPreset(CameraDefinitions.getById(perspective.ordinal())); + packet.setSetInstruction(setInstruction); + session.sendUpstreamPacket(packet); + } + + @Override + public @Nullable CameraPerspective forcedCameraPerspective() { + return this.cameraPerspective; + } + + @Override + public void sendCameraFade(@NonNull CameraFade fade) { + Objects.requireNonNull(fade, "fade cannot be null!"); + CameraFadeInstruction fadeInstruction = new CameraFadeInstruction(); + fadeInstruction.setColor(fade.color()); + fadeInstruction.setTimeData( + new CameraFadeInstruction.TimeData( + fade.fadeInSeconds(), + fade.fadeHoldSeconds(), + fade.fadeOutSeconds() + ) + ); + + CameraInstructionPacket packet = new CameraInstructionPacket(); + packet.setFadeInstruction(fadeInstruction); + session.sendUpstreamPacket(packet); + } + + @Override + public void sendCameraPosition(@NonNull CameraPosition movement) { + Objects.requireNonNull(movement, "movement cannot be null!"); + this.cameraPerspective = CameraPerspective.FREE; // Movements only work with the free preset + CameraSetInstruction setInstruction = new CameraSetInstruction(); + + CameraEaseType easeType = movement.easeType(); + if (easeType != null) { + setInstruction.setEase(new CameraSetInstruction.EaseData( + CameraEase.fromName(easeType.id()), + movement.easeSeconds() + )); + } + + Vector3f facingPosition = movement.facingPosition(); + if (facingPosition != null) { + setInstruction.setFacing(facingPosition); + } + + setInstruction.setPos(movement.position()); + setInstruction.setRot(Vector2f.from(movement.rotationX(), movement.rotationY())); + setInstruction.setPreset(CameraDefinitions.getByFunctionality(movement.playerPositionForAudio(), movement.renderPlayerEffects())); + + CameraInstructionPacket packet = new CameraInstructionPacket(); + packet.setSetInstruction(setInstruction); + + // If present, also send the fade + CameraFade fade = movement.cameraFade(); + if (fade != null) { + CameraFadeInstruction fadeInstruction = new CameraFadeInstruction(); + fadeInstruction.setColor(fade.color()); + fadeInstruction.setTimeData( + new CameraFadeInstruction.TimeData( + fade.fadeInSeconds(), + fade.fadeHoldSeconds(), + fade.fadeOutSeconds() + ) + ); + packet.setFadeInstruction(fadeInstruction); + } + session.sendUpstreamPacket(packet); + } + + @Override + public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) { + Objects.requireNonNull(type, "camera shake type must be non null!"); + CameraShakePacket packet = new CameraShakePacket(); + packet.setIntensity(intensity); + packet.setDuration(duration); + packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL); + packet.setShakeAction(CameraShakeAction.ADD); + session.sendUpstreamPacket(packet); + } + + @Override + public void stopCameraShake() { + CameraShakePacket packet = new CameraShakePacket(); + // CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null + packet.setShakeType(CameraShakeType.POSITIONAL); + packet.setShakeAction(CameraShakeAction.STOP); + session.sendUpstreamPacket(packet); + } + + @Override + public void sendFog(String... fogNameSpaces) { + Collections.addAll(this.appliedFog, fogNameSpaces); + + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.appliedFog); + session.sendUpstreamPacket(packet); + } + + @Override + public void removeFog(String... fogNameSpaces) { + if (fogNameSpaces.length == 0) { + this.appliedFog.clear(); + } else { + for (String id : fogNameSpaces) { + this.appliedFog.remove(id); + } + } + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.appliedFog); + session.sendUpstreamPacket(packet); + } + + @Override + public @NonNull Set fogEffects() { + // Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME) + return Set.copyOf(this.appliedFog); + } + + @Override + public boolean lockCamera(boolean lock, @NonNull UUID owner) { + Objects.requireNonNull(owner, "owner cannot be null!"); + if (lock) { + this.cameraLockOwners.add(owner); + } else { + this.cameraLockOwners.remove(owner); + } + + session.lockInputs(isCameraLocked(), session.entities().isMovementLocked()); + return isCameraLocked(); + } + + @Override + public boolean isCameraLocked() { + return !this.cameraLockOwners.isEmpty(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java new file mode 100644 index 000000000..648e70c81 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraFade.java @@ -0,0 +1,104 @@ +/* + * 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.impl.camera; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; +import org.geysermc.geyser.api.bedrock.camera.CameraFade; + +import java.awt.Color; +import java.util.Objects; + +public record GeyserCameraFade( + Color color, + float fadeInSeconds, + float fadeHoldSeconds, + float fadeOutSeconds + +) implements CameraFade { + public static class Builder implements CameraFade.Builder { + private Color color; + private float fadeInSeconds; + private float fadeHoldSeconds; + private float fadeOutSeconds; + + @Override + public CameraFade.Builder color(@NonNull Color color) { + Objects.requireNonNull(color, "color cannot be null!"); + this.color = color; + return this; + } + + @Override + public CameraFade.Builder fadeInSeconds(@IntRange(from = 0, to = 10) float fadeInSeconds) { + if (fadeInSeconds < 0f) { + throw new IllegalArgumentException("Fade in seconds must be at least 0 seconds"); + } + + if (fadeInSeconds > 10f) { + throw new IllegalArgumentException("Fade in seconds must be at most 10 seconds"); + } + this.fadeInSeconds = fadeInSeconds; + return this; + } + + @Override + public CameraFade.Builder fadeHoldSeconds(@IntRange(from = 0, to = 10) float fadeHoldSeconds) { + if (fadeHoldSeconds < 0f) { + throw new IllegalArgumentException("Fade hold seconds must be at least 0 seconds"); + } + + if (fadeHoldSeconds > 10f) { + throw new IllegalArgumentException("Fade hold seconds must be at most 10 seconds"); + } + this.fadeHoldSeconds = fadeHoldSeconds; + return this; + } + + @Override + public CameraFade.Builder fadeOutSeconds(@IntRange(from = 0, to = 10) float fadeOutSeconds) { + if (fadeOutSeconds < 0f) { + throw new IllegalArgumentException("Fade out seconds must be at least 0 seconds"); + } + + if (fadeOutSeconds > 10f) { + throw new IllegalArgumentException("Fade out seconds must be at most 10 seconds"); + } + this.fadeOutSeconds = fadeOutSeconds; + return this; + } + + @Override + public CameraFade build() { + Objects.requireNonNull(color, "color must be non null!"); + if (fadeInSeconds + fadeHoldSeconds + fadeOutSeconds < 0.5f) { + throw new IllegalArgumentException("Total fade time (in, hold, out) must be at least 0.5 seconds"); + } + + return new GeyserCameraFade(color, fadeInSeconds, fadeHoldSeconds, fadeOutSeconds); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java new file mode 100644 index 000000000..13b382465 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraPosition.java @@ -0,0 +1,131 @@ +/* + * 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.impl.camera; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; +import org.cloudburstmc.math.vector.Vector3f; +import org.geysermc.geyser.api.bedrock.camera.CameraEaseType; +import org.geysermc.geyser.api.bedrock.camera.CameraFade; +import org.geysermc.geyser.api.bedrock.camera.CameraPosition; + +import java.util.Objects; + +public record GeyserCameraPosition(CameraFade cameraFade, + boolean renderPlayerEffects, + boolean playerPositionForAudio, + CameraEaseType easeType, + float easeSeconds, + Vector3f position, + @IntRange(from = -90, to = 90) int rotationX, + int rotationY, + Vector3f facingPosition +) implements CameraPosition { + + public static class Builder implements CameraPosition.Builder { + private CameraFade cameraFade; + private boolean renderPlayerEffects; + private boolean playerPositionForAudio; + private CameraEaseType easeType; + private float easeSeconds; + private Vector3f position; + private @IntRange(from = -90, to = 90) int rotationX; + private int rotationY; + private Vector3f facingPosition; + + @Override + public CameraPosition.Builder cameraFade(@Nullable CameraFade cameraFade) { + this.cameraFade = cameraFade; + return this; + } + + @Override + public CameraPosition.Builder renderPlayerEffects(boolean renderPlayerEffects) { + this.renderPlayerEffects = renderPlayerEffects; + return this; + } + + @Override + public CameraPosition.Builder playerPositionForAudio(boolean playerPositionForAudio) { + this.playerPositionForAudio = playerPositionForAudio; + return this; + } + + @Override + public CameraPosition.Builder easeType(@Nullable CameraEaseType easeType) { + this.easeType = easeType; + return this; + } + + @Override + public CameraPosition.Builder easeSeconds(float easeSeconds) { + if (easeSeconds < 0) { + throw new IllegalArgumentException("Camera ease duration cannot be negative!"); + } + this.easeSeconds = easeSeconds; + return this; + } + + @Override + public CameraPosition.Builder position(@NonNull Vector3f position) { + Objects.requireNonNull(position, "camera position cannot be null!"); + this.position = position; + return this; + } + + @Override + public CameraPosition.Builder rotationX(int rotationX) { + if (rotationX < -90 || rotationX > 90) { + throw new IllegalArgumentException("x-axis rotation needs to be between -90 and 90 degrees."); + } + this.rotationX = rotationX; + return this; + } + + @Override + public CameraPosition.Builder rotationY(int rotationY) { + this.rotationY = rotationY; + return this; + } + + @Override + public CameraPosition.Builder facingPosition(@Nullable Vector3f facingPosition) { + this.facingPosition = facingPosition; + return this; + } + + @Override + public CameraPosition build() { + if (easeSeconds > 0 && easeType == null) { + throw new IllegalArgumentException("Camera ease type cannot be null if ease duration is greater than 0"); + } + + Objects.requireNonNull(position, "camera position must be non null!"); + return new GeyserCameraPosition(cameraFade, renderPlayerEffects, playerPositionForAudio, easeType, easeSeconds, position, rotationX, rotationY, facingPosition); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java index c86c370bb..2906a9be3 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemData.java @@ -115,7 +115,7 @@ public class GeyserCustomItemData implements CustomItemData { return tags; } - public static class CustomItemDataBuilder implements Builder { + public static class Builder implements CustomItemData.Builder { protected String name = null; protected CustomItemOptions customItemOptions = null; diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java index 1434c49d9..966035743 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserCustomItemOptions.java @@ -37,7 +37,7 @@ public record GeyserCustomItemOptions(TriState unbreakable, boolean defaultItem) implements CustomItemOptions { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - public static class CustomItemOptionsBuilder implements CustomItemOptions.Builder { + public static class Builder implements CustomItemOptions.Builder { private TriState unbreakable = TriState.NOT_SET; private OptionalInt customModelData = OptionalInt.empty(); private OptionalInt damagePredicate = OptionalInt.empty(); diff --git a/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java index 52b0ee7d0..bb4e60589 100644 --- a/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java +++ b/core/src/main/java/org/geysermc/geyser/item/GeyserNonVanillaCustomItemData.java @@ -59,7 +59,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i private final boolean canAlwaysEat; private final boolean isChargeable; - public GeyserNonVanillaCustomItemData(NonVanillaCustomItemDataBuilder builder) { + public GeyserNonVanillaCustomItemData(Builder builder) { super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand, builder.displayHandheld, builder.textureSize, builder.renderOffsets, builder.tags); @@ -168,7 +168,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i return isChargeable; } - public static class NonVanillaCustomItemDataBuilder extends GeyserCustomItemData.CustomItemDataBuilder implements NonVanillaCustomItemData.Builder { + public static class Builder extends GeyserCustomItemData.Builder implements NonVanillaCustomItemData.Builder { private String identifier = null; private int javaId = -1; @@ -197,49 +197,49 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i private boolean chargeable = false; @Override - public NonVanillaCustomItemData.Builder name(@NonNull String name) { - return (NonVanillaCustomItemData.Builder) super.name(name); + public Builder name(@NonNull String name) { + return (Builder) super.name(name); } @Override - public NonVanillaCustomItemData.Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) { + public Builder customItemOptions(@NonNull CustomItemOptions customItemOptions) { //Do nothing, as that value won't be read return this; } @Override - public NonVanillaCustomItemData.Builder allowOffhand(boolean allowOffhand) { - return (NonVanillaCustomItemData.Builder) super.allowOffhand(allowOffhand); + public Builder allowOffhand(boolean allowOffhand) { + return (Builder) super.allowOffhand(allowOffhand); } @Override - public NonVanillaCustomItemData.Builder displayHandheld(boolean displayHandheld) { - return (NonVanillaCustomItemData.Builder) super.displayHandheld(displayHandheld); + public Builder displayHandheld(boolean displayHandheld) { + return (Builder) super.displayHandheld(displayHandheld); } @Override - public NonVanillaCustomItemData.Builder displayName(@NonNull String displayName) { - return (NonVanillaCustomItemData.Builder) super.displayName(displayName); + public Builder displayName(@NonNull String displayName) { + return (Builder) super.displayName(displayName); } @Override - public NonVanillaCustomItemData.Builder icon(@NonNull String icon) { - return (NonVanillaCustomItemData.Builder) super.icon(icon); + public Builder icon(@NonNull String icon) { + return (Builder) super.icon(icon); } @Override - public NonVanillaCustomItemData.Builder textureSize(int textureSize) { - return (NonVanillaCustomItemData.Builder) super.textureSize(textureSize); + public Builder textureSize(int textureSize) { + return (Builder) super.textureSize(textureSize); } @Override - public NonVanillaCustomItemData.Builder renderOffsets(CustomRenderOffsets renderOffsets) { - return (NonVanillaCustomItemData.Builder) super.renderOffsets(renderOffsets); + public Builder renderOffsets(CustomRenderOffsets renderOffsets) { + return (Builder) super.renderOffsets(renderOffsets); } @Override - public NonVanillaCustomItemData.Builder tags(@Nullable Set tags) { - return (NonVanillaCustomItemData.Builder) super.tags(tags); + public Builder tags(@Nullable Set tags) { + return (Builder) super.tags(tags); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java index e401567e2..1fa863d55 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java @@ -62,7 +62,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { boolean placeAir; Set tags; - private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) { + private GeyserCustomBlockComponents(Builder builder) { this.selectionBox = builder.selectionBox; this.collisionBox = builder.collisionBox; this.displayName = builder.displayName; @@ -157,7 +157,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return tags; } - public static class CustomBlockComponentsBuilder implements Builder { + public static class Builder implements CustomBlockComponents.Builder { protected BoxComponent selectionBox; protected BoxComponent collisionBox; protected String displayName; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java index dd58ebcb7..d717e33c5 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java @@ -54,7 +54,7 @@ public class GeyserCustomBlockData implements CustomBlockData { private final Map defaultProperties; - GeyserCustomBlockData(CustomBlockDataBuilder builder) { + GeyserCustomBlockData(Builder builder) { this.name = builder.name; if (name == null) { throw new IllegalStateException("Name must be set"); @@ -141,10 +141,10 @@ public class GeyserCustomBlockData implements CustomBlockData { @Override public CustomBlockState.@NonNull Builder blockStateBuilder() { - return new GeyserCustomBlockState.CustomBlockStateBuilder(this); + return new GeyserCustomBlockState.Builder(this); } - public static class CustomBlockDataBuilder implements Builder { + public static class Builder implements CustomBlockData.Builder { private String name; private boolean includedInCreativeInventory; private CreativeCategory creativeCategory; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockState.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockState.java index d147ffedc..b9a8fe5a2 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockState.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockState.java @@ -64,7 +64,7 @@ public class GeyserCustomBlockState implements CustomBlockState { } @RequiredArgsConstructor - public static class CustomBlockStateBuilder implements CustomBlockState.Builder { + public static class Builder implements CustomBlockState.Builder { private final CustomBlockData blockData; private final Object2ObjectMap properties = new Object2ObjectOpenHashMap<>(); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserGeometryComponent.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserGeometryComponent.java index c23fc87a2..1e2d13ae6 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserGeometryComponent.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserGeometryComponent.java @@ -37,7 +37,7 @@ public class GeyserGeometryComponent implements GeometryComponent { private final String identifier; private final Map boneVisibility; - GeyserGeometryComponent(GeometryComponentBuilder builder) { + GeyserGeometryComponent(Builder builder) { this.identifier = builder.identifier; this.boneVisibility = builder.boneVisibility; } @@ -52,18 +52,18 @@ public class GeyserGeometryComponent implements GeometryComponent { return boneVisibility; } - public static class GeometryComponentBuilder implements Builder { + public static class Builder implements GeometryComponent.Builder { private String identifier; private Map boneVisibility; @Override - public GeometryComponent.Builder identifier(@NonNull String identifier) { + public Builder identifier(@NonNull String identifier) { this.identifier = identifier; return this; } @Override - public GeometryComponent.Builder boneVisibility(@Nullable Map boneVisibility) { + public Builder boneVisibility(@Nullable Map boneVisibility) { this.boneVisibility = boneVisibility; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java index 5604a543e..8028a4355 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserJavaBlockState.java @@ -18,7 +18,7 @@ public class GeyserJavaBlockState implements JavaBlockState { String pistonBehavior; boolean hasBlockEntity; - private GeyserJavaBlockState(JavaBlockStateBuilder builder) { + private GeyserJavaBlockState(Builder builder) { this.identifier = builder.identifier; this.javaId = builder.javaId; this.stateGroupId = builder.stateGroupId; @@ -81,7 +81,7 @@ public class GeyserJavaBlockState implements JavaBlockState { return hasBlockEntity; } - public static class JavaBlockStateBuilder implements Builder { + public static class Builder implements JavaBlockState.Builder { private String identifier; private int javaId; private int stateGroupId; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserMaterialInstance.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserMaterialInstance.java index 527b8fe79..acc16bf58 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserMaterialInstance.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserMaterialInstance.java @@ -36,7 +36,7 @@ public class GeyserMaterialInstance implements MaterialInstance { private final boolean faceDimming; private final boolean ambientOcclusion; - GeyserMaterialInstance(MaterialInstanceBuilder builder) { + GeyserMaterialInstance(Builder builder) { this.texture = builder.texture; this.renderMethod = builder.renderMethod; this.faceDimming = builder.faceDimming; @@ -63,7 +63,7 @@ public class GeyserMaterialInstance implements MaterialInstance { return ambientOcclusion; } - public static class MaterialInstanceBuilder implements Builder { + public static class Builder implements MaterialInstance.Builder { private String texture; private String renderMethod; private boolean faceDimming; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserNonVanillaCustomBlockData.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserNonVanillaCustomBlockData.java index 8b5056a6f..1f557d0b0 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserNonVanillaCustomBlockData.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserNonVanillaCustomBlockData.java @@ -37,7 +37,7 @@ import java.util.List; public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData implements NonVanillaCustomBlockData { private final String namespace; - GeyserNonVanillaCustomBlockData(NonVanillaCustomBlockDataBuilder builder) { + GeyserNonVanillaCustomBlockData(Builder builder) { super(builder); this.namespace = builder.namespace; @@ -56,58 +56,58 @@ public class GeyserNonVanillaCustomBlockData extends GeyserCustomBlockData imple return this.namespace; } - public static class NonVanillaCustomBlockDataBuilder extends CustomBlockDataBuilder implements NonVanillaCustomBlockData.Builder { + public static class Builder extends GeyserCustomBlockData.Builder implements NonVanillaCustomBlockData.Builder { private String namespace; @Override - public NonVanillaCustomBlockDataBuilder namespace(@NonNull String namespace) { + public Builder namespace(@NonNull String namespace) { this.namespace = namespace; return this; } @Override - public NonVanillaCustomBlockDataBuilder name(@NonNull String name) { - return (NonVanillaCustomBlockDataBuilder) super.name(name); + public Builder name(@NonNull String name) { + return (Builder) super.name(name); } @Override - public NonVanillaCustomBlockDataBuilder includedInCreativeInventory(boolean includedInCreativeInventory) { - return (NonVanillaCustomBlockDataBuilder) super.includedInCreativeInventory(includedInCreativeInventory); + public Builder includedInCreativeInventory(boolean includedInCreativeInventory) { + return (Builder) super.includedInCreativeInventory(includedInCreativeInventory); } @Override - public NonVanillaCustomBlockDataBuilder creativeCategory(@Nullable CreativeCategory creativeCategories) { - return (NonVanillaCustomBlockDataBuilder) super.creativeCategory(creativeCategories); + public Builder creativeCategory(@Nullable CreativeCategory creativeCategories) { + return (Builder) super.creativeCategory(creativeCategories); } @Override - public NonVanillaCustomBlockDataBuilder creativeGroup(@Nullable String creativeGroup) { - return (NonVanillaCustomBlockDataBuilder) super.creativeGroup(creativeGroup); + public Builder creativeGroup(@Nullable String creativeGroup) { + return (Builder) super.creativeGroup(creativeGroup); } @Override - public NonVanillaCustomBlockDataBuilder components(@NonNull CustomBlockComponents components) { - return (NonVanillaCustomBlockDataBuilder) super.components(components); + public Builder components(@NonNull CustomBlockComponents components) { + return (Builder) super.components(components); } @Override - public NonVanillaCustomBlockDataBuilder booleanProperty(@NonNull String propertyName) { - return (NonVanillaCustomBlockDataBuilder) super.booleanProperty(propertyName); + public Builder booleanProperty(@NonNull String propertyName) { + return (Builder) super.booleanProperty(propertyName); } @Override - public NonVanillaCustomBlockDataBuilder intProperty(@NonNull String propertyName, List values) { - return (NonVanillaCustomBlockDataBuilder) super.intProperty(propertyName, values); + public Builder intProperty(@NonNull String propertyName, List values) { + return (Builder) super.intProperty(propertyName, values); } @Override - public NonVanillaCustomBlockDataBuilder stringProperty(@NonNull String propertyName, List values) { - return (NonVanillaCustomBlockDataBuilder) super.stringProperty(propertyName, values); + public Builder stringProperty(@NonNull String propertyName, List values) { + return (Builder) super.stringProperty(propertyName, values); } @Override - public NonVanillaCustomBlockDataBuilder permutations(@NonNull List permutations) { - return (NonVanillaCustomBlockDataBuilder) super.permutations(permutations); + public Builder permutations(@NonNull List permutations) { + return (Builder) super.permutations(permutations); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index cf794261b..0cfcc3d46 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -100,6 +100,16 @@ public class LoggingPacketHandler implements BedrockPacketHandler { return defaultHandler(packet); } + @Override + public PacketSignal handle(CameraPresetsPacket packet) { + return defaultHandler(packet); + } + + @Override + public PacketSignal handle(CameraInstructionPacket packet) { + return defaultHandler(packet); + } + @Override public PacketSignal handle(CommandBlockUpdatePacket packet) { return defaultHandler(packet); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 13d7a4d77..4b159438c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.registry.loader; +import org.geysermc.geyser.api.bedrock.camera.CameraFade; +import org.geysermc.geyser.api.bedrock.camera.CameraPosition; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; @@ -38,6 +40,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; +import org.geysermc.geyser.impl.camera.GeyserCameraFade; +import org.geysermc.geyser.impl.camera.GeyserCameraPosition; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.item.GeyserCustomItemData; @@ -65,20 +69,24 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov // misc providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); - providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()); - providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder()); - providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.JavaBlockStateBuilder()); - providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.NonVanillaCustomBlockDataBuilder()); - providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.MaterialInstanceBuilder()); - providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.GeometryComponentBuilder()); + providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.Builder()); + providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.Builder()); + providers.put(JavaBlockState.Builder.class, args -> new GeyserJavaBlockState.Builder()); + providers.put(NonVanillaCustomBlockData.Builder.class, args -> new GeyserNonVanillaCustomBlockData.Builder()); + providers.put(MaterialInstance.Builder.class, args -> new GeyserMaterialInstance.Builder()); + providers.put(GeometryComponent.Builder.class, args -> new GeyserGeometryComponent.Builder()); providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0])); providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0])); // items - providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder()); - providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder()); - providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder()); + providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.Builder()); + providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.Builder()); + providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.Builder()); + + // cameras + providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); + providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder()); return providers; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index 1a498c3fa..0bc55c7b1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -35,17 +35,22 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockPermutation; import org.geysermc.geyser.api.block.custom.CustomBlockState; -import org.geysermc.geyser.api.block.custom.component.*; +import org.geysermc.geyser.api.block.custom.component.BoxComponent; +import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; +import org.geysermc.geyser.api.block.custom.component.GeometryComponent; +import org.geysermc.geyser.api.block.custom.component.MaterialInstance; +import org.geysermc.geyser.api.block.custom.component.PlacementConditions; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.BlockFilterType; import org.geysermc.geyser.api.block.custom.component.PlacementConditions.Face; +import org.geysermc.geyser.api.block.custom.component.TransformationComponent; import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.util.CreativeCategory; import org.geysermc.geyser.item.exception.InvalidCustomMappingsFileException; -import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder; -import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder; -import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder; -import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder; +import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; +import org.geysermc.geyser.level.block.GeyserCustomBlockData; +import org.geysermc.geyser.level.block.GeyserGeometryComponent; +import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.level.physics.BoundingBox; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.mappings.util.CustomBlockComponentsMapping; @@ -57,7 +62,14 @@ import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.MathUtils; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -248,7 +260,7 @@ public class MappingsReader_v1 extends MappingsReader { boolean onlyOverrideStates = node.has("only_override_states") && node.get("only_override_states").asBoolean(); // Create the data for the overall block - CustomBlockData.Builder customBlockDataBuilder = new CustomBlockDataBuilder() + CustomBlockData.Builder customBlockDataBuilder = new GeyserCustomBlockData.Builder() .name(name) .includedInCreativeInventory(includedInCreativeInventory) .creativeCategory(creativeCategory) @@ -360,7 +372,7 @@ public class MappingsReader_v1 extends MappingsReader { int id = BlockRegistries.JAVA_IDENTIFIER_TO_ID.getOrDefault(stateKey, -1); BoxComponent boxComponent = createBoxComponent(id); BoxComponent extendedBoxComponent = createExtendedBoxComponent(id); - CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder() + CustomBlockComponents.Builder builder = new GeyserCustomBlockComponents.Builder() .collisionBox(boxComponent) .selectionBox(boxComponent); @@ -392,12 +404,12 @@ public class MappingsReader_v1 extends MappingsReader { if (node.has("geometry")) { if (node.get("geometry").isTextual()) { - builder.geometry(new GeometryComponentBuilder() + builder.geometry(new GeyserGeometryComponent.Builder() .identifier(node.get("geometry").asText()) .build()); } else { JsonNode geometry = node.get("geometry"); - GeometryComponentBuilder geometryBuilder = new GeometryComponentBuilder(); + GeometryComponent.Builder geometryBuilder = new GeyserGeometryComponent.Builder(); if (geometry.has("identifier")) { geometryBuilder.identifier(geometry.get("identifier").asText()); } @@ -654,7 +666,7 @@ public class MappingsReader_v1 extends MappingsReader { ambientOcclusion = node.get("ambient_occlusion").asBoolean(); } - return new MaterialInstanceBuilder() + return new GeyserMaterialInstance.Builder() .texture(texture) .renderMethod(renderMethod) .faceDimming(faceDimming) diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java index e4f45ac85..2a9948f45 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java @@ -23,11 +23,11 @@ import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty; import org.geysermc.geyser.api.block.custom.property.PropertyType; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; +import org.geysermc.geyser.level.block.GeyserCustomBlockData; import org.geysermc.geyser.level.block.GeyserCustomBlockState; -import org.geysermc.geyser.level.block.GeyserCustomBlockComponents.CustomBlockComponentsBuilder; -import org.geysermc.geyser.level.block.GeyserCustomBlockData.CustomBlockDataBuilder; -import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder; -import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder; +import org.geysermc.geyser.level.block.GeyserGeometryComponent; +import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.CustomSkull; @@ -479,20 +479,20 @@ public class CustomBlockRegistryPopulator { } private static CustomBlockData createExtendedCollisionBlock(BoxComponent boxComponent, int extendedCollisionBlock) { - return new CustomBlockDataBuilder() + return new GeyserCustomBlockData.Builder() .name("extended_collision_" + extendedCollisionBlock) .components( - new CustomBlockComponentsBuilder() + new GeyserCustomBlockComponents.Builder() .collisionBox(boxComponent) .selectionBox(BoxComponent.emptyBox()) - .materialInstance("*", new MaterialInstanceBuilder() + .materialInstance("*", new GeyserMaterialInstance.Builder() .texture("glass") .renderMethod("alpha_test") .faceDimming(false) .ambientOcclusion(false) .build()) .lightDampening(0) - .geometry(new GeometryComponentBuilder() + .geometry(new GeyserGeometryComponent.Builder() .identifier("geometry.invisible") .build()) .build()) diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java index 5fe8a0edf..9e85f30ff 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java @@ -34,8 +34,8 @@ import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; import org.geysermc.geyser.api.block.custom.component.TransformationComponent; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; -import org.geysermc.geyser.level.block.GeyserGeometryComponent.GeometryComponentBuilder; -import org.geysermc.geyser.level.block.GeyserMaterialInstance.MaterialInstanceBuilder; +import org.geysermc.geyser.level.block.GeyserGeometryComponent; +import org.geysermc.geyser.level.block.GeyserMaterialInstance; import java.util.ArrayList; import java.util.List; @@ -65,9 +65,9 @@ public class CustomSkull { public CustomSkull(String skinHash) { this.skinHash = skinHash; - CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + CustomBlockComponents components = new GeyserCustomBlockComponents.Builder() .destructibleByMining(1.5f) - .materialInstance("*", new MaterialInstanceBuilder() + .materialInstance("*", new GeyserMaterialInstance.Builder() .texture("geyser." + skinHash + "_player_skin") .renderMethod("alpha_test") .faceDimming(true) @@ -82,7 +82,7 @@ public class CustomSkull { addFloorPermutations(permutations); addWallPermutations(permutations); - customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder() + customBlockData = new GeyserCustomBlockData.Builder() .name("player_skull_" + skinHash) .components(components) .intProperty(BITS_A_PROPERTY, IntStream.rangeClosed(0, 6).boxed().toList()) // This gives us exactly 21 block states @@ -114,8 +114,8 @@ public class CustomSkull { } private void addDefaultPermutation(List permutations) { - CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() - .geometry(new GeometryComponentBuilder() + CustomBlockComponents components = new GeyserCustomBlockComponents.Builder() + .geometry(new GeyserGeometryComponent.Builder() .identifier("geometry.geyser.player_skull_hand") .build()) .transformation(new TransformationComponent(0, 180, 0)) @@ -131,10 +131,10 @@ public class CustomSkull { for (int quadrant = 0; quadrant < 4; quadrant++) { for (int i = 0; i < 4; i++) { int floorRotation = 4 * quadrant + i; - CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + CustomBlockComponents components = new GeyserCustomBlockComponents.Builder() .selectionBox(FLOOR_BOX) .collisionBox(FLOOR_BOX) - .geometry(new GeometryComponentBuilder() + .geometry(new GeyserGeometryComponent.Builder() .identifier("geometry.geyser.player_skull_floor_" + quadrantNames[i]) .build()) .transformation(new TransformationComponent(0, ROTATIONS[quadrant], 0)) @@ -150,10 +150,10 @@ public class CustomSkull { private void addWallPermutations(List permutations) { for (int i = 0; i < 4; i++) { - CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + CustomBlockComponents components = new GeyserCustomBlockComponents.Builder() .selectionBox(WALL_BOX) .collisionBox(WALL_BOX) - .geometry(new GeometryComponentBuilder() + .geometry(new GeyserGeometryComponent.Builder() .identifier("geometry.geyser.player_skull_wall") .build()) .transformation(new TransformationComponent(0, ROTATIONS[i], 0)) diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 5501e990e..d40b07939 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -101,8 +101,10 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.bedrock.camera.CameraData; import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent; @@ -110,10 +112,13 @@ import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.impl.camera.CameraDefinitions; +import org.geysermc.geyser.impl.camera.GeyserCameraData; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.GeyserEntityData; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -151,7 +156,16 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ScheduledFuture; @@ -550,11 +564,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private boolean waitingForStatistics = false; - /** - * All fog effects that are currently applied to the client. - */ - private final Set appliedFog = new HashSet<>(); - private final Set emotes; /** @@ -586,6 +595,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ private final Queue keepAliveCache = new ConcurrentLinkedQueue<>(); + private final GeyserCameraData cameraData; + + private final GeyserEntityData entityData; + private MinecraftProtocol protocol; public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) { @@ -607,6 +620,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.skullCache = new SkullCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); + this.cameraData = new GeyserCameraData(this); + this.entityData = new GeyserEntityData(this); this.worldBorder = new WorldBorder(this); @@ -667,6 +682,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get()); upstream.sendPacket(entityPacket); + CameraPresetsPacket cameraPresetsPacket = new CameraPresetsPacket(); + cameraPresetsPacket.getPresets().addAll(CameraDefinitions.CAMERA_PRESETS); + upstream.sendPacket(cameraPresetsPacket); + CreativeContentPacket creativePacket = new CreativeContentPacket(); creativePacket.setContents(this.itemMappings.getCreativeItems()); upstream.sendPacket(creativePacket); @@ -1182,12 +1201,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Set the mood if (shouldShowFog && !isInWorldBorderWarningArea) { isInWorldBorderWarningArea = true; - sendFog("minecraft:fog_crimson_forest"); + camera().sendFog("minecraft:fog_crimson_forest"); } } if (!shouldShowFog && isInWorldBorderWarningArea) { // Clear fog as we are outside the world border now - removeFog("minecraft:fog_crimson_forest"); + camera().removeFog("minecraft:fog_crimson_forest"); isInWorldBorderWarningArea = false; } @@ -1480,6 +1499,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private void startGame() { this.upstream.getCodecHelper().setItemDefinitions(this.itemMappings); this.upstream.getCodecHelper().setBlockDefinitions((DefinitionRegistry) this.blockMappings); //FIXME + this.upstream.getCodecHelper().setCameraPresetDefinitions(CameraDefinitions.CAMERA_DEFINITIONS); StartGamePacket startGamePacket = new StartGamePacket(); startGamePacket.setUniqueEntityId(playerEntity.getGeyserId()); @@ -1980,72 +2000,66 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public @NonNull CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId) { - CompletableFuture future = new CompletableFuture<>(); - ensureInEventLoop(() -> future.complete(this.entityCache.getEntityByJavaId(javaId))); - return future; + return entities().entityByJavaId(javaId); } @Override public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId) { - Entity entity = (Entity) emoter; - if (entity.getSession() != this) { - throw new IllegalStateException("Given entity must be from this session!"); + entities().showEmote(emoter, emoteId); + } + + public void lockInputs(boolean camera, boolean movement) { + UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket(); + final int cameraOffset = 1 << 1; + final int movementOffset = 1 << 2; + + int result = 0; + if (camera) { + result |= cameraOffset; + } + if (movement) { + result |= movementOffset; } - EmotePacket packet = new EmotePacket(); - packet.setRuntimeEntityId(entity.getGeyserId()); - packet.setXuid(""); - packet.setPlatformId(""); // BDS sends empty - packet.setEmoteId(emoteId); + packet.setLockComponentData(result); + packet.setServerPosition(this.playerEntity.getPosition()); + sendUpstreamPacket(packet); } + @Override + public @NonNull CameraData camera() { + return this.cameraData; + } + + @Override + public @NonNull EntityData entities() { + return this.entityData; + } + @Override public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) { - CameraShakePacket packet = new CameraShakePacket(); - packet.setIntensity(intensity); - packet.setDuration(duration); - packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL); - packet.setShakeAction(CameraShakeAction.ADD); - sendUpstreamPacket(packet); + this.cameraData.shakeCamera(intensity, duration, type); } @Override public void stopCameraShake() { - CameraShakePacket packet = new CameraShakePacket(); - // CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null - packet.setShakeType(CameraShakeType.POSITIONAL); - packet.setShakeAction(CameraShakeAction.STOP); - sendUpstreamPacket(packet); + this.cameraData.stopCameraShake(); } @Override public void sendFog(String... fogNameSpaces) { - Collections.addAll(this.appliedFog, fogNameSpaces); - - PlayerFogPacket packet = new PlayerFogPacket(); - packet.getFogStack().addAll(this.appliedFog); - sendUpstreamPacket(packet); + this.cameraData.sendFog(fogNameSpaces); } @Override public void removeFog(String... fogNameSpaces) { - if (fogNameSpaces.length == 0) { - this.appliedFog.clear(); - } else { - for (String id : fogNameSpaces) { - this.appliedFog.remove(id); - } - } - PlayerFogPacket packet = new PlayerFogPacket(); - packet.getFogStack().addAll(this.appliedFog); - sendUpstreamPacket(packet); + this.cameraData.removeFog(fogNameSpaces); } @Override public @NonNull Set fogEffects() { - // Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME) - return Set.copyOf(this.appliedFog); + return this.cameraData.fogEffects(); } public void addCommandEnum(String name, String enums) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 377f1d2cf..23c19e84f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -139,7 +139,7 @@ public class JavaLoginTranslator extends PacketTranslator