Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextDecoration;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.Ability;
import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
import org.cloudburstmc.protocol.bedrock.data.GameType;
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
Expand All @@ -52,13 +53,18 @@
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.Pose;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;

import java.util.Collections;
import java.util.List;
Expand All @@ -70,6 +76,7 @@
@Getter @Setter
public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
private static final Style SPECTATOR_MODE_STYLING = Style.style(TextDecoration.ITALIC);
protected static final List<AbilityLayer> BASE_ABILITY_LAYER;

static {
Expand Down Expand Up @@ -104,6 +111,17 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
*/
private @Nullable ParrotEntity rightParrot;

/**
* The gamemode as it's sent in the player info packet
*/
private GameMode gameMode = GameMode.SURVIVAL;

/**
* The tablist display name component as sent by the Java server
*/
@Setter
private @Nullable Component tabListDisplayName;

/**
* Whether this player is currently listed.
*/
Expand Down Expand Up @@ -140,13 +158,15 @@ public void spawnEntity() {
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
addPlayerPacket.setDeviceId("");
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
addPlayerPacket.setGameType(EntityUtils.toBedrockGamemode(gameMode));
addPlayerPacket.setAbilityLayers(BASE_ABILITY_LAYER); // Recommended to be added since 1.19.10, but only needed here for permissions viewing
addPlayerPacket.getMetadata().putFlags(flags);

// Since 1.20.60, the nametag does not show properly if this is not set :/
// The nametag does disappear properly when the player is invisible though.
dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) 1);
// Otherwise, tab display names would be shown
dirtyMetadata.put(EntityDataTypes.NAME, username);
dirtyMetadata.apply(addPlayerPacket.getMetadata());

