3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-20 06:50:09 +01:00

Merge remote-tracking branch 'origin/master' into feature/dedicated-api-repo

Dieser Commit ist enthalten in:
Tim203 2023-02-04 21:25:11 +01:00
Commit effe046308
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 736F3CD49EF01DBF
15 geänderte Dateien mit 276 neuen und 156 gelöschten Zeilen

Datei anzeigen

@ -195,6 +195,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserConfig.loadFloodgate(this); geyserConfig.loadFloodgate(this);
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
if (!INITIALIZED) { if (!INITIALIZED) {
// Needs to be an anonymous inner class otherwise Bukkit complains about missing classes // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes
Bukkit.getPluginManager().registerEvents(new Listener() { Bukkit.getPluginManager().registerEvents(new Listener() {
@ -206,9 +209,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
} }
}, this); }, this);
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
this.geyserCommandManager.init();
// Because Bukkit locks its command map upon startup, we need to // Because Bukkit locks its command map upon startup, we need to
// add our plugin commands in onEnable, but populating the executor // add our plugin commands in onEnable, but populating the executor
// can happen at any time // can happen at any time

Datei anzeigen

@ -100,6 +100,7 @@ public class PlayerEntity extends LivingEntity {
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
this.username = username; this.username = username;
this.nametag = username;
this.texturesProperty = texturesProperty; this.texturesProperty = texturesProperty;
} }
@ -119,7 +120,7 @@ public class PlayerEntity extends LivingEntity {
} }
// The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now // The name can't be updated later (the entity metadata for it is ignored), so we need to check for this now
updateDisplayName(null, false); updateDisplayName(session.getWorldCache().getScoreboard().getTeamFor(username));
AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); AddPlayerPacket addPlayerPacket = new AddPlayerPacket();
addPlayerPacket.setUuid(uuid); addPlayerPacket.setUuid(uuid);
@ -315,19 +316,10 @@ public class PlayerEntity extends LivingEntity {
} }
//todo this will become common entity logic once UUID support is implemented for them //todo this will become common entity logic once UUID support is implemented for them
/** public void updateDisplayName(@Nullable Team team) {
* @param useGivenTeam even if there is no team, update the username in the entity metadata anyway, and don't look for a team
*/
public void updateDisplayName(@Nullable Team team, boolean useGivenTeam) {
if (team == null && !useGivenTeam) {
// Only search for the team if we are not supposed to use the given team
// If the given team is null, this is intentional that we are being removed from the team
team = session.getWorldCache().getScoreboard().getTeamFor(username);
}
boolean needsUpdate; boolean needsUpdate;
String newDisplayName = this.username;
if (team != null) { if (team != null) {
String newDisplayName;
if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { if (team.isVisibleFor(session.getPlayerEntity().getUsername())) {
TeamColor color = team.getColor(); TeamColor color = team.getColor();
String chatColor = MessageTranslator.toChatColor(color); String chatColor = MessageTranslator.toChatColor(color);
@ -339,23 +331,16 @@ public class PlayerEntity extends LivingEntity {
// The name is not visible to the session player; clear name // The name is not visible to the session player; clear name
newDisplayName = ""; newDisplayName = "";
} }
needsUpdate = useGivenTeam && !newDisplayName.equals(nametag); needsUpdate = !newDisplayName.equals(this.nametag);
nametag = newDisplayName; this.nametag = newDisplayName;
dirtyMetadata.put(EntityData.NAMETAG, newDisplayName);
} else if (useGivenTeam) {
// The name has reset, if it was previously something else
needsUpdate = !newDisplayName.equals(nametag);
dirtyMetadata.put(EntityData.NAMETAG, this.username);
} else { } else {
needsUpdate = false; // The name has reset, if it was previously something else
needsUpdate = !this.nametag.equals(this.username);
this.nametag = this.username;
} }
if (needsUpdate) { if (needsUpdate) {
// Update the metadata as it won't be updated later dirtyMetadata.put(EntityData.NAMETAG, this.nametag);
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.getMetadata().put(EntityData.NAMETAG, newDisplayName);
packet.setRuntimeEntityId(geyserId);
session.sendUpstreamPacket(packet);
} }
} }

