diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index b6c9a97fa..bac915437 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -169,12 +169,13 @@ public class GeyserConnector { if (config.isDebugMode()) { ex.printStackTrace(); } + config.getRemote().setAddress(InetAddress.getLoopbackAddress().getHostAddress()); } } String remoteAddress = config.getRemote().getAddress(); - int remotePort = config.getRemote().getPort(); // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { + int remotePort; try { // Searches for a server address and a port from a SRV record of the specified host name InitialDirContext ctx = new InitialDirContext(); 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 652ec1339..b6af30fed 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -111,6 +111,8 @@ public interface GeyserConfiguration { String getServerName(); + int getCompressionLevel(); + boolean isEnableProxyProtocol(); List getProxyProtocolWhitelistedIPs(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index e9adfe12b..f8b652e53 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -147,6 +147,13 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("server-name") private String serverName = GeyserConnector.NAME; + @JsonProperty("compression-level") + private int compressionLevel = 6; + + public int getCompressionLevel() { + return Math.max(-1, Math.min(compressionLevel, 9)); + } + @JsonProperty("enable-proxy-protocol") private boolean enableProxyProtocol = false; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index 41073246e..1fe8d4362 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -44,6 +45,10 @@ public class AbstractHorseEntity extends AnimalEntity { // Specifies the size of the entity's inventory. Required to place slots in the entity. metadata.put(EntityData.CONTAINER_BASE_SIZE, 2); + // Add dummy health attribute since LivingEntity updates the attribute for us + attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(20, 20)); + // Add horse jump strength attribute to allow donkeys and mules to jump + attributes.put(AttributeType.HORSE_JUMP_STRENGTH, AttributeType.HORSE_JUMP_STRENGTH.getAttribute(0.5f, 2)); } @Override @@ -85,9 +90,15 @@ public class AbstractHorseEntity extends AnimalEntity { } // Needed to control horses - metadata.getFlags().setFlag(EntityFlag.CAN_POWER_JUMP, true); + boolean canPowerJump = entityType != EntityType.LLAMA && entityType != EntityType.TRADER_LLAMA; + metadata.getFlags().setFlag(EntityFlag.CAN_POWER_JUMP, canPowerJump); metadata.getFlags().setFlag(EntityFlag.WASD_CONTROLLED, true); super.updateBedrockMetadata(entityMetadata, session); + + if (entityMetadata.getId() == 8) { + // Update the health attribute + updateBedrockAttributes(session); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index d575f9521..48e321932 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -32,7 +32,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class LlamaEntity extends ChestedHorseEntity { @@ -52,16 +52,13 @@ public class LlamaEntity extends ChestedHorseEntity { if (entityMetadata.getId() == 20) { // Bedrock treats llama decoration as armor MobArmorEquipmentPacket equipmentPacket = new MobArmorEquipmentPacket(); - equipmentPacket.setRuntimeEntityId(getGeyserId()); + equipmentPacket.setRuntimeEntityId(geyserId); // -1 means no armor - if ((int) entityMetadata.getValue() != -1) { - // The damage value is the dye color that Java sends us + int carpetIndex = (int) entityMetadata.getValue(); + if (carpetIndex > -1 && carpetIndex <= 15) { + // The damage value is the dye color that Java sends us, for pre-1.16.220 // The item is always going to be a carpet - equipmentPacket.setChestplate(ItemData.builder() - .id(BlockTranslator.CARPET) - .damage((int) entityMetadata.getValue()) - .count(1) - .build()); + equipmentPacket.setChestplate(ItemRegistry.CARPETS.get(carpetIndex)); } else { equipmentPacket.setChestplate(ItemData.AIR); } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java index e9ad81a6a..68f65f9af 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java @@ -28,7 +28,6 @@ package org.geysermc.connector.inventory; import com.nukkitx.protocol.bedrock.data.inventory.EnchantData; import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; import lombok.Getter; -import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import java.util.Arrays; @@ -38,7 +37,6 @@ import java.util.List; /** * A mutable "wrapper" around {@link EnchantOptionData} */ -@Setter public class GeyserEnchantOption { private static final List EMPTY = Collections.emptyList(); /** @@ -57,6 +55,12 @@ public class GeyserEnchantOption { @Getter private final int javaIndex; + /** + * Whether the enchantment details have actually changed. + * Used to mitigate weird packet spamming pre-1.14, causing the net ID to always update. + */ + private boolean hasChanged; + private int xpCost = 0; private int javaEnchantIndex = -1; private int bedrockEnchantIndex = -1; @@ -67,8 +71,35 @@ public class GeyserEnchantOption { } public EnchantOptionData build(GeyserSession session) { + this.hasChanged = false; return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId()); } + + public boolean hasChanged() { + return hasChanged; + } + + public void setXpCost(int xpCost) { + if (this.xpCost != xpCost) { + hasChanged = true; + this.xpCost = xpCost; + } + } + + public void setEnchantIndex(int javaEnchantIndex, int bedrockEnchantIndex) { + if (this.javaEnchantIndex != javaEnchantIndex) { + hasChanged = true; + this.javaEnchantIndex = javaEnchantIndex; + this.bedrockEnchantIndex = bedrockEnchantIndex; + } + } + + public void setEnchantLevel(int enchantLevel) { + if (this.enchantLevel != enchantLevel) { + hasChanged = true; + this.enchantLevel = enchantLevel; + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index bd5030a8b..554ae9504 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -159,6 +159,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public void onSessionCreation(BedrockServerSession bedrockServerSession) { bedrockServerSession.setLogging(true); + bedrockServerSession.setCompressionLevel(connector.getConfig().getBedrock().getCompressionLevel()); bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(connector, new GeyserSession(connector, bedrockServerSession))); // Set the packet codec to default just in case we need to send disconnect packets. bedrockServerSession.setPacketCodec(BedrockProtocol.DEFAULT_BEDROCK_CODEC); 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 2fd4ba39b..f1ce484aa 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 @@ -352,6 +352,12 @@ public class GeyserSession implements CommandSender { @Setter private long lastMovementTimestamp = System.currentTimeMillis(); + /** + * Used to send a ClientVehicleMovePacket for every PlayerInputPacket after idling on a boat/horse for more than 100ms + */ + @Setter + private long lastVehicleMoveTimestamp = System.currentTimeMillis(); + /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java index f0b5a1752..b053a204c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMoveEntityAbsoluteTranslator.java @@ -41,6 +41,8 @@ public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator 0) { + sendMovement = true; + } + } + } + if (sendMovement) { + long timeSinceVehicleMove = System.currentTimeMillis() - session.getLastVehicleMoveTimestamp(); + if (timeSinceVehicleMove >= 100) { + Vector3f vehiclePosition = vehicle.getPosition(); + Vector3f vehicleRotation = vehicle.getRotation(); + + if (vehicle instanceof BoatEntity) { + // Remove some Y position to prevents boats flying up + vehiclePosition = vehiclePosition.down(EntityType.BOAT.getOffset()); + } + + ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket( + vehiclePosition.getX(), vehiclePosition.getY(), vehiclePosition.getZ(), + vehicleRotation.getX() - 90, vehicleRotation.getY() + ); + session.sendDownstreamPacket(clientVehicleMovePacket); + } + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockRiderJumpTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockRiderJumpTranslator.java new file mode 100644 index 000000000..5d375daee --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockRiderJumpTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 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.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.protocol.bedrock.packet.RiderJumpPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = RiderJumpPacket.class) +public class BedrockRiderJumpTranslator extends PacketTranslator { + @Override + public void translate(RiderJumpPacket packet, GeyserSession session) { + Entity vehicle = session.getRidingVehicleEntity(); + if (vehicle instanceof AbstractHorseEntity) { + ClientPlayerStatePacket playerStatePacket = new ClientPlayerStatePacket((int) vehicle.getEntityId(), PlayerState.START_HORSE_JUMP, packet.getJumpStrength()); + session.sendDownstreamPacket(playerStatePacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index 03f8bb104..6e03c7df3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -34,6 +34,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; import org.geysermc.connector.inventory.EnchantingContainer; +import org.geysermc.connector.inventory.GeyserEnchantOption; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; @@ -67,18 +68,20 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla case 6: // Enchantment type slotToUpdate = key - 4; - int index = value; - if (index != -1) { - Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[index].name().toLowerCase()); + // "value" here is the Java enchant ordinal, so that does not need to be changed + // The Bedrock index might need changed, so let's look it up and see. + int bedrockIndex = value; + if (bedrockIndex != -1) { + Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[bedrockIndex].name().toLowerCase()); if (enchantment != null) { // Convert the Java enchantment index to Bedrock's - index = enchantment.ordinal(); + bedrockIndex = enchantment.ordinal(); } else { - index = -1; + // There is no Bedrock enchantment equivalent + bedrockIndex = -1; } } - enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setJavaEnchantIndex(value); - enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setBedrockEnchantIndex(index); + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(value, bedrockIndex); break; case 7: case 8: @@ -91,8 +94,9 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla default: return; } - if (shouldUpdate) { - enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].build(session); + GeyserEnchantOption enchantOption = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate]; + if (shouldUpdate && enchantOption.hasChanged()) { + enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantOption.build(session); PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket(); packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions())); session.sendUpstreamPacket(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index c56ba249f..7550bc818 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -92,6 +92,10 @@ public class ItemRegistry { * Bucket item entries (excluding the milk bucket), used in BedrockInventoryTransactionTranslator.java */ public static final IntSet BUCKETS = new IntArraySet(); + /** + * Carpet item data, used in LlamaEntity.java + */ + public static final List CARPETS = new ArrayList<>(16); /** * Crossbow item entry, used in PillagerEntity.java */ @@ -452,6 +456,13 @@ public class ItemRegistry { BOATS.add(entry.getValue().get("bedrock_id").intValue()); } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); + } else if (entry.getKey().contains("_carpet")) { + // This should be the numerical order Java sends as an integer value for llamas + CARPETS.add(ItemData.builder() + .id(itemEntry.getBedrockId()) + .damage(itemEntry.getBedrockData()) + .count(1) + .blockRuntimeId(itemEntry.getBedrockBlockId()).build()); } itemNames.add(entry.getKey()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java index 1a236f551..1c628503a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityVelocityTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.java.entity; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -47,6 +48,12 @@ public class JavaEntityVelocityTranslator extends PacketTranslator itemFrames = new Object2IntOpenHashMap<>(); private final Map flowerPotBlocks = new HashMap<>(); - // Bedrock carpet ID, used in LlamaEntity.java for decoration - public static final int CARPET = 171; - public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 387dbf4a1..3f23712f3 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -23,6 +23,9 @@ bedrock: motd2: "Another Geyser server." # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" + # How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but + # the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable. + compression-level: 6 # Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy # in front of your Geyser instance. enable-proxy-protocol: false