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