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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
72
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/AzimuthWaypoint.java
vendored
Normal file
72
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/AzimuthWaypoint.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
50
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/ChunkWaypoint.java
vendored
Normal file
50
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/ChunkWaypoint.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
47
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/CoordinatesWaypoint.java
vendored
Normal file
47
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/CoordinatesWaypoint.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
140
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/GeyserWaypoint.java
vendored
Normal file
140
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/GeyserWaypoint.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
31
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/TickingWaypoint.java
vendored
Normal file
31
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/TickingWaypoint.java
vendored
Normal 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();
|
||||
}
|
||||
148
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/WaypointCache.java
vendored
Normal file
148
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/WaypointCache.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -35,5 +35,6 @@ public class JavaTrackedWaypointTranslator extends PacketTranslator<ClientboundT
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundTrackedWaypointPacket packet) {
|
||||
session.getWaypointCache().handlePacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user