Merge remote-tracking branch 'origin/master' into feature/extensions-gradle

This commit is contained in:
RednedEpic 2022-03-19 20:48:14 -05:00
commit 02cda38259
21 changed files with 284 additions and 131 deletions

View File

@ -20,13 +20,13 @@ Special thanks to the DragonProxy project for being a trailblazer in protocol tr
### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18.2.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser.
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
[![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4)
## Links:
- Website: https://geysermc.org
- Docs: https://github.com/GeyserMC/Geyser/wiki
- Docs: https://wiki.geysermc.org/geyser/
- Download: https://ci.geysermc.org
- Discord: https://discord.gg/geysermc
- Donate: https://opencollective.com/geysermc
@ -39,7 +39,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set
- Structure block UI
## What can't be fixed
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page.
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
## Compiling
1. Clone the repo to your computer

View File

@ -26,6 +26,7 @@
package org.geysermc.api.connection;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.common.value.qual.IntRange;
import java.util.UUID;
@ -55,5 +56,13 @@ public interface Connection {
*/
String xuid();
/**
* Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are
* currently playing on.
*
* @param address The address of the server
* @param port The port of the server
* @return true if the transfer was a success
*/
boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port);
}

View File

@ -23,31 +23,23 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.protocol.java;
package org.geysermc.floodgate.pluginmessage;
import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import com.google.common.base.Charsets;
import java.util.Collections;
public final class PluginMessageChannels {
public static final String SKIN = "floodgate:skin";
public static final String FORM = "floodgate:form";
public static final String TRANSFER = "floodgate:transfer";
/**
* Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
*/
@Translator(packet = ClientboundRecipePacket.class)
public class JavaRecipeTranslator extends PacketTranslator<ClientboundRecipePacket> {
private static final byte[] FLOODGATE_REGISTER_DATA = String.join("\0", SKIN, FORM, TRANSFER).getBytes(Charsets.UTF_8);
@Override
public void translate(GeyserSession session, ClientboundRecipePacket packet) {
if (packet.getAction() == UnlockRecipesAction.REMOVE) {
for (String identifier : packet.getRecipes()) {
session.getUnlockedRecipes().remove(identifier);
}
} else {
Collections.addAll(session.getUnlockedRecipes(), packet.getRecipes());
}
/**
* Get the prebuilt register data as a byte array
*
* @return the register data of the Floodgate channels
*/
public static byte[] getFloodgateRegisterData() {
return FLOODGATE_REGISTER_DATA;
}
}

View File

@ -100,7 +100,7 @@ public class GeyserSession {
}
public void login() {
this.handle.login();
throw new UnsupportedOperationException();
}
public void authenticate(String username) {
@ -120,7 +120,7 @@ public class GeyserSession {
}
public void close() {
this.handle.close();
throw new UnsupportedOperationException();
}
public void executeInEventLoop(Runnable runnable) {

View File

@ -99,13 +99,15 @@ public class LivingEntity extends Entity {
public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) {
byte xd = entityMetadata.getPrimitiveValue();
// Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like
// you're "mining" with ex. a shield.
boolean isUsingItem = (xd & 0x01) == 0x01;
boolean isUsingOffhand = (xd & 0x02) == 0x02;
ItemMapping shield = session.getItemMappings().getStoredItems().shield();
boolean isUsingShield = (getHand().getId() == shield.getBedrockId() ||
getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId());
setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield);
setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01);
boolean isUsingShield = hasShield(isUsingOffhand, shield);
setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield);
// Override the blocking
setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield);
// Riptide spin attack
setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04);
@ -142,6 +144,14 @@ public class LivingEntity extends Entity {
}
}
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
if (offhand) {
return offHand.getId() == shieldMapping.getBedrockId();
} else {
return hand.getId() == shieldMapping.getBedrockId();
}
}
@Override
protected boolean isShaking() {
return isMaxFrozenState;

View File

@ -61,7 +61,13 @@ public class TameableEntity extends AnimalEntity {
// Note: Must be set for wolf collar color to work
if (entityMetadata.getValue().isPresent()) {
// Owner UUID of entity
Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get());
UUID uuid = entityMetadata.getValue().get();
Entity entity;
if (uuid.equals(session.getPlayerEntity().getUuid())) {
entity = session.getPlayerEntity();
} else {
entity = session.getEntityCache().getPlayerEntity(uuid);
}
// Used as both a check since the player isn't in the entity cache and a normal fallback
if (entity == null) {
// Set to tame, but indicate that we are not the player that owns this

View File

@ -38,6 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.AttributeUtils;
@ -167,6 +168,16 @@ public class SessionPlayerEntity extends PlayerEntity {
return super.createHealthAttribute();
}
@Override
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
// Must be overridden to point to the player's inventory cache
if (offhand) {
return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId();
} else {
return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId();
}
}
@Override
public void updateBedrockMetadata() {
super.updateBedrockMetadata();

View File

@ -36,8 +36,11 @@ import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.ProtocolState;
import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility;
import com.github.steveice10.mc.protocol.data.game.setting.SkinPart;
import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic;
@ -46,6 +49,8 @@ import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientInte
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Session;
@ -73,6 +78,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.common.PlatformType;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.util.FormBuilder;
@ -97,6 +103,7 @@ import org.geysermc.geyser.level.physics.CollisionManager;
import org.geysermc.geyser.network.netty.LocalSession;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.BlockMappings;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.geyser.session.auth.AuthType;
@ -107,10 +114,7 @@ import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.*;
import javax.annotation.Nonnull;
import java.net.ConnectException;
@ -349,7 +353,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
private final Set<String> unlockedRecipes;
private final AtomicInteger lastRecipeNetId;
/**
@ -423,6 +426,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private long lastVehicleMoveTimestamp = System.currentTimeMillis();
/**
* Counts how many ticks have occurred since an arm animation started.
* -1 means there is no active arm swing.
*/
@Getter(AccessLevel.NONE)
private int armAnimationTicks = -1;
/**
* Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
*/
@ -527,7 +537,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.playerInventory = new PlayerInventory();
this.openInventory = null;
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.unlockedRecipes = new ObjectOpenHashSet<>();
this.lastRecipeNetId = new AtomicInteger(1);
this.spawned = false;
@ -617,17 +626,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
upstream.sendPacket(gamerulePacket);
}
public void login() {
if (this.remoteAuthType != AuthType.ONLINE) {
if (this.remoteAuthType == AuthType.OFFLINE) {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.offline"));
} else {
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.login.floodgate"));
}
authenticate(authData.name());
}
}
public void authenticate(String username) {
authenticate(username, "");
}
@ -1038,10 +1036,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
closed = true;
}
public void close() {
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode()));
}
/**
* Executes a task and prints a stack trace if an error occurs.
*/
@ -1109,6 +1103,34 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
for (Tickable entity : entityCache.getTickableEntities()) {
entity.tick();
}
if (armAnimationTicks != -1) {
// As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the
// player's effect status, but the animation can cut short if the duration suddenly decreases
// (from suddenly no longer having mining fatigue, for example)
// This math is referenced from Java Edition 1.18.2
int swingTotalDuration;
int hasteLevel = Math.max(effectCache.getHaste(), effectCache.getConduitPower());
if (hasteLevel > 0) {
swingTotalDuration = 6 - hasteLevel;
} else {
int miningFatigueLevel = effectCache.getMiningFatigue();
if (miningFatigueLevel > 0) {
swingTotalDuration = 6 + miningFatigueLevel * 2;
} else {
swingTotalDuration = 6;
}
}
if (++armAnimationTicks >= swingTotalDuration) {
if (sneaking) {
// Attempt to re-activate blocking as our swing animation is up
if (attemptToBlock()) {
playerEntity.updateBedrockMetadata();
}
}
armAnimationTicks = -1;
}
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
@ -1118,7 +1140,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.authData = authData;
}
public void setSneaking(boolean sneaking) {
public void startSneaking() {
// Toggle the shield, if there is no ongoing arm animation
// This matches Bedrock Edition behavior as of 1.18.12
if (armAnimationTicks == -1) {
attemptToBlock();
}
setSneaking(true);
}
public void stopSneaking() {
disableBlocking();
setSneaking(false);
}
private void setSneaking(boolean sneaking) {
this.sneaking = sneaking;
// Update pose and bounding box on our end
@ -1203,6 +1241,54 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return null;
}
/**
* Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display
* blocking and sends a packet to the Java server.
*/
private boolean attemptToBlock() {
ItemMapping shield = itemMappings.getStoredItems().shield();
ServerboundUseItemPacket useItemPacket;
if (playerInventory.getItemInHand().getJavaId() == shield.getJavaId()) {
useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
} else if (playerInventory.getOffhand().getJavaId() == shield.getJavaId()) {
useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND);
} else {
// No blocking
return false;
}
sendDownstreamPacket(useItemPacket);
playerEntity.setFlag(EntityFlag.BLOCKING, true);
// Metadata should be updated later
return true;
}
/**
* Starts ticking the amount of time that the Bedrock client has been swinging their arm, and disables blocking if
* blocking.
*/
public void activateArmAnimationTicking() {
armAnimationTicks = 0;
if (disableBlocking()) {
playerEntity.updateBedrockMetadata();
}
}
/**
* Indicates to the client to stop blocking and tells the Java server the same.
*/
private boolean disableBlocking() {
if (playerEntity.getFlag(EntityFlag.BLOCKING)) {
ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM,
BlockUtils.POSITION_ZERO, Direction.DOWN);
sendDownstreamPacket(releaseItemPacket);
playerEntity.setFlag(EntityFlag.BLOCKING, false);
return true;
}
return false;
}
/**
* Will be overwritten for GeyserConnect.
*/
@ -1225,6 +1311,21 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return authData.xuid();
}
@SuppressWarnings("ConstantConditions") // Need to enforce the parameter annotations
@Override
public boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port) {
if (address == null || address.isBlank()) {
throw new IllegalArgumentException("Server address cannot be null or blank");
} else if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Server port must be between 0 and 65535, was " + port);
}
TransferPacket transferPacket = new TransferPacket();
transferPacket.setAddress(address);
transferPacket.setPort(port);
sendUpstreamPacket(transferPacket);
return true;
}
@Override
public void sendMessage(String message) {
TextPacket textPacket = new TextPacket();

View File

@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.session.GeyserSession;
@ -48,8 +49,6 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import static org.geysermc.geyser.util.PluginMessageUtils.getSkinChannel;
public final class FloodgateSkinUploader {
private final ObjectMapper JACKSON = new ObjectMapper();
private final List<String> skinQueue = new ArrayList<>();
@ -126,7 +125,7 @@ public final class FloodgateSkinUploader {
byte[] bytes = (value + '\0' + signature)
.getBytes(StandardCharsets.UTF_8);
PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes);
PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes);
}
break;
case LOG_MESSAGE:

View File

@ -601,6 +601,8 @@ public class SkinProvider {
HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection();
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION);
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
BufferedImage image = ImageIO.read(con.getInputStream());
if (image == null) throw new NullPointerException();

View File

@ -48,8 +48,10 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
switch (packet.getAction()) {
case SWING_ARM ->
// Delay so entity damage can be processed first
session.scheduleInEventLoop(() ->
session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)),
session.scheduleInEventLoop(() -> {
session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND));
session.activateArmAnimationTicking();
},
25,
TimeUnit.MILLISECONDS
);

View File

@ -411,22 +411,20 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
//https://wiki.vg/Protocol#Interact_Entity
switch (packet.getActionType()) {
case 0: //Interact
processEntityInteraction(session, packet, entity);
break;
case 1: //Attack
case 0 -> processEntityInteraction(session, packet, entity); // Interact
case 1 -> { // Attack
int entityId;
if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) {
// Redirects the attack to its body entity, this only happens when
// attacking the underbelly of the ender dragon
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId() + 3,
InteractAction.ATTACK, session.isSneaking());
session.sendDownstreamPacket(attackPacket);
entityId = entity.getEntityId() + 3;
} else {
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId(),
InteractAction.ATTACK, session.isSneaking());
session.sendDownstreamPacket(attackPacket);
entityId = entity.getEntityId();
}
break;
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entityId,
InteractAction.ATTACK, session.isSneaking());
session.sendDownstreamPacket(attackPacket);
}
}
break;
}

