Add API to show/hide GUI elements

We can also use this for a nicer looking spectator mode GUI :)
This commit is contained in:
onebeastchris 2024-05-29 23:36:40 +02:00
parent 0fcf0f9b4f
commit a910733c91
4 changed files with 200 additions and 3 deletions

View file

@ -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<GuiElement>} to hide
*/
void hideElements(@NonNull Set<GuiElement> 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<GuiElement>} to reset
*/
void resetElements(@NonNull Set<GuiElement> 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<GuiElement> hiddenElements();
}

View file

@ -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
}

View file

@ -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<UUID> cameraLockOwners = new HashSet<>();
/**
* All currently hidden HUD elements
*/
private final Set<GuiElement> 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<GuiElement> elements) {
Objects.requireNonNull(elements);
this.hiddenHudElements.removeAll(elements);
SetHudPacket packet = new SetHudPacket();
packet.setVisibility(HudVisibility.HIDE);
Set<HudElement> 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<GuiElement> 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<GuiElement> hiddenElements() {
return Set.copyOf(hiddenHudElements);
}
}

View file

@ -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<GuiElement> 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.
*