Datei anzeigen

@ -26,17 +26,20 @@
package org.geysermc.geyser.entity.type.player; package org.geysermc.geyser.entity.type.player;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.GameType; import com.nukkitx.protocol.bedrock.data.GameType;
import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
import lombok.Getter;
import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache; import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkullSkinManager; import org.geysermc.geyser.skin.SkullSkinManager;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -46,6 +49,12 @@ import java.util.concurrent.TimeUnit;
*/ */
public class SkullPlayerEntity extends PlayerEntity { public class SkullPlayerEntity extends PlayerEntity {
@Getter
private UUID skullUUID;
@Getter
private Vector3i skullPosition;
public SkullPlayerEntity(GeyserSession session, long geyserId) { public SkullPlayerEntity(GeyserSession session, long geyserId) {
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null); super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
} }
@ -102,11 +111,14 @@ public class SkullPlayerEntity extends PlayerEntity {
} }
public void updateSkull(SkullCache.Skull skull) { public void updateSkull(SkullCache.Skull skull) {
if (!skull.getTexturesProperty().equals(getTexturesProperty())) { skullPosition = skull.getPosition();
if (!Objects.equals(skull.getTexturesProperty(), getTexturesProperty()) || !Objects.equals(skullUUID, skull.getUuid())) {
// Make skull invisible as we change skins // Make skull invisible as we change skins
setFlag(EntityFlag.INVISIBLE, true); setFlag(EntityFlag.INVISIBLE, true);
updateBedrockMetadata(); updateBedrockMetadata();
skullUUID = skull.getUuid();
setTexturesProperty(skull.getTexturesProperty()); setTexturesProperty(skull.getTexturesProperty());
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> { SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {

Datei anzeigen

@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.data.ScoreInfo;
import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket;
import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket;
import com.nukkitx.protocol.bedrock.packet.SetScorePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter; import lombok.Getter;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
@ -37,6 +38,7 @@ import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
@ -55,6 +57,13 @@ public final class Scoreboard {
@Getter @Getter
private final Map<ScoreboardPosition, Objective> objectiveSlots = new EnumMap<>(ScoreboardPosition.class); private final Map<ScoreboardPosition, Objective> objectiveSlots = new EnumMap<>(ScoreboardPosition.class);
private final Map<String, Team> teams = new ConcurrentHashMap<>(); // updated on multiple threads private final Map<String, Team> teams = new ConcurrentHashMap<>(); // updated on multiple threads
/**
* Required to preserve vanilla behavior, which also uses a map.
* Otherwise, for example, if TAB has a team for a player and vanilla has a team, "race conditions" that do not
* match vanilla could occur.
*/
@Getter
private final Map<String, Team> playerToTeam = new Object2ObjectOpenHashMap<>();
private int lastAddScoreCount = 0; private int lastAddScoreCount = 0;
private int lastRemoveScoreCount = 0; private int lastRemoveScoreCount = 0;
@ -132,6 +141,10 @@ public final class Scoreboard {
team = new Team(this, teamName); team = new Team(this, teamName);
team.addEntities(players); team.addEntities(players);
teams.put(teamName, team); teams.put(teamName, team);
// Update command parameters - is safe to send even if the command enum doesn't exist on the client (as of 1.19.51)
session.addCommandEnum("Geyser_Teams", team.getId());
return team; return team;
} }
@ -328,12 +341,7 @@ public final class Scoreboard {
} }
public Team getTeamFor(String entity) { public Team getTeamFor(String entity) {
for (Team team : teams.values()) { return playerToTeam.get(entity);
if (team.hasEntity(entity)) {
return team;
}
}
return null;
} }
public void removeTeam(String teamName) { public void removeTeam(String teamName) {
@ -343,7 +351,17 @@ public final class Scoreboard {
// We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly // We need to use the direct entities list here, so #refreshSessionPlayerDisplays also updates accordingly
// With the player's lack of a team in visibility checks // With the player's lack of a team in visibility checks
updateEntityNames(remove, remove.getEntities(), true); updateEntityNames(remove, remove.getEntities(), true);
for (String name : remove.getEntities()) {
playerToTeam.remove(name, remove);
} }
session.removeCommandEnum("Geyser_Teams", remove.getId());
}
}
@Contract("-> new")
public String[] getTeamNames() {
return teams.keySet().toArray(new String[0]);
} }
/** /**
@ -368,7 +386,8 @@ public final class Scoreboard {
for (Entity entity : session.getEntityCache().getEntities().values()) { for (Entity entity : session.getEntityCache().getEntities().values()) {
// This more complex logic is for the future to iterate over all entities, not just players // This more complex logic is for the future to iterate over all entities, not just players
if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) { if (entity instanceof PlayerEntity player && names.remove(player.getUsername())) {
player.updateDisplayName(team, true); player.updateDisplayName(team);
player.updateBedrockMetadata();
if (names.isEmpty()) { if (names.isEmpty()) {
break; break;
} }
@ -384,7 +403,8 @@ public final class Scoreboard {
for (Entity entity : session.getEntityCache().getEntities().values()) { for (Entity entity : session.getEntityCache().getEntities().values()) {
if (entity instanceof PlayerEntity player) { if (entity instanceof PlayerEntity player) {
Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername()); Team playerTeam = session.getWorldCache().getScoreboard().getTeamFor(player.getUsername());
player.updateDisplayName(playerTeam, true); player.updateDisplayName(playerTeam);
player.updateBedrockMetadata();
} }
} }
} }

Datei anzeigen

@ -65,6 +65,7 @@ public final class Team {
if (entities.add(name)) { if (entities.add(name)) {
added.add(name); added.add(name);
} }
scoreboard.getPlayerToTeam().put(name, this);
} }
if (added.isEmpty()) { if (added.isEmpty()) {
@ -93,6 +94,7 @@ public final class Team {
if (entities.remove(name)) { if (entities.remove(name)) {
removed.add(name); removed.add(name);
} }
scoreboard.getPlayerToTeam().remove(name, this);
} }
return removed; return removed;
} }

Datei anzeigen

@ -67,7 +67,9 @@ import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.data.command.CommandEnumData;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.command.SoftEnumUpdateType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.packet.*;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -1402,6 +1404,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
} }
public void setServerRenderDistance(int renderDistance) { public void setServerRenderDistance(int renderDistance) {
// +1 is for Fabric and Spigot
// Without the client misses loading some chunks per https://github.com/GeyserMC/Geyser/issues/3490
// Fog still appears essentially normally
renderDistance = renderDistance + 1;
this.serverRenderDistance = renderDistance; this.serverRenderDistance = renderDistance;
ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket(); ChunkRadiusUpdatedPacket chunkRadiusUpdatedPacket = new ChunkRadiusUpdatedPacket();
@ -1891,4 +1897,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
sendUpstreamPacket(transferPacket); sendUpstreamPacket(transferPacket);
return true; return true;
} }
public void addCommandEnum(String name, String... enums) {
softEnumPacket(name, SoftEnumUpdateType.ADD, enums);
}
public void removeCommandEnum(String name, String... enums) {
softEnumPacket(name, SoftEnumUpdateType.REMOVE, enums);
}
private void softEnumPacket(String name, SoftEnumUpdateType type, String... enums) {
UpdateSoftEnumPacket packet = new UpdateSoftEnumPacket();
packet.setType(type);
packet.setSoftEnum(new CommandEnumData(name, enums, true));
sendUpstreamPacket(packet);
}
} }

Datei anzeigen

@ -71,8 +71,9 @@ public class SkullCache {
this.skullRenderDistanceSquared = distance * distance; this.skullRenderDistanceSquared = distance * distance;
} }
public void putSkull(Vector3i position, String texturesProperty, int blockState) { public void putSkull(Vector3i position, UUID uuid, String texturesProperty, int blockState) {
Skull skull = skulls.computeIfAbsent(position, Skull::new); Skull skull = skulls.computeIfAbsent(position, Skull::new);
skull.uuid = uuid;
skull.texturesProperty = texturesProperty; skull.texturesProperty = texturesProperty;
skull.blockState = blockState; skull.blockState = blockState;
@ -201,6 +202,7 @@ public class SkullCache {
@RequiredArgsConstructor @RequiredArgsConstructor
@Data @Data
public static class Skull { public static class Skull {
private UUID uuid;
private String texturesProperty; private String texturesProperty;
private int blockState; private int blockState;
private SkullPlayerEntity entity; private SkullPlayerEntity entity;

Datei anzeigen

@ -35,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.session.auth.BedrockClientData;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
@ -69,7 +70,7 @@ public class SkinManager {
// The server either didn't have a texture to send, or we didn't have the texture ID cached. // The server either didn't have a texture to send, or we didn't have the texture ID cached.
// Let's see if this player is a Bedrock player, and if so, let's pull their skin. // Let's see if this player is a Bedrock player, and if so, let's pull their skin.
// Otherwise, grab the default player skin // Otherwise, grab the default player skin
SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity); SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity.getUuid());
if (skin == null) { if (skin == null) {
skin = fallbackSkinData.skin(); skin = fallbackSkinData.skin();
geometry = fallbackSkinData.geometry(); geometry = fallbackSkinData.geometry();
@ -255,24 +256,28 @@ public class SkinManager {
* @return The built GameProfileData * @return The built GameProfileData
*/ */
public static @Nullable GameProfileData from(PlayerEntity entity) { public static @Nullable GameProfileData from(PlayerEntity entity) {
try {
String texturesProperty = entity.getTexturesProperty(); String texturesProperty = entity.getTexturesProperty();
if (texturesProperty == null) { if (texturesProperty == null) {
// Likely offline mode // Likely offline mode
return null; return null;
} }
try {
return loadFromJson(texturesProperty); return loadFromJson(texturesProperty);
} catch (IOException exception) { } catch (Exception exception) {
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); if (entity instanceof SkullPlayerEntity skullEntity) {
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for skull at " + skullEntity.getSkullPosition() + " with Value: " + texturesProperty);
} else {
GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername() + " with Value: " + texturesProperty);
}
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
exception.printStackTrace(); exception.printStackTrace();
} }
}
return null; return null;
} }
}
private static GameProfileData loadFromJson(String encodedJson) throws IOException { private static GameProfileData loadFromJson(String encodedJson) throws IOException, IllegalArgumentException {
JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8)); JsonNode skinObject = GeyserImpl.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(encodedJson), StandardCharsets.UTF_8));
JsonNode textures = skinObject.get("textures"); JsonNode textures = skinObject.get("textures");
@ -285,14 +290,23 @@ public class SkinManager {
return null; return null;
} }
String skinUrl = skinTexture.get("url").asText().replace("http://", "https://"); String skinUrl;
JsonNode skinUrlNode = skinTexture.get("url");
if (skinUrlNode != null && skinUrlNode.isTextual()) {
skinUrl = skinUrlNode.asText().replace("http://", "https://");
} else {
return null;
}
boolean isAlex = skinTexture.has("metadata"); boolean isAlex = skinTexture.has("metadata");
String capeUrl = null; String capeUrl = null;
JsonNode capeTexture = textures.get("CAPE"); JsonNode capeTexture = textures.get("CAPE");
if (capeTexture != null) { if (capeTexture != null) {
capeUrl = capeTexture.get("url").asText().replace("http://", "https://"); JsonNode capeUrlNode = capeTexture.get("url");
if (capeUrlNode != null && capeUrlNode.isTextual()) {
capeUrl = capeUrlNode.asText().replace("http://", "https://");
}
} }
return new GameProfileData(skinUrl, capeUrl, isAlex); return new GameProfileData(skinUrl, capeUrl, isAlex);

