From a910733c91aadb228218630d7679e9d450a597bc Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Wed, 29 May 2024 23:36:40 +0200 Subject: [PATCH] Add API to show/hide GUI elements We can also use this for a nicer looking spectator mode GUI :) --- .../geyser/api/bedrock/camera/CameraData.java | 46 ++++++++++ .../geyser/api/bedrock/camera/GuiElement.java | 42 ++++++++++ .../geyser/impl/camera/GeyserCameraData.java | 84 ++++++++++++++++++- .../geyser/session/GeyserSession.java | 31 ++++++- 4 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java 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 index 2f715fa1e..3c6e24575 100644 --- 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 @@ -145,4 +145,50 @@ public interface CameraData { * @return whether the camera is currently locked */ boolean isCameraLocked(); + + /** + * Hides a {@link GuiElement} on the client's side. + * + * @param element the {@link GuiElement} to hide + */ + void hideElement(@NonNull GuiElement element); + + /** + * Hides a set of {@link GuiElement}'s on the client's side. + * + * @param elements the {@link Set} to hide + */ + void hideElements(@NonNull Set elements); + + /** + * Resets a {@link GuiElement} on the client's side. + * This makes the client decide on its own - e.g. based on client settings - + * whether to show or hide the gui element. + * + * @param element the {@link GuiElement} to reset + */ + void resetElement(@NonNull GuiElement element); + + /** + * Resets a set of {@link GuiElement} on the client's side. + * This makes the client decide on its own - e.g. based on client settings - + * whether to show or hide the gui elements. + * + * @param elements the {@link Set} to reset + */ + void resetElements(@NonNull Set elements); + + /** + * Determines whether a {@link GuiElement} is currently hidden. + * + * @param element the {@link GuiElement} to check + */ + boolean isHudElementHidden(@NonNull GuiElement element); + + /** + * Returns the currently hidden {@link GuiElement}. + * + * @return an immutable set of all currently hidden {@link GuiElement}s + */ + @NonNull Set hiddenElements(); } \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java new file mode 100644 index 000000000..1b861b2ab --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 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.api.bedrock.camera; + +public enum GuiElement { + PAPER_DOLL, + ARMOR, + TOOL_TIPS, + TOUCH_CONTROLS, + CROSSHAIR, + HOTBAR, + HEALTH, + PROGRESS_BAR, + FOOD_BAR, + AIR_BUBBLES_BAR, + VEHICLE_HEALTH, + EFFECTS_BAR, + ITEM_TEXT_POPUP +} 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 index 2a93c89e3..a59189c31 100644 --- a/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java +++ b/core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java @@ -32,16 +32,29 @@ 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.HudElement; +import org.cloudburstmc.protocol.bedrock.data.HudVisibility; 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.*; +import org.cloudburstmc.protocol.bedrock.packet.SetHudPacket; +import org.geysermc.geyser.api.bedrock.camera.CameraData; +import org.geysermc.geyser.api.bedrock.camera.CameraEaseType; +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.api.bedrock.camera.GuiElement; import org.geysermc.geyser.session.GeyserSession; -import java.util.*; +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 { @@ -57,6 +70,11 @@ public class GeyserCameraData implements CameraData { private final Set cameraLockOwners = new HashSet<>(); + /** + * All currently hidden HUD elements + */ + private final Set hiddenHudElements = new HashSet<>(); + public GeyserCameraData(GeyserSession session) { this.session = session; } @@ -223,4 +241,66 @@ public class GeyserCameraData implements CameraData { public boolean isCameraLocked() { return !this.cameraLockOwners.isEmpty(); } + + @Override + public void hideElement(@NonNull GuiElement element) { + Objects.requireNonNull(element); + this.hiddenHudElements.add(element); + + SetHudPacket packet = new SetHudPacket(); + packet.setVisibility(HudVisibility.HIDE); + packet.getElements().add(HudElement.values()[element.ordinal()]); + session.sendUpstreamPacket(packet); + } + + @Override + public void hideElements(@NonNull Set elements) { + Objects.requireNonNull(elements); + this.hiddenHudElements.removeAll(elements); + + SetHudPacket packet = new SetHudPacket(); + packet.setVisibility(HudVisibility.HIDE); + Set elementSet = packet.getElements(); + elements.forEach((element) -> elementSet.add(HudElement.values()[element.ordinal()])); + session.sendUpstreamPacket(packet); + } + + @Override + public void resetElement(@NonNull GuiElement element) { + Objects.requireNonNull(element); + this.hiddenHudElements.remove(element); + + SetHudPacket packet = new SetHudPacket(); + packet.setVisibility(HudVisibility.RESET); + packet.getElements().add(HudElement.values()[element.ordinal()]); + session.sendUpstreamPacket(packet); + } + + @Override + public void resetElements(@NonNull Set elements) { + Objects.requireNonNull(elements); + this.hiddenHudElements.removeAll(elements); + + // This is unfortunate, but resetting multiple elements doesn't work otherwise + if (!hiddenHudElements.isEmpty()) { + elements.forEach(this::resetElement); + return; + } + + SetHudPacket packet = new SetHudPacket(); + packet.setVisibility(HudVisibility.RESET); + packet.getElements().addAll(Set.of(HudElement.values())); + session.sendUpstreamPacket(packet); + } + + @Override + public boolean isHudElementHidden(@NonNull GuiElement element) { + Objects.requireNonNull(element); + return this.hiddenHudElements.contains(element); + } + + @Override + public @NonNull Set hiddenElements() { + return Set.copyOf(hiddenHudElements); + } } \ No newline at end of file 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 2b7ec0a97..a16725cab 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.session; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.auth.service.MsaAuthenticationService; +import org.geysermc.geyser.api.bedrock.camera.GuiElement; import org.geysermc.mcprotocollib.protocol.MinecraftConstants; import org.geysermc.mcprotocollib.protocol.MinecraftProtocol; import org.geysermc.mcprotocollib.protocol.data.ProtocolState; @@ -288,7 +289,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ private volatile boolean closed; - @Setter private GameMode gameMode = GameMode.SURVIVAL; /** @@ -581,6 +581,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private MinecraftProtocol protocol; + /** + * A set of elements to hide when the player is in spectator mode. + * Helps with tidying up the GUI; Java-style. + */ + private static final Set SPECTATOR_HUD = Set.of( + GuiElement.AIR_BUBBLES_BAR, + GuiElement.ARMOR, + GuiElement.HEALTH, + GuiElement.FOOD_BAR, + GuiElement.PROGRESS_BAR, + GuiElement.TOOL_TIPS + ); + public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) { this.geyser = geyser; this.upstream = new UpstreamSession(bedrockServerSession); @@ -1321,6 +1334,22 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } } + public void setGameMode(GameMode newGamemode) { + boolean currentlySpectator = gameMode == GameMode.SPECTATOR; + this.gameMode = newGamemode; + + // Hide/Unhide GUI elements as needed + if (newGamemode == GameMode.SPECTATOR) { + if (!currentlySpectator) { + this.cameraData.hideElements(SPECTATOR_HUD); + } + } else { + if (currentlySpectator) { + this.cameraData.resetElements(SPECTATOR_HUD); + } + } + } + /** * Adjusts speed if the player is crawling. *