setFlagsDirty(false);
Expand Down Expand Up @@ -385,9 +405,7 @@ public String teamIdentifier() {

@Override
protected void setNametag(@Nullable String nametag, boolean fromDisplayName) {
// when fromDisplayName, LivingEntity will call scoreboard code. After that
// setNametag is called again with fromDisplayName on false
if (nametag == null && !fromDisplayName) {
if (nametag == null) {
// nametag = null means reset, so reset it back to username
nametag = username;
}
Expand Down Expand Up @@ -482,6 +500,24 @@ public UUID getTabListUuid() {
return getUuid();
}

public String getTabListDisplayName() {
boolean spectator = gameMode == GameMode.SPECTATOR;
// First: Use manual override sent in the player list, if present
if (tabListDisplayName != null) {
return MessageTranslator.convertMessageRaw(spectator ?
tabListDisplayName.style(SPECTATOR_MODE_STYLING) : tabListDisplayName, session.locale());
}

// Second: apply styling from team, if in one
Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(getUsername());
if (playerTeam != null) {
return playerTeam.formatTabDisplay(username, spectator);
}

// Finally: Return username, optionally italic if in spectator mode
return spectator ? ChatColor.ITALIC + username : username;
}

@Override
public Vector3f position() {
return this.position.down(definition.offset());
Expand Down
126 changes: 87 additions & 39 deletions core/src/main/java/org/geysermc/geyser/scoreboard/Team.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@
package org.geysermc.geyser.scoreboard;

import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.PlayerListUtils;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;

Expand Down Expand Up @@ -132,6 +138,19 @@ public String displayName(String score) {
return chatColor + prefix + ChatColor.RESET + chatColor + score + ChatColor.RESET + chatColor + suffix;
}

public String formatTabDisplay(String username, boolean spectator) {
String chatColor = ChatColor.chatColorFor(color);
if (ChatColor.RESET.equals(chatColor)) {
chatColor = "";
}

if (spectator && !ChatColor.ITALIC.equals(chatColor)) {
chatColor += ChatColor.ITALIC;
}
// also add reset because setting the color does not reset the formatting, unlike Java
return chatColor + prefix + ChatColor.RESET + chatColor + username + ChatColor.RESET + chatColor + suffix;
}

public boolean isVisibleFor(String entity) {
return switch (nameTagVisibility) {
case HIDE_FOR_OTHER_TEAMS -> {
Expand Down Expand Up @@ -175,19 +194,20 @@ public void updateProperties(Component name, Component prefix, Component suffix,
return;
}

if (!this.name.equals(oldName)
|| !this.prefix.equals(oldPrefix)
// Avoid updating player list entries if just the name changed
boolean stylingChange = !this.prefix.equals(oldPrefix)
|| !this.suffix.equals(oldSuffix)
|| color != oldColor) {
|| color != oldColor;
if (!this.name.equals(oldName) || stylingChange) {
markChanged();
updateEntities();
updateEntities(managedEntities, this, stylingChange);
return;
}

if (isVisibleFor(playerName()) != oldVisible) {
// if just the visibility changed, we only have to update the entities.
// We don't have to mark it as changed
updateEntities();
updateEntities(managedEntities, this, false);
}
}

Expand All @@ -210,20 +230,46 @@ public void remove() {
scoreboard.getPlayerToTeam().remove(name);
}

updateEntities(managedEntities, null, true);
}

/**
* Iterates through the provided entity collection to perform nametag, metadata and tablist updates.
*/
public void updateEntities(Collection<Entity> entities, Team team, boolean updatePlayerList) {
if (entities().contains(playerName())) {
refreshAllEntities();
refreshAllEntities(updatePlayerList);
return;
}
for (Entity entity : managedEntities) {
entity.updateNametag(null);
entity.updateBedrockMetadata();
}

iterateAndUpdate(entities.iterator(), updatePlayerList, ($, $$) -> true, $ -> team, false);
}

private void updateEntities() {
for (Entity entity : managedEntities) {
entity.updateNametag(this);
entity.updateBedrockMetadata();
/**
* Iterates through the provided entity iterator, and, if the predicate matches, updates the nametag based on the
* team function. It further updates the player list entries.
*/
public void iterateAndUpdate(Iterator<Entity> iterator, boolean updatePlayerList, BiPredicate<Entity, Iterator<Entity>> predicate, Function<Entity, Team> function, boolean addSessionPlayer) {
Set<PlayerEntity> entries = updatePlayerList ? new HashSet<>() : null;
while (iterator.hasNext()) {
Entity entity = iterator.next();
if (predicate.test(entity, iterator)) {
entity.updateNametag(function.apply(entity));
entity.updateBedrockMetadata();
if (entries != null && entity instanceof PlayerEntity player) {
entries.add(player);
}
}
}

if (entries != null) {
if (addSessionPlayer) {
entries.add(session().getPlayerEntity());
}

if (!entries.isEmpty()) {
PlayerListUtils.updateEntries(session(), entries);
}
}
}

Expand All @@ -247,40 +293,36 @@ private void addAddedEntities(Set<String> names) {
if (names.isEmpty()) {
return;
}
boolean containsSelf = names.contains(playerName());

for (Entity entity : session().getEntityCache().getEntities().values()) {
if (names.contains(entity.teamIdentifier())) {
managedEntities.add(entity);
if (!containsSelf) {
entity.updateNametag(this);
entity.updateBedrockMetadata();
boolean containsSelf = names.contains(playerName());
iterateAndUpdate(session().getEntityCache().getEntities().values().iterator(),
requiresPlayerListUpdate(),
(entity, $) -> {
if (names.contains(entity.teamIdentifier())) {
managedEntities.add(entity);
return !containsSelf;
}
}
}
return false;
},
$ -> this, false);

if (containsSelf) {
refreshAllEntities();
refreshAllEntities(requiresPlayerListUpdate());
}
}

private void removeRemovedEntities(Set<String> names) {
boolean containsSelf = names.contains(playerName());

var iterator = managedEntities.iterator();
while (iterator.hasNext()) {
var entity = iterator.next();
iterateAndUpdate(managedEntities.iterator(), requiresPlayerListUpdate(), (entity, iterator) -> {
if (names.contains(entity.teamIdentifier())) {
iterator.remove();
if (!containsSelf) {
entity.updateNametag(null);
entity.updateBedrockMetadata();
}
return !containsSelf;
}
}
return false;
}, $ -> null, false);

if (containsSelf) {
refreshAllEntities();
refreshAllEntities(requiresPlayerListUpdate());
}
}

Expand All @@ -293,11 +335,13 @@ private void removeManagedEntity(String name) {
managedEntities.removeIf(entity -> name.equals(entity.teamIdentifier()));
}

private void refreshAllEntities() {
for (Entity entity : session().getEntityCache().getEntities().values()) {
entity.updateNametag(scoreboard.getTeamFor(entity.teamIdentifier()));
entity.updateBedrockMetadata();
}
private void refreshAllEntities(boolean updatePlayerList) {
iterateAndUpdate(
session().getEntityCache().getEntities().values().iterator(),
updatePlayerList,
($, $$) -> true,
entity -> scoreboard.getTeamFor(entity.teamIdentifier()),
true);
}

private GeyserSession session() {
Expand All @@ -324,6 +368,10 @@ public Set<String> entities() {
return entities;
}

private boolean requiresPlayerListUpdate() {
return !prefix.isEmpty() || !suffix.isEmpty() || color() != TeamColor.RESET;
}

@Override
public int hashCode() {
return id.hashCode();
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/geysermc/geyser/skin/SkinManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, Pla
return buildEntryManually(
session,
playerEntity.getUuid(),
playerEntity.getUsername(),
playerEntity.getTabListDisplayName(),
playerEntity.getGeyserId(),
skin,
cape,
Expand Down Expand Up @@ -153,7 +153,7 @@ public static void sendSkinPacket(GeyserSession session, PlayerEntity entity, Sk
PlayerListPacket.Entry updatedEntry = buildEntryManually(
session,
entity.getUuid(),
entity.getUsername(),
entity.getTabListDisplayName(),
entity.getGeyserId(),
skin,
cape,
Expand Down
Loading