View File

@ -28,7 +28,10 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.entity.player.*;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
@ -39,10 +42,8 @@ import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -105,38 +106,13 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
session.sendDownstreamPacket(startSneakPacket);
// Toggle the shield, if relevant
PlayerInventory playerInv = session.getPlayerInventory();
ItemMapping shield = session.getItemMappings().getMapping("minecraft:shield");
if ((playerInv.getItemInHand().getJavaId() == shield.getJavaId()) ||
(playerInv.getOffhand().getJavaId() == shield.getJavaId())) {
ServerboundUseItemPacket useItemPacket;
if (playerInv.getItemInHand().getJavaId() == shield.getJavaId()) {
useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
} else {
// Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent
useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND);
}
session.sendDownstreamPacket(useItemPacket);
session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, true);
// metadata will be updated when sneaking
}
session.setSneaking(true);
session.startSneaking();
break;
case STOP_SNEAK:
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
session.sendDownstreamPacket(stopSneakPacket);
// Stop shield, if necessary
if (session.getPlayerEntity().getFlag(EntityFlag.BLOCKING)) {
ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO, Direction.DOWN);
session.sendDownstreamPacket(releaseItemPacket);
session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, false);
// metadata will be updated when sneaking
}
session.setSneaking(false);
session.stopSneaking();
break;
case START_SPRINT:
if (!entity.getFlag(EntityFlag.SWIMMING)) {

View File

@ -27,6 +27,8 @@ package org.geysermc.geyser.translator.protocol.java;
import com.github.steveice10.mc.protocol.data.game.command.CommandNode;
import com.github.steveice10.mc.protocol.data.game.command.CommandParser;
import com.github.steveice10.mc.protocol.data.game.command.properties.ResourceProperties;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCommandsPacket;
import com.nukkitx.protocol.bedrock.data.command.CommandData;
import com.nukkitx.protocol.bedrock.data.command.CommandEnumData;
@ -59,6 +61,7 @@ import java.util.*;
public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommandsPacket> {
private static final String[] ALL_EFFECT_IDENTIFIERS = EntityUtils.getAllEffectIdentifiers();
private static final String[] ATTRIBUTES = AttributeType.Builtin.BUILTIN.keySet().toArray(new String[0]);
private static final String[] ENUM_BOOLEAN = {"true", "false"};
private static final String[] VALID_COLORS;
private static final String[] VALID_SCOREBOARD_SLOTS;
@ -209,10 +212,11 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
* Convert Java edition command types to Bedrock edition
*
* @param session the session
* @param parser Command type to convert
* @param node Command type to convert
* @return Bedrock parameter data type
*/
private static Object mapCommandType(GeyserSession session, CommandParser parser) {
private static Object mapCommandType(GeyserSession session, CommandNode node) {
CommandParser parser = node.getParser();
if (parser == null) {
return CommandParam.STRING;
}
@ -235,6 +239,14 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
case COLOR -> VALID_COLORS;
case SCOREBOARD_SLOT -> VALID_SCOREBOARD_SLOTS;
case MOB_EFFECT -> ALL_EFFECT_IDENTIFIERS;
case RESOURCE, RESOURCE_OR_TAG -> {
String resource = ((ResourceProperties) node.getProperties()).getRegistryKey();
if (resource.equals("minecraft:attribute")) {
yield ATTRIBUTES;
} else {
yield CommandParam.STRING;
}
}
default -> CommandParam.STRING;
};
}
@ -308,7 +320,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
}
} else {
// Put the non-enum param into the list
Object mappedType = mapCommandType(session, paramNode.getParser());
Object mappedType = mapCommandType(session, paramNode);
CommandEnumData enumData = null;
CommandParam type = null;
if (mappedType instanceof String[]) {

View File

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCu
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundCustomPayloadPacket;
import com.google.common.base.Charsets;
import com.nukkitx.protocol.bedrock.packet.TransferPacket;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.session.auth.AuthType;
@ -54,7 +55,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
String channel = packet.getChannel();
if (channel.equals("floodgate:form")) {
if (channel.equals(PluginMessageChannels.FORM)) {
byte[] data = packet.getData();
// receive: first byte is form type, second and third are the id, remaining is the form data
@ -81,7 +82,7 @@ public class JavaCustomPayloadTranslator extends PacketTranslator<ClientboundCus
});
session.sendForm(form);
} else if (channel.equals("floodgate:transfer")) {
} else if (channel.equals(PluginMessageChannels.TRANSFER)) {
byte[] data = packet.getData();
// port, 4 bytes. remaining data, address.

View File

@ -26,18 +26,67 @@
package org.geysermc.geyser.translator.protocol.java;
import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundLoginDisconnectPacket;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.List;
@Translator(packet = ClientboundLoginDisconnectPacket.class)
public class JavaLoginDisconnectTranslator extends PacketTranslator<ClientboundLoginDisconnectPacket> {
@Override
public void translate(GeyserSession session, ClientboundLoginDisconnectPacket packet) {
Component disconnectReason = packet.getReason();
boolean isOutdatedMessage = false;
if (disconnectReason instanceof TranslatableComponent component) {
String key = component.key();
isOutdatedMessage = "multiplayer.disconnect.incompatible".equals(key) ||
// Legacy string (starting from at least 1.15.2)
"multiplayer.disconnect.outdated_server".equals(key)
// Reproduced on 1.15.2 server with ViaVersion 4.0.0-21w20a with 1.18.2 Java client
|| key.startsWith("Outdated server!");
} else {
if (disconnectReason instanceof TextComponent component) {
if (component.content().startsWith("Outdated server!")) {
// Reproduced with vanilla 1.8.8 server and 1.18.2 Java client
isOutdatedMessage = true;
} else {
List<Component> children = component.children();
for (int i = 0; i < children.size(); i++) {
if (children.get(i) instanceof TextComponent child && child.content().startsWith("Outdated server!")) {
// Reproduced on Paper 1.17.1
isOutdatedMessage = true;
break;
}
}
}
}
}
String serverDisconnectMessage = MessageTranslator.convertMessage(disconnectReason, session.locale());
String disconnectMessage;
if (isOutdatedMessage) {
String locale = session.locale();
PlatformType platform = session.getGeyser().getPlatformType();
String outdatedType = (platform == PlatformType.BUNGEECORD || platform == PlatformType.VELOCITY) ?
"geyser.network.remote.outdated.proxy" : "geyser.network.remote.outdated.server";
disconnectMessage = GeyserLocale.getPlayerLocaleString(outdatedType, locale, MinecraftProtocol.getJavaVersions().get(0)) + '\n'
+ GeyserLocale.getPlayerLocaleString("geyser.network.remote.original_disconnect_message", locale, serverDisconnectMessage);
} else {
disconnectMessage = serverDisconnectMessage;
}
// The client doesn't manually get disconnected so we have to do it ourselves
session.disconnect(MessageTranslator.convertMessage(packet.getReason(), session.locale()));
session.disconnect(disconnectMessage);
}
@Override

View File

@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.auth.AuthType;
@ -102,7 +103,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
// register the plugin messaging channels used in Floodgate
if (session.getRemoteAuthType() == AuthType.FLOODGATE) {
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageUtils.getFloodgateRegisterData()));
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket("minecraft:register", PluginMessageChannels.getFloodgateRegisterData()));
}
if (!newDimension.equals(session.getDimension())) {

View File

@ -201,7 +201,6 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
session.sendUpstreamPacket(craftingDataPacket);
session.setCraftingRecipes(recipeMap);
session.getUnlockedRecipes().clear();
session.setStonecutterRecipes(stonecutterRecipeMap);
session.getLastRecipeNetId().set(netId);
}

View File

@ -50,6 +50,9 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
switch (packet.getAnimation()) {
case SWING_ARM:
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
if (entity.getEntityId() == session.getPlayerEntity().getEntityId()) {
session.activateArmAnimationTicking();
}
break;
case SWING_OFFHAND:
// Use the OptionalPack to trigger the animation

View File

@ -33,9 +33,7 @@ import org.geysermc.geyser.session.GeyserSession;
import java.nio.ByteBuffer;
public class PluginMessageUtils {
private static final String SKIN_CHANNEL = "floodgate:skin";
private static final byte[] GEYSER_BRAND_DATA;
private static final byte[] FLOODGATE_REGISTER_DATA;
static {
byte[] data = GeyserImpl.NAME.getBytes(Charsets.UTF_8);
@ -44,8 +42,6 @@ public class PluginMessageUtils {
.put(writeVarInt(data.length))
.put(data)
.array();
FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8);
}
/**
@ -57,22 +53,6 @@ public class PluginMessageUtils {
return GEYSER_BRAND_DATA;
}
/**
* Get the prebuilt register data as a byte array
*
* @return the register data of the Floodgate channels
*/
public static byte[] getFloodgateRegisterData() {
return FLOODGATE_REGISTER_DATA;
}
/**
* Returns the skin channel used in Floodgate
*/
public static String getSkinChannel() {
return SKIN_CHANNEL;
}
public static void sendMessage(GeyserSession session, String channel, byte[] data) {
session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(channel, data));
}

View File

@ -52,6 +52,8 @@ public class WebUtils {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); // Otherwise Java 8 fails on checking updates
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
return connectionToString(con);
} catch (Exception e) {