mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
Merge remote-tracking branch 'origin/master' into feature/extensions-gradle
This commit is contained in:
commit
02cda38259
21 changed files with 284 additions and 131 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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[]) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue