From ff05c986901e21640fba6cd5457afb0071a0b732 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 2 Jul 2023 17:00:46 -0400 Subject: [PATCH] Camera shake and fog effect api (#3931) --- .../api/bedrock/camera/CameraShake.java | 31 +++++++ .../api/connection/GeyserConnection.java | 45 +++++++++- build.gradle.kts | 2 +- .../geyser/level/BedrockDimension.java | 6 +- .../geyser/session/GeyserSession.java | 86 ++++++++++++------- .../protocol/java/JavaLoginTranslator.java | 2 +- .../geysermc/geyser/util/DimensionUtils.java | 16 ++-- gradle.properties | 6 -- 8 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java 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 new file mode 100644 index 000000000..18bafb428 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.bedrock.camera; + +public enum CameraShake { + POSITIONAL, + ROTATIONAL; +} 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 8d695bf02..7094812a0 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,10 +29,12 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.connection.Connection; +import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; +import java.util.Set; import java.util.concurrent.CompletableFuture; /** @@ -47,9 +49,50 @@ public interface GeyserConnection extends Connection, CommandSource { CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId); /** + * Displays a player entity as emoting to this client. * * @param emoter the player entity emoting. - * @param emoteId the emote ID to send to the client. + * @param emoteId the emote ID to send to this client. */ void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId); + + /** + * Shakes the client's camera.

+ * 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 shake of any type. + */ + void stopCameraShake(); + + /** + * Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client. + *

+ * 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(); } diff --git a/build.gradle.kts b/build.gradle.kts index c64667ad2..d8fe1c769 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { allprojects { group = "org.geysermc.geyser" - version = "2.1.1-SNAPSHOT" + version = "2.1.2-SNAPSHOT" description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers." tasks.withType { diff --git a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java index 78c6b2c6a..250c0f7a4 100644 --- a/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/BedrockDimension.java @@ -35,7 +35,7 @@ package org.geysermc.geyser.level; * @param doUpperHeightWarn whether to warn in the console if the Java dimension height exceeds Bedrock's. */ public record BedrockDimension(int minY, int height, boolean doUpperHeightWarn) { - public static BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); - public static BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); - public static BedrockDimension THE_END = new BedrockDimension(0, 256, true); + public static final BedrockDimension OVERWORLD = new BedrockDimension(-64, 384, true); + public static final BedrockDimension THE_NETHER = new BedrockDimension(0, 128, false); + public static final BedrockDimension THE_END = new BedrockDimension(0, 256, true); } 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 73dd253f3..dba4bd112 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -98,6 +98,7 @@ import org.cloudburstmc.protocol.common.util.OptionalBoolean; import org.geysermc.api.util.BedrockPlatform; import org.geysermc.api.util.InputMode; import org.geysermc.api.util.UiProfile; +import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; @@ -533,7 +534,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private boolean waitingForStatistics = false; - private final Set fogNameSpaces = new HashSet<>(); + /** + * All fog effects that are currently applied to the client. + */ + private final Set appliedFog = new HashSet<>(); private final Set emotes; @@ -1828,38 +1832,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } } - /** - * Send the following fog IDs, as well as the cached ones, to the client. - * - * Fog IDs can be found here: - * https://wiki.bedrock.dev/documentation/fog-ids.html - * - * @param fogNameSpaces the fog ids to add - */ - public void sendFog(String... fogNameSpaces) { - this.fogNameSpaces.addAll(Arrays.asList(fogNameSpaces)); - - PlayerFogPacket packet = new PlayerFogPacket(); - packet.getFogStack().addAll(this.fogNameSpaces); - sendUpstreamPacket(packet); - } - - /** - * Removes the following fog IDs from the client and the cache. - * - * @param fogNameSpaces the fog ids to remove - */ - public void removeFog(String... fogNameSpaces) { - if (fogNameSpaces.length == 0) { - this.fogNameSpaces.clear(); - } else { - this.fogNameSpaces.removeAll(Arrays.asList(fogNameSpaces)); - } - PlayerFogPacket packet = new PlayerFogPacket(); - packet.getFogStack().addAll(this.fogNameSpaces); - sendUpstreamPacket(packet); - } - public boolean canUseCommandBlocks() { return instabuild && opPermissionLevel >= 2; } @@ -1971,6 +1943,54 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { sendUpstreamPacket(packet); } + @Override + public void shakeCamera(float intensity, float duration, @NonNull CameraShake type) { + CameraShakePacket packet = new CameraShakePacket(); + packet.setIntensity(intensity); + packet.setDuration(duration); + packet.setShakeType(type == CameraShake.POSITIONAL ? CameraShakeType.POSITIONAL : CameraShakeType.ROTATIONAL); + packet.setShakeAction(CameraShakeAction.ADD); + sendUpstreamPacket(packet); + } + + @Override + public void stopCameraShake() { + CameraShakePacket packet = new CameraShakePacket(); + // CameraShakeAction.STOP removes all types regardless of the given type, but regardless it can't be null + packet.setShakeType(CameraShakeType.POSITIONAL); + packet.setShakeAction(CameraShakeAction.STOP); + sendUpstreamPacket(packet); + } + + @Override + public void sendFog(String... fogNameSpaces) { + Collections.addAll(this.appliedFog, fogNameSpaces); + + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.appliedFog); + sendUpstreamPacket(packet); + } + + @Override + public void removeFog(String... fogNameSpaces) { + if (fogNameSpaces.length == 0) { + this.appliedFog.clear(); + } else { + for (String id : fogNameSpaces) { + this.appliedFog.remove(id); + } + } + PlayerFogPacket packet = new PlayerFogPacket(); + packet.getFogStack().addAll(this.appliedFog); + sendUpstreamPacket(packet); + } + + @Override + public @NonNull Set fogEffects() { + // Use a copy so that sendFog/removeFog can be called while iterating the returned set (avoid CME) + return Set.copyOf(this.appliedFog); + } + public void addCommandEnum(String name, String enums) { softEnumPacket(name, SoftEnumUpdateType.ADD, enums); } 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 3d9f08ec7..9f7fd4f40 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 @@ -148,7 +148,7 @@ public class JavaLoginTranslator extends PacketTranslator