Datei anzeigen

@ -26,9 +26,6 @@
package org.geysermc.geyser.skin; package org.geysermc.geyser.skin;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import it.unimi.dsi.fastutil.bytes.ByteArrays; import it.unimi.dsi.fastutil.bytes.ByteArrays;
@ -172,14 +169,13 @@ public class SkinProvider {
/** /**
* If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback. * If skin data fails to apply, or there is no skin data to apply, determine what skin we should give as a fallback.
*/ */
static SkinData determineFallbackSkinData(PlayerEntity entity) { static SkinData determineFallbackSkinData(UUID uuid) {
Skin skin = null; Skin skin = null;
Cape cape = null; Cape cape = null;
SkinGeometry geometry = SkinGeometry.WIDE; SkinGeometry geometry = SkinGeometry.WIDE;
if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) { if (GeyserImpl.getInstance().getConfig().getRemote().authType() != AuthType.ONLINE) {
// Let's see if this player is a Bedrock player, and if so, let's pull their skin. // Let's see if this player is a Bedrock player, and if so, let's pull their skin.
UUID uuid = entity.getUuid();
GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid);
if (session != null) { if (session != null) {
String skinId = session.getClientData().getSkinId(); String skinId = session.getClientData().getSkinId();
@ -192,7 +188,7 @@ public class SkinProvider {
if (skin == null) { if (skin == null) {
// We don't have a skin for the player right now. Fall back to a default. // We don't have a skin for the player right now. Fall back to a default.
ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(entity.getUuid()); ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(uuid);
skin = providedSkin.getData(); skin = providedSkin.getData();
geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE;
} }
@ -232,7 +228,7 @@ public class SkinProvider {
SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity);
if (data == null) { if (data == null) {
// This player likely does not have a textures property // This player likely does not have a textures property
return CompletableFuture.completedFuture(determineFallbackSkinData(entity)); return CompletableFuture.completedFuture(determineFallbackSkinData(entity.getUuid()));
} }
return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl())
@ -597,48 +593,23 @@ public class SkinProvider {
} }
/** /**
* If a skull has a username but no textures, request them. * Request textures from a player's UUID
* *
* @param skullOwner the CompoundTag of the skull with no textures * @param uuid the player's UUID without any hyphens
* @return a completable GameProfile with textures included * @return a completable GameProfile with textures included
*/ */
public static CompletableFuture<String> requestTexturesFromUsername(CompoundTag skullOwner) { public static CompletableFuture<String> requestTexturesFromUUID(String uuid) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
Tag uuidTag = skullOwner.get("Id");
String uuidToString = "";
JsonNode node;
boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check
if (!retrieveUuidFromInternet) {
int[] uuidAsArray = ((IntArrayTag) uuidTag).getValue();
// thank u viaversion
UUID uuid = new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
retrieveUuidFromInternet = uuid.version() != 4;
uuidToString = uuid.toString().replace("-", "");
}
try { try {
if (retrieveUuidFromInternet) { JsonNode node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid);
// Offline skin, or no present UUID
node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + skullOwner.get("Name").getValue());
JsonNode id = node.get("id");
if (id == null) {
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + skullOwner.get("Name").getValue());
return null;
}
uuidToString = id.asText();
}
// Get textures from UUID
node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString);
JsonNode properties = node.get("properties"); JsonNode properties = node.get("properties");
if (properties == null) { if (properties == null) {
GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuid);
return null; return null;
} }
return node.get("properties").get(0).get("value").asText(); return node.get("properties").get(0).get("value").asText();
} catch (Exception e) { } catch (Exception e) {
GeyserImpl.getInstance().getLogger().debug("Unable to request textures for " + uuid);
if (GeyserImpl.getInstance().getConfig().isDebugMode()) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace(); e.printStackTrace();
} }
@ -647,6 +618,37 @@ public class SkinProvider {
}, EXECUTOR_SERVICE); }, EXECUTOR_SERVICE);
} }
/**
* Request textures from a player's username
*
* @param username the player's username
* @return a completable GameProfile with textures included
*/
public static CompletableFuture<String> requestTexturesFromUsername(String username) {
return CompletableFuture.supplyAsync(() -> {
try {
// Offline skin, or no present UUID
JsonNode node = WebUtils.getJson("https://api.mojang.com/users/profiles/minecraft/" + username);
JsonNode id = node.get("id");
if (id == null) {
GeyserImpl.getInstance().getLogger().debug("No UUID found in Mojang response for " + username);
return null;
}
return id.asText();
} catch (Exception e) {
if (GeyserImpl.getInstance().getConfig().isDebugMode()) {
e.printStackTrace();
}
return null;
}
}, EXECUTOR_SERVICE).thenCompose(uuid -> {
if (uuid == null) {
return CompletableFuture.completedFuture(null);
}
return requestTexturesFromUUID(uuid);
});
}
private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException {
if (provider == CapeProvider.FIVEZIG) if (provider == CapeProvider.FIVEZIG)
return readFiveZigCape(imageUrl); return readFiveZigCape(imageUrl);

Datei anzeigen

@ -29,11 +29,12 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData;
import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin;
import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections; import java.util.Collections;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
public class SkullSkinManager extends SkinManager { public class SkullSkinManager extends SkinManager {
@ -48,12 +49,9 @@ public class SkullSkinManager extends SkinManager {
); );
} }
public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session,
Consumer<SkinProvider.Skin> skinConsumer) { Consumer<SkinProvider.Skin> skinConsumer) {
GameProfileData data = GameProfileData.from(entity); BiConsumer<SkinProvider.Skin, Throwable> applySkin = (skin, throwable) -> {
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
.whenCompleteAsync((skin, throwable) -> {
try { try {
PlayerSkinPacket packet = new PlayerSkinPacket(); PlayerSkinPacket packet = new PlayerSkinPacket();
packet.setUuid(entity.getUuid()); packet.setUuid(entity.getUuid());
@ -69,7 +67,19 @@ public class SkullSkinManager extends SkinManager {
if (skinConsumer != null) { if (skinConsumer != null) {
skinConsumer.accept(skin); skinConsumer.accept(skin);
} }
}); };
GameProfileData data = GameProfileData.from(entity);
if (data == null) {
GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() +
" with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID());
// No texture available, fallback using the UUID
SkinProvider.SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID());
applySkin.accept(fallback.skin(), null);
} else {
SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true)
.whenCompleteAsync(applySkin);
}
} }
} }

Datei anzeigen

@ -52,6 +52,11 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator {
ItemMapping boxMapping = session.getItemMappings().getMapping(Identifier.formalize(((StringTag) itemData.get("id")).getValue())); ItemMapping boxMapping = session.getItemMappings().getMapping(Identifier.formalize(((StringTag) itemData.get("id")).getValue()));
if (boxMapping == null) {
// If invalid ID
continue;
}
boxItemTag.put(new StringTag("Name", boxMapping.getBedrockIdentifier())); boxItemTag.put(new StringTag("Name", boxMapping.getBedrockIdentifier()));
boxItemTag.put(new ShortTag("Damage", (short) boxMapping.getBedrockData())); boxItemTag.put(new ShortTag("Damage", (short) boxMapping.getBedrockData()));
boxItemTag.put(new ByteTag("Count", MathUtils.getNbtByte(itemData.get("Count").getValue()))); boxItemTag.put(new ByteTag("Count", MathUtils.getNbtByte(itemData.get("Count").getValue())));

Datei anzeigen

@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.level.block.entity;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
@ -35,7 +36,10 @@ import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.skin.SkinProvider;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@BlockEntity(type = BlockEntityType.SKULL) @BlockEntity(type = BlockEntityType.SKULL)
@ -53,12 +57,32 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
builder.put("SkullType", skullVariant); builder.put("SkullType", skullVariant);
} }
private static CompletableFuture<String> getTextures(CompoundTag tag) { private static UUID getUUID(CompoundTag owner) {
CompoundTag owner = tag.get("SkullOwner"); if (owner.get("Id") instanceof IntArrayTag uuidTag && uuidTag.length() == 4) {
if (owner != null) { int[] uuidAsArray = uuidTag.getValue();
// thank u viaversion
return new UUID((long) uuidAsArray[0] << 32 | ((long) uuidAsArray[1] & 0xFFFFFFFFL),
(long) uuidAsArray[2] << 32 | ((long) uuidAsArray[3] & 0xFFFFFFFFL));
}
// Convert username to an offline UUID
String username = null;
if (owner.get("Name") instanceof StringTag nameTag) {
username = nameTag.getValue().toLowerCase(Locale.ROOT);
}
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
}
private static CompletableFuture<String> getTextures(CompoundTag owner, UUID uuid) {
CompoundTag properties = owner.get("Properties"); CompoundTag properties = owner.get("Properties");
if (properties == null) { if (properties == null) {
return SkinProvider.requestTexturesFromUsername(owner); if (uuid != null && uuid.version() == 4) {
String uuidString = uuid.toString().replace("-", "");
return SkinProvider.requestTexturesFromUUID(uuidString);
} else if (owner.get("Name") instanceof StringTag nameTag) {
// Fall back to username if UUID was missing or was an offline mode UUID
return SkinProvider.requestTexturesFromUsername(nameTag.getValue());
}
return CompletableFuture.completedFuture(null);
} }
ListTag textures = properties.get("textures"); ListTag textures = properties.get("textures");
@ -66,20 +90,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements
StringTag texture = (StringTag) tag1.get("Value"); StringTag texture = (StringTag) tag1.get("Value");
return CompletableFuture.completedFuture(texture.getValue()); return CompletableFuture.completedFuture(texture.getValue());
} }
return CompletableFuture.completedFuture(null);
}
public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) { public static void translateSkull(GeyserSession session, CompoundTag tag, int posX, int posY, int posZ, int blockState) {
Vector3i blockPosition = Vector3i.from(posX, posY, posZ); Vector3i blockPosition = Vector3i.from(posX, posY, posZ);
getTextures(tag).whenComplete((texturesProperty, throwable) -> { CompoundTag owner = tag.get("SkullOwner");
if (texturesProperty == null) { if (owner == null) {
session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); session.getSkullCache().removeSkull(blockPosition);
return; return;
} }
UUID uuid = getUUID(owner);
getTextures(owner, uuid).whenComplete((texturesProperty, throwable) -> {
if (session.getEventLoop().inEventLoop()) { if (session.getEventLoop().inEventLoop()) {
session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState);
} else { } else {
session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState)); session.executeInEventLoop(() -> session.getSkullCache().putSkull(blockPosition, uuid, texturesProperty, blockState));
} }
}); });
} }

Datei anzeigen

@ -92,11 +92,29 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
if (isValidMove(session, entity.getPosition(), packet.getPosition())) { if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT); Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
if (position != null) { // A null return value cancels the packet if (position != null) { // A null return value cancels the packet
boolean onGround = packet.isOnGround();
boolean teleportThroughVoidFloor;
// Compare positions here for void floor fix below before the player's position variable is set to the packet position
if (entity.getPosition().getY() >= packet.getPosition().getY()) {
int floorY = position.getFloorY();
// The void floor is offset about 40 blocks below the bottom of the world
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
int voidFloorLocation = bedrockDimension.minY() - 40;
teleportThroughVoidFloor = floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation;
if (teleportThroughVoidFloor) {
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
onGround = false;
}
} else {
teleportThroughVoidFloor = false;
}
Packet movePacket; Packet movePacket;
if (rotationChanged) { if (rotationChanged) {
// Send rotation updates as well // Send rotation updates as well
movePacket = new ServerboundMovePlayerPosRotPacket( movePacket = new ServerboundMovePlayerPosRotPacket(
packet.isOnGround(), onGround,
position.getX(), position.getY(), position.getZ(), position.getX(), position.getY(), position.getZ(),
yaw, pitch yaw, pitch
); );
@ -105,24 +123,16 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
entity.setHeadYaw(headYaw); entity.setHeadYaw(headYaw);
} else { } else {
// Rotation did not change; don't send an update with rotation // Rotation did not change; don't send an update with rotation
movePacket = new ServerboundMovePlayerPosPacket(packet.isOnGround(), position.getX(), position.getY(), position.getZ()); movePacket = new ServerboundMovePlayerPosPacket(onGround, position.getX(), position.getY(), position.getZ());
} }
// Compare positions here for void floor fix below before the player's position variable is set to the packet position
boolean notMovingUp = entity.getPosition().getY() >= packet.getPosition().getY();
entity.setPositionManual(packet.getPosition()); entity.setPositionManual(packet.getPosition());
entity.setOnGround(packet.isOnGround()); entity.setOnGround(onGround);
// Send final movement changes // Send final movement changes
session.sendDownstreamPacket(movePacket); session.sendDownstreamPacket(movePacket);
if (notMovingUp) { if (teleportThroughVoidFloor) {
int floorY = position.getFloorY();
// The void floor is offset about 40 blocks below the bottom of the world
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
int voidFloorLocation = bedrockDimension.minY() - 40;
if (floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation) {
// Work around there being a floor at the bottom of the world and teleport the player below it // Work around there being a floor at the bottom of the world and teleport the player below it
// Moving from below to above the void floor works fine // Moving from below to above the void floor works fine
entity.setPosition(entity.getPosition().sub(0, 4f, 0)); entity.setPosition(entity.getPosition().sub(0, 4f, 0));
@ -134,7 +144,6 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
} }
}
session.getSkullCache().updateVisibleSkulls(); session.getSkullCache().updateVisibleSkulls();
} }

