From 2a05dd57ffe8208fcf0ed903baa379d3e415e557 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 30 Mar 2022 22:30:49 -0400 Subject: [PATCH] Don't store GameProfile class of players This stores repetitive information, and also we don't currently use the signature, so it's wasted memory. --- .../entity/type/player/PlayerEntity.java | 20 +++++++---- .../type/player/SessionPlayerEntity.java | 3 +- .../entity/type/player/SkullPlayerEntity.java | 7 ++-- .../geyser/skin/FakeHeadProvider.java | 18 ++-------- .../org/geysermc/geyser/skin/SkinManager.java | 36 +++++++++---------- .../geysermc/geyser/skin/SkinProvider.java | 12 ++----- .../geyser/skin/SkullSkinManager.java | 2 +- .../entity/SkullBlockEntityTranslator.java | 28 +++++---------- .../player/JavaPlayerInfoTranslator.java | 18 +++++++--- .../entity/spawn/JavaAddPlayerTranslator.java | 4 ++- 10 files changed, 68 insertions(+), 80 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 70b5ede99..58f04a756 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; @@ -61,15 +60,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator; import javax.annotation.Nullable; import java.util.Collections; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; @Getter @Setter public class PlayerEntity extends LivingEntity { public static final float SNEAKING_POSE_HEIGHT = 1.5f; - private GameProfile profile; private String username; - private boolean playerList = true; // Player is in the player list + private boolean playerList = true; // Player is in the player list + + /** + * The textures property from the GameProfile. + */ + @Nullable + private String texturesProperty; private Vector3i bedPosition; @@ -82,11 +87,12 @@ public class PlayerEntity extends LivingEntity { */ private ParrotEntity rightParrot; - public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { - super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw); + 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); - profile = gameProfile; - username = gameProfile.getName(); + this.username = username; + this.texturesProperty = texturesProperty; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 077f82171..ae8d23810 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute; import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; @@ -71,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity { private int fakeTradeXp; public SessionPlayerEntity(GeyserSession session) { - super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0); + super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null); valid = true; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java index 847abf2a9..ce1615816 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SkullPlayerEntity.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.entity.type.player; -import com.github.steveice10.mc.auth.data.GameProfile; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.PlayerPermission; @@ -36,6 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import lombok.Getter; import org.geysermc.geyser.session.GeyserSession; +import java.util.UUID; + /** * A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no * custom player skulls in Bedrock. @@ -48,8 +49,8 @@ public class SkullPlayerEntity extends PlayerEntity { @Getter private final int blockState; - public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) { - super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation); + public SkullPlayerEntity(GeyserSession session, long geyserId, Vector3f position, float rotation, int blockState, String texturesProperty) { + super(session, 0, geyserId, UUID.randomUUID(), position, Vector3f.ZERO, rotation, 0, rotation, "", texturesProperty); this.blockState = blockState; setPlayerList(false); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 66354b494..6794af498 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.skin; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -106,7 +105,7 @@ public class FakeHeadProvider { session.getPlayerWithCustomHeads().add(entity.getUuid()); - GameProfile.Property texturesProperty = entity.getProfile().getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); SkinProvider.EXECUTOR_SERVICE.execute(() -> { try { @@ -182,7 +181,7 @@ public class FakeHeadProvider { @Getter @Setter private static class FakeHeadEntry { - private final GameProfile.Property texturesProperty; + private final String texturesProperty; private final String fakeHeadSkinUrl; private PlayerEntity entity; @@ -192,18 +191,7 @@ public class FakeHeadProvider { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FakeHeadEntry that = (FakeHeadEntry) o; - return equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); - } - - private boolean equals(GameProfile.Property a, GameProfile.Property b) { - //TODO actually fix this in MCAuthLib - if (a == b) { - return true; - } - if (a == null || b == null) { - return false; - } - return Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getValue(), b.getValue()) && Objects.equals(a.getSignature(), b.getSignature()); + return Objects.equals(texturesProperty, that.texturesProperty) && Objects.equals(fakeHeadSkinUrl, that.fakeHeadSkinUrl); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 4269110f5..4eb92c3ac 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; @@ -34,9 +33,9 @@ import com.nukkitx.protocol.bedrock.data.skin.ImageData; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.auth.AuthType; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.text.GeyserLocale; @@ -54,7 +53,7 @@ public class SkinManager { * Builds a Bedrock player list entry from our existing, cached Bedrock skin information */ public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { - GameProfileData data = GameProfileData.from(playerEntity.getProfile()); + GameProfileData data = GameProfileData.from(playerEntity); SkinProvider.Cape cape = SkinProvider.getCachedCape(data.capeUrl()); SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); @@ -65,8 +64,8 @@ public class SkinManager { return buildEntryManually( session, - playerEntity.getProfile().getId(), - playerEntity.getProfile().getName(), + playerEntity.getUuid(), + playerEntity.getUsername(), playerEntity.getGeyserId(), skin.getTextureUrl(), skin.getSkinData(), @@ -227,31 +226,31 @@ public class SkinManager { } /** - * Generate the GameProfileData from the given GameProfile + * Generate the GameProfileData from the given player entity * - * @param profile GameProfile to build the GameProfileData from + * @param entity entity to build the GameProfileData from * @return The built GameProfileData */ - public static GameProfileData from(GameProfile profile) { + public static GameProfileData from(PlayerEntity entity) { try { - GameProfile.Property skinProperty = profile.getProperty("textures"); + String texturesProperty = entity.getTexturesProperty(); - if (skinProperty == null) { + if (texturesProperty == null) { // Likely offline mode - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } - GameProfileData data = loadFromJson(skinProperty.getValue()); + GameProfileData data = loadFromJson(texturesProperty); if (data != null) { return data; } else { - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } catch (IOException exception) { - GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName()); + GeyserImpl.getInstance().getLogger().debug("Something went wrong while processing skin for " + entity.getUsername()); if (GeyserImpl.getInstance().getConfig().isDebugMode()) { exception.printStackTrace(); } - return loadBedrockOrOfflineSkin(profile); + return loadBedrockOrOfflineSkin(entity); } } @@ -280,14 +279,15 @@ public class SkinManager { * @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this * is a Bedrock player. */ - private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) { + private static GameProfileData loadBedrockOrOfflineSkin(PlayerEntity entity) { // Fallback to the offline mode of working it out - boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1); + UUID uuid = entity.getUuid(); + boolean isAlex = (Math.abs(uuid.hashCode() % 2) == 1); String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserImpl.getInstance().getConfig().getRemote().getAuthType() != AuthType.ONLINE) { - GeyserSession session = GeyserImpl.getInstance().connectionByUuid(profile.getId()); + GeyserSession session = GeyserImpl.getInstance().connectionByUuid(uuid); if (session != null) { skinUrl = session.getClientData().getSkinId(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 282f6875a..43cf30b47 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.skin; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; import com.github.steveice10.opennbt.tag.builtin.Tag; @@ -53,7 +52,6 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.List; import java.util.*; import java.util.concurrent.*; import java.util.function.Predicate; @@ -157,7 +155,7 @@ public class SkinProvider { } public static CompletableFuture requestSkinData(PlayerEntity entity) { - SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity.getProfile()); + SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); return requestSkinAndCape(entity.getUuid(), data.skinUrl(), data.capeUrl()) .thenApplyAsync(skinAndCape -> { @@ -546,12 +544,11 @@ public class SkinProvider { * @param skullOwner the CompoundTag of the skull with no textures * @return a completable GameProfile with textures included */ - public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { + public static CompletableFuture requestTexturesFromUsername(CompoundTag skullOwner) { return CompletableFuture.supplyAsync(() -> { Tag uuidTag = skullOwner.get("Id"); String uuidToString = ""; JsonNode node; - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); boolean retrieveUuidFromInternet = !(uuidTag instanceof IntArrayTag); // also covers null check if (!retrieveUuidFromInternet) { @@ -577,15 +574,12 @@ public class SkinProvider { // Get textures from UUID node = WebUtils.getJson("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidToString); - List profileProperties = new ArrayList<>(); JsonNode properties = node.get("properties"); if (properties == null) { GeyserImpl.getInstance().getLogger().debug("No properties found in Mojang response for " + uuidToString); return null; } - profileProperties.add(new GameProfile.Property("textures", node.get("properties").get(0).get("value").asText())); - gameProfile.setProperties(profileProperties); - return gameProfile; + return node.get("properties").get(0).get("value").asText(); } catch (Exception e) { if (GeyserImpl.getInstance().getConfig().isDebugMode()) { e.printStackTrace(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 009d98c87..58054e9c5 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -50,7 +50,7 @@ public class SkullSkinManager extends SkinManager { public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, Consumer skinConsumer) { - GameProfileData data = GameProfileData.from(entity.getProfile()); + GameProfileData data = GameProfileData.from(entity); SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) .whenCompleteAsync((skin, throwable) -> { diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 8fc8732f7..50d79c10f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.translator.level.block.entity; -import com.github.steveice10.mc.auth.data.GameProfile; 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.ListTag; @@ -35,15 +34,12 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.skin.SkullSkinManager; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -62,7 +58,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements builder.put("SkullType", skullVariant); } - public static CompletableFuture getProfile(CompoundTag tag) { + private static CompletableFuture getTextures(CompoundTag tag) { CompoundTag owner = tag.get("SkullOwner"); if (owner != null) { CompoundTag properties = owner.get("Properties"); @@ -73,13 +69,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements ListTag textures = properties.get("textures"); LinkedHashMap tag1 = (LinkedHashMap) textures.get(0).getValue(); StringTag texture = (StringTag) tag1.get("Value"); - - List profileProperties = new ArrayList<>(); - - GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); - profileProperties.add(new GameProfile.Property("textures", texture.getValue())); - gameProfile.setProperties(profileProperties); - return CompletableFuture.completedFuture(gameProfile); + return CompletableFuture.completedFuture(texture.getValue()); } return CompletableFuture.completedFuture(null); } @@ -108,21 +98,21 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements Vector3i blockPosition = Vector3i.from(posX, posY, posZ); Vector3f entityPosition = Vector3f.from(x, y, z); - getProfile(tag).whenComplete((gameProfile, throwable) -> { - if (gameProfile == null) { + getTextures(tag).whenComplete((texturesProperty, throwable) -> { + if (texturesProperty == null) { session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); return; } if (session.getEventLoop().inEventLoop()) { - spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState); + spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState); } else { - session.executeInEventLoop(() -> spawnPlayer(session, gameProfile, blockPosition, entityPosition, rotation, blockState)); + session.executeInEventLoop(() -> spawnPlayer(session, texturesProperty, blockPosition, entityPosition, rotation, blockState)); } }); } - private static void spawnPlayer(GeyserSession session, GameProfile profile, Vector3i blockPosition, + private static void spawnPlayer(GeyserSession session, String texturesProperty, Vector3i blockPosition, Vector3f entityPosition, float rotation, int blockState) { long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); @@ -132,7 +122,7 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements existingSkull.despawnEntity(blockPosition); } - SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, profile, entityPosition, rotation, blockState); + SkullPlayerEntity player = new SkullPlayerEntity(session, geyserId, entityPosition, rotation, blockState, texturesProperty); // Cache entity session.getSkullCache().put(blockPosition, player); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java index fd9e5887d..993da7746 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerInfoTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.entity.player; +import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket; @@ -50,31 +51,38 @@ public class JavaPlayerInfoTranslator extends PacketTranslator { + GameProfile profile = entry.getProfile(); PlayerEntity playerEntity; - boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); + boolean self = profile.getId().equals(session.getPlayerEntity().getUuid()); if (self) { // Entity is ourself playerEntity = session.getPlayerEntity(); } else { - playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + playerEntity = session.getEntityCache().getPlayerEntity(profile.getId()); } + GameProfile.Property textures = profile.getProperty("textures"); + String texturesProperty = textures == null ? null : textures.getValue(); + if (playerEntity == null) { // It's a new player playerEntity = new PlayerEntity( session, -1, session.getEntityCache().getNextEntityId().incrementAndGet(), - entry.getProfile(), + profile.getId(), Vector3f.ZERO, Vector3f.ZERO, - 0, 0, 0 + 0, 0, 0, + profile.getName(), + texturesProperty ); session.getEntityCache().addPlayerEntity(playerEntity); } else { - playerEntity.setProfile(entry.getProfile()); + playerEntity.setUsername(profile.getName()); + playerEntity.setTexturesProperty(texturesProperty); } playerEntity.setPlayerList(true); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java index f0b1b4874..c54b75f4f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddPlayerTranslator.java @@ -48,7 +48,9 @@ public class JavaAddPlayerTranslator extends PacketTranslator