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..f208879d1 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,36 @@ public interface CameraData {
* @return whether the camera is currently locked
*/
boolean isCameraLocked();
-}
\ No newline at end of file
+
+ /**
+ * Hides a {@link GuiElement} on the client's side.
+ *
+ * @param element the {@link GuiElement} to hide
+ */
+ void hideElement(@NonNull GuiElement... element);
+
+ /**
+ * 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.
+ *
+ * If no elements are specified, this will reset all currently hidden elements
+ *
+ * @param element the {@link GuiElement} to reset
+ */
+ void resetElement(@NonNull GuiElement @Nullable... element);
+
+ /**
+ * 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}s.
+ *
+ * @return an unmodifiable view of all currently hidden {@link GuiElement}s
+ */
+ @NonNull Set hiddenElements();
+}
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..4d3653648
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/bedrock/camera/GuiElement.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * Represent GUI elements on the players HUD display.
+ * These can be hidden using {@link CameraData#hideElement(GuiElement...)},
+ * and one can reset their visibility using {@link CameraData#resetElement(GuiElement...)}.
+ */
+public class GuiElement {
+ public static final GuiElement PAPER_DOLL = new GuiElement(0);
+ public static final GuiElement ARMOR = new GuiElement(1);
+ public static final GuiElement TOOL_TIPS = new GuiElement(2);
+ public static final GuiElement TOUCH_CONTROLS = new GuiElement(3);
+ public static final GuiElement CROSSHAIR = new GuiElement(4);
+ public static final GuiElement HOTBAR = new GuiElement(5);
+ public static final GuiElement HEALTH = new GuiElement(6);
+ public static final GuiElement PROGRESS_BAR = new GuiElement(7);
+ public static final GuiElement FOOD_BAR = new GuiElement(8);
+ public static final GuiElement AIR_BUBBLES_BAR = new GuiElement(9);
+ public static final GuiElement VEHICLE_HEALTH = new GuiElement(10);
+ public static final GuiElement EFFECTS_BAR = new GuiElement(11);
+ public static final GuiElement ITEM_TEXT_POPUP = new GuiElement(12);
+
+ private GuiElement(int id) {
+ this.id = id;
+ }
+
+ private final int id;
+
+ /**
+ * Internal use only; don't depend on these values being consistent.
+ */
+ public int id() {
+ return this.id;
+ }
+}
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..7582502b3 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,24 +32,50 @@ 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 org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
-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 {
+ private static final HudElement[] HUD_ELEMENT_VALUES = HudElement.values();
+ private static final Set ALL_HUD_ELEMENTS = Set.of(HUD_ELEMENT_VALUES);
+
+ /**
+ * An array of elements to hide when the player is in spectator mode.
+ * Helps with tidying up the GUI; Java-style.
+ */
+ private static final GuiElement[] SPECTATOR_HIDDEN_ELEMENTS = {
+ GuiElement.AIR_BUBBLES_BAR,
+ GuiElement.ARMOR,
+ GuiElement.HEALTH,
+ GuiElement.FOOD_BAR,
+ GuiElement.PROGRESS_BAR,
+ GuiElement.TOOL_TIPS
+ };
private final GeyserSession session;
- @Getter
- private CameraPerspective cameraPerspective;
-
/**
* All fog effects that are currently applied to the client.
*/
@@ -57,6 +83,14 @@ public class GeyserCameraData implements CameraData {
private final Set cameraLockOwners = new HashSet<>();
+ /**
+ * All currently hidden HUD elements
+ */
+ private final Set hiddenHudElements = new HashSet<>();
+
+ @Getter
+ private CameraPerspective cameraPerspective;
+
public GeyserCameraData(GeyserSession session) {
this.session = session;
}
@@ -223,4 +257,67 @@ public class GeyserCameraData implements CameraData {
public boolean isCameraLocked() {
return !this.cameraLockOwners.isEmpty();
}
-}
\ No newline at end of file
+
+ @Override
+ public void hideElement(GuiElement... elements) {
+ Objects.requireNonNull(elements);
+ SetHudPacket packet = new SetHudPacket();
+ packet.setVisibility(HudVisibility.HIDE);
+ Set elementSet = packet.getElements();
+
+ for (GuiElement element : elements) {
+ this.hiddenHudElements.add(element);
+ elementSet.add(HUD_ELEMENT_VALUES[element.id()]);
+ }
+
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public void resetElement(GuiElement... elements) {
+ SetHudPacket packet = new SetHudPacket();
+ packet.setVisibility(HudVisibility.RESET);
+ Set elementSet = packet.getElements();
+
+ if (elements != null && elements.length != 0) {
+ for (GuiElement element : elements) {
+ this.hiddenHudElements.remove(element);
+ elementSet.add(HUD_ELEMENT_VALUES[element.id()]);
+ }
+ } else {
+ this.hiddenHudElements.clear();
+ elementSet.addAll(ALL_HUD_ELEMENTS);
+ }
+
+ session.sendUpstreamPacket(packet);
+ }
+
+ @Override
+ public boolean isHudElementHidden(@NonNull GuiElement element) {
+ Objects.requireNonNull(element);
+ return this.hiddenHudElements.contains(element);
+ }
+
+ @Override
+ public @NonNull Set hiddenElements() {
+ return Collections.unmodifiableSet(hiddenHudElements);
+ }
+
+ /**
+ * Deals with hiding hud elements while in spectator.
+ *
+ * @param currentlySpectator whether the player is currently in spectator mode
+ * @param newGameMode the new GameMode to switch to
+ */
+ public void handleGameModeChange(boolean currentlySpectator, GameMode newGameMode) {
+ if (newGameMode == GameMode.SPECTATOR) {
+ if (!currentlySpectator) {
+ hideElement(SPECTATOR_HIDDEN_ELEMENTS);
+ }
+ } else {
+ if (currentlySpectator) {
+ resetElement(SPECTATOR_HIDDEN_ELEMENTS);
+ }
+ }
+ }
+}
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 17010e966..fc000b95f 100644
--- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
+++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
@@ -284,7 +284,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
private volatile boolean closed;
- @Setter
private GameMode gameMode = GameMode.SURVIVAL;
/**
@@ -1302,6 +1301,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
}
}
+ public void setGameMode(GameMode newGamemode) {
+ boolean currentlySpectator = this.gameMode == GameMode.SPECTATOR;
+ this.gameMode = newGamemode;
+ this.cameraData.handleGameModeChange(currentlySpectator, newGamemode);
+ }
+
/**
* Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display
* blocking and sends a packet to the Java server.
diff --git a/gradle.properties b/gradle.properties
index 40d8a36db..ea473906a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,5 +7,5 @@ org.gradle.vfs.watch=false
group=org.geysermc
id=geyser
-version=2.3.1-SNAPSHOT
+version=2.3.2-SNAPSHOT
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.