Datei anzeigen

@ -248,6 +248,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
case RESOURCE -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false); case RESOURCE -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), false);
case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true); case RESOURCE_OR_TAG -> handleResource(context, ((ResourceProperties) node.getProperties()).getRegistryKey(), true);
case DIMENSION -> context.session.getLevels(); case DIMENSION -> context.session.getLevels();
case TEAM -> context.getTeams(); // Note: as of Java 1.19.3, objectives are currently parsed from the server
default -> CommandParam.STRING; default -> CommandParam.STRING;
}; };
} }
@ -282,6 +283,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
private Object biomesNoTags; private Object biomesNoTags;
private String[] blockStates; private String[] blockStates;
private String[] entityTypes; private String[] entityTypes;
private CommandEnumData teams;
CommandBuilderContext(GeyserSession session) { CommandBuilderContext(GeyserSession session) {
this.session = session; this.session = session;
@ -318,6 +320,14 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
} }
return (entityTypes = Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0])); return (entityTypes = Registries.JAVA_ENTITY_IDENTIFIERS.get().keySet().toArray(new String[0]));
} }
private CommandEnumData getTeams() {
if (teams != null) {
return teams;
}
return (teams = new CommandEnumData("Geyser_Teams",
session.getWorldCache().getScoreboard().getTeamNames(), true));
}
} }
@Getter @Getter
@ -387,7 +397,10 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
CommandEnumData enumData = null; CommandEnumData enumData = null;
CommandParam type = null; CommandParam type = null;
boolean optional = this.paramNode.isExecutable(); boolean optional = this.paramNode.isExecutable();
if (mappedType instanceof String[]) { if (mappedType instanceof CommandEnumData) {
// Likely to specify isSoft, to be possibly updated later.
enumData = (CommandEnumData) mappedType;
} else if (mappedType instanceof String[]) {
enumData = new CommandEnumData(getEnumDataName(paramNode).toLowerCase(Locale.ROOT), (String[]) mappedType, false); enumData = new CommandEnumData(getEnumDataName(paramNode).toLowerCase(Locale.ROOT), (String[]) mappedType, false);
} else { } else {
type = (CommandParam) mappedType; type = (CommandParam) mappedType;

Datei anzeigen

@ -8,10 +8,10 @@ netty = "4.1.80.Final"
guava = "29.0-jre" guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8 gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1" websocket = "1.5.1"
protocol = "2.9.15-20221129.204554-2" protocol = "2.9.15-20230106.005737-3"
raknet = "1.6.28-20220125.214016-6" raknet = "1.6.28-20220125.214016-6"
mcauthlib = "d9d773e" mcauthlib = "d9d773e"
mcprotocollib = "1.19.3-20230104.210231-9" mcprotocollib = "1.19.3-20230107.194116-10"
packetlib = "3.0.1" packetlib = "3.0.1"
adventure = "4.12.0-20220629.025215-9" adventure = "4.12.0-20220629.025215-9"
adventure-platform = "4.1.2" adventure-platform = "4.1.2"