diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java index d5226245..e7beca79 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitPlugin.java @@ -25,6 +25,7 @@ package org.geysermc.platform.bukkit; +import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; @@ -56,6 +57,15 @@ public class GeyserBukkitPlugin extends JavaPlugin implements GeyserBootstrap { saveConfig(); } + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!Bukkit.getIp().equals("0.0.0.0")) { + getConfig().set("remote.address", Bukkit.getIp()); + } + + getConfig().set("remote.port", Bukkit.getPort()); + saveConfig(); + this.geyserLogger = new GeyserBukkitLogger(getLogger(), geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.BUKKIT, this); diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index 3483c293..72f166c9 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -25,11 +25,11 @@ package org.geysermc.platform.bungeecord; +import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.ConfigurationProvider; import net.md_5.bungee.config.YamlConfiguration; - import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -40,6 +40,7 @@ import org.geysermc.platform.bungeecord.command.GeyserBungeeCommandManager; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; import java.nio.file.Files; import java.util.UUID; import java.util.logging.Level; @@ -81,8 +82,31 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserConfig = new GeyserBungeeConfiguration(getDataFolder(), configuration); + boolean configHasChanged = false; + + if (getProxy().getConfig().getListeners().size() == 1) { + ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0]; + + InetSocketAddress javaAddr = listener.getHost(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + configuration.set("remote.address", javaAddr.getHostString()); + } + + configuration.set("remote.port", javaAddr.getPort()); + + configHasChanged = true; + } + if (geyserConfig.getMetrics().getUniqueId().equals("generateduuid")) { configuration.set("metrics.uuid", UUID.randomUUID().toString()); + + configHasChanged = true; + } + + if (configHasChanged) { try { ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, new File(getDataFolder(), "config.yml")); } catch (IOException ex) { diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java index efcf489d..e8459550 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -34,9 +34,7 @@ import org.geysermc.connector.GeyserConfiguration; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class GeyserSpongeConfiguration implements GeyserConfiguration { @@ -60,7 +58,8 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration { if (node.getNode("userAuths").getValue() == null) return; - for (String key : (List) node.getNode("userAuths").getValue()) { + List userAuths = new ArrayList(((LinkedHashMap)node.getNode("userAuths").getValue()).keySet()); + for (String key : userAuths) { userAuthInfo.put(key, new SpongeUserAuthenticationInfo(key)); } } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 57470e28..01d71f1c 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,13 +26,12 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; - +import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; - import org.geysermc.common.PlatformType; -import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.utils.FileUtils; import org.geysermc.platform.sponge.command.GeyserSpongeCommandExecutor; @@ -47,6 +46,7 @@ import org.spongepowered.api.plugin.Plugin; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Sponge", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -79,14 +79,31 @@ public class GeyserSpongePlugin implements GeyserBootstrap { } ConfigurationLoader loader = YAMLConfigurationLoader.builder().setPath(configFile.toPath()).build(); + ConfigurationNode config; try { - this.geyserConfig = new GeyserSpongeConfiguration(configDir, loader.load()); + config = loader.load(); + this.geyserConfig = new GeyserSpongeConfiguration(configDir, config); } catch (IOException ex) { logger.warn("Failed to load config.yml!"); ex.printStackTrace(); return; } + ConfigurationNode serverIP = config.getNode("remote").getNode("address"); + ConfigurationNode serverPort = config.getNode("remote").getNode("port"); + + if (Sponge.getServer().getBoundAddress().isPresent()) { + InetSocketAddress javaAddr = Sponge.getServer().getBoundAddress().get(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + serverIP.setValue("127.0.0.1"); + } + + serverPort.setValue(javaAddr.getPort()); + } + this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.SPONGE, this); this.geyserCommandManager = new GeyserSpongeCommandManager(Sponge.getCommandManager(), connector); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java index 62ae20e2..5f509834 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import lombok.Setter; import org.geysermc.connector.GeyserConfiguration; @@ -89,7 +90,10 @@ public class GeyserVelocityConfiguration implements GeyserConfiguration { @Getter public static class RemoteConfiguration implements IRemoteConfiguration { + @Setter private String address; + + @Setter private int port; private String motd1; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index 6e721a5f..f155fc82 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -33,6 +33,7 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.proxy.ProxyServer; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -43,6 +44,7 @@ import org.slf4j.Logger; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.UUID; @Plugin(id = "geyser", name = GeyserConnector.NAME + "-Velocity", version = GeyserConnector.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -51,6 +53,9 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Inject private Logger logger; + @Inject + private ProxyServer server; + @Inject private CommandManager commandManager; @@ -73,6 +78,16 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { ex.printStackTrace(); } + InetSocketAddress javaAddr = server.getBoundAddress(); + + // Don't change the ip if its listening on all interfaces + // By default this should be 127.0.0.1 but may need to be changed in some circumstances + if (!javaAddr.getHostString().equals("0.0.0.0")) { + geyserConfig.getRemote().setAddress(javaAddr.getHostString()); + } + + geyserConfig.getRemote().setPort(javaAddr.getPort()); + this.geyserLogger = new GeyserVelocityLogger(logger, geyserConfig.isDebugMode()); this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); 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 5596ca7a..54355d99 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -158,11 +158,11 @@ public class Entity { session.getUpstream().sendPacket(moveEntityPacket); } - public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround) { - moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround); + public void moveAbsolute(GeyserSession session, Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) { + moveAbsolute(session, position, Vector3f.from(yaw, pitch, yaw), isOnGround, teleported); } - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround) { + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { setPosition(position); setRotation(rotation); @@ -171,7 +171,7 @@ public class Entity { moveEntityPacket.setPosition(position); moveEntityPacket.setRotation(getBedrockRotation()); moveEntityPacket.setOnGround(isOnGround); - moveEntityPacket.setTeleported(false); + moveEntityPacket.setTeleported(teleported); session.getUpstream().sendPacket(moveEntityPacket); } 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 d0ac2aeb..d4732168 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -83,7 +83,7 @@ public class PlayerEntity extends LivingEntity { addPlayerPacket.setUsername(username); addPlayerPacket.setRuntimeEntityId(geyserId); addPlayerPacket.setUniqueEntityId(geyserId); - addPlayerPacket.setPosition(position); + addPlayerPacket.setPosition(position.clone().sub(0, EntityType.PLAYER.getOffset(), 0)); addPlayerPacket.setRotation(getBedrockRotation()); addPlayerPacket.setMotion(motion); addPlayerPacket.setHand(hand); @@ -101,6 +101,8 @@ public class PlayerEntity extends LivingEntity { } public void sendPlayer(GeyserSession session) { + if(session.getEntityCache().getPlayerEntity(uuid) == null) + return; if (getLastSkinUpdate() == -1) { if (playerList) { PlayerListPacket playerList = new PlayerListPacket(); @@ -128,7 +130,7 @@ public class PlayerEntity extends LivingEntity { } @Override - public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround) { + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { setPosition(position); setRotation(rotation); @@ -137,7 +139,11 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setPosition(this.position); movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); + + if (teleported) { + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } session.getUpstream().sendPacket(movePlayerPacket); } 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 0b4a57dc..70574032 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 @@ -30,6 +30,7 @@ import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsExcepti import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -84,8 +85,10 @@ public class GeyserSession implements CommandSender { private final UpstreamSession upstream; private RemoteServer remoteServer; private Client downstream; - @Setter private AuthData authData; - @Setter private BedrockClientData clientData; + @Setter + private AuthData authData; + @Setter + private BedrockClientData clientData; private PlayerEntity playerEntity; private PlayerInventory inventory; @@ -95,6 +98,8 @@ public class GeyserSession implements CommandSender { private InventoryCache inventoryCache; private ScoreboardCache scoreboardCache; private WindowCache windowCache; + @Setter + private TeleportCache teleportCache; private DataCache javaPacketCache; @@ -450,4 +455,19 @@ public class GeyserSession implements CommandSender { // startGamePacket.setMovementServerAuthoritative(true); upstream.sendPacket(startGamePacket); } + + public boolean confirmTeleport(Vector3f position) { + if (teleportCache != null) { + if (!teleportCache.canConfirm(position)) { + GeyserConnector.getInstance().getLogger().debug("Unconfirmed Teleport " + teleportCache.getTeleportConfirmId() + + " Ignore movement " + position + " expected " + teleportCache); + return false; + } + int teleportId = teleportCache.getTeleportConfirmId(); + teleportCache = null; + ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(teleportId); + getDownstream().getSession().send(teleportConfirmPacket); + } + return true; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java new file mode 100644 index 00000000..55b7e6fa --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/TeleportCache.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 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.network.session.cache; + +import com.nukkitx.math.vector.Vector3f; +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class TeleportCache { + + private static final double ERROR = 0.2; + private static final double ERROR_Y = 0.5; + + private double x, y, z; + private int teleportConfirmId; + + public boolean canConfirm(Vector3f position) { + return (Math.abs(this.x - position.getX()) < ERROR && + Math.abs(this.y - position.getY()) < ERROR_Y && + Math.abs(this.z - position.getZ()) < ERROR); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java index 356dcf98..423ffd39 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/ItemStackTranslator.java @@ -126,7 +126,7 @@ public abstract class ItemStackTranslator { if (tag instanceof StringTag) { StringTag stringTag = (StringTag) tag; - return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), MessageUtils.getBedrockMessage(Message.fromString(stringTag.getValue()))); + return new com.nukkitx.nbt.tag.StringTag(stringTag.getName(), stringTag.getValue()); } if (tag instanceof ListTag) { 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 0ae3d88a..be158746 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 @@ -34,7 +34,6 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -59,23 +58,31 @@ public class BedrockMovePlayerTranslator extends PacketTranslator= 28 && 31 >= action.getSlot())) { - if (!HAS_RECEIVED_MESSAGE.contains(session.getPlayerEntity().getEntityId())) { - // TODO: Allow the crafting table to be used with non-standalone versions - session.sendMessage("The creative crafting table cannot be used as it's incompatible with Minecraft: Java Edition."); - HAS_RECEIVED_MESSAGE.add(session.getPlayerEntity().getEntityId()); - } updateInventory(session, inventory); InventoryUtils.updateCursor(session); return; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 0c9cb6e1..a0ec2eee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -38,6 +38,7 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.LocaleUtils; @AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { @@ -60,7 +61,7 @@ public class BlockInventoryHolder extends InventoryHolder { .intTag("x", position.getX()) .intTag("y", position.getY()) .intTag("z", position.getZ()) - .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + .stringTag("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).buildRootTag(); BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 5e0361c0..f59b82ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -45,8 +45,8 @@ public class ItemTranslator { private Int2ObjectMap itemTranslators = new Int2ObjectOpenHashMap(); private List nbtItemTranslators; private Map javaIdentifierMap = new HashMap<>(); - - // Shield ID, used in Entity.java + + // Shield ID, used in Entity.java public static final int SHIELD = 829; public void init() { @@ -91,7 +91,6 @@ public class ItemTranslator { ItemEntry javaItem = getItem(data); ItemStack itemStack; - ItemStackTranslator itemStackTranslator = itemTranslators.get(javaItem.getJavaId()); if (itemStackTranslator != null) { itemStack = itemStackTranslator.translateToJava(data, javaItem); @@ -116,19 +115,21 @@ public class ItemTranslator { ItemEntry bedrockItem = getItem(stack); - if (stack != null && stack.getNbt() != null) { + ItemStack itemStack = new ItemStack(stack.getId(), stack.getAmount(), stack.getNbt() != null ? stack.getNbt().clone() : null); + + if (itemStack.getNbt() != null) { for (NbtItemStackTranslator translator : nbtItemTranslators) { if (translator.acceptItem(bedrockItem)) { - translator.translateToBedrock(stack.getNbt(), bedrockItem); + translator.translateToBedrock(itemStack.getNbt(), bedrockItem); } } } ItemStackTranslator itemStackTranslator = itemTranslators.get(bedrockItem.getJavaId()); if (itemStackTranslator != null) { - return itemStackTranslator.translateToBedrock(stack, bedrockItem); + return itemStackTranslator.translateToBedrock(itemStack, bedrockItem); } else { - return DEFAULT_TRANSLATOR.translateToBedrock(stack, bedrockItem); + return DEFAULT_TRANSLATOR.translateToBedrock(itemStack, bedrockItem); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java new file mode 100644 index 00000000..9458ec4f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2020 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.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.NbtItemStackTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +@ItemRemapper(priority = -1) +public class BasicItemTranslator extends NbtItemStackTranslator { + + @Override + public void translateToBedrock(CompoundTag itemTag, ItemEntry itemEntry) { + if (itemTag.contains("display")) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("Name")) { + StringTag nameTag = displayTag.get("Name"); + try { + displayTag.put(new StringTag("Name", toBedrockMessage(nameTag))); + } catch (Exception ex) { + } + } + + if (displayTag.contains("Lore")) { + ListTag loreTag = displayTag.get("Lore"); + List lore = new ArrayList<>(); + for (Tag tag : loreTag.getValue()) { + if (!(tag instanceof StringTag)) return; + try { + lore.add(new StringTag("", toBedrockMessage((StringTag) tag))); + } catch (Exception ex) { + } + } + displayTag.put(new ListTag("Lore", lore)); + } + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + if (itemTag.contains("display")) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag.contains("Name")) { + StringTag nameTag = displayTag.get("Name"); + displayTag.put(new StringTag("Name", toJavaMessage(nameTag))); + } + + if (displayTag.contains("Lore")) { + ListTag loreTag = displayTag.get("Lore"); + List lore = new ArrayList<>(); + for (Tag tag : loreTag.getValue()) { + if (!(tag instanceof StringTag)) return; + lore.add(new StringTag("", "§r" + toJavaMessage((StringTag) tag))); + } + displayTag.put(new ListTag("Lore", lore)); + } + } + } + + private String toJavaMessage(StringTag tag) { + String message = tag.getValue(); + if (message == null) return null; + if (message.startsWith("§r")) { + message = message.replaceFirst("§r", ""); + } + Component component = TextComponent.of(message); + return GsonComponentSerializer.INSTANCE.serialize(component); + } + + private String toBedrockMessage(StringTag tag) { + String message = tag.getValue(); + if (message == null) return null; + TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message); + String legacy = LegacyComponentSerializer.legacy().serialize(component); + if (hasFormatting(LegacyComponentSerializer.legacy().deserialize(legacy))) { + return "§r" + legacy; + } + return legacy; + } + + private boolean hasFormatting(Component component) { + if (component.hasStyling()) return true; + for (Component child : component.children()) { + if (hasFormatting(child)) { + return true; + } + } + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java index febfc9ce..9a468672 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityTeleportTranslator.java @@ -44,6 +44,6 @@ public class JavaEntityTeleportTranslator extends PacketTranslator { @Override public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession session) { - Entity entity = session.getPlayerEntity(); + PlayerEntity entity = session.getPlayerEntity(); if (entity == null) return; @@ -94,18 +94,21 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator 1.5 || (yDis < 1.45 || yDis > (session.isJumping() ? 4.3 : (session.isSprinting() ? 2.5 : 1.9))) || zDis > 1.5) { - entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), true); + session.setTeleportCache(new TeleportCache(packet.getX(), packet.getY(), packet.getZ(), packet.getTeleportId())); + entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), true, true); + } else { + ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); + session.getDownstream().getSession().send(teleportConfirmPacket); } } - - ClientTeleportConfirmPacket teleportConfirmPacket = new ClientTeleportConfirmPacket(packet.getTeleportId()); - session.getDownstream().getSession().send(teleportConfirmPacket); } } 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 331eb094..e01b95e9 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,21 @@ package org.geysermc.connector.network.translators.java.entity.spawn; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; +import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.GeyserConnector; 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.Translator; import org.geysermc.connector.utils.SkinUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnPlayerPacket; -import com.nukkitx.math.vector.Vector3f; - @Translator(packet = ServerSpawnPlayerPacket.class) public class JavaSpawnPlayerTranslator extends PacketTranslator { @Override public void translate(ServerSpawnPlayerPacket packet, GeyserSession session) { - Vector3f position = Vector3f.from(packet.getX(), packet.getY() - EntityType.PLAYER.getOffset(), packet.getZ()); + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); Vector3f rotation = Vector3f.from(packet.getYaw(), packet.getPitch(), packet.getYaw()); PlayerEntity entity = session.getEntityCache().getPlayerEntity(packet.getUuid()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 3c17983c..b07ad2e7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -43,6 +43,7 @@ public class BlockStateValues { private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SHULKERBOX_DIRECTIONS = new Object2ByteOpenHashMap<>(); /** * Determines if the block state contains Bedrock block information @@ -71,13 +72,19 @@ public class BlockStateValues { if (skullRotation != null) { BlockStateValues.SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); } + + JsonNode shulkerDirection = entry.getValue().get("shulker_direction"); + if (shulkerDirection != null) { + BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); + } } /** * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. * This gives an integer color that Bedrock can use. + * * @param state BlockState of the block - * @return banner color integer or -1 if no color + * @return Banner color integer or -1 if no color */ public static int getBannerColor(BlockState state) { if (BANNER_COLORS.containsKey(state)) { @@ -89,8 +96,9 @@ public class BlockStateValues { /** * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. + * * @param state BlockState of the block - * @return bed color byte or -1 if no color + * @return Bed color byte or -1 if no color */ public static byte getBedColor(BlockState state) { if (BED_COLORS.containsKey(state)) { @@ -102,8 +110,9 @@ public class BlockStateValues { /** * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. * This gives a byte variant ID that Bedrock can use. + * * @param state BlockState of the block - * @return skull variant byte or -1 if no variant + * @return Skull variant byte or -1 if no variant */ public static byte getSkullVariant(BlockState state) { if (SKULL_VARIANTS.containsKey(state)) { @@ -113,9 +122,11 @@ public class BlockStateValues { } /** + * Skull rotations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte rotation that Bedrock can use. * * @param state BlockState of the block - * @return skull rotation value or -1 if no value + * @return Skull rotation value or -1 if no value */ public static byte getSkullRotation(BlockState state) { if (SKULL_ROTATIONS.containsKey(state)) { @@ -124,4 +135,18 @@ public class BlockStateValues { return -1; } + + /** + * Shulker box directions are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte direction that Bedrock can use. + * + * @param state BlockState of the block + * @return Shulker direction value or -1 if no value + */ + public static byte getShulkerBoxDirection(BlockState state) { + if (SHULKERBOX_DIRECTIONS.containsKey(state)) { + return SHULKERBOX_DIRECTIONS.getByte(state); + } + return -1; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 0fc9abda..2034b3d5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -56,7 +56,10 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement List tagsList = new ArrayList<>(); if (tag.contains("Patterns")) { for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { - tagsList.add(getPattern((CompoundTag) patternTag)); + com.nukkitx.nbt.tag.CompoundTag newPatternTag = getPattern((CompoundTag) patternTag); + if (newPatternTag != null) { + tagsList.add(newPatternTag); + } } com.nukkitx.nbt.tag.ListTag bedrockPatterns = new com.nukkitx.nbt.tag.ListTag<>("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, tagsList); @@ -82,10 +85,24 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement return tagBuilder.buildRootTag(); } + /** + * Convert the Java edition pattern nbt to Bedrock edition, null if the pattern doesn't exist + * + * @param pattern Java edition pattern nbt + * @return The Bedrock edition format pattern nbt + */ protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) { + String patternName = (String) pattern.get("Pattern").getValue(); + + // Return null if its the globe pattern as it doesn't exist on bedrock + if (patternName.equals("glb")) { + return null; + } + return CompoundTagBuilder.builder() .intTag("Color", 15 - (int) pattern.get("Color").getValue()) .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) + .stringTag("Pattern", patternName) .buildRootTag(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java new file mode 100644 index 00000000..b0b8fa3d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019-2020 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.network.translators.world.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "ShulkerBox", delay = false, regex = "shulker_box") +public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + + byte direction = BlockStateValues.getShulkerBoxDirection(blockState); + // Just in case... + if (direction == -1) direction = 1; + tags.add(new ByteTag("facing", direction)); + + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.byteTag("facing", (byte)1); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index ac111c71..478e42d1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -27,6 +27,7 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.mc.protocol.data.message.*; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -121,20 +122,22 @@ public class MessageUtils { } public static String getBedrockMessage(Message message) { - Component component; if (isMessage(message.getText())) { - component = GsonComponentSerializer.INSTANCE.deserialize(message.getText()); + return getBedrockMessage(message.getText()); } else { - component = GsonComponentSerializer.INSTANCE.deserialize(message.toJsonString()); + return getBedrockMessage(message.toJsonString()); } - return LegacyComponentSerializer.legacy().serialize(component); } public static String getBedrockMessage(String message) { - Component component = GsonComponentSerializer.INSTANCE.deserialize(message); + Component component = phraseJavaMessage(message); return LegacyComponentSerializer.legacy().serialize(component); } + public static Component phraseJavaMessage(String message) { + return GsonComponentSerializer.INSTANCE.deserialize(message); + } + public static String getJavaMessage(String message) { Component component = LegacyComponentSerializer.legacy().deserialize(message); return GsonComponentSerializer.INSTANCE.serialize(component); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index ade03c54..7c2b7fc0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -25,16 +25,22 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.Getter; - +import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URL; +import java.util.Arrays; +import java.util.Base64; import java.util.Map; import java.util.UUID; import java.util.concurrent.*; @@ -52,6 +58,7 @@ public class SkinProvider { private static Map cachedCapes = new ConcurrentHashMap<>(); private static Map> requestedCapes = new ConcurrentHashMap<>(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes public static boolean hasSkinCached(UUID uuid) { @@ -74,9 +81,10 @@ public class SkinProvider { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), - getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5) + getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); GeyserConnector.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); @@ -112,11 +120,11 @@ public class SkinProvider { return future; } - public static CompletableFuture requestCape(String capeUrl, boolean newThread) { + public static CompletableFuture requestCape(String capeUrl, CapeProvider provider, 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 officialCape = provider == CapeProvider.MINECRAFT; boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn(); if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) { @@ -126,14 +134,14 @@ public class SkinProvider { CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), EXECUTOR_SERVICE) + future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), EXECUTOR_SERVICE) .whenCompleteAsync((cape, throwable) -> { cachedCapes.put(capeUrl, cape); requestedCapes.remove(capeUrl); }); requestedCapes.put(capeUrl, future); } else { - Cape cape = supplyCape(capeUrl); // blocking + Cape cape = supplyCape(capeUrl, provider); // blocking future = CompletableFuture.completedFuture(cape); cachedCapes.put(capeUrl, cape); } @@ -143,9 +151,9 @@ public class SkinProvider { public static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, String username, boolean newThread) { if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { - for (UnofficalCape cape : UnofficalCape.VALUES) { + for (CapeProvider provider : CapeProvider.VALUES) { Cape cape1 = getOrDefault( - requestCape(cape.getUrlFor(playerId, username), newThread), + requestCape(provider.getUrlFor(playerId, username), provider, newThread), EMPTY_CAPE, 4 ); if (!cape1.isFailed()) { @@ -159,15 +167,15 @@ public class SkinProvider { private static Skin supplySkin(UUID uuid, String textureUrl) { byte[] skin = EMPTY_SKIN.getSkinData(); try { - skin = requestImage(textureUrl, false); + skin = requestImage(textureUrl, null); } catch (Exception ignored) {} // just ignore I guess return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); } - private static Cape supplyCape(String capeUrl) { + private static Cape supplyCape(String capeUrl, CapeProvider provider) { byte[] cape = new byte[0]; try { - cape = requestImage(capeUrl, true); + cape = requestImage(capeUrl, provider); } catch (Exception ignored) {} // just ignore I guess String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage @@ -181,11 +189,12 @@ public class SkinProvider { ); } - private static byte[] requestImage(String imageUrl, boolean cape) throws Exception { - BufferedImage image = ImageIO.read(new URL(imageUrl)); + private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception { + BufferedImage image = downloadImage(imageUrl, provider); GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl); - if (cape) { + // if the requested image is an cape + if (provider != null) { image = image.getWidth() > 64 ? scale(image) : image; BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); Graphics g = newImage.createGraphics(); @@ -209,7 +218,25 @@ public class SkinProvider { } } - private static BufferedImage scale (BufferedImage bufferedImage) { + private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { + if (provider == CapeProvider.FIVEZIG) + return readFiveZigCape(imageUrl); + BufferedImage image = ImageIO.read(new URL(imageUrl)); + if (image == null) throw new NullPointerException(); + return image; + } + + private static BufferedImage readFiveZigCape(String url) throws IOException { + JsonNode element = OBJECT_MAPPER.readTree(WebUtils.getBody(url)); + if (element != null && element.isObject()) { + JsonNode capeElement = element.get("d"); + if (capeElement == null || capeElement.isNull()) return null; + return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(capeElement.textValue()))); + } + return null; + } + + private static BufferedImage scale(BufferedImage bufferedImage) { BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); @@ -262,14 +289,16 @@ public class SkinProvider { * Sorted by 'priority' */ @AllArgsConstructor + @NoArgsConstructor @Getter - public enum UnofficalCape { + public enum CapeProvider { + MINECRAFT, 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), + LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), + FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); - public static final UnofficalCape[] VALUES = values(); + public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); private String url; private CapeUrlType type; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 012bd73a..9ce025e7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -155,7 +155,7 @@ public class SkinUtils { cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( cape, entity.getUuid(), entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.UnofficalCape.VALUES.length * 3); + ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); } if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {