diff --git a/connector/pom.xml b/connector/pom.xml index e344370fc..25d52a007 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -75,7 +75,7 @@ com.nukkitx.protocol bedrock-v361 - 2.1.2 + 2.3.0 compile diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 5962ab08a..b78fc2279 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -34,7 +34,6 @@ import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @Getter public class GeyserConfiguration { - private BedrockConfiguration bedrock; private RemoteConfiguration remote; @@ -52,5 +51,8 @@ public class GeyserConfiguration { @JsonProperty("general-thread-pool") private int generalThreadPool; + @JsonProperty("allow-third-party-capes") + private boolean allowThirdPartyCapes; + private MetricInfo metrics; } diff --git a/connector/src/main/java/org/geysermc/connector/console/GeyserLogger.java b/connector/src/main/java/org/geysermc/connector/console/GeyserLogger.java index db31bfc18..0ff122ea2 100644 --- a/connector/src/main/java/org/geysermc/connector/console/GeyserLogger.java +++ b/connector/src/main/java/org/geysermc/connector/console/GeyserLogger.java @@ -25,10 +25,11 @@ package org.geysermc.connector.console; -import org.geysermc.api.ChatColor; import io.sentry.Sentry; +import org.geysermc.api.ChatColor; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.Date; import java.util.logging.*; @@ -108,7 +109,7 @@ public class GeyserLogger implements org.geysermc.api.logger.Logger { @Override public void error(String message, Throwable error) { waitFor(); - System.out.println(printConsole(ChatColor.RED + message + "\n" + error.getMessage(), colored)); + System.out.println(printConsole(ChatColor.RED + message + "\n" + error, colored)); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2b2ad0544..af8e4e31a 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -25,8 +25,8 @@ package org.geysermc.connector.entity; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityPropertiesPacket; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityDataDictionary; import com.nukkitx.protocol.bedrock.data.EntityFlag; @@ -49,7 +49,6 @@ import java.util.*; @Getter @Setter public class Entity { - protected long entityId; protected long geyserId; @@ -58,7 +57,9 @@ public class Entity { protected Vector3f position; protected Vector3f motion; - // 1 - pitch, 2 - yaw, 3 - roll (head yaw) + /** + * x = Yaw, y = Pitch, z = HeadYaw + */ protected Vector3f rotation; protected int scale = 1; @@ -90,7 +91,7 @@ public class Entity { addEntityPacket.setUniqueEntityId(geyserId); addEntityPacket.setPosition(position); addEntityPacket.setMotion(motion); - addEntityPacket.setRotation(rotation); + addEntityPacket.setRotation(getBedrockRotation()); addEntityPacket.setEntityType(entityType.getType()); addEntityPacket.getMetadata().putAll(getMetadata()); @@ -100,36 +101,37 @@ public class Entity { GeyserLogger.DEFAULT.debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } - public void despawnEntity(GeyserSession session) { - if (!valid) return; + /** + * @return can be deleted + */ + public boolean despawnEntity(GeyserSession session) { + if (!valid) return true; RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); removeEntityPacket.setUniqueEntityId(geyserId); session.getUpstream().sendPacket(removeEntityPacket); valid = false; + return true; } - public void moveRelative(double relX, double relY, double relZ, float pitch, float yaw) { - moveRelative(relX, relY, relZ, new Vector3f(pitch, yaw, 0)); + public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch) { + moveRelative(relX, relY, relZ, Vector3f.from(yaw, pitch, yaw)); } public void moveRelative(double relX, double relY, double relZ, Vector3f rotation) { - if (relX == 0 && relY == 0 && relZ == 0 && position.getX() == 0 && position.getY() == 0) - return; - - this.rotation = rotation; - this.position = new Vector3f(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); + setRotation(rotation); + this.position = Vector3f.from(position.getX() + relX, position.getY() + relY, position.getZ() + relZ); this.movePending = true; } - public void moveAbsolute(Vector3f position, float pitch, float yaw) { - moveAbsolute(position, new Vector3f(pitch, yaw, 0)); + public void moveAbsolute(Vector3f position, float yaw, float pitch) { + moveAbsolute(position, Vector3f.from(yaw, pitch, yaw)); } public void moveAbsolute(Vector3f position, Vector3f rotation) { - this.position = position; - this.rotation = rotation; + setPosition(position); + setRotation(rotation); this.movePending = true; } @@ -138,14 +140,13 @@ public class Entity { flags.setFlag(EntityFlag.HAS_GRAVITY, true); flags.setFlag(EntityFlag.HAS_COLLISION, true); flags.setFlag(EntityFlag.CAN_SHOW_NAME, true); - flags.setFlag(EntityFlag.NO_AI, false); + flags.setFlag(EntityFlag.CAN_CLIMB, true); EntityDataDictionary dictionary = new EntityDataDictionary(); - dictionary.put(EntityData.NAMETAG, ""); - dictionary.put(EntityData.ENTITY_AGE, 0); dictionary.put(EntityData.SCALE, 1f); dictionary.put(EntityData.MAX_AIR, (short) 400); dictionary.put(EntityData.AIR, (short) 0); + dictionary.put(EntityData.LEAD_HOLDER_EID, -1L); dictionary.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); dictionary.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); dictionary.putFlags(flags); @@ -185,4 +186,28 @@ public class Entity { ServerEntityPropertiesPacket entityPropertiesPacket = new ServerEntityPropertiesPacket((int) entityId, attributes); session.getDownstream().getSession().send(entityPropertiesPacket); } + + public void setPosition(Vector3f position) { + if (is(PlayerEntity.class)) { + this.position = position.add(0, entityType.getOffset(), 0); + return; + } + this.position = position; + } + + /** + * x = Pitch, y = HeadYaw, z = Yaw + */ + public Vector3f getBedrockRotation() { + return Vector3f.from(rotation.getY(), rotation.getZ(), rotation.getX()); + } + + @SuppressWarnings("unchecked") + public I as(Class entityClass) { + return entityClass.isInstance(this) ? (I) this : null; + } + + public boolean is(Class entityClass) { + return entityClass.isInstance(this); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java index 036829e8a..89c1532f2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ExpOrbEntity.java @@ -25,7 +25,7 @@ package org.geysermc.connector.entity; -import com.flowpowered.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.SpawnExperienceOrbPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java index 6b1b1c117..f9f6e37fd 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PaintingEntity.java @@ -1,6 +1,6 @@ package org.geysermc.connector.entity; -import com.flowpowered.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.AddPaintingPacket; import lombok.Getter; import lombok.Setter; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index ff3a2ed4d..67393114c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -25,35 +25,42 @@ package org.geysermc.connector.entity; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.auth.data.GameProfile; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import lombok.Getter; import lombok.Setter; +import org.geysermc.api.Geyser; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.SkinUtils; import java.util.UUID; @Getter @Setter public class PlayerEntity extends Entity { + private GameProfile profile; private UUID uuid; private String username; - - private ItemData hand; + private long lastSkinUpdate = -1; + private boolean playerList = true; private ItemData helmet; private ItemData chestplate; private ItemData leggings; private ItemData boots; + private ItemData hand = ItemData.of(0, (short) 0, 0); public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); + profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + if (geyserId == 1) valid = true; } // TODO: Break this into an EquippableEntity class @@ -70,27 +77,62 @@ public class PlayerEntity extends Entity { session.getUpstream().sendPacket(armorEquipmentPacket); } + @Override + public boolean despawnEntity(GeyserSession session) { + super.despawnEntity(session); + return !playerList; // don't remove from cache when still on playerlist + } + @Override public void spawnEntity(GeyserSession session) { + if (geyserId == 1) return; + AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); - addPlayerPacket.setRuntimeEntityId(geyserId); - addPlayerPacket.setUniqueEntityId(geyserId); addPlayerPacket.setUuid(uuid); addPlayerPacket.setUsername(username); - addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.setRuntimeEntityId(geyserId); + addPlayerPacket.setUniqueEntityId(geyserId); addPlayerPacket.setPosition(position); + addPlayerPacket.setRotation(getBedrockRotation()); addPlayerPacket.setMotion(motion); - addPlayerPacket.setRotation(rotation); addPlayerPacket.setHand(hand); - addPlayerPacket.getMetadata().putAll(getMetadata()); addPlayerPacket.setPlayerFlags(0); addPlayerPacket.setCommandPermission(0); addPlayerPacket.setWorldFlags(0); addPlayerPacket.setPlayerPermission(0); addPlayerPacket.setCustomFlags(0); - addPlayerPacket.setDeviceId("WIN10"); + addPlayerPacket.setDeviceId(""); + addPlayerPacket.setPlatformChatId(""); + addPlayerPacket.getMetadata().putAll(getMetadata()); valid = true; session.getUpstream().sendPacket(addPlayerPacket); } + + public void sendPlayer(GeyserSession session) { + if (getLastSkinUpdate() == -1) { + if (playerList) { + PlayerListPacket playerList = new PlayerListPacket(); + playerList.setType(PlayerListPacket.Type.ADD); + playerList.getEntries().add(SkinUtils.buildDefaultEntry(profile, geyserId)); + session.getUpstream().sendPacket(playerList); + } + } + + if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) { + session.getEntityCache().spawnEntity(this); + } else { + spawnEntity(session); + } + + if (!playerList) { + // remove from playerlist if player isn't on playerlist + Geyser.getGeneralThreadPool().execute(() -> { + PlayerListPacket playerList = new PlayerListPacket(); + playerList.setType(PlayerListPacket.Type.REMOVE); + playerList.getEntries().add(new PlayerListPacket.Entry(uuid)); + session.getUpstream().sendPacket(playerList); + }); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 02611f2f6..c4e6f92b5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -40,8 +40,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean translateAndDefault(BedrockPacket packet) { - Registry.BEDROCK.translate(packet.getClass(), packet, session); - return defaultHandler(packet); + return Registry.BEDROCK.translate(packet.getClass(), packet, session); } @Override @@ -66,7 +65,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ResourcePackClientResponsePacket textPacket) { - connector.getLogger().debug("Handled " + textPacket.getClass().getSimpleName()); switch (textPacket.getStatus()) { case COMPLETED: session.connect(connector.getRemoteServer()); @@ -88,7 +86,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - connector.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName()); return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormData()); } @@ -111,7 +108,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { - connector.getLogger().debug("Handled packet: " + packet.getClass().getSimpleName()); if (!session.isLoggedIn() && !session.isLoggingIn()) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthenticationData().getName())) { @@ -119,7 +115,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } // else we were able to log the user in return true; - } else if (session.isLoggingIn()) { + } + if (session.isLoggingIn()) { session.sendMessage("Please wait until you are logged in..."); } @@ -127,27 +124,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } @Override - public boolean handle(AnimatePacket packet) { - return translateAndDefault(packet); - } - - @Override - public boolean handle(CommandRequestPacket packet) { - return translateAndDefault(packet); - } - - @Override - public boolean handle(TextPacket packet) { - return translateAndDefault(packet); - } - - @Override - public boolean handle(MobEquipmentPacket packet) { - return translateAndDefault(packet); - } - - @Override - public boolean handle(PlayerActionPacket packet) { + boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a4cf425d2..b907187d9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -25,10 +25,6 @@ package org.geysermc.connector.network.session; -import com.flowpowered.math.vector.Vector2f; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector3i; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; @@ -40,6 +36,10 @@ import com.github.steveice10.packetlib.event.session.PacketReceivedEvent; import com.github.steveice10.packetlib.event.session.SessionAdapter; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpSessionFactory; +import com.nukkitx.math.vector.Vector2f; +import com.nukkitx.math.vector.Vector2i; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.GamePublishSetting; import com.nukkitx.protocol.bedrock.data.GameRule; @@ -54,7 +54,6 @@ import org.geysermc.api.session.AuthData; import org.geysermc.api.window.FormWindow; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.Registry; @@ -67,7 +66,7 @@ import java.util.UUID; public class GeyserSession implements Player { private final GeyserConnector connector; - private final BedrockServerSession upstream; + private final UpstreamSession upstream; private RemoteServer remoteServer; private Client downstream; private AuthData authenticationData; @@ -102,7 +101,7 @@ public class GeyserSession implements Player { public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; - this.upstream = bedrockServerSession; + this.upstream = new UpstreamSession(bedrockServerSession); this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); @@ -110,7 +109,7 @@ public class GeyserSession implements Player { this.scoreboardCache = new ScoreboardCache(this); this.windowCache = new WindowCache(this); - this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), -1, 1, new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), new Vector3f(0, 0, 0)); + this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); this.inventory = new PlayerInventory(); this.javaPacketCache = new DataCache<>(); @@ -176,8 +175,9 @@ public class GeyserSession implements Player { @Override public void packetReceived(PacketReceivedEvent event) { - if (!closed) + if (!closed) { Registry.JAVA.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); + } } }); @@ -257,15 +257,15 @@ public class GeyserSession implements Player { startGamePacket.setUniqueEntityId(playerEntity.getGeyserId()); startGamePacket.setRuntimeEntityId(playerEntity.getGeyserId()); startGamePacket.setPlayerGamemode(0); - startGamePacket.setPlayerPosition(new Vector3f(0, 69, 0)); - startGamePacket.setRotation(new Vector2f(1, 1)); + startGamePacket.setPlayerPosition(Vector3f.from(0, 69, 0)); + startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(0); startGamePacket.setDimensionId(playerEntity.getDimension()); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGamemode(0); startGamePacket.setDifficulty(1); - startGamePacket.setDefaultSpawn(new Vector3i(0, 0, 0)); + startGamePacket.setDefaultSpawn(Vector3i.ZERO); startGamePacket.setAcheivementsDisabled(true); startGamePacket.setTime(0); startGamePacket.setEduLevel(false); @@ -297,7 +297,7 @@ public class GeyserSession implements Player { startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setCachedPalette(Toolbox.CACHED_PALLETE.copy()); + startGamePacket.setCachedPalette(Toolbox.CACHED_PALLETE.retainedDuplicate()); startGamePacket.setItemEntries(Toolbox.ITEMS); upstream.sendPacket(startGamePacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java new file mode 100644 index 000000000..f8691bea6 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java @@ -0,0 +1,37 @@ +package org.geysermc.connector.network.session; + +import com.nukkitx.protocol.bedrock.BedrockPacket; +import com.nukkitx.protocol.bedrock.BedrockServerSession; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.net.InetSocketAddress; + +@RequiredArgsConstructor +public class UpstreamSession { + @Getter private final BedrockServerSession session; + @Getter @Setter + private boolean initialized = false; + + public void sendPacket(@NonNull BedrockPacket packet) { + session.sendPacket(packet); + } + + public void sendPacketImmediately(@NonNull BedrockPacket packet) { + session.sendPacketImmediately(packet); + } + + public void disconnect(String reason) { + session.disconnect(reason); + } + + public boolean isClosed() { + return session.isClosed(); + } + + public InetSocketAddress getAddress() { + return session.getAddress(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index a619533d3..3bb702db4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -30,9 +30,7 @@ import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; /** @@ -46,6 +44,7 @@ public class EntityCache { private Map entities = new HashMap<>(); private Map entityIdTranslations = new HashMap<>(); private Map playerEntities = new HashMap<>(); + private Map bossbars = new HashMap<>(); @Getter private AtomicLong nextEntityId = new AtomicLong(2L); @@ -61,17 +60,18 @@ public class EntityCache { entity.spawnEntity(session); } - public void removeEntity(Entity entity) { - if (entity == null) return; - - Long geyserId = entityIdTranslations.remove(entity.getEntityId()); - if (geyserId != null) { - entities.remove(geyserId); - if (entity instanceof PlayerEntity) { - playerEntities.remove(((PlayerEntity) entity).getUuid()); + public boolean removeEntity(Entity entity, boolean force) { + if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) { + Long geyserId = entityIdTranslations.remove(entity.getEntityId()); + if (geyserId != null) { + entities.remove(geyserId); + if (entity.is(PlayerEntity.class)) { + playerEntities.remove(entity.as(PlayerEntity.class).getUuid()); + } } + return true; } - entity.despawnEntity(session); + return false; } public Entity getEntityByGeyserId(long geyserId) { @@ -82,6 +82,16 @@ public class EntityCache { return entities.get(entityIdTranslations.get(javaId)); } + public Set getEntitiesByType(Class entityType) { + Set entitiesOfType = new HashSet<>(); + for (Entity entity : (entityType == PlayerEntity.class ? playerEntities : entities).values()) { + if (entity.is(entityType)) { + entitiesOfType.add(entity.as(entityType)); + } + } + return entitiesOfType; + } + public void addPlayerEntity(PlayerEntity entity) { playerEntities.put(entity.getUuid(), entity); } @@ -93,4 +103,18 @@ public class EntityCache { public void removePlayerEntity(UUID uuid) { playerEntities.remove(uuid); } + + public long addBossBar(UUID uuid) { + long entityId = getNextEntityId().incrementAndGet(); + bossbars.put(uuid, entityId); + return entityId; + } + + public long getBossBar(UUID uuid) { + return bossbars.containsKey(uuid) ? bossbars.get(uuid) : -1; + } + + public long removeBossBar(UUID uuid) { + return bossbars.remove(uuid); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ScoreboardCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ScoreboardCache.java index 147a5355d..c24a0a9d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ScoreboardCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ScoreboardCache.java @@ -25,27 +25,31 @@ package org.geysermc.connector.network.session.cache; -import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import lombok.Getter; -import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; -public class ScoreboardCache { +import java.util.Collection; +@Getter +public class ScoreboardCache { private GeyserSession session; + private Scoreboard scoreboard; public ScoreboardCache(GeyserSession session) { this.session = session; + this.scoreboard = new Scoreboard(session); } - @Getter - @Setter - private Scoreboard scoreboard; - public void removeScoreboard() { - RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); - removeObjectivePacket.setObjectiveId(scoreboard.getObjective().getObjectiveName()); - session.getUpstream().sendPacket(removeObjectivePacket); + if (scoreboard != null) { + Collection objectives = scoreboard.getObjectives().values(); + scoreboard = new Scoreboard(session); + + for (Objective objective : objectives) { + scoreboard.despawnObjective(objective); + } + } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java b/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java index a4efde575..3867c8b52 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/Registry.java @@ -34,7 +34,6 @@ import java.util.HashMap; import java.util.Map; public class Registry { - private final Map, PacketTranslator> MAP = new HashMap<>(); public static final Registry JAVA = new Registry<>(); @@ -48,14 +47,18 @@ public class Registry { BEDROCK.MAP.put(clazz, translator); } - public

void translate(Class clazz, P packet, GeyserSession session) { - try { - if (MAP.containsKey(clazz)) { - ((PacketTranslator

) MAP.get(clazz)).translate(packet, session); + public

boolean translate(Class clazz, P packet, GeyserSession session) { + if (!session.getUpstream().isClosed() && !session.isClosed()) { + try { + if (MAP.containsKey(clazz)) { + ((PacketTranslator

) MAP.get(clazz)).translate(packet, session); + return true; + } + } catch (Throwable ex) { + GeyserLogger.DEFAULT.error("Could not translate packet " + packet.getClass().getSimpleName(), ex); + ex.printStackTrace(); } - } catch (NullPointerException ex) { - GeyserLogger.DEFAULT.debug("Could not translate packet " + packet.getClass().getSimpleName()); - ex.printStackTrace(); } + return false; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java index 7481f2fa0..242bcda4b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/TranslatorsInit.java @@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.Serv import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.*; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; @@ -52,14 +53,12 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.java.*; import org.geysermc.connector.network.translators.java.entity.*; -import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerActionAckTranslator; -import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerHealthTranslator; -import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerPositionRotationTranslator; -import org.geysermc.connector.network.translators.java.entity.player.JavaPlayerSetExperienceTranslator; +import org.geysermc.connector.network.translators.java.entity.player.*; import org.geysermc.connector.network.translators.java.entity.spawn.*; import org.geysermc.connector.network.translators.java.inventory.OpenWindowPacketTranslator; import org.geysermc.connector.network.translators.java.scoreboard.JavaDisplayScoreboardTranslator; import org.geysermc.connector.network.translators.java.scoreboard.JavaScoreboardObjectiveTranslator; +import org.geysermc.connector.network.translators.java.scoreboard.JavaTeamTranslator; import org.geysermc.connector.network.translators.java.scoreboard.JavaUpdateScoreTranslator; import org.geysermc.connector.network.translators.java.window.JavaOpenWindowTranslator; import org.geysermc.connector.network.translators.java.window.JavaSetSlotTranslator; @@ -115,6 +114,7 @@ public class TranslatorsInit { Registry.registerJava(ServerEntityRotationPacket.class, new JavaEntityRotationTranslator()); Registry.registerJava(ServerEntityHeadLookPacket.class, new JavaEntityHeadLookTranslator()); Registry.registerJava(ServerEntityMetadataPacket.class, new JavaEntityMetadataTranslator()); + Registry.registerJava(ServerBossBarPacket.class, new JavaBossBarTranslator()); Registry.registerJava(ServerSpawnExpOrbPacket.class, new JavaSpawnExpOrbTranslator()); Registry.registerJava(ServerSpawnGlobalEntityPacket.class, new JavaSpawnGlobalEntityTranslator()); @@ -138,6 +138,7 @@ public class TranslatorsInit { Registry.registerJava(ServerScoreboardObjectivePacket.class, new JavaScoreboardObjectiveTranslator()); Registry.registerJava(ServerDisplayScoreboardPacket.class, new JavaDisplayScoreboardTranslator()); Registry.registerJava(ServerUpdateScorePacket.class, new JavaUpdateScoreTranslator()); + Registry.registerJava(ServerTeamPacket.class, new JavaTeamTranslator()); Registry.registerJava(ServerBlockChangePacket.class, new JavaBlockChangeTranslator()); Registry.registerJava(ServerMultiBlockChangePacket.class, new JavaMultiBlockChangeTranslator()); @@ -146,11 +147,12 @@ public class TranslatorsInit { Registry.registerBedrock(AnimatePacket.class, new BedrockAnimateTranslator()); Registry.registerBedrock(CommandRequestPacket.class, new BedrockCommandRequestTranslator()); - Registry.registerBedrock(TextPacket.class, new BedrockTextTranslator()); - Registry.registerBedrock(MobEquipmentPacket.class, new BedrockMobEquipmentTranslator()); - Registry.registerBedrock(PlayerActionPacket.class, new BedrockActionTranslator()); - Registry.registerBedrock(MovePlayerPacket.class, new BedrockMovePlayerTranslator()); Registry.registerBedrock(InventoryTransactionPacket.class, new BedrockInventoryTransactionTranslator()); + Registry.registerBedrock(MobEquipmentPacket.class, new BedrockMobEquipmentTranslator()); + Registry.registerBedrock(MovePlayerPacket.class, new BedrockMovePlayerTranslator()); + Registry.registerBedrock(PlayerActionPacket.class, new BedrockActionTranslator()); + Registry.registerBedrock(SetLocalPlayerAsInitializedPacket.class, new BedrockPlayerInitializedTranslator()); + Registry.registerBedrock(TextPacket.class, new BedrockTextTranslator()); itemTranslator = new ItemTranslator(); blockTranslator = new BlockTranslator(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java index fdab51ba6..b8382b8ce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.bedrock; -import com.flowpowered.math.vector.Vector3i; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; @@ -34,6 +33,7 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java index 2fad8c219..9b2060183 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInteractTranslator.java @@ -25,21 +25,11 @@ package org.geysermc.connector.network.translators.bedrock; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector3i; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.InteractPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; -import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java index d74bb8337..e4ab1b1d3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMovePlayerTranslator.java @@ -25,39 +25,58 @@ package org.geysermc.connector.network.translators.bedrock; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.item.BedrockItem; public class BedrockMovePlayerTranslator extends PacketTranslator { - @Override public void translate(MovePlayerPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); + PlayerEntity entity = session.getPlayerEntity(); if (entity == null || !session.isSpawned()) return; + // can cause invalid moves when packet queue is not empty + if (!session.getUpstream().isInitialized()) { + MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket(); + moveEntityBack.setRuntimeEntityId(entity.getGeyserId()); + moveEntityBack.setPosition(entity.getPosition()); + moveEntityBack.setRotation(entity.getBedrockRotation()); + moveEntityBack.setTeleported(true); + moveEntityBack.setOnGround(true); + session.getUpstream().sendPacketImmediately(moveEntityBack); + return; + } + if (!isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) { session.getConnector().getLogger().info("Recalculating position..."); recalculatePosition(session, entity, entity.getPosition()); return; } - ClientPlayerPositionRotationPacket playerPositionRotationPacket = new ClientPlayerPositionRotationPacket( - packet.isOnGround(), packet.getPosition().getX(), Math.ceil((packet.getPosition().getY() - EntityType.PLAYER.getOffset()) * 2) / 2, - packet.getPosition().getZ(), packet.getRotation().getY(), packet.getRotation().getX()); + double javaY = packet.getPosition().getY() - EntityType.PLAYER.getOffset(); - entity.moveAbsolute(packet.getPosition(), packet.getRotation()); + ClientPlayerPositionRotationPacket playerPositionRotationPacket = new ClientPlayerPositionRotationPacket( + packet.isOnGround(), packet.getPosition().getX(), Math.ceil(javaY * 2) / 2, + packet.getPosition().getZ(), packet.getRotation().getY(), packet.getRotation().getX() + ); + + // head yaw, pitch, head yaw + Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY()); + + entity.moveAbsolute(packet.getPosition().sub(0, EntityType.PLAYER.getOffset(), 0), rotation); boolean colliding = false; Position position = new Position((int) packet.getPosition().getX(), - (int) Math.ceil((packet.getPosition().getY() - EntityType.PLAYER.getOffset()) * 2) / 2, (int) packet.getPosition().getZ()); + (int) Math.ceil(javaY * 2) / 2, (int) packet.getPosition().getZ()); BedrockItem block = session.getChunkCache().getBlockAt(position); if (!block.getIdentifier().contains("air")) @@ -82,7 +101,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator 10 || yRange > 10 || zRange > 10) { + if ((xRange + yRange + zRange) > 100) { session.getConnector().getLogger().warning(session.getName() + " moved too quickly." + " current position: " + currentPosition + ", new position: " + newPosition); @@ -102,7 +121,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator { + @Override + public void translate(SetLocalPlayerAsInitializedPacket packet, GeyserSession session) { + if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) { + if (!session.getUpstream().isInitialized()) { + session.getUpstream().setInitialized(true); + + for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) { + if (!entity.isValid()) { + entity.sendPlayer(session); + // async skin loading + SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session)); + } + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index 596bd3896..ef22b6305 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -12,7 +12,7 @@ public class BlockTranslator { public BedrockItem getBedrockBlock(BlockState state) { BedrockItem bedrockItem = Remapper.BLOCK_REMAPPER.convertToBedrockB(new ItemStack(state.getId())); if (bedrockItem == null) { - GeyserLogger.DEFAULT.debug("Missing mapping for java block " + state.getId() + "/nPlease report this to Geyser."); + GeyserLogger.DEFAULT.debug("Missing mapping for java block " + state.getId() + "\nPlease report this to Geyser."); return BedrockItem.DIRT; // so we can walk and not getting stuck x) } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java index b6f36ac05..862117f92 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java @@ -25,8 +25,7 @@ package org.geysermc.connector.network.translators.inventory; -import com.flowpowered.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; @@ -34,7 +33,6 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.TranslatorsInit; -import org.geysermc.connector.utils.InventoryUtils; public class GenericInventoryTranslator extends InventoryTranslator { @@ -48,35 +46,27 @@ public class GenericInventoryTranslator extends InventoryTranslator { ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); containerOpenPacket.setWindowId((byte) inventory.getId()); containerOpenPacket.setType((byte) 0); - containerOpenPacket.setBlockPosition(new Vector3i(0, 0, 0)); + containerOpenPacket.setBlockPosition(Vector3i.ZERO); session.getUpstream().sendPacket(containerOpenPacket); } @Override public void updateInventory(GeyserSession session, Inventory inventory) { - ContainerId containerId = InventoryUtils.getContainerId(inventory.getId()); - if (containerId == null) - return; - ItemData[] bedrockItems = new ItemData[inventory.getItems().length]; for (int i = 0; i < bedrockItems.length; i++) { bedrockItems[i] = TranslatorsInit.getItemTranslator().translateToBedrock(inventory.getItems()[i]); } InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(containerId); + contentPacket.setContainerId(inventory.getId()); contentPacket.setContents(bedrockItems); session.getUpstream().sendPacket(contentPacket); } @Override public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - ContainerId containerId = InventoryUtils.getContainerId(inventory.getId()); - if (containerId == null) - return; - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(containerId); + slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(TranslatorsInit.getItemTranslator().translateToBedrock(inventory.getItems()[slot])); slotPacket.setInventorySlot(slot); session.getUpstream().sendPacket(slotPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java new file mode 100644 index 000000000..3073e469c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java @@ -0,0 +1,77 @@ +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.EntityData; +import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; +import com.nukkitx.protocol.bedrock.packet.BossEventPacket; +import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.utils.MessageUtils; + +public class JavaBossBarTranslator extends PacketTranslator { + @Override + public void translate(ServerBossBarPacket packet, GeyserSession session) { + BossEventPacket bossEventPacket = new BossEventPacket(); + bossEventPacket.setBossUniqueEntityId(session.getEntityCache().getBossBar(packet.getUUID())); + + switch (packet.getAction()) { + case ADD: + long entityId = session.getEntityCache().addBossBar(packet.getUUID()); + addBossEntity(session, entityId); + + bossEventPacket.setType(BossEventPacket.Type.SHOW); + bossEventPacket.setBossUniqueEntityId(entityId); + bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); + bossEventPacket.setHealthPercentage(packet.getHealth()); + bossEventPacket.setColor(0); //ignored by client + bossEventPacket.setOverlay(1); + bossEventPacket.setDarkenSky(0); + break; + case UPDATE_TITLE: + bossEventPacket.setType(BossEventPacket.Type.TITLE); + bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle())); + break; + case UPDATE_HEALTH: + bossEventPacket.setType(BossEventPacket.Type.HEALTH_PERCENTAGE); + bossEventPacket.setHealthPercentage(packet.getHealth()); + break; + case REMOVE: + bossEventPacket.setType(BossEventPacket.Type.HIDE); + removeBossEntity(session, session.getEntityCache().removeBossBar(packet.getUUID())); + break; + case UPDATE_STYLE: + case UPDATE_FLAGS: + //todo + return; + } + + session.getUpstream().sendPacket(bossEventPacket); + } + + /** + * Bedrock still needs an entity to display the BossBar.
+ * Just like 1.8 but it doesn't care about which entity + */ + private void addBossEntity(GeyserSession session, long entityId) { + AddEntityPacket addEntityPacket = new AddEntityPacket(); + addEntityPacket.setUniqueEntityId(entityId); + addEntityPacket.setRuntimeEntityId(entityId); + addEntityPacket.setIdentifier("minecraft:creeper"); + addEntityPacket.setEntityType(33); + addEntityPacket.setPosition(session.getPlayerEntity().getPosition()); + addEntityPacket.setRotation(Vector3f.ZERO); + addEntityPacket.setMotion(Vector3f.ZERO); + addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work? + + session.getUpstream().sendPacket(addEntityPacket); + } + + private void removeBossEntity(GeyserSession session, long entityId) { + RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket(); + removeEntityPacket.setUniqueEntityId(entityId); + + session.getUpstream().sendPacket(removeEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index cc5838c90..e604e20a3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -43,12 +43,16 @@ public class JavaChatTranslator extends PacketTranslator { switch (packet.getType()) { case CHAT: textPacket.setType(TextPacket.Type.CHAT); + break; case SYSTEM: textPacket.setType(TextPacket.Type.SYSTEM); + break; case NOTIFICATION: textPacket.setType(TextPacket.Type.TIP); + break; default: textPacket.setType(TextPacket.Type.RAW); + break; } if (packet.getMessage() instanceof TranslationMessage) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java index 55da3c733..5bd481a94 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityDestroyTranslator.java @@ -36,7 +36,10 @@ public class JavaEntityDestroyTranslator extends PacketTranslator { + @Override + public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) { + if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) return; + + PlayerListPacket translate = new PlayerListPacket(); + translate.setType(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Type.ADD : PlayerListPacket.Type.REMOVE); + + for (PlayerListEntry entry : packet.getEntries()) { + if (packet.getAction() == PlayerListEntryAction.ADD_PLAYER) { + boolean self = entry.getProfile().getId().equals(session.getPlayerEntity().getUuid()); + + PlayerEntity playerEntity = session.getPlayerEntity(); + if (self) playerEntity.setProfile(entry.getProfile()); + else { + playerEntity = new PlayerEntity( + entry.getProfile(), + -1, + session.getEntityCache().getNextEntityId().incrementAndGet(), + Vector3f.ZERO, + Vector3f.ZERO, + Vector3f.ZERO + ); + } + + session.getEntityCache().addPlayerEntity(playerEntity); + playerEntity.setPlayerList(true); + + translate.getEntries().add(SkinUtils.buildCachedEntry(entry.getProfile(), playerEntity.getGeyserId())); + } else { + PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); + if (entity != null && entity.isValid()) { + // remove from tablist but player entity is still there + entity.setPlayerList(false); + } else { + // just remove it from caching + session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); + } + translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId())); + } + } + + if (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized()) { + session.getUpstream().sendPacket(translate); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java index 831b8f104..0eb150803 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java @@ -25,9 +25,9 @@ package org.geysermc.connector.network.translators.java.entity.player; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket; +import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.console.GeyserLogger; @@ -48,7 +48,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator { - private static ProvidedSkinData providedSkinData = ProvidedSkinData.getProvidedSkin("bedrock/skin/model_steve.json"); - private static byte[] providedSkin = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); - - @Override - public void translate(ServerPlayerListEntryPacket packet, GeyserSession session) { - if (packet.getAction() != PlayerListEntryAction.ADD_PLAYER && packet.getAction() != PlayerListEntryAction.REMOVE_PLAYER) return; - - PlayerListPacket translate = new PlayerListPacket(); - translate.setType(packet.getAction() == PlayerListEntryAction.ADD_PLAYER ? PlayerListPacket.Type.ADD : PlayerListPacket.Type.REMOVE); - - for (PlayerListEntry entry : packet.getEntries()) { - PlayerListPacket.Entry entry1 = new PlayerListPacket.Entry(entry.getProfile().getId()); - - if (packet.getAction() == PlayerListEntryAction.ADD_PLAYER) { - long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); - - session.getEntityCache().addPlayerEntity(new PlayerEntity( - entry.getProfile(), - -1, - geyserId, - Vector3f.ZERO, - Vector3f.ZERO, - Vector3f.ZERO - )); - - entry1.setName(entry.getProfile().getName()); - entry1.setEntityId(geyserId); - entry1.setSkinId(providedSkinData.getSkinId()); - entry1.setSkinData(providedSkin); - entry1.setCapeData(new byte[0]); - entry1.setGeometryName(providedSkinData.getGeometryId()); - entry1.setGeometryData(providedSkinData.getGeometryDataEncoded()); - entry1.setXuid(""); - entry1.setPlatformChatId("WIN10"); - } else { - session.getEntityCache().removePlayerEntity(entry.getProfile().getId()); - } - translate.getEntries().add(entry1); - } - - session.getUpstream().sendPacket(translate); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java index 324909572..9d1c9822d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnExpOrbTranslator.java @@ -25,26 +25,24 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnExpOrbPacket; -import com.nukkitx.protocol.bedrock.packet.SpawnExperienceOrbPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ExpOrbEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.utils.EntityUtils; public class JavaSpawnExpOrbTranslator extends PacketTranslator { @Override public void translate(ServerSpawnExpOrbPacket packet, GeyserSession session) { - Vector3f position = new Vector3f(packet.getX(), packet.getY(), packet.getZ()); - Entity entity = new ExpOrbEntity(packet.getExp(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), - EntityType.EXPERIENCE_ORB, position, new Vector3f(0, 0, 0), new Vector3f(0, 0, 0)); + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); - if (entity == null) - return; + Entity entity = new ExpOrbEntity( + packet.getExp(), packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(), + EntityType.EXPERIENCE_ORB, position, Vector3f.ZERO, Vector3f.ZERO + ); session.getEntityCache().spawnEntity(entity); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java index cb6b9f126..f836478d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnGlobalEntityTranslator.java @@ -25,8 +25,8 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnGlobalEntityPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -36,14 +36,13 @@ public class JavaSpawnGlobalEntityTranslator extends PacketTranslator { // #slowdownbrother, just don't execute it directly PaintingEntity entity = new PaintingEntity( diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 94a8432ac..688a00bae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -25,23 +25,25 @@ package org.geysermc.connector.network.translators.java.entity.spawn; -import com.flowpowered.math.vector.Vector3f; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.api.Geyser; import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.utils.SkinUtils; public class JavaSpawnPlayerTranslator extends PacketTranslator { @Override public void translate(ServerSpawnPlayerPacket packet, GeyserSession session) { - Vector3f position = new Vector3f(packet.getX(), packet.getY(), packet.getZ()); - Vector3f rotation = new Vector3f(packet.getPitch(), packet.getYaw(), packet.getYaw()); + Vector3f position = Vector3f.from(packet.getX(), packet.getY() - EntityType.PLAYER.getOffset(), packet.getZ()); + Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw()); PlayerEntity entity = session.getEntityCache().getPlayerEntity(packet.getUUID()); if (entity == null) { - Geyser.getLogger().error("Haven't received PlayerListEntry packet before spawning player! We ignore the player"); + Geyser.getLogger().error("Haven't received PlayerListEntry packet before spawning player! We ignore the player " + packet.getUUID()); return; } @@ -49,6 +51,8 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator entity.sendPlayer(session)); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java index 429857aa3..cc4a04757 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java @@ -27,20 +27,14 @@ package org.geysermc.connector.network.translators.java.scoreboard; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerDisplayScoreboardPacket; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.ScoreboardCache; import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.scoreboard.Scoreboard; public class JavaDisplayScoreboardTranslator extends PacketTranslator { @Override public void translate(ServerDisplayScoreboardPacket packet, GeyserSession session) { - try { - ScoreboardCache cache = session.getScoreboardCache(); - Scoreboard scoreboard = new Scoreboard(session); - cache.setScoreboard(scoreboard); - } catch (Exception ex) { - ex.printStackTrace(); - } + session.getScoreboardCache().getScoreboard().registerNewObjective( + packet.getScoreboardName(), packet.getPosition() + ); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index fb60217cd..a90e87971 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -25,43 +25,39 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.ScoreboardCache; import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardObjective; import org.geysermc.connector.utils.MessageUtils; public class JavaScoreboardObjectiveTranslator extends PacketTranslator { @Override public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) { - try { - ScoreboardCache cache = session.getScoreboardCache(); - Scoreboard scoreboard = new Scoreboard(session); - if (cache.getScoreboard() != null) - scoreboard = cache.getScoreboard(); + ScoreboardCache cache = session.getScoreboardCache(); + Scoreboard scoreboard = cache.getScoreboard(); - switch (packet.getAction()) { - case ADD: - ScoreboardObjective objective = scoreboard.registerNewObjective(packet.getName()); - objective.setDisplaySlot(ScoreboardObjective.DisplaySlot.SIDEBAR); - objective.setDisplayName(MessageUtils.getBedrockMessage(packet.getDisplayName())); - break; - case UPDATE: - ScoreboardObjective updateObj = scoreboard.getObjective(packet.getName()); - updateObj.setDisplayName(MessageUtils.getBedrockMessage(packet.getDisplayName())); - break; - case REMOVE: - scoreboard.unregisterObjective(packet.getName()); - break; - } + Objective objective = scoreboard.getObjective(packet.getName()); - scoreboard.onUpdate(); - cache.setScoreboard(scoreboard); - } catch (Exception ex) { - ex.printStackTrace(); + if (objective == null && packet.getAction() != ObjectiveAction.REMOVE) { + objective = scoreboard.registerNewObjective(packet.getName(), true); } + + switch (packet.getAction()) { + case ADD: + case UPDATE: + objective.setDisplayName(MessageUtils.getBedrockMessage(packet.getDisplayName())); + objective.setType(packet.getType().ordinal()); + break; + case REMOVE: + scoreboard.unregisterObjective(packet.getName()); + break; + } + + if (objective != null && !objective.isTemp()) scoreboard.onUpdate(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java index aa364fef7..887e072b3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java @@ -26,13 +26,52 @@ package org.geysermc.connector.network.translators.java.scoreboard; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerTeamPacket; +import org.geysermc.api.Geyser; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.scoreboard.Scoreboard; +import org.geysermc.connector.scoreboard.UpdateType; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; public class JavaTeamTranslator extends PacketTranslator { @Override public void translate(ServerTeamPacket packet, GeyserSession session) { + Geyser.getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction()+" "+ Arrays.toString(packet.getPlayers())); + Scoreboard scoreboard = session.getScoreboardCache().getScoreboard(); + switch (packet.getAction()) { + case CREATE: + scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) + .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) + .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())); + break; + case UPDATE: + scoreboard.getTeam(packet.getTeamName()) + .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix())) + .setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix())) + .setUpdateType(UpdateType.UPDATE); + break; + case ADD_PLAYER: + scoreboard.getTeam(packet.getTeamName()).addEntities(packet.getPlayers()); + break; + case REMOVE_PLAYER: + scoreboard.getTeam(packet.getTeamName()).removeEntities(packet.getPlayers()); + break; + case REMOVE: + scoreboard.removeTeam(packet.getTeamName()); + break; + } + scoreboard.onUpdate(); + } + + private Set toPlayerSet(String[] players) { + return new HashSet<>(Arrays.asList(players)); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index 83b01ae02..7d21deaf0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -25,38 +25,41 @@ package org.geysermc.connector.network.translators.java.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; -import com.nukkitx.protocol.bedrock.packet.SetScorePacket; +import org.geysermc.api.Geyser; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.ScoreboardCache; import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; -import org.geysermc.connector.scoreboard.ScoreboardObjective; public class JavaUpdateScoreTranslator extends PacketTranslator { @Override public void translate(ServerUpdateScorePacket packet, GeyserSession session) { try { - ScoreboardCache cache = session.getScoreboardCache(); - Scoreboard scoreboard = new Scoreboard(session); - if (cache.getScoreboard() != null) - scoreboard = cache.getScoreboard(); + Scoreboard scoreboard = session.getScoreboardCache().getScoreboard(); - ScoreboardObjective objective = scoreboard.getObjective(packet.getObjective()); - if (objective == null) { - objective = scoreboard.registerNewObjective(packet.getObjective()); + Objective objective = scoreboard.getObjective(packet.getObjective()); + if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) { + Geyser.getLogger().info("Tried to update score without the existence of its requested objective '" + packet.getObjective() + '\''); + return; } switch (packet.getAction()) { - case REMOVE: - objective.registerScore(packet.getEntry(), packet.getEntry(), packet.getValue(), SetScorePacket.Action.REMOVE); - break; case ADD_OR_UPDATE: - objective.registerScore(packet.getEntry(), packet.getEntry(), packet.getValue(), SetScorePacket.Action.SET); + objective.setScore(packet.getEntry(), packet.getValue()); + break; + case REMOVE: + if (objective != null) { + objective.resetScore(packet.getEntry()); + } else { + for (Objective objective1 : scoreboard.getObjectives().values()) { + objective1.resetScore(packet.getEntry()); + } + } break; } - cache.setScoreboard(scoreboard); scoreboard.onUpdate(); } catch (Exception ex) { ex.printStackTrace(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index 63d1b927d..6ce675338 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -1,8 +1,8 @@ package org.geysermc.connector.network.translators.java.world; -import com.flowpowered.math.vector.Vector3i; import com.github.steveice10.mc.protocol.data.game.world.block.BlockChangeRecord; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -16,10 +16,11 @@ public class JavaBlockChangeTranslator extends PacketTranslator scores = new HashMap<>(); + + private Objective(Scoreboard scoreboard) { + this.id = scoreboard.getNextId().getAndIncrement(); + this.scoreboard = scoreboard; + } + + /** + * /!\ This method is made for temporary objectives until the real objective is received + */ + public Objective(Scoreboard scoreboard, String objectiveName) { + this(scoreboard); + this.objectiveName = objectiveName; + this.temp = true; + } + + public Objective(Scoreboard scoreboard, String objectiveName, ScoreboardPosition displaySlot, String displayName, int type) { + this(scoreboard, objectiveName, displaySlot.name().toLowerCase(), displayName, type); + } + + public Objective(Scoreboard scoreboard, String objectiveName, String displaySlot, String displayName, int type) { + this(scoreboard); + this.objectiveName = objectiveName; + this.displaySlot = displaySlot; + this.displayName = displayName; + this.type = type; + } + + public void registerScore(String id, int score) { + if (!scores.containsKey(id)) { + Score score1 = new Score(this, id) + .setScore(score) + .setTeam(scoreboard.getTeamFor(id)); + scores.put(id, score1); + } + } + + public void setScore(String id, int score) { + if (scores.containsKey(id)) { + scores.get(id).setScore(score).setUpdateType(UpdateType.ADD); + } else { + registerScore(id, score); + } + } + + public void setScoreText(String oldText, String newText) { + if (!scores.containsKey(oldText) || oldText.equals(newText)) return; + Score oldScore = scores.get(oldText); + + Score newScore = new Score(this, newText) + .setScore(oldScore.getScore()) + .setTeam(scoreboard.getTeamFor(newText)); + + scores.put(newText, newScore); + oldScore.setUpdateType(UpdateType.REMOVE); + } + + public int getScore(String id) { + if (scores.containsKey(id)) { + return scores.get(id).getScore(); + } + return 0; + } + + public Score getScore(int line) { + for (Score score : scores.values()) { + if (score.getScore() == line) return score; + } + return null; + } + + public void resetScore(String id) { + if (scores.containsKey(id)) { + scores.get(id).setUpdateType(UpdateType.REMOVE); + } + } + + public void removeScore(String id) { + scores.remove(id); + } + + public Objective setDisplayName(String displayName) { + this.displayName = displayName; + if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + return this; + } + + public Objective setType(int type) { + this.type = type; + if (updateType == UpdateType.NOTHING) updateType = UpdateType.UPDATE; + return this; + } + + public void removeTemp(ScoreboardPosition displaySlot) { + if (temp) { + temp = false; + this.displaySlot = displaySlot.name().toLowerCase(); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index 9535b0947..f4778d953 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -25,43 +25,40 @@ package org.geysermc.connector.scoreboard; -import com.nukkitx.protocol.bedrock.packet.SetScorePacket; import lombok.Getter; import lombok.Setter; +import lombok.experimental.Accessors; -import java.util.Random; - -/** - * Adapted from: https://github.com/Ragnok123/GTScoreboard - */ +@Getter @Setter +@Accessors(chain = true) public class Score { + private Objective objective; + private long id; - @Getter - @Setter + private UpdateType updateType = UpdateType.ADD; + private String name; + private Team team; private int score; + private int oldScore = Integer.MIN_VALUE; - @Getter - private long scoreboardId; - - private ScoreboardObjective objective; - - @Getter - @Setter - private String fakePlayer; - - @Getter - @Setter - private SetScorePacket.Action action = SetScorePacket.Action.SET; - - private boolean modified = false; - - @Getter - @Setter - private String fakeId; - - public Score(ScoreboardObjective objective, String fakePlayer) { - this.scoreboardId = -new Random().nextLong(); + public Score(Objective objective, String name) { + this.id = objective.getScoreboard().getNextId().getAndIncrement(); this.objective = objective; - this.fakePlayer = fakePlayer; + this.name = name; + } + + public String getDisplayName() { + if (team != null && team.getUpdateType() != UpdateType.REMOVE) { + return team.getPrefix() + name + team.getSuffix(); + } + return name; + } + + public Score setScore(int score) { + if (oldScore == Integer.MIN_VALUE) { + this.oldScore = score; + } + this.score = score; + return this; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 6b6337993..bb014ad4f 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -25,111 +25,228 @@ package org.geysermc.connector.scoreboard; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.nukkitx.protocol.bedrock.data.ScoreInfo; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; import lombok.Getter; -import lombok.Setter; +import org.geysermc.api.Geyser; import org.geysermc.connector.network.session.GeyserSession; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; -/** - * Adapted from: https://github.com/Ragnok123/GTScoreboard - */ +import static org.geysermc.connector.scoreboard.UpdateType.*; + +@Getter public class Scoreboard { - - @Getter - private ScoreboardObjective objective; - private GeyserSession session; + private AtomicLong nextId = new AtomicLong(0); - @Getter - @Setter - private long id; - - private Map objectiveMap = new HashMap(); + private Map objectives = new HashMap<>(); + private Map teams = new HashMap<>(); public Scoreboard(GeyserSession session) { this.session = session; - - id = new Random().nextLong(); } - public ScoreboardObjective registerNewObjective(String objectiveName) { - ScoreboardObjective objective = new ScoreboardObjective(); - objective.setObjectiveName(objectiveName); - this.objective = objective; - if (!objectiveMap.containsKey(objectiveName)) { - objectiveMap.put(objectiveName, objective); - } - + public Objective registerNewObjective(String objectiveId, boolean temp) { + if (!temp || objectives.containsKey(objectiveId)) return objectives.get(objectiveId); + Objective objective = new Objective(this, objectiveId); + objectives.put(objectiveId, objective); return objective; } - public ScoreboardObjective getObjective(String objectiveName) { - ScoreboardObjective objective = null; - if (objectiveMap.containsKey(objectiveName) && this.objective.getObjectiveName().contains(objectiveName)) { - objective = this.objective; + public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { + Objective objective = null; + if (objectives.containsKey(objectiveId)) { + objective = objectives.get(objectiveId); + if (objective.isTemp()) objective.removeTemp(displaySlot); + else { + despawnObjective(objective); + objective = null; + } + } + if (objective == null) { + objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); + objectives.put(objectiveId, objective); } - return objective; } - public void setObjective(String objectiveName) { - if (objectiveMap.containsKey(objectiveName)) - objective = objectiveMap.get(objectiveName); + public Team registerNewTeam(String teamName, Set players) { + if (teams.containsKey(teamName)) { + Geyser.getLogger().info("Ignoring team " + teamName + ". It overrides without removing old team."); + return getTeam(teamName); + } + + Team team = new Team(this, teamName).setEntities(players); + teams.put(teamName, team); + + for (Objective objective : objectives.values()) { + for (Score score : objective.getScores().values()) { + if (players.contains(score.getName())) { + score.setTeam(team); + } + } + } + return team; + } + + public Objective getObjective(String objectiveName) { + return objectives.get(objectiveName); + } + + public Team getTeam(String teamName) { + return teams.get(teamName); } public void unregisterObjective(String objectiveName) { - if (!objectiveMap.containsKey(objectiveName)) - return; + Objective objective = getObjective(objectiveName); + if (objective != null) objective.setUpdateType(REMOVE); + } - if (objective.getObjectiveName().equals(objectiveName)) { - objective = null; - } - - objectiveMap.remove(objectiveName); + public void removeTeam(String teamName) { + Team remove = teams.remove(teamName); + if (remove != null) remove.setUpdateType(REMOVE); } public void onUpdate() { - if (objective == null) - return; + Set changedObjectives = new HashSet<>(); + List addScores = new ArrayList<>(); + List removeScores = new ArrayList<>(); + for (String objectiveId : new ArrayList<>(objectives.keySet())) { + Objective objective = objectives.get(objectiveId); + if (objective.isTemp()) { + Geyser.getLogger().debug("Ignoring temp Scoreboard Objective '"+ objectiveId +'\''); + continue; + } + + if (objective.getUpdateType() != NOTHING) changedObjectives.add(objective); + + boolean globalUpdate = objective.getUpdateType() == UPDATE; + boolean globalAdd = objective.getUpdateType() == ADD || globalUpdate; + boolean globalRemove = objective.getUpdateType() == REMOVE || globalUpdate; + + boolean hasUpdate = globalUpdate; + + List handledScores = new ArrayList<>(); + for (String identifier : new HashSet<>(objective.getScores().keySet())) { + Score score = objective.getScores().get(identifier); + Team team = score.getTeam(); + + boolean inTeam = team != null && team.getEntities().contains(score.getName()); + + boolean teamAdd = team != null && (team.getUpdateType() == ADD || team.getUpdateType() == UPDATE); + boolean teamRemove = team != null && (team.getUpdateType() == REMOVE || team.getUpdateType() == UPDATE); + + if (team != null && (team.getUpdateType() == REMOVE || !inTeam)) score.setTeam(null); + + boolean add = (hasUpdate || globalAdd || teamAdd || teamRemove || score.getUpdateType() == ADD || score.getUpdateType() == UPDATE) && (score.getUpdateType() != REMOVE); + boolean remove = hasUpdate || globalRemove || teamAdd || teamRemove || score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + + boolean updated = false; + if (!hasUpdate) { + updated = hasUpdate = add; + } + + if (updated) { + for (Score score1 : handledScores) { + ScoreInfo scoreInfo = new ScoreInfo(score1.getId(), score1.getObjective().getObjectiveName(), score1.getScore(), score1.getDisplayName()); + addScores.add(scoreInfo); + removeScores.add(scoreInfo); + } + } + + if (add) { + addScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getScore(), score.getDisplayName())); + } + if (remove) { + removeScores.add(new ScoreInfo(score.getId(), score.getObjective().getObjectiveName(), score.getOldScore(), score.getDisplayName())); + } + score.setOldScore(score.getScore()); + + if (score.getUpdateType() == REMOVE) { + objective.removeScore(score.getName()); + } + + if (add || remove) { + changedObjectives.add(objective); + } else { // stays the same like before + handledScores.add(score); + } + score.setUpdateType(NOTHING); + } + } + + for (Objective objective : changedObjectives) { + boolean update = objective.getUpdateType() == NOTHING || objective.getUpdateType() == UPDATE; + if (objective.getUpdateType() == REMOVE || update) { + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); + removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); + session.getUpstream().sendPacket(removeObjectivePacket); + if (objective.getUpdateType() == REMOVE) { + objectives.remove(objective.getObjectiveName()); // now we can deregister + } + } + if (objective.getUpdateType() == ADD || update) { + SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); + displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); + displayObjectivePacket.setDisplayName(objective.getDisplayName()); + displayObjectivePacket.setCriteria("dummy"); + displayObjectivePacket.setDisplaySlot(objective.getDisplaySlot()); + displayObjectivePacket.setSortOrder(1); // ?? + session.getUpstream().sendPacket(displayObjectivePacket); + } + objective.setUpdateType(NOTHING); + } + + if (!removeScores.isEmpty()) { + SetScorePacket setScorePacket = new SetScorePacket(); + setScorePacket.setAction(SetScorePacket.Action.REMOVE); + setScorePacket.setInfos(removeScores); + session.getUpstream().sendPacket(setScorePacket); + } + + if (!addScores.isEmpty()) { + SetScorePacket setScorePacket = new SetScorePacket(); + setScorePacket.setAction(SetScorePacket.Action.SET); + setScorePacket.setInfos(addScores); + session.getUpstream().sendPacket(setScorePacket); + } + } + + public void despawnObjective(Objective objective) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.getUpstream().sendPacket(removeObjectivePacket); + objectives.remove(objective.getDisplayName()); - SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); - displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); - displayObjectivePacket.setDisplayName(objective.getDisplayName()); - displayObjectivePacket.setCriteria("dummy"); - displayObjectivePacket.setDisplaySlot("sidebar"); - displayObjectivePacket.setSortOrder(1); - session.getUpstream().sendPacket(displayObjectivePacket); - - Map fakeMap = new HashMap(); - for (Map.Entry entry : objective.getScores().entrySet()) { - fakeMap.put(entry.getKey(), entry.getValue()); + List toRemove = new ArrayList<>(); + for (String identifier : objective.getScores().keySet()) { + Score score = objective.getScores().get(identifier); + toRemove.add(new ScoreInfo( + score.getId(), score.getObjective().getObjectiveName(), + 0, "" + )); } - for (String string : fakeMap.keySet()) { - Score score = fakeMap.get(string); - ScoreInfo scoreInfo = new ScoreInfo(score.getScoreboardId(), objective.getObjectiveName(), score.getScore(), score.getFakePlayer()); - + if (!toRemove.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(score.getAction()); - setScorePacket.setInfos(Arrays.asList(scoreInfo)); + setScorePacket.setAction(SetScorePacket.Action.REMOVE); + setScorePacket.setInfos(toRemove); session.getUpstream().sendPacket(setScorePacket); - - if (score.getAction() == SetScorePacket.Action.REMOVE) { - String id = score.getFakeId(); - objective.getScores().remove(id); - } } } + + public Team getTeamFor(String entity) { + for (Team team : teams.values()) { + if (team.getEntities().contains(entity)) { + return team; + } + } + return null; + } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardObjective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardObjective.java deleted file mode 100644 index f90c474e5..000000000 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/ScoreboardObjective.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2019 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.connector.scoreboard; - -import com.nukkitx.protocol.bedrock.packet.SetScorePacket; -import lombok.Getter; -import lombok.Setter; - -import java.util.HashMap; -import java.util.Map; - -/** - * Adapted from: https://github.com/Ragnok123/GTScoreboard - */ -public class ScoreboardObjective { - - @Getter - @Setter - private int scoreboardTick = 0; - - @Getter - @Setter - private String objectiveName; - - @Getter - @Setter - private DisplaySlot displaySlot; - - @Getter - @Setter - private String displayName; - - @Getter - private Map scores = new HashMap(); - - public void registerScore(String id, String fake, int value) { - registerScore(id, fake, value, SetScorePacket.Action.SET); - } - - public void registerScore(String id, String fake, int value, SetScorePacket.Action action) { - Score score = new Score(this, fake); - score.setScore(value); - score.setFakeId(id); - score.setAction(action); - if (!scores.containsKey(id)) { - scores.put(id, score); - } else { - setScore(id, value); - } - } - - public void setScore(String id, int value) { - if (scores.containsKey(id)) { - Score modifiedScore = scores.get(id); - modifiedScore.setScore(value); - scores.remove(id); - scores.put(id, modifiedScore); - } - } - - public void setScoreText(String id, String text) { - if (scores.containsKey(id)) { - Score newScore = new Score(this, text); - newScore.setScore(scores.get(id).getScore()); - newScore.setFakeId(id); - scores.remove(id); - scores.put(id, newScore); - } - } - - public int getScore(String id) { - int i = 0; - if (scores.containsKey(id)) { - Score score = scores.get(id); - i = score.getScore(); - } - - return i; - } - - public Score getScore(int line) { - Score score = null; - for (Map.Entry entry : scores.entrySet()) { - if (entry.getValue().getScore() == line) - return entry.getValue(); - } - - return null; - } - - public void resetScore(String id) { - if (scores.containsKey(id)) { - Score modifiedScore = scores.get(id); - modifiedScore.setAction(SetScorePacket.Action.REMOVE); - scores.remove(id); - scores.put(id, modifiedScore); - } - } - - public enum DisplaySlot { - - SIDEBAR, - LIST, - BELOWNAME; - - } -} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java new file mode 100644 index 000000000..7dcb5f3f5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -0,0 +1,55 @@ +package org.geysermc.connector.scoreboard; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Getter +@Setter +@Accessors(chain = true) +public class Team { + private final Scoreboard scoreboard; + private final String id; + + private UpdateType updateType = UpdateType.ADD; + private String name; + private String prefix; + private String suffix; + private Set entities = new HashSet<>(); + + + public Team(Scoreboard scoreboard, String id) { + this.scoreboard = scoreboard; + this.id = id; + } + + public void addEntities(String... names) { + List added = new ArrayList<>(); + for (String name : names) { + if (!entities.contains(name)) { + entities.add(name); + added.add(name); + } + } + setUpdateType(UpdateType.UPDATE); + for (Objective objective : scoreboard.getObjectives().values()) { + for (Score score : objective.getScores().values()) { + if (added.contains(score.getName())) { + score.setTeam(this); + } + } + } + } + + public void removeEntities(String... names) { + for (String name : names) { + entities.remove(name); + } + setUpdateType(UpdateType.UPDATE); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java b/connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java new file mode 100644 index 000000000..e0465db3f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/UpdateType.java @@ -0,0 +1,15 @@ +package org.geysermc.connector.scoreboard; + +public enum UpdateType { + REMOVE, + /** + * Nothing has changed, it's cool + */ + NOTHING, + ADD, + /** + * Hey, something has been updated!
+ * Only used in {@link Objective Objective} + */ + UPDATE +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/GeyserUtils.java b/connector/src/main/java/org/geysermc/connector/utils/GeyserUtils.java deleted file mode 100644 index 13a188205..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/GeyserUtils.java +++ /dev/null @@ -1,662 +0,0 @@ -package org.geysermc.connector.utils; - -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3d; -import com.flowpowered.math.vector.Vector3i; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.DecoderException; - -import java.lang.reflect.Array; -import java.text.MessageFormat; -import java.util.List; -import java.util.UUID; -import java.util.function.*; - -public class GeyserUtils { - - public static final int FLAG_RUNTIME = 1; - - public static final int GLOBAL_PALETTE_BITS_PER_BLOCK = 14; - - public static final int SECTION_COUNT_BLOCKS = 16; - - public static final int SECTION_COUNT_LIGHT = 18; - - public static final int BLOCKS_IN_SECTION = 16 * 16 * 16; - - public static final int LIGHT_DATA_LENGTH = BLOCKS_IN_SECTION / 2; - - public static final int EMPTY_SUBCHUNK_BYTES = BLOCKS_IN_SECTION / 8; - - public static final int SUBCHUNK_VERSION = 8; - - public static void writeEmpty(ByteBuf to) { - - to.writeByte(storageHeader(1)); - - to.writeZero(EMPTY_SUBCHUNK_BYTES); - - } - - - - protected static final int storageHeader(int bitsPerBlock) { - - return (bitsPerBlock << 1) | FLAG_RUNTIME; - - } - - public static void writeEmptySubChunk(ByteBuf out) { - - out.writeBytes(new byte[4096 + 4096]); - - } - - public static void skipPosition(ByteBuf from) { - from.skipBytes(Long.BYTES); - } - - public static Vector3d readPosition(ByteBuf from) { - long l = from.readLong(); - return new Vector3d( - (int) (l >> 38), (int) (l & 0xFFF), (int) ((l << 26) >> 38) - ); - } - - public static void readPEPosition(ByteBuf from) { - readSVarInt(from); - readVarInt(from); - readSVarInt(from); - } - - public static Vector3d readLegacyPositionI(ByteBuf from) { - return new Vector3d(from.readInt(), from.readInt(), from.readInt()); - } - - public static void writePosition(ByteBuf to, Vector3i position) { - to.writeLong(((position.getX() & 0x3FFFFFFL) << 38) | ((position.getZ() & 0x3FFFFFFL) << 12) | (position.getY() & 0xFFFL)); - } - - public static void writeLegacyPositionL(ByteBuf to, Vector3d position) { - to.writeLong((((int) position.getX() & 0x3FFFFFFL) << 38) | (((int) position.getY() & 0xFFFL) << 26) | ((int) position.getZ() & 0x3FFFFFFL)); - } - - public static void writePEPosition(ByteBuf to, Vector3d position) { - writeSVarInt(to, (int) position.getX()); - writeVarInt(to, (int) position.getY()); - writeSVarInt(to, (int) position.getZ()); - } - - public static void writeLegacyPositionB(ByteBuf to, Vector3d position) { - to.writeInt((int) position.getX()); - to.writeByte((int) position.getY()); - to.writeInt((int) position.getZ()); - } - - public static void writeLegacyPositionS(ByteBuf to, Vector3d position) { - to.writeInt((int) position.getX()); - to.writeShort((int) position.getY()); - to.writeInt((int) position.getZ()); - } - - public static void writeLegacyPositionI(ByteBuf to, Vector3d position) { - to.writeInt((int) position.getX()); - to.writeInt((int) position.getY()); - to.writeInt((int) position.getZ()); - } - - public static Vector2i readIntChunkCoord(ByteBuf from) { - return new Vector2i(from.readInt(), from.readInt()); - } - - public static Vector2i readVarIntChunkCoord(ByteBuf from) { - return new Vector2i(readVarInt(from), readVarInt(from)); - } - - public static void writeIntChunkCoord(ByteBuf to, Vector2i chunk) { - to.writeInt(chunk.getX()); - to.writeInt(chunk.getY()); - } - - public static Vector2i readPEChunkCoord(ByteBuf from) { - return new Vector2i(readSVarInt(from), readSVarInt(from)); - } - - public static void writePEChunkCoord(ByteBuf to, Vector2i chunk) { - writeSVarInt(to, chunk.getX()); - writeSVarInt(to, chunk.getY()); - } - - public static int readLocalCoord(ByteBuf from) { - return from.readUnsignedShort(); - } - - public static void writeLocalCoord(ByteBuf to, int coord) { - to.writeShort(coord); - } - - public static void writeVarIntChunkCoord(ByteBuf to, Vector2i chunk) { - writeVarInt(to, chunk.getX()); - writeVarInt(to, chunk.getY()); - } - - - - public static final int MAX_LENGTH = 5; - - - - public static void writeFixedSizeVarInt(ByteBuf to, int i) { - - int writerIndex = to.writerIndex(); - - while ((i & 0xFFFFFF80) != 0x0) { - - to.writeByte(i | 0x80); - - i >>>= 7; - - } - - int paddingBytes = MAX_LENGTH - (to.writerIndex() - writerIndex) - 1; - - if (paddingBytes == 0) { - - to.writeByte(i); - - } else { - - to.writeByte(i | 0x80); - - while (--paddingBytes > 0) { - - to.writeByte(0x80); - - } - - to.writeByte(0); - - } - - } - - - - public static int readVarInt(ByteBuf from) { - - int value = 0; - - int length = 0; - - byte part; - - do { - - part = from.readByte(); - - value |= (part & 0x7F) << (length++ * 7); - - if (length > MAX_LENGTH) { - - throw new DecoderException("VarInt too big"); - - } - - } while (part < 0); - - return value; - - } - - - - public static void writeVarInt(ByteBuf to, int i) { - - while ((i & 0xFFFFFF80) != 0x0) { - - to.writeByte(i | 0x80); - - i >>>= 7; - - } - - to.writeByte(i); - - } - - - - public static int readSVarInt(ByteBuf from) { - - int varint = readVarInt(from); - - return (varint >> 1) ^ -(varint & 1); - - } - - - - public static void writeSVarInt(ByteBuf to, int varint) { - - writeVarInt(to, (varint << 1) ^ (varint >> 31)); - - } - - - - public static long readVarLong(ByteBuf from) { - - long varlong = 0L; - - int length = 0; - - byte part; - - do { - - part = from.readByte(); - - varlong |= (part & 0x7F) << (length++ * 7); - - if (length > 10) { - - throw new RuntimeException("VarLong too big"); - - } - - } while ((part & 0x80) == 0x80); - - return varlong; - - } - - - - public static void writeVarLong(ByteBuf to, long varlong) { - - while ((varlong & 0xFFFFFFFFFFFFFF80L) != 0x0L) { - - to.writeByte((int) (varlong & 0x7FL) | 0x80); - - varlong >>>= 7; - - } - - to.writeByte((int) varlong); - - } - - - - public static long readSVarLong(ByteBuf from) { - - long varlong = readVarLong(from); - - return (varlong >> 1) ^ -(varlong & 1); - - } - - - - public static void writeSVarLong(ByteBuf to, long varlong) { - - writeVarLong(to, (varlong << 1) ^ (varlong >> 63)); - - } - - - public static ByteBuf readShortByteArraySlice(ByteBuf from, int limit) { - - int length = from.readShort(); - - checkLimit(length, limit); - - return from.readSlice(length); - - } - - - - @SuppressWarnings("unchecked") - - public static T[] readShortTArray(ByteBuf from, Class tclass, Function elementReader) { - - T[] array = (T[]) Array.newInstance(tclass, from.readShort()); - - for (int i = 0; i < array.length; i++) { - - array[i] = elementReader.apply(from); - - } - - return array; - - } - - - - - - public static byte[] readVarIntByteArray(ByteBuf from) { - - return readBytes(from, readVarInt(from)); - - } - - - - public static ByteBuf readVarIntByteArraySlice(ByteBuf from, int limit) { - - int length = readVarInt(from); - - checkLimit(length, limit); - - return from.readSlice(length); - - } - - - - public static ByteBuf readVarIntByteArraySlice(ByteBuf from) { - - return from.readSlice(readVarInt(from)); - - } - - - - @SuppressWarnings("unchecked") - - public static T[] readVarIntTArray(ByteBuf from, Class tclass, Function elementReader) { - - T[] array = (T[]) Array.newInstance(tclass, readVarInt(from)); - - for (int i = 0; i < array.length; i++) { - - array[i] = elementReader.apply(from); - - } - - return array; - - } - - - - public static int[] readVarIntVarIntArray(ByteBuf from) { - - int[] array = new int[readVarInt(from)]; - - for (int i = 0; i < array.length; i++) { - - array[i] = readVarInt(from); - - } - - return array; - - } - - - - - - public static void writeShortByteArray(ByteBuf to, ByteBuf data) { - - to.writeShort(data.readableBytes()); - - to.writeBytes(data); - - } - - - - public static void writeShortByteArray(ByteBuf to, byte[] data) { - - to.writeShort(data.length); - - to.writeBytes(data); - - } - - - - public static void writeShortByteArray(ByteBuf to, Consumer dataWriter) { - - writeLengthPrefixedBytes(to, (lTo, length) -> lTo.writeShort(length), dataWriter); - - } - - - - public static void writeShortTArray(ByteBuf to, T[] array, BiConsumer elementWriter) { - - to.writeShort(array.length); - - for (T element : array) { - - elementWriter.accept(to, element); - - } - - } - - - - - - public static void writeVarIntByteArray(ByteBuf to, ByteBuf data) { - - writeVarInt(to, data.readableBytes()); - - to.writeBytes(data); - - } - - - - public static void writeVarIntByteArray(ByteBuf to, byte[] data) { - - writeVarInt(to, data.length); - - to.writeBytes(data); - - } - - - - public static void writeVarIntByteArray(ByteBuf to, Consumer dataWriter) { - - writeLengthPrefixedBytes(to, GeyserUtils::writeFixedSizeVarInt, dataWriter); - - } - - - - public static void writeVarIntTArray(ByteBuf to, ToIntFunction arrayWriter) { - - writeSizePrefixedData(to, GeyserUtils::writeFixedSizeVarInt, arrayWriter); - - } - - - - public static void writeVarIntTArray(ByteBuf to, T[] array, BiConsumer elementWriter) { - - writeVarInt(to, array.length); - - for (T element : array) { - - elementWriter.accept(to, element); - - } - - } - - - - public static void writeVarIntTArray(ByteBuf to, List array, BiConsumer elementWriter) { - - writeVarInt(to, array.size()); - - for (T element : array) { - - elementWriter.accept(to, element); - - } - - } - - - public static void writeVarIntEnum(ByteBuf to, Enum e) { - - writeVarInt(to, e.ordinal()); - - } - - - - public static void writeByteEnum(ByteBuf to, Enum e) { - - to.writeByte(e.ordinal()); - - } - - - - public static UUID readUUID(ByteBuf from) { - - return new UUID(from.readLong(), from.readLong()); - - } - - - - public static void writeUUID(ByteBuf to, UUID uuid) { - - to.writeLong(uuid.getMostSignificantBits()); - - to.writeLong(uuid.getLeastSignificantBits()); - - } - - - - public static void writePEUUID(ByteBuf to, UUID uuid) { - - to.writeLongLE(uuid.getMostSignificantBits()); - - to.writeLongLE(uuid.getLeastSignificantBits()); - - } - - - - public static byte[] readAllBytes(ByteBuf buf) { - - return readBytes(buf, buf.readableBytes()); - - } - - - - public static ByteBuf readAllBytesSlice(ByteBuf from) { - - return from.readSlice(from.readableBytes()); - - } - - - - public static ByteBuf readAllBytesSlice(ByteBuf buf, int limit) { - - checkLimit(buf.readableBytes(), limit); - - return readAllBytesSlice(buf); - - } - - - - public static byte[] readBytes(ByteBuf buf, int length) { - - byte[] result = new byte[length]; - - buf.readBytes(result); - - return result; - - } - - - - protected static void checkLimit(int length, int limit) { - - if (length > limit) { - - throw new DecoderException(MessageFormat.format("Size {0} is bigger than allowed {1}", length, limit)); - - } - - } - - - - public static void writeLengthPrefixedBytes(ByteBuf to, ObjIntConsumer lengthWriter, Consumer dataWriter) { - - int lengthWriterIndex = to.writerIndex(); - - lengthWriter.accept(to, 0); - - int writerIndexDataStart = to.writerIndex(); - - dataWriter.accept(to); - - int writerIndexDataEnd = to.writerIndex(); - - to.writerIndex(lengthWriterIndex); - - lengthWriter.accept(to, writerIndexDataEnd - writerIndexDataStart); - - to.writerIndex(writerIndexDataEnd); - - } - - - - public static void writeSizePrefixedData(ByteBuf to, ObjIntConsumer sizeWriter, ToIntFunction dataWriter) { - - int sizeWriterIndex = to.writerIndex(); - - sizeWriter.accept(to, 0); - - int size = dataWriter.applyAsInt(to); - - int writerIndexDataEnd = to.writerIndex(); - - to.writerIndex(sizeWriterIndex); - - sizeWriter.accept(to, size); - - to.writerIndex(writerIndexDataEnd); - - } - - - private static int getAnvilIndex(int x, int y, int z) { - - return (y << 8) + (z << 4) + x; - - } - - public static boolean instanceOf(Class clazz, Object o) { - try { - T t = (T) o; - return true; - } catch (Exception e) { - return false; - } - } - - -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 0e0d9c858..a56b6ed7f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -110,13 +110,4 @@ public class InventoryUtils { items[packet.getSlot()] = packet.getItem(); translator.updateSlot(session, openInventory, packet.getSlot()); } - - public static ContainerId getContainerId(int id) { - for (ContainerId value : ContainerId.values()) { - if (value.id() == id) - return value; - } - - return null; - } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index a68e8b5cc..b94f13cdc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -112,7 +112,7 @@ public class LoginEncryptionUtils { byte[] token = EncryptionUtils.generateRandomToken(); SecretKey encryptionKey = EncryptionUtils.getSecretKey(serverKeyPair.getPrivate(), key, token); - session.getUpstream().enableEncryption(encryptionKey); + session.getUpstream().getSession().enableEncryption(encryptionKey); ServerToClientHandshakePacket packet = new ServerToClientHandshakePacket(); packet.setJwt(EncryptionUtils.createHandshakeJwt(serverKeyPair, token).serialize()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkinData.java b/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkinData.java deleted file mode 100644 index 9618edcd9..000000000 --- a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkinData.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.geysermc.connector.utils; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import lombok.Getter; -import org.apache.commons.codec.Charsets; - -import java.util.Base64; - -@Getter -public class ProvidedSkinData { - private static final Gson gson = new GsonBuilder().create(); - private String skinId; - private String skinName; - private String geometryId; - private ObjectNode geometryData; - - public static ProvidedSkinData getProvidedSkin(String skinName) { - try { - ObjectMapper objectMapper = new ObjectMapper(new JsonFactory()); - return objectMapper.readValue(ProvidedSkinData.class.getClassLoader().getResource(skinName), ProvidedSkinData.class); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - } - - public String getGeometryDataEncoded() { - try { - return new String(Base64.getEncoder().encode(geometryData.toString().getBytes(Charsets.UTF_8))); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/utils/Remapper.java b/connector/src/main/java/org/geysermc/connector/utils/Remapper.java index c1dc77234..668f4cbb4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Remapper.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Remapper.java @@ -3,20 +3,15 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.protocol.bedrock.data.ItemData; import org.geysermc.connector.network.translators.block.JavaBlock; +import org.geysermc.connector.network.translators.block.type.*; import org.geysermc.connector.network.translators.item.BedrockItem; import org.geysermc.connector.network.translators.item.JavaItem; -import org.geysermc.connector.network.translators.block.type.ColoredBlock; -import org.geysermc.connector.network.translators.block.type.DyeColor; -import org.geysermc.connector.network.translators.block.type.StoneType; -import org.geysermc.connector.network.translators.block.type.WoodBlock; -import org.geysermc.connector.network.translators.block.type.WoodType; import java.util.HashMap; import java.util.Map; public class Remapper { - - public static final String MINECRAFT = "minecraft:"; + public static final String MINECRAFT_PREFIX = "minecraft:"; public static final Remapper ITEM_REMAPPER = new Remapper(); public static final Remapper BLOCK_REMAPPER = new Remapper(); @@ -37,7 +32,7 @@ public class Remapper { // Colorable block remapping for (ColoredBlock coloredBlock : ColoredBlock.values()) { - if (!getBedrockIdentifier(coloredBlock.name()).equalsIgnoreCase(bedrockItem.getIdentifier().replace(MINECRAFT, ""))) + if (!getBedrockIdentifier(coloredBlock.name()).equalsIgnoreCase(bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, ""))) continue; // The item must be colorable @@ -46,13 +41,13 @@ public class Remapper { continue; // Add the color to the identifier - identifier = MINECRAFT + color.name().toLowerCase() + "_" + coloredBlock.name().toLowerCase(); + identifier = MINECRAFT_PREFIX + color.name().toLowerCase() + "_" + coloredBlock.name().toLowerCase(); } } // Wood remapping for (WoodBlock woodBlock : WoodBlock.values()) { - if (!getBedrockIdentifier(woodBlock.name()).equalsIgnoreCase(bedrockItem.getIdentifier().replace(MINECRAFT, ""))) + if (!getBedrockIdentifier(woodBlock.name()).equalsIgnoreCase(bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, ""))) continue; if (isTool(bedrockItem.getIdentifier())) @@ -65,36 +60,36 @@ public class Remapper { if (woodType.getId() != bedrockItem.getData()) continue; - identifier = MINECRAFT + woodType.name().toLowerCase() + "_" + woodBlock.name().toLowerCase(); + identifier = MINECRAFT_PREFIX + woodType.name().toLowerCase() + "_" + woodBlock.name().toLowerCase(); } } // Stone remapping - if (bedrockItem.getIdentifier().replace(MINECRAFT, "").equalsIgnoreCase("stone") && !isTool(bedrockItem.getIdentifier())) { + if (bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, "").equalsIgnoreCase("stone") && !isTool(bedrockItem.getIdentifier())) { for (StoneType stoneType : StoneType.values()) { if (stoneType.getId() != bedrockItem.getData()) continue; // Set the identifier to stone - identifier = MINECRAFT + stoneType.name().toLowerCase(); + identifier = MINECRAFT_PREFIX + stoneType.name().toLowerCase(); } } // Grass remapping - if (bedrockItem.getIdentifier().replace(MINECRAFT, "").equalsIgnoreCase("grass")) { - identifier = MINECRAFT + "grass_block"; + if (bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, "").equalsIgnoreCase("grass")) { + identifier = MINECRAFT_PREFIX + "grass_block"; } - if (bedrockItem.getIdentifier().replace(MINECRAFT, "").equalsIgnoreCase("tallgrass")) { - identifier = MINECRAFT + "grass"; + if (bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, "").equalsIgnoreCase("tallgrass")) { + identifier = MINECRAFT_PREFIX + "grass"; } // Dirt remapping - if (bedrockItem.getIdentifier().replace(MINECRAFT, "").equalsIgnoreCase("dirt")) { + if (bedrockItem.getIdentifier().replace(MINECRAFT_PREFIX, "").equalsIgnoreCase("dirt")) { if (bedrockItem.getData() == 0) - identifier = MINECRAFT + "dirt"; + identifier = MINECRAFT_PREFIX + "dirt"; else - identifier = MINECRAFT + "coarse_dirt"; + identifier = MINECRAFT_PREFIX + "coarse_dirt"; } for (Map.Entry javaItemEntry : javaItems.entrySet()) { @@ -115,34 +110,19 @@ public class Remapper { return bedrockToJava.get(bedrockItem.getValue()); } - return null; } public BedrockItem convertToBedrock(ItemStack item) { - for (Map.Entry javaItem : Toolbox.JAVA_ITEMS.entrySet()) { - if (javaItem.getValue().getId() != item.getId()) - continue; - - return javaToBedrock.get(javaItem.getValue()); - } - - return null; + JavaItem javaItem = Toolbox.JAVA_ITEMS.get(item.getId()); + return javaItem != null ? javaToBedrock.get(javaItem) : null; } public BedrockItem convertToBedrockB(ItemStack block) { - for (Map.Entry javaItem : Toolbox.JAVA_BLOCKS.entrySet()) { - if (javaItem.getValue().getId() != block.getId()) - continue; - - return javaToBedrock.get(javaItem.getValue()); - } - - return null; + JavaBlock javaBlock = Toolbox.JAVA_BLOCKS.get(block.getId()); + return javaBlock != null ? javaToBedrock.get(javaBlock) : null; } - - private static String getBedrockIdentifier(String javaIdentifier) { javaIdentifier = javaIdentifier.toLowerCase(); javaIdentifier = javaIdentifier.replace("terracotta", "stained_hardened_clay"); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java new file mode 100644 index 000000000..2d2aceabf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -0,0 +1,268 @@ +package org.geysermc.connector.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.api.Geyser; +import org.geysermc.connector.GeyserConnector; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.*; + +public class SkinProvider { + public static final Gson GSON = new GsonBuilder().create(); + public static final boolean ALLOW_THIRD_PARTY_CAPES = ((GeyserConnector)Geyser.getConnector()).getConfig().isAllowThirdPartyCapes(); + private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); + + public static final Skin EMPTY_SKIN = new Skin(-1, ""); + public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); + private static Map cachedSkins = new ConcurrentHashMap<>(); + private static Map> requestedSkins = new ConcurrentHashMap<>(); + + public static final Cape EMPTY_CAPE = new Cape("", new byte[0], -1, true); + private static Map cachedCapes = new ConcurrentHashMap<>(); + private static Map> requestedCapes = new ConcurrentHashMap<>(); + + private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes + + public static boolean hasSkinCached(UUID uuid) { + return cachedSkins.containsKey(uuid); + } + + public static boolean hasCapeCached(String capeUrl) { + return cachedCapes.containsKey(capeUrl); + } + + public static Skin getCachedSkin(UUID uuid) { + return cachedSkins.getOrDefault(uuid, EMPTY_SKIN); + } + + public static Cape getCachedCape(String capeUrl) { + return capeUrl != null ? cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE) : EMPTY_CAPE; + } + + public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { + return CompletableFuture.supplyAsync(() -> { + long time = System.currentTimeMillis(); + + SkinAndCape skinAndCape = new SkinAndCape( + getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), + getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5) + ); + + Geyser.getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); + return skinAndCape; + }, EXECUTOR_SERVICE); + } + + public static CompletableFuture requestSkin(UUID playerId, String textureUrl, boolean newThread) { + if (textureUrl == null || textureUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_SKIN); + if (requestedSkins.containsKey(playerId)) return requestedSkins.get(playerId); // already requested + + if ((System.currentTimeMillis() - CACHE_INTERVAL) < cachedSkins.getOrDefault(playerId, EMPTY_SKIN).getRequestedOn()) { + // no need to update, still cached + return CompletableFuture.completedFuture(cachedSkins.get(playerId)); + } + + CompletableFuture future; + if (newThread) { + future = CompletableFuture.supplyAsync(() -> supplySkin(playerId, textureUrl), EXECUTOR_SERVICE) + .whenCompleteAsync((skin, throwable) -> { + if (!cachedSkins.getOrDefault(playerId, EMPTY_SKIN).getTextureUrl().equals(textureUrl)) { + skin.updated = true; + cachedSkins.put(playerId, skin); + } + requestedSkins.remove(skin.getSkinOwner()); + }); + requestedSkins.put(playerId, future); + } else { + Skin skin = supplySkin(playerId, textureUrl); + future = CompletableFuture.completedFuture(skin); + cachedSkins.put(playerId, skin); + } + return future; + } + + public static CompletableFuture requestCape(String capeUrl, boolean newThread) { + if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); + if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested + + boolean officialCape = capeUrl.startsWith("https://textures.minecraft.net"); + boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn(); + + if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) { + // the cape is an official cape (static) or the cape doesn't need a update yet + return CompletableFuture.completedFuture(cachedCapes.get(capeUrl)); + } + + CompletableFuture future; + if (newThread) { + future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), EXECUTOR_SERVICE) + .whenCompleteAsync((cape, throwable) -> { + cachedCapes.put(capeUrl, cape); + requestedCapes.remove(capeUrl); + }); + requestedCapes.put(capeUrl, future); + } else { + Cape cape = supplyCape(capeUrl); // blocking + future = CompletableFuture.completedFuture(cape); + cachedCapes.put(capeUrl, cape); + } + return future; + } + + public static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, + String username, boolean newThread) { + if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { + for (UnofficalCape cape : UnofficalCape.VALUES) { + Cape cape1 = getOrDefault( + requestCape(cape.getUrlFor(playerId, username), newThread), + EMPTY_CAPE, 4 + ); + if (!cape1.isFailed()) { + return CompletableFuture.completedFuture(cape1); + } + } + } + return CompletableFuture.completedFuture(officialCape); + } + + private static Skin supplySkin(UUID uuid, String textureUrl) { + byte[] skin = EMPTY_SKIN.getSkinData(); + try { + skin = requestImage(textureUrl, false); + } catch (Exception ignored) {} // just ignore I guess + return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); + } + + private static Cape supplyCape(String capeUrl) { + byte[] cape = new byte[0]; + try { + cape = requestImage(capeUrl, true); + } catch (Exception ignored) {} // just ignore I guess + + return new Cape( + capeUrl, + cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(), + System.currentTimeMillis(), + cape.length == 0 + ); + } + + private static byte[] requestImage(String imageUrl, boolean cape) throws Exception { + BufferedImage image = ImageIO.read(new URL(imageUrl)); + Geyser.getLogger().debug("Downloaded " + imageUrl); + + if (cape) { + BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); + + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, 64, 32, null); + g.dispose(); + image = newImage; + } + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4); + try { + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgba = image.getRGB(x, y); + outputStream.write((rgba >> 16) & 0xFF); + outputStream.write((rgba >> 8) & 0xFF); + outputStream.write(rgba & 0xFF); + outputStream.write((rgba >> 24) & 0xFF); + } + } + image.flush(); + return outputStream.toByteArray(); + } finally { + try { + outputStream.close(); + } catch (IOException ignored) {} + } + } + + public static T getOrDefault(CompletableFuture future, T defaultValue, int timeoutInSeconds) { + try { + return future.get(timeoutInSeconds, TimeUnit.SECONDS); + } catch (Exception ignored) {} + return defaultValue; + } + + @AllArgsConstructor + @Getter + public static class SkinAndCape { + private Skin skin; + private Cape cape; + } + + @AllArgsConstructor + @Getter + public static class Skin { + private UUID skinOwner; + private String textureUrl; + private byte[] skinData = STEVE_SKIN; + private long requestedOn; + private boolean updated; + + private Skin(long requestedOn, String textureUrl) { + this.requestedOn = requestedOn; + this.textureUrl = textureUrl; + } + } + + @AllArgsConstructor + @Getter + public static class Cape { + private String textureUrl; + private byte[] capeData; + private long requestedOn; + private boolean failed; + } + + /* + * Sorted by 'priority' + */ + @AllArgsConstructor + @Getter + public enum UnofficalCape { + OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), + LABYMOD("http://capes.labymod.net/capes/%s.png", CapeUrlType.UUID_DASHED), + FIVEZIG("http://textures.5zig.net/2/%s", CapeUrlType.UUID), + MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); + + public static final UnofficalCape[] VALUES = values(); + private String url; + private CapeUrlType type; + + public String getUrlFor(String type) { + return String.format(url, type); + } + + public String getUrlFor(UUID uuid, String username) { + return getUrlFor(toRequestedType(type, uuid, username)); + } + + public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { + switch (type) { + case UUID: return uuid.toString().replace("-", ""); + case UUID_DASHED: return uuid.toString(); + default: return username; + } + } + } + + public enum CapeUrlType { + USERNAME, + UUID, + UUID_DASHED + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java new file mode 100644 index 000000000..1202b4df8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -0,0 +1,148 @@ +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.google.gson.JsonObject; +import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.commons.codec.Charsets; +import org.geysermc.api.Geyser; +import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.Base64; +import java.util.UUID; +import java.util.function.Consumer; + +public class SkinUtils { + public static PlayerListPacket.Entry buildCachedEntry(GameProfile profile, long geyserId) { + GameProfileData data = GameProfileData.from(profile); + + return buildEntryManually( + profile.getId(), + profile.getName(), + geyserId, + profile.getIdAsString(), + SkinProvider.getCachedSkin(profile.getId()).getSkinData(), + SkinProvider.getCachedCape(data.getCapeUrl()).getCapeData(), + "geometry.humanoid.custom" + (data.isAlex() ? "Slim" : ""), + "" + ); + } + + public static PlayerListPacket.Entry buildDefaultEntry(GameProfile profile, long geyserId) { + return buildEntryManually( + profile.getId(), + profile.getName(), + geyserId, + profile.getIdAsString(), + SkinProvider.STEVE_SKIN, + SkinProvider.EMPTY_CAPE.getCapeData(), + "geometry.humanoid", + "" + ); + } + + public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String username, long geyserId, + String skinId, byte[] skinData, byte[] capeData, + String geometryName, String geometryData) { + PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid); + entry.setName(username); + entry.setEntityId(geyserId); + entry.setSkinId(skinId); + entry.setSkinData(skinData != null ? skinData : SkinProvider.STEVE_SKIN); + entry.setCapeData(capeData); + entry.setGeometryName(geometryName); + entry.setGeometryData(geometryData); + entry.setXuid(""); + entry.setPlatformChatId(""); + return entry; + } + + @AllArgsConstructor + @Getter + public static class GameProfileData { + private String skinUrl; + private String capeUrl; + private boolean alex; + + public static GameProfileData from(GameProfile profile) { + try { + GameProfile.Property skinProperty = profile.getProperty("textures"); + + JsonObject skinObject = SkinProvider.GSON.fromJson(new String(Base64.getDecoder().decode(skinProperty.getValue()), Charsets.UTF_8), JsonObject.class); + JsonObject textures = skinObject.getAsJsonObject("textures"); + + JsonObject skinTexture = textures.getAsJsonObject("SKIN"); + String skinUrl = skinTexture.get("url").getAsString(); + + boolean isAlex = skinTexture.has("metadata"); + + String capeUrl = null; + if (textures.has("CAPE")) { + JsonObject capeTexture = textures.getAsJsonObject("CAPE"); + capeUrl = capeTexture.get("url").getAsString(); + } + + return new GameProfileData(skinUrl, capeUrl, isAlex); + } catch (Exception exception) { + // return default skin with default cape when texture data is invalid + Geyser.getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); + return new GameProfileData("", "", false); + } + } + } + + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, + Consumer skinAndCapeConsumer) { + Geyser.getGeneralThreadPool().execute(() -> { + SkinUtils.GameProfileData data = SkinUtils.GameProfileData.from(entity.getProfile()); + + SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) + .whenCompleteAsync((skinAndCape, throwable) -> { + try { + SkinProvider.Skin skin = skinAndCape.getSkin(); + SkinProvider.Cape cape = skinAndCape.getCape(); + + if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { + cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( + cape, entity.getUuid(), + entity.getUsername(), false + ), SkinProvider.EMPTY_CAPE, SkinProvider.UnofficalCape.VALUES.length * 3); + } + + if (entity.getLastSkinUpdate() < skin.getRequestedOn()) { + entity.setLastSkinUpdate(skin.getRequestedOn()); + + if (session.getUpstream().isInitialized()) { + PlayerListPacket.Entry updatedEntry = SkinUtils.buildEntryManually( + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + entity.getUuid().toString(), + skin.getSkinData(), + cape.getCapeData(), + "geometry.humanoid.custom" + (data.isAlex() ? "Slim" : ""), + "" + ); + + PlayerListPacket playerRemovePacket = new PlayerListPacket(); + playerRemovePacket.setType(PlayerListPacket.Type.REMOVE); + playerRemovePacket.getEntries().add(updatedEntry); + session.getUpstream().sendPacket(playerRemovePacket); + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setType(PlayerListPacket.Type.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.getUpstream().sendPacket(playerAddPacket); + } + } + } catch (Exception e) { + Geyser.getLogger().error("Failed getting skin for " + entity.getUuid(), e); + } + + if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); + }); + }); + } +} diff --git a/connector/src/main/resources/bedrock/skin/model_steve.json b/connector/src/main/resources/bedrock/skin/model_steve.json deleted file mode 100644 index 514308fe4..000000000 --- a/connector/src/main/resources/bedrock/skin/model_steve.json +++ /dev/null @@ -1,383 +0,0 @@ -{ - "skinId" : "c18e65aa-7b21-4637-9b63-8ad63622ef01_Custom", - "skinName" : "skin.Standard.Custom", - "geometryId" : "geometry.humanoid.custom", - "geometryData" : - { - "geometry.humanoid": { - "bones": [ - { - "name": "body", - "pivot": [ 0.0, 24.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 16 ] - } - ] - }, - - { - "name": "waist", - "neverRender": true, - "pivot": [ 0.0, 12.0, 0.0 ] - }, - - { - "name": "head", - "pivot": [ 0.0, 24.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 0, 0 ] - } - ] - }, - - { - "name": "hat", - "pivot": [ 0.0, 24.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 32, 0 ], - "inflate": 0.5 - } - ], - "neverRender": true - }, - - { - "name": "rightArm", - "pivot": [ -5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ -8.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 16 ] - } - ] - }, - - { - "name": "leftArm", - "pivot": [ 5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 16 ] - } - ], - "mirror": true - }, - - { - "name": "rightLeg", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ] - }, - - { - "name": "leftLeg", - "pivot": [ 1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ], - "mirror": true - } - ] - }, - - "geometry.cape": { - "texturewidth": 64, - "textureheight": 32, - - "bones": [ - { - "name": "cape", - "pivot": [ 0.0, 24.0, -3.0 ], - "cubes": [ - { - "origin": [ -5.0, 8.0, -3.0 ], - "size": [ 10, 16, 1 ], - "uv": [ 0, 0 ] - } - ], - "material": "alpha" - } - ] - }, - "geometry.humanoid.custom:geometry.humanoid": { - "bones": [ - { - "name": "hat", - "neverRender": false, - "material": "alpha", - "pivot": [ 0.0, 24.0, 0.0 ] - }, - { - "name": "leftArm", - "reset": true, - "mirror": false, - "pivot": [ 5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 32, 48 ] - } - ] - }, - - { - "name": "rightArm", - "reset": true, - "pivot": [ -5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ -8.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 16 ] - } - ] - }, - - { - "name": "rightItem", - "pivot": [ -6, 15, 1 ], - "neverRender": true, - "parent": "rightArm" - }, - - { - "name": "leftSleeve", - "pivot": [ 5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 48, 48 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "rightSleeve", - "pivot": [ -5.0, 22.0, 0.0 ], - "cubes": [ - { - "origin": [ -8.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 32 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "leftLeg", - "reset": true, - "mirror": false, - "pivot": [ 1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 16, 48 ] - } - ] - }, - - { - "name": "leftPants", - "pivot": [ 1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 48 ], - "inflate": 0.25 - } - ], - "pos": [ 1.9, 12, 0 ], - "material": "alpha" - }, - - { - "name": "rightPants", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 32 ], - "inflate": 0.25 - } - ], - "pos": [ -1.9, 12, 0 ], - "material": "alpha" - }, - - { - "name": "jacket", - "pivot": [ 0.0, 24.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 32 ], - "inflate": 0.25 - } - ], - "material": "alpha" - } - ] - }, - "geometry.humanoid.customSlim:geometry.humanoid": { - - "bones": [ - { - "name": "hat", - "neverRender": false, - "material": "alpha" - }, - { - "name": "leftArm", - "reset": true, - "mirror": false, - "pivot": [ 5.0, 21.5, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 32, 48 ] - } - ] - }, - - { - "name": "rightArm", - "reset": true, - "pivot": [ -5.0, 21.5, 0.0 ], - "cubes": [ - { - "origin": [ -7.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 40, 16 ] - } - ] - }, - - { - "pivot": [ -6, 14.5, 1 ], - "neverRender": true, - "name": "rightItem", - "parent": "rightArm" - }, - - { - "name": "leftSleeve", - "pivot": [ 5.0, 21.5, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 48, 48 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "rightSleeve", - "pivot": [ -5.0, 21.5, 0.0 ], - "cubes": [ - { - "origin": [ -7.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 40, 32 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "leftLeg", - "reset": true, - "mirror": false, - "pivot": [ 1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 16, 48 ] - } - ] - }, - - { - "name": "leftPants", - "pivot": [ 1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 48 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "rightPants", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 32 ], - "inflate": 0.25 - } - ], - "material": "alpha" - }, - - { - "name": "jacket", - "pivot": [ 0.0, 24.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 32 ], - "inflate": 0.25 - } - ], - "material": "alpha" - } - ] - } - - } - -} \ No newline at end of file diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index d2dff626c..840614ea8 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -49,6 +49,10 @@ debug-mode: false # Thread pool size general-thread-pool: 32 +# Allow third party capes to be visible. Currently allowing: +# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes +allow-third-party-capes: true + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. @@ -57,8 +61,4 @@ metrics: # If metrics should be enabled enabled: true # UUID of server, don't change! - uuid: generateduuid - - - - + uuid: generateduuid \ No newline at end of file