1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-30 20:29:19 +00:00

Implement waypoints/locator bar

This commit is contained in:
Eclipse
2025-06-14 10:05:45 +00:00
parent e62bed5c4e
commit 6a06a72246
16 changed files with 520 additions and 10 deletions

View File

@@ -103,6 +103,11 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
*/
private @Nullable ParrotEntity rightParrot;
/**
* Whether this player is currently listed.
*/
private boolean listed = false;
public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position,
Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) {
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);

View File

@@ -31,6 +31,7 @@ import lombok.Getter;
* This enum stores each gamerule along with the value type and the default.
* It is used to construct the list for the settings menu
*/
// TODO gamerules with feature flags (e.g. minecart speed with minecart experiment)
public enum GameRule {
ANNOUNCEADVANCEMENTS("announceAdvancements", true), // JE only
COMMANDBLOCKOUTPUT("commandBlockOutput", true),
@@ -66,7 +67,8 @@ public enum GameRule {
SHOWDEATHMESSAGES("showDeathMessages", true),
SPAWNRADIUS("spawnRadius", 10),
SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", true), // JE only
UNIVERSALANGER("universalAnger", false); // JE only
UNIVERSALANGER("universalAnger", false),
LOCATORBAR("locatorBar", true);
public static final GameRule[] VALUES = values();

View File

@@ -244,6 +244,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
stackPacket.getExperiments().add(new ExperimentData("experimental_graphics", true));
// Enables 2025 Content Drop 2 features
stackPacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
// Enables the locator bar for clients below 1.21.90
stackPacket.getExperiments().add(new ExperimentData("locator_bar", true));
session.sendUpstreamPacket(stackPacket);
}

View File

@@ -173,6 +173,7 @@ import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.session.cache.StructureBlockCache;
import org.geysermc.geyser.session.cache.TagCache;
import org.geysermc.geyser.session.cache.TeleportCache;
import org.geysermc.geyser.session.cache.waypoint.WaypointCache;
import org.geysermc.geyser.session.cache.WorldBorder;
import org.geysermc.geyser.session.cache.WorldCache;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
@@ -287,6 +288,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final SkullCache skullCache;
private final StructureBlockCache structureBlockCache;
private final TagCache tagCache;
private final WaypointCache waypointCache;
private final WorldCache worldCache;
@Setter
@@ -312,7 +314,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private @Nullable InventoryHolder<? extends Inventory> inventoryHolder;
@Getter
private final DialogManager dialogManager = new DialogManager(this);
/**
@@ -736,6 +737,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.skullCache = new SkullCache(this);
this.structureBlockCache = new StructureBlockCache();
this.tagCache = new TagCache(this);
this.waypointCache = new WaypointCache(this);
this.worldCache = new WorldCache(this);
this.cameraData = new GeyserCameraData(this);
this.entityData = new GeyserEntityData(this);
@@ -1263,6 +1265,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
}
dialogManager.tick();
waypointCache.tick();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
@@ -1693,6 +1696,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
startGamePacket.getExperiments().add(new ExperimentData("experimental_graphics", true));
// Enables 2025 Content Drop 2 features
startGamePacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
// Enables the locator bar for clients below 1.21.90
startGamePacket.getExperiments().add(new ExperimentData("locator_bar", true));
startGamePacket.setVanillaVersion("*");
startGamePacket.setInventoriesServerAuthoritative(true);

View File

@@ -62,7 +62,7 @@ public final class TagCache {
this.session = session;
}
public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) {
public void loadPacket(ClientboundUpdateTagsPacket packet) {
Map<Key, Map<Key, int[]>> allTags = packet.getTags();
GeyserLogger logger = session.getGeyser().getLogger();

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.AzimuthWaypointData;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.WaypointData;
import java.awt.Color;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
public class AzimuthWaypoint extends GeyserWaypoint implements TickingWaypoint {
// In Java, this waypoint always appears really far, so set the distance far here too,
// This also makes the waypoint more accurate on the bar and less susceptible to the player moving
private static final float WAYPOINT_DISTANCE = 1000.0F;
// The angle, in radians, where the waypoint should appear on the bar
private float angle = 0.0F;
public AzimuthWaypoint(GeyserSession session, Optional<UUID> uuid, OptionalLong entityId, Color color) {
super(session, uuid, entityId, color);
}
@Override
public void setData(WaypointData data) {
angle = ((AzimuthWaypointData) data).angle();
updatePosition();
}
@Override
public void tick() {
// Update position so that it remains accurate to the angle as the player moves around
updatePosition();
sendLocationPacket(false);
}
private void updatePosition() {
Vector3f playerPosition = session.getPlayerEntity().position();
// Unit circle math!
float dx = (float) (Math.cos(angle) * WAYPOINT_DISTANCE);
float dz = (float) -(Math.sin(angle) * WAYPOINT_DISTANCE);
// Set Y to the player's Y since this waypoint always appears in the centre of the bar on Java
position = Vector3f.from(playerPosition.getX() + dx, playerPosition.getY(), playerPosition.getZ() + dz);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
import org.cloudburstmc.math.vector.Vector3f;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.ChunkWaypointData;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.WaypointData;
import java.awt.Color;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
public class ChunkWaypoint extends GeyserWaypoint {
public ChunkWaypoint(GeyserSession session, Optional<UUID> uuid, OptionalLong entityId, Color color) {
super(session, uuid, entityId, color);
}
@Override
public void setData(WaypointData data) {
ChunkWaypointData chunk = (ChunkWaypointData) data;
// Set position in centre of chunk
position = Vector3f.from(chunk.chunkX() * 16.0F + 8.0F, session.getPlayerEntity().position().getY(), chunk.chunkZ() * 16.0F + 8.0F);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.Vec3iWaypointData;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.WaypointData;
import java.awt.Color;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
public class CoordinatesWaypoint extends GeyserWaypoint {
public CoordinatesWaypoint(GeyserSession session, Optional<UUID> uuid, OptionalLong entityId, Color color) {
super(session, uuid, entityId, color);
}
@Override
public void setData(WaypointData data) {
position = ((Vec3iWaypointData) data).vector().toFloat();
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayerLocationPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.TrackedWaypoint;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.WaypointData;
import java.awt.Color;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
@Accessors(fluent = true)
public abstract class GeyserWaypoint {
protected final GeyserSession session;
@Getter
private final Color color;
private final UUID entityUuid;
private long entityId;
private boolean sendListPackets;
protected Vector3f position = Vector3f.ZERO;
private Vector3f lastSent = null;
public GeyserWaypoint(GeyserSession session, Optional<UUID> uuid, OptionalLong entityId, Color color) {
this.session = session;
this.color = color;
this.entityUuid = uuid.orElseGet(UUID::randomUUID);
this.entityId = entityId.orElseGet(() -> session.getEntityCache().getNextEntityId().incrementAndGet());
this.sendListPackets = entityId.isEmpty();
}
public void track(WaypointData data) {
sendListPackets(PlayerListPacket.Action.ADD);
update(data);
}
public void update(WaypointData data) {
setData(data);
sendLocationPacket(false);
}
public void untrack() {
PlayerLocationPacket packet = new PlayerLocationPacket();
packet.setType(PlayerLocationPacket.Type.HIDE);
packet.setTargetEntityId(entityId);
session.sendUpstreamPacket(packet);
sendListPackets(PlayerListPacket.Action.REMOVE);
}
public void setPlayer(PlayerEntity entity) {
if (sendListPackets) {
untrack();
entityId = entity.getGeyserId();
sendListPackets = false;
sendLocationPacket(true);
}
}
protected void sendLocationPacket(boolean force) {
if (force || lastSent == null || position.distanceSquared(lastSent) > 1.0F) {
PlayerLocationPacket packet = new PlayerLocationPacket();
packet.setType(PlayerLocationPacket.Type.COORDINATES);
packet.setTargetEntityId(entityId);
packet.setPosition(position);
session.sendUpstreamPacket(packet);
lastSent = position;
}
}
private void sendListPackets(PlayerListPacket.Action action) {
if (sendListPackets) {
PlayerListPacket packet = new PlayerListPacket();
packet.setAction(action);
PlayerListPacket.Entry entry = new PlayerListPacket.Entry(entityUuid);
entry.setEntityId(entityId);
entry.setColor(color);
packet.getEntries().add(entry);
session.sendUpstreamPacket(packet);
}
}
public abstract void setData(WaypointData data);
public static @Nullable GeyserWaypoint create(GeyserSession session, Optional<UUID> uuid, OptionalLong entityId, TrackedWaypoint waypoint) {
Color color = getWaypointColor(waypoint);
return switch (waypoint.type()) {
case EMPTY -> null;
case VEC3I -> new CoordinatesWaypoint(session, uuid, entityId, color);
case CHUNK -> new ChunkWaypoint(session, uuid, entityId, color);
case AZIMUTH -> new AzimuthWaypoint(session, uuid, entityId, color);
};
}
private static Color getWaypointColor(TrackedWaypoint waypoint) {
// Use icon's colour, or calculate from UUID/ID if it is not specified
// This is similar to how Java does it, but they do some brightness modifications too, which is a lot of math (see LocatorBarRenderer)
return waypoint.icon().color()
.or(() -> Optional.ofNullable(waypoint.uuid()).map(UUID::hashCode))
.or(() -> Optional.ofNullable(waypoint.id()).map(String::hashCode))
.map(i -> new Color(i & 0xFFFFFF))
.orElseThrow();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
public interface TickingWaypoint {
void tick();
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2025 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.session.cache.waypoint;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.TrackedWaypoint;
import org.geysermc.mcprotocollib.protocol.data.game.level.waypoint.WaypointOperation;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundTrackedWaypointPacket;
import java.awt.Color;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.UUID;
public final class WaypointCache {
private final GeyserSession session;
private final Map<String, GeyserWaypoint> waypoints = new Object2ObjectOpenHashMap<>();
private final Map<UUID, Color> waypointColors = new Object2ObjectOpenHashMap<>();
public WaypointCache(GeyserSession session) {
this.session = session;
}
public void handlePacket(ClientboundTrackedWaypointPacket packet) {
switch (packet.getOperation()) {
case TRACK -> track(packet.getWaypoint());
case UNTRACK -> untrack(packet.getWaypoint());
case UPDATE -> update(packet.getWaypoint());
}
if (packet.getOperation() == WaypointOperation.TRACK || packet.getOperation()== WaypointOperation.UNTRACK) {
// Only show locator bar when there are waypoints on it
// This is equivalent to Java, and the Java locatorBar game rule won't work otherwise
session.sendGameRule("locatorBar", !waypoints.isEmpty());
}
}
public void trackPlayer(PlayerEntity player) {
GeyserWaypoint waypoint = waypoints.get(player.getUuid().toString());
if (waypoint != null) {
// This will remove the fake player packet previously sent to the client,
// and change the waypoint to use the player's entity ID instead.
// This is important because sometimes a waypoint is sent before player info, so a fake player packet is sent to the client
// When the player becomes listed the right colour will already be used, this is always put in the colours map, no matter if the
// player info existed or not
waypoint.setPlayer(player);
}
}
public Optional<Color> getWaypointColor(UUID uuid) {
return Optional.ofNullable(waypointColors.get(uuid));
}
public void tick() {
for (GeyserWaypoint waypoint : waypoints.values()) {
if (waypoint instanceof TickingWaypoint ticking) {
ticking.tick();
}
}
}
private void track(TrackedWaypoint waypoint) {
untrack(waypoint);
Optional<UUID> uuid = Optional.ofNullable(waypoint.uuid());
Optional<PlayerEntity> player = session.getEntityCache().getAllPlayerEntities().stream()
.filter(entity -> entity.getUuid().equals(waypoint.uuid()))
.findFirst();
OptionalLong playerId = player.stream().mapToLong(PlayerEntity::getGeyserId).findFirst();
GeyserWaypoint tracked = GeyserWaypoint.create(session, uuid, playerId, waypoint);
if (tracked != null) {
uuid.ifPresent(id -> waypointColors.put(id, tracked.color()));
// Resend player entry with new waypoint colour
player.ifPresent(this::updatePlayerEntry);
tracked.track(waypoint.data());
waypoints.put(waypointId(waypoint), tracked);
}
}
private void update(TrackedWaypoint waypoint) {
getWaypoint(waypoint).ifPresent(tracked -> tracked.update(waypoint.data()));
}
private void untrack(TrackedWaypoint waypoint) {
getWaypoint(waypoint).ifPresent(GeyserWaypoint::untrack);
waypoints.remove(waypointId(waypoint));
waypointColors.remove(waypoint.uuid());
}
private Optional<GeyserWaypoint> getWaypoint(TrackedWaypoint waypoint) {
return Optional.ofNullable(waypoints.get(waypointId(waypoint)));
}
private static String waypointId(TrackedWaypoint waypoint) {
return Optional.ofNullable(waypoint.uuid())
.map(UUID::toString)
.orElse(waypoint.id());
}
private void updatePlayerEntry(PlayerEntity player) {
// No need to resend the entry if the player wasn't listed anyway,
// it will become listed later with the right colour
if (!player.isListed()) {
return;
}
PlayerListPacket.Entry entry = SkinManager.buildCachedEntry(session, player);
PlayerListPacket removePacket = new PlayerListPacket();
removePacket.setAction(PlayerListPacket.Action.REMOVE);
removePacket.getEntries().add(entry);
session.sendUpstreamPacket(removePacket);
PlayerListPacket addPacket = new PlayerListPacket();
addPacket.setAction(PlayerListPacket.Action.ADD);
addPacket.getEntries().add(entry);
session.sendUpstreamPacket(addPacket);
}
}

View File

@@ -84,6 +84,9 @@ public class SkinManager {
}
}
// Default to white when waypoint colour is unknown, which is the most visible
Color color = session.getWaypointCache().getWaypointColor(playerEntity.getUuid()).orElse(Color.WHITE);
return buildEntryManually(
session,
playerEntity.getUuid(),
@@ -91,7 +94,8 @@ public class SkinManager {
playerEntity.getGeyserId(),
skin,
cape,
geometry
geometry,
color
);
}
@@ -101,7 +105,7 @@ public class SkinManager {
public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId,
Skin skin,
Cape cape,
SkinGeometry geometry) {
SkinGeometry geometry, Color color) {
SerializedSkin serializedSkin = getSkin(session, skin.textureUrl(), skin, cape, geometry);
// This attempts to find the XUID of the player so profile images show up for Xbox accounts
@@ -129,8 +133,7 @@ public class SkinManager {
entry.setPlatformChatId("");
entry.setTeacher(false);
entry.setTrustedSkin(true);
// Without a color set, player list entries will not show up.
entry.setColor(Color.BLACK);
entry.setColor(color);
return entry;
}
@@ -138,6 +141,7 @@ public class SkinManager {
Skin skin = skinData.skin();
Cape cape = skinData.cape();
SkinGeometry geometry = skinData.geometry();
Color color = session.getWaypointCache().getWaypointColor(entity.getUuid()).orElse(Color.WHITE);
if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
PlayerListPacket.Entry updatedEntry = buildEntryManually(
@@ -147,7 +151,8 @@ public class SkinManager {
entity.getGeyserId(),
skin,
cape,
geometry
geometry,
color
);
PlayerListPacket playerAddPacket = new PlayerListPacket();

View File

@@ -255,7 +255,7 @@ public final class ItemTranslator {
// convert the modifier tag to a lore entry
String loreEntry = attributeToLore(session, entry.getAttribute(), entry.getModifier(), entry.getDisplay(), language);
if (loreEntry == null) {
continue; // invalid or failed
continue; // invalid, failed, or hidden
}
slotsToModifiers.computeIfAbsent(entry.getSlot(), s -> new ArrayList<>()).add(loreEntry);

View File

@@ -35,6 +35,6 @@ public class JavaUpdateTagsTranslator extends PacketTranslator<ClientboundUpdate
@Override
public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) {
session.getTagCache().loadPacket(session, packet);
session.getTagCache().loadPacket(packet);
}
}

View File

@@ -89,6 +89,7 @@ public class JavaPlayerInfoUpdateTranslator extends PacketTranslator<Clientbound
);
session.getEntityCache().addPlayerEntity(playerEntity);
session.getWaypointCache().trackPlayer(playerEntity);
}
playerEntity.setUsername(name);
playerEntity.setTexturesProperty(texturesProperty);
@@ -117,6 +118,7 @@ public class JavaPlayerInfoUpdateTranslator extends PacketTranslator<Clientbound
} else {
toRemove.add(new PlayerListPacket.Entry(entity.getTabListUuid()));
}
entity.setListed(entry.isListed());
}
if (!toAdd.isEmpty()) {

View File

@@ -35,5 +35,6 @@ public class JavaTrackedWaypointTranslator extends PacketTranslator<ClientboundT
@Override
public void translate(GeyserSession session, ClientboundTrackedWaypointPacket packet) {
session.getWaypointCache().handlePacket(packet);
}
}