{
if (MAP.containsKey(clazz)) {
((PacketTranslator) MAP.get(clazz)).translate(packet, session);
return true;
+ } else {
+ GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
}
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error("Could not translate packet " + packet.getClass().getSimpleName(), ex);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java
index ebc45ff0..7ab71389 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockActionTranslator.java
@@ -33,12 +33,10 @@ import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
-import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
-import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
@@ -101,10 +99,7 @@ public class BedrockActionTranslator extends PacketTranslator {
@Override
public void translate(AnimatePacket packet, GeyserSession session) {
+ // Stop the player sending animations before they have fully spawned into the server
+ if (!session.isSpawned()) {
+ return;
+ }
+
switch (packet.getAction()) {
case SWING_ARM:
- ClientPlayerSwingArmPacket swingArmPacket = new ClientPlayerSwingArmPacket(Hand.MAIN_HAND);
- session.getDownstream().getSession().send(swingArmPacket);
+ // Delay so entity damage can be processed first
+ session.getConnector().getGeneralThreadPool().schedule(() ->
+ session.getDownstream().getSession().send(new ClientPlayerSwingArmPacket(Hand.MAIN_HAND)),
+ 25,
+ TimeUnit.MILLISECONDS
+ );
break;
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
index 28cbf4c4..e7d4c748 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockCommandRequestTranslator.java
@@ -34,6 +34,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import com.nukkitx.protocol.bedrock.packet.CommandRequestPacket;
+import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = CommandRequestPacket.class)
public class BedrockCommandRequestTranslator extends PacketTranslator {
@@ -45,7 +46,13 @@ public class BedrockCommandRequestTranslator extends PacketTranslator {
+public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslator {
@Override
public void translate(SetLocalPlayerAsInitializedPacket packet, GeyserSession session) {
if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) {
if (!session.getUpstream().isInitialized()) {
session.getUpstream().setInitialized(true);
+ session.login();
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
if (!entity.isValid()) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java
index 1c841631..4dfe4c76 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockTextTranslator.java
@@ -31,6 +31,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
+import org.geysermc.connector.utils.MessageUtils;
@Translator(packet = TextPacket.class)
public class BedrockTextTranslator extends PacketTranslator {
@@ -38,12 +39,24 @@ public class BedrockTextTranslator extends PacketTranslator {
@Override
public void translate(TextPacket packet, GeyserSession session) {
if (packet.getMessage().charAt(0) == '.') {
- ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage().replace(".", "/"));
+ String message = packet.getMessage().replace(".", "/").trim();
+
+ if (MessageUtils.isTooLong(message, session)) {
+ return;
+ }
+
+ ClientChatPacket chatPacket = new ClientChatPacket(message);
session.getDownstream().getSession().send(chatPacket);
return;
}
- ClientChatPacket chatPacket = new ClientChatPacket(packet.getMessage());
+ String message = packet.getMessage().trim();
+
+ if (MessageUtils.isTooLong(message, session)) {
+ return;
+ }
+
+ ClientChatPacket chatPacket = new ClientChatPacket(message);
session.getDownstream().getSession().send(chatPacket);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java
index c782e099..58f6ff4e 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java
@@ -55,6 +55,9 @@ public class BlockTranslator {
private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>();
private static final IntSet WATERLOGGED = new IntOpenHashSet();
+ // Bedrock carpet ID, used in LlamaEntity.java for decoration
+ public static final int CARPET = 171;
+
private static final int BLOCK_STATE_VERSION = 17760256;
static {
@@ -100,7 +103,8 @@ public class BlockTranslator {
if ("minecraft:water[level=0]".equals(javaId)) {
waterRuntimeId = bedrockRuntimeId;
}
- boolean waterlogged = entry.getValue().has("waterlogged") && entry.getValue().get("waterlogged").booleanValue();
+ boolean waterlogged = entry.getKey().contains("waterlogged=true")
+ || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass");
if (waterlogged) {
BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId));
@@ -177,6 +181,10 @@ public class BlockTranslator {
return JAVA_TO_BEDROCK_BLOCK_MAP.get(state.getId());
}
+ public static int getBedrockBlockId(int javaId) {
+ return JAVA_TO_BEDROCK_BLOCK_MAP.get(javaId);
+ }
+
public static BlockState getJavaBlockState(int bedrockId) {
return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId);
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
index 99d416bb..68f88bb2 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java
@@ -72,7 +72,18 @@ public class ItemTranslator {
if (stack.getNbt() == null) {
return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount());
}
- return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(stack.getNbt()));
+
+ // TODO: Create proper transformers instead of shoving everything here
+ CompoundTag tag = stack.getNbt();
+ IntTag mapId = tag.get("map");
+
+ if (mapId != null) {
+ tag.put(new StringTag("map_uuid", mapId.getValue().toString()));
+ tag.put(new IntTag("map_name_index", mapId.getValue()));
+ }
+
+
+ return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount(), translateToBedrockNBT(tag));
}
public ItemEntry getItem(ItemStack stack) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java
index 3c1452a5..2c32ef6f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaBossBarTranslator.java
@@ -26,80 +26,38 @@
package org.geysermc.connector.network.translators.java;
import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.session.cache.BossBar;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.MessageUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerBossBarPacket;
-import com.nukkitx.math.vector.Vector3f;
-import com.nukkitx.protocol.bedrock.data.EntityData;
-import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
-import com.nukkitx.protocol.bedrock.packet.BossEventPacket;
-import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
+
+import java.awt.*;
@Translator(packet = ServerBossBarPacket.class)
public class JavaBossBarTranslator extends PacketTranslator {
@Override
public void translate(ServerBossBarPacket packet, GeyserSession session) {
- BossEventPacket bossEventPacket = new BossEventPacket();
- bossEventPacket.setBossUniqueEntityId(session.getEntityCache().getBossBar(packet.getUuid()));
-
+ BossBar bossBar = session.getEntityCache().getBossBar(packet.getUuid());
switch (packet.getAction()) {
case ADD:
- long entityId = session.getEntityCache().addBossBar(packet.getUuid());
- addBossEntity(session, entityId);
-
- bossEventPacket.setAction(BossEventPacket.Action.SHOW);
- bossEventPacket.setBossUniqueEntityId(entityId);
- bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle()));
- bossEventPacket.setHealthPercentage(packet.getHealth());
- bossEventPacket.setColor(0); //ignored by client
- bossEventPacket.setOverlay(1);
- bossEventPacket.setDarkenSky(0);
+ long entityId = session.getEntityCache().getNextEntityId().incrementAndGet();
+ bossBar = new BossBar(session, entityId, packet.getTitle(), packet.getHealth(), 0, 1, 0);
+ session.getEntityCache().addBossBar(packet.getUuid(), bossBar);
break;
case UPDATE_TITLE:
- bossEventPacket.setAction(BossEventPacket.Action.TITLE);
- bossEventPacket.setTitle(MessageUtils.getBedrockMessage(packet.getTitle()));
+ if (bossBar != null) bossBar.updateTitle(packet.getTitle());
break;
case UPDATE_HEALTH:
- bossEventPacket.setAction(BossEventPacket.Action.HEALTH_PERCENTAGE);
- bossEventPacket.setHealthPercentage(packet.getHealth());
+ if (bossBar != null) bossBar.updateHealth(packet.getHealth());
break;
case REMOVE:
- bossEventPacket.setAction(BossEventPacket.Action.HIDE);
- removeBossEntity(session, session.getEntityCache().removeBossBar(packet.getUuid()));
+ session.getEntityCache().removeBossBar(packet.getUuid());
break;
case UPDATE_STYLE:
case UPDATE_FLAGS:
//todo
return;
}
-
- session.getUpstream().sendPacket(bossEventPacket);
- }
-
- /**
- * Bedrock still needs an entity to display the BossBar.
- * Just like 1.8 but it doesn't care about which entity
- */
- private void addBossEntity(GeyserSession session, long entityId) {
- AddEntityPacket addEntityPacket = new AddEntityPacket();
- addEntityPacket.setUniqueEntityId(entityId);
- addEntityPacket.setRuntimeEntityId(entityId);
- addEntityPacket.setIdentifier("minecraft:creeper");
- addEntityPacket.setEntityType(33);
- addEntityPacket.setPosition(session.getPlayerEntity().getPosition());
- addEntityPacket.setRotation(Vector3f.ZERO);
- addEntityPacket.setMotion(Vector3f.ZERO);
- addEntityPacket.getMetadata().put(EntityData.SCALE, 0.01F); // scale = 0 doesn't work?
-
- session.getUpstream().sendPacket(addEntityPacket);
- }
-
- private void removeBossEntity(GeyserSession session, long entityId) {
- RemoveEntityPacket removeEntityPacket = new RemoveEntityPacket();
- removeEntityPacket.setUniqueEntityId(entityId);
-
- session.getUpstream().sendPacket(removeEntityPacket);
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
index 226bd971..a527866c 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java
@@ -34,6 +34,8 @@ import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
+import java.util.List;
+
@Translator(packet = ServerChatPacket.class)
public class JavaChatTranslator extends PacketTranslator {
@@ -58,14 +60,20 @@ public class JavaChatTranslator extends PacketTranslator {
break;
}
+ String locale = session.getClientData().getLanguageCode();
+
if (packet.getMessage() instanceof TranslationMessage) {
textPacket.setType(TextPacket.Type.TRANSLATION);
textPacket.setNeedsTranslation(true);
- textPacket.setParameters(MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams()));
- textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage()));
+
+ List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams(), locale);
+ textPacket.setParameters(paramsTranslated);
+
+ textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true), paramsTranslated));
} else {
textPacket.setNeedsTranslation(false);
- textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage()));
+
+ textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false));
}
session.getUpstream().sendPacket(textPacket);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
index 0d6caaed..34fe2271 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java
@@ -29,7 +29,6 @@ import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
-import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.utils.DimensionUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket;
@@ -69,7 +68,6 @@ public class JavaJoinGameTranslator extends PacketTranslator {
+
+ private static byte[] brandData;
+
+ static {
+ byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8);
+ byte[] varInt = writeVarInt(data.length);
+ brandData = new byte[varInt.length + data.length];
+ System.arraycopy(varInt, 0, brandData, 0, varInt.length);
+ System.arraycopy(data, 0, brandData, varInt.length, data.length);
+ }
+
+
@Override
public void translate(ServerPluginMessagePacket packet, GeyserSession session) {
if (packet.getChannel().equals("minecraft:brand")) {
session.getDownstream().getSession().send(
- new ClientPluginMessagePacket(packet.getChannel(), GeyserConnector.NAME.getBytes())
+ new ClientPluginMessagePacket(packet.getChannel(), brandData)
);
}
}
+
+ private static byte[] writeVarInt(int value) {
+ byte[] data = new byte[getVarIntLength(value)];
+ int index = 0;
+ do {
+ byte temp = (byte)(value & 0b01111111);
+ value >>>= 7;
+ if (value != 0) {
+ temp |= 0b10000000;
+ }
+ data[index] = temp;
+ index++;
+ } while (value != 0);
+ return data;
+ }
+
+ private static int getVarIntLength(int number) {
+ if ((number & 0xFFFFFF80) == 0) {
+ return 1;
+ } else if ((number & 0xFFFFC000) == 0) {
+ return 2;
+ } else if ((number & 0xFFE00000) == 0) {
+ return 3;
+ } else if ((number & 0xF0000000) == 0) {
+ return 4;
+ }
+ return 5;
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java
index 185aab54..b28e9010 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java
@@ -25,6 +25,9 @@
package org.geysermc.connector.network.translators.java;
+import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.protocol.bedrock.data.LevelEventType;
+import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.network.session.GeyserSession;
@@ -35,6 +38,8 @@ import org.geysermc.connector.utils.DimensionUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
+import java.util.concurrent.ThreadLocalRandom;
+
@Translator(packet = ServerRespawnPacket.class)
public class JavaRespawnTranslator extends PacketTranslator {
@@ -53,6 +58,12 @@ public class JavaRespawnTranslator extends PacketTranslator
session.getUpstream().sendPacket(playerGameTypePacket);
session.setGameMode(packet.getGamemode());
+ LevelEventPacket stopRainPacket = new LevelEventPacket();
+ stopRainPacket.setType(LevelEventType.STOP_RAIN);
+ stopRainPacket.setData(ThreadLocalRandom.current().nextInt(50000) + 10000);
+ stopRainPacket.setPosition(Vector3f.ZERO);
+ session.getUpstream().sendPacket(stopRainPacket);
+
if (entity.getDimension() != DimensionUtils.javaToBedrock(packet.getDimension())) {
DimensionUtils.switchDimension(session, packet.getDimension());
} else {
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java
new file mode 100644
index 00000000..195da60c
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaServerDeclareCommandsTranslator.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.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.packet.ingame.server.ServerDeclareCommandsPacket;
+import com.nukkitx.protocol.bedrock.data.CommandData;
+import com.nukkitx.protocol.bedrock.data.CommandEnumData;
+import com.nukkitx.protocol.bedrock.data.CommandParamData;
+import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket;
+import lombok.Getter;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+
+import java.util.*;
+
+@Translator(packet = ServerDeclareCommandsPacket.class)
+public class JavaServerDeclareCommandsTranslator extends PacketTranslator {
+ @Override
+ public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) {
+ List commandData = new ArrayList<>();
+ Map commands = new HashMap<>();
+ Map> commandArgs = new HashMap<>();
+
+ // Get the first node, it should be a root node
+ CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()];
+
+ // Loop through the root nodes to get all commands
+ for (int nodeIndex : rootNode.getChildIndices()) {
+ CommandNode node = packet.getNodes()[nodeIndex];
+
+ // Make sure we dont have duplicated commands (happens if there is more than 1 root node)
+ if (commands.containsKey(nodeIndex)) { continue; }
+
+ // Get and update the commandArgs list with the found arguments
+ if (node.getChildIndices().length >= 1) {
+ for (int childIndex : node.getChildIndices()) {
+ commandArgs.putIfAbsent(nodeIndex, new ArrayList<>());
+ commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]);
+ }
+ }
+
+ // Insert the command name into the list
+ commands.put(nodeIndex, node.getName());
+ }
+
+ // The command flags, not sure what these do apart from break things
+ List flags = new ArrayList<>();
+
+ // Loop through all the found commands
+ for (int commandID : commands.keySet()) {
+ String commandName = commands.get(commandID);
+
+ // Create a basic alias
+ CommandEnumData aliases = new CommandEnumData( commandName + "Aliases", new String[] { commandName.toLowerCase() }, false);
+
+ // Get and parse all params
+ CommandParamData[][] params = getParams(commandID, packet.getNodes()[commandID], packet.getNodes());
+
+ // Build the completed command and add it to the final list
+ CommandData data = new CommandData(commandName, "", flags, (byte) 0, aliases, params);
+ commandData.add(data);
+ }
+
+ // Add our commands to the AvailableCommandsPacket for the bedrock client
+ AvailableCommandsPacket availableCommandsPacket = new AvailableCommandsPacket();
+ for (CommandData data : commandData) {
+ availableCommandsPacket.getCommands().add(data);
+ }
+
+ GeyserConnector.getInstance().getLogger().debug("Sending command packet of " + commandData.size() + " commands");
+
+ // Finally, send the commands to the client
+ session.getUpstream().sendPacket(availableCommandsPacket);
+ }
+
+ private CommandParamData[][] getParams(int commandID, CommandNode commandNode, CommandNode[] allNodes) {
+ // Check if the command is an alias and redirect it
+ if (commandNode.getRedirectIndex() != -1) {
+ GeyserConnector.getInstance().getLogger().debug("Redirecting command " + commandNode.getName() + " to " + allNodes[commandNode.getRedirectIndex()].getName());
+ commandNode = allNodes[commandNode.getRedirectIndex()];
+ }
+
+ if (commandNode.getChildIndices().length >= 1) {
+ // Create the root param node and build all the children
+ ParamInfo rootParam = new ParamInfo(commandNode, null);
+ rootParam.buildChildren(allNodes);
+
+ List treeData = rootParam.getTree();
+ CommandParamData[][] params = new CommandParamData[treeData.size()][];
+
+ // Fill the nested params array
+ int i = 0;
+ for (CommandParamData[] tree : treeData) {
+ params[i] = tree;
+ i++;
+ }
+
+ return params;
+ }
+
+ return new CommandParamData[0][0];
+ }
+
+ private CommandParamData.Type mapCommandType(CommandParser parser) {
+ if (parser == null) { return CommandParamData.Type.STRING; }
+
+ switch (parser) {
+ case FLOAT:
+ return CommandParamData.Type.FLOAT;
+
+ case INTEGER:
+ return CommandParamData.Type.INT;
+
+ case ENTITY:
+ case GAME_PROFILE:
+ return CommandParamData.Type.TARGET;
+
+ case BLOCK_POS:
+ return CommandParamData.Type.BLOCK_POSITION;
+
+ case COLUMN_POS:
+ case VEC3:
+ return CommandParamData.Type.POSITION;
+
+ case MESSAGE:
+ return CommandParamData.Type.MESSAGE;
+
+ case NBT:
+ case NBT_COMPOUND_TAG:
+ case NBT_TAG:
+ case NBT_PATH:
+ return CommandParamData.Type.JSON;
+
+ case RESOURCE_LOCATION:
+ return CommandParamData.Type.FILE_PATH;
+
+ case INT_RANGE:
+ return CommandParamData.Type.INT_RANGE;
+
+ case BOOL:
+ case DOUBLE:
+ case STRING:
+ case VEC2:
+ case BLOCK_STATE:
+ case BLOCK_PREDICATE:
+ case ITEM_STACK:
+ case ITEM_PREDICATE:
+ case COLOR:
+ case COMPONENT:
+ case OBJECTIVE:
+ case OBJECTIVE_CRITERIA:
+ case OPERATION: // Possibly OPERATOR
+ case PARTICLE:
+ case ROTATION:
+ case SCOREBOARD_SLOT:
+ case SCORE_HOLDER:
+ case SWIZZLE:
+ case TEAM:
+ case ITEM_SLOT:
+ case MOB_EFFECT:
+ case FUNCTION:
+ case ENTITY_ANCHOR:
+ case RANGE:
+ case FLOAT_RANGE:
+ case ITEM_ENCHANTMENT:
+ case ENTITY_SUMMON:
+ case DIMENSION:
+ case TIME:
+ default:
+ return CommandParamData.Type.STRING;
+ }
+ }
+
+ @Getter
+ private class ParamInfo {
+ private CommandNode paramNode;
+ private CommandParamData paramData;
+ private List children;
+
+ public ParamInfo(CommandNode paramNode, CommandParamData paramData) {
+ this.paramNode = paramNode;
+ this.paramData = paramData;
+ this.children = new ArrayList<>();
+ }
+
+ public void buildChildren(CommandNode[] allNodes) {
+ int enumIndex = -1;
+
+ for (int paramID : paramNode.getChildIndices()) {
+ CommandNode paramNode = allNodes[paramID];
+
+ if (paramNode.getParser() == null) {
+ if (enumIndex == -1) {
+ enumIndex = children.size();
+
+ // Create the new enum command
+ CommandEnumData enumData = new CommandEnumData(paramNode.getName(), new String[] { paramNode.getName() }, false);
+ children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, enumData, mapCommandType(paramNode.getParser()), null, Collections.emptyList())));
+ } else {
+ // Get the existing enum
+ ParamInfo enumParamInfo = children.get(enumIndex);
+
+ // Extend the current list of enum values
+ String[] enumOptions = Arrays.copyOf(enumParamInfo.getParamData().getEnumData().getValues(), enumParamInfo.getParamData().getEnumData().getValues().length + 1);
+ enumOptions[enumOptions.length - 1] = paramNode.getName();
+
+ // Re-create the command using the updated values
+ CommandEnumData enumData = new CommandEnumData(enumParamInfo.getParamData().getEnumData().getName(), enumOptions, false);
+ children.set(enumIndex, new ParamInfo(enumParamInfo.getParamNode(), new CommandParamData(enumParamInfo.getParamData().getName(), false, enumData, enumParamInfo.getParamData().getType(), null, Collections.emptyList())));
+ }
+ }else{
+ // Put the non-enum param into the list
+ children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), false, null, mapCommandType(paramNode.getParser()), null, Collections.emptyList())));
+ }
+ }
+
+ // Recursively build all child options
+ for (ParamInfo child : children) {
+ child.buildChildren(allNodes);
+ }
+ }
+
+ public List getTree() {
+ List treeParamData = new ArrayList<>();
+
+ for (ParamInfo child : children) {
+ // Get the tree from the child
+ List childTree = child.getTree();
+
+ // Un-pack the tree append the child node to it and push into the list
+ for (CommandParamData[] subchild : childTree) {
+ CommandParamData[] tmpTree = new ArrayList() {
+ {
+ add(child.getParamData());
+ addAll(Arrays.asList(subchild));
+ }
+ }.toArray(new CommandParamData[0]);
+
+ treeParamData.add(tmpTree);
+ }
+
+ // If we have no more child parameters just the child
+ if (childTree.size() == 0) {
+ treeParamData.add(new CommandParamData[] { child.getParamData() });
+ }
+ }
+
+ return treeParamData;
+ }
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
index 0f56bc93..cfeb2371 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerPositionRotationTranslator.java
@@ -30,6 +30,7 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.utils.ChunkUtils;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerPositionRotationPacket;
@@ -86,6 +87,8 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator entityClass = type.getEntityClass();
try {
- Constructor extends Entity> entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class,
- Vector3f.class, Vector3f.class, Vector3f.class);
+ Entity entity;
+ if (packet.getType() == ObjectType.FALLING_BLOCK) {
+ entity = new FallingBlockEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
+ type, position, motion, rotation, ((FallingBlockData) packet.getData()).getId());
+ } else {
+ Constructor extends Entity> entityConstructor = entityClass.getConstructor(long.class, long.class, EntityType.class,
+ Vector3f.class, Vector3f.class, Vector3f.class);
- Entity entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
- type, position, motion, rotation
- );
+ entity = entityConstructor.newInstance(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
+ type, position, motion, rotation
+ );
+ }
session.getEntityCache().spawnEntity(entity);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java
index a832f3d7..c9d1ccfe 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaTeamTranslator.java
@@ -52,12 +52,14 @@ public class JavaTeamTranslator extends PacketTranslator {
case CREATE:
scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers()))
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
+ .setColor(packet.getColor())
.setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix()))
.setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix()));
break;
case UPDATE:
scoreboard.getTeam(packet.getTeamName())
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
+ .setColor(packet.getColor())
.setPrefix(MessageUtils.getBedrockMessage(packet.getPrefix()))
.setSuffix(MessageUtils.getBedrockMessage(packet.getSuffix()))
.setUpdateType(UpdateType.UPDATE);
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
index 4687dfbb..bb73c5f0 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java
@@ -36,7 +36,6 @@ import org.geysermc.connector.world.chunk.ChunkSection;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
-import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.network.VarInts;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
@@ -48,63 +47,47 @@ public class JavaChunkDataTranslator extends PacketTranslator {
try {
- if (packet.getColumn().getBiomeData() != null) { //Full chunk
- ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
- ByteBuf byteBuf = Unpooled.buffer(32);
- ChunkSection[] sections = chunkData.sections;
+ ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
+ ByteBuf byteBuf = Unpooled.buffer(32);
+ ChunkSection[] sections = chunkData.sections;
- int sectionCount = sections.length - 1;
- while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
- sectionCount--;
- }
- sectionCount++;
-
- for (int i = 0; i < sectionCount; i++) {
- ChunkSection section = chunkData.sections[i];
- section.writeToNetwork(byteBuf);
- }
-
- byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
-
- byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
- byteBuf.writeByte(0); // Border blocks - Edu edition only
- VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
-
- byte[] payload = new byte[byteBuf.writerIndex()];
- byteBuf.readBytes(payload);
-
- LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
- levelChunkPacket.setSubChunksLength(sectionCount);
- levelChunkPacket.setCachingEnabled(false);
- levelChunkPacket.setChunkX(packet.getColumn().getX());
- levelChunkPacket.setChunkZ(packet.getColumn().getZ());
- levelChunkPacket.setData(payload);
- session.getUpstream().sendPacket(levelChunkPacket);
- } else {
- final int xOffset = packet.getColumn().getX() << 4;
- final int zOffset = packet.getColumn().getZ() << 4;
- Chunk[] chunks = packet.getColumn().getChunks();
- for (int i = 0; i < chunks.length; i++) {
- Chunk chunk = chunks[i];
- if (chunk == null) continue;
- final int yOffset = i * 16;
- for (int x = 0; x < 16; x++) {
- for (int y = 0; y < 16; y++) {
- for (int z = 0; z < 16; z++) {
- BlockState blockState = chunk.get(x, y, z);
- Vector3i pos = Vector3i.from(
- x + xOffset,
- y + yOffset,
- z + zOffset);
- ChunkUtils.updateBlock(session, blockState, pos);
- }
- }
- }
- }
+ int sectionCount = sections.length - 1;
+ while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
+ sectionCount--;
}
+ sectionCount++;
+
+ for (int i = 0; i < sectionCount; i++) {
+ ChunkSection section = chunkData.sections[i];
+ section.writeToNetwork(byteBuf);
+ }
+
+ byte[] bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
+
+ byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
+ byteBuf.writeByte(0); // Border blocks - Edu edition only
+ VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
+
+ byte[] payload = new byte[byteBuf.writerIndex()];
+ byteBuf.readBytes(payload);
+
+ LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
+ levelChunkPacket.setSubChunksLength(sectionCount);
+ levelChunkPacket.setCachingEnabled(false);
+ levelChunkPacket.setChunkX(packet.getColumn().getX());
+ levelChunkPacket.setChunkZ(packet.getColumn().getZ());
+ levelChunkPacket.setData(payload);
+ session.getUpstream().sendPacket(levelChunkPacket);
} catch (Exception ex) {
ex.printStackTrace();
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java
new file mode 100644
index 00000000..b4287b08
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaCollectItemTranslator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.java.world;
+
+import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityCollectItemPacket;
+import com.nukkitx.protocol.bedrock.packet.TakeItemEntityPacket;
+import org.geysermc.connector.entity.Entity;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+
+@Translator(packet = ServerEntityCollectItemPacket.class)
+public class JavaCollectItemTranslator extends PacketTranslator {
+
+ @Override
+ public void translate(ServerEntityCollectItemPacket packet, GeyserSession session) {
+ // This is the definition of translating - both packets take the same values
+ TakeItemEntityPacket takeItemEntityPacket = new TakeItemEntityPacket();
+ // Collected entity is the item
+ Entity collectedEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectedEntityId());
+ // Collector is the entity picking up the item
+ Entity collectorEntity;
+ if (packet.getCollectorEntityId() == session.getPlayerEntity().getEntityId()) {
+ collectorEntity = session.getPlayerEntity();
+ } else {
+ collectorEntity = session.getEntityCache().getEntityByJavaId(packet.getCollectorEntityId());
+ }
+ takeItemEntityPacket.setRuntimeEntityId(collectorEntity.getGeyserId());
+ takeItemEntityPacket.setItemRuntimeEntityId(collectedEntity.getGeyserId());
+ session.getUpstream().sendPacket(takeItemEntityPacket);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java
new file mode 100644
index 00000000..28022c16
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaMapDataTranslator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.network.translators.java.world;
+
+import com.github.steveice10.mc.protocol.data.game.world.map.MapData;
+import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerMapDataPacket;
+import com.nukkitx.protocol.bedrock.packet.ClientboundMapItemDataPacket;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.connector.network.translators.PacketTranslator;
+import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.utils.MapColor;
+
+@Translator(packet = ServerMapDataPacket.class)
+public class JavaMapDataTranslator extends PacketTranslator {
+ @Override
+ public void translate(ServerMapDataPacket packet, GeyserSession session) {
+ ClientboundMapItemDataPacket mapItemDataPacket = new ClientboundMapItemDataPacket();
+
+ mapItemDataPacket.setUniqueMapId(packet.getMapId());
+ mapItemDataPacket.setDimensionId(session.getPlayerEntity().getDimension());
+ mapItemDataPacket.setLocked(packet.isLocked());
+ mapItemDataPacket.setScale(packet.getScale());
+
+ MapData data = packet.getData();
+ if (data != null) {
+ mapItemDataPacket.setXOffset(data.getX());
+ mapItemDataPacket.setYOffset(data.getY());
+ mapItemDataPacket.setWidth(data.getColumns());
+ mapItemDataPacket.setHeight(data.getRows());
+
+ // Every int entry is an ARGB color
+ int[] colors = new int[data.getData().length];
+
+ int idx = 0;
+ for (byte colorId : data.getData()) {
+ colors[idx] = MapColor.fromId(colorId).toARGB();
+ idx++;
+ }
+
+ mapItemDataPacket.setColors(colors);
+ }
+
+ session.getUpstream().getSession().sendPacket(mapItemDataPacket);
+ }
+}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
index ccb69856..36533844 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTimeTranslator.java
@@ -25,6 +25,10 @@
package org.geysermc.connector.network.translators.java.world;
+import com.nukkitx.protocol.bedrock.data.GameRuleData;
+import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
+import it.unimi.dsi.fastutil.longs.Long2LongMap;
+import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@@ -35,11 +39,37 @@ import com.nukkitx.protocol.bedrock.packet.SetTimePacket;
@Translator(packet = ServerUpdateTimePacket.class)
public class JavaUpdateTimeTranslator extends PacketTranslator {
+ // If negative, the last time is stored so we know it's not some plugin behavior doing weird things.
+ // Per-player for multi-world support
+ private static final Long2LongMap LAST_RECORDED_TIMES = new Long2LongOpenHashMap();
+
@Override
public void translate(ServerUpdateTimePacket packet, GeyserSession session) {
- // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
- SetTimePacket setTimePacket = new SetTimePacket();
- setTimePacket.setTime((int) Math.abs(packet.getTime()) % 24000);
- session.getUpstream().sendPacket(setTimePacket);
+
+ // Bedrock sends a GameRulesChangedPacket if there is no daylight cycle
+ // Java just sends a negative long if there is no daylight cycle
+ long lastTime = LAST_RECORDED_TIMES.getOrDefault(session.getPlayerEntity().getEntityId(), 0);
+ long time = packet.getTime();
+
+ if (lastTime != time) {
+ // https://minecraft.gamepedia.com/Day-night_cycle#24-hour_Minecraft_day
+ SetTimePacket setTimePacket = new SetTimePacket();
+ setTimePacket.setTime((int) Math.abs(time) % 24000);
+ session.getUpstream().sendPacket(setTimePacket);
+ // TODO: Performance efficient to always do this?
+ LAST_RECORDED_TIMES.put(session.getPlayerEntity().getEntityId(), time);
+ }
+ if (lastTime < 0 && time >= 0) {
+ setDoDaylightCycleGamerule(session, true);
+ } else if (lastTime != time && time < 0) {
+ setDoDaylightCycleGamerule(session, false);
+ }
}
+
+ private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
+ GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
+ gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle));
+ session.getUpstream().sendPacket(gameRulesChangedPacket);
+ }
+
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java
index 452e86e4..63ade153 100644
--- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java
+++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateViewPositionTranslator.java
@@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.java.world;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
+import org.geysermc.connector.utils.ChunkUtils;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateViewPositionPacket;
import com.nukkitx.math.vector.Vector3i;
@@ -38,9 +39,8 @@ public class JavaUpdateViewPositionTranslator extends PacketTranslator entities = new ObjectOpenHashSet<>();
-
public Team(Scoreboard scoreboard, String id) {
this.scoreboard = scoreboard;
this.id = id;
diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
index 2acda2c2..a35b2cc5 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java
@@ -29,8 +29,10 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
+import com.nukkitx.math.vector.Vector2i;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
+import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.Translators;
@@ -74,6 +76,20 @@ public class ChunkUtils {
return chunkData;
}
+ public static void updateChunkPosition(GeyserSession session, Vector3i position) {
+ Vector2i chunkPos = session.getLastChunkPosition();
+ Vector2i newChunkPos = Vector2i.from(position.getX() >> 4, position.getZ() >> 4);
+
+ if (chunkPos == null || !chunkPos.equals(newChunkPos)) {
+ NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket();
+ chunkPublisherUpdatePacket.setPosition(position);
+ chunkPublisherUpdatePacket.setRadius(session.getRenderDistance() << 4);
+ session.getUpstream().sendPacket(chunkPublisherUpdatePacket);
+
+ session.setLastChunkPosition(newChunkPos);
+ }
+ }
+
public static void updateBlock(GeyserSession session, BlockState blockState, Position position) {
Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ());
updateBlock(session, blockState, pos);
diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
index c7ecaafb..199c5a5c 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java
@@ -52,6 +52,7 @@ public class DimensionUtils {
player.setDimension(bedrockDimension);
player.setPosition(pos.toFloat());
session.setSpawned(false);
+ session.setLastChunkPosition(null);
//let java server handle portal travel sound
StopSoundPacket stopSoundPacket = new StopSoundPacket();
diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
index 3070e743..0938fa7c 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java
@@ -65,4 +65,23 @@ public class FileUtils {
return file;
}
+
+ public static void writeFile(File file, char[] data) throws IOException {
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+
+ FileOutputStream fos = new FileOutputStream(file);
+
+ for (char c : data) {
+ fos.write(c);
+ }
+
+ fos.flush();
+ fos.close();
+ }
+
+ public static void writeFile(String name, char[] data) throws IOException {
+ writeFile(new File(name), data);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
new file mode 100644
index 00000000..e8555eb0
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.utils;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+import org.geysermc.connector.GeyserConnector;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.zip.ZipFile;
+
+public class LocaleUtils {
+
+ public static final Map> LOCALE_MAPPINGS = new HashMap<>();
+
+ private static final Map ASSET_MAP = new HashMap<>();
+
+ private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us");
+
+ private static String smallestURL = "";
+
+ static {
+ // Create the locales folder
+ File localesFolder = new File("locales/");
+ localesFolder.mkdir();
+
+ // Download the latest asset list and cache it
+ generateAssetCache();
+ downloadAndLoadLocale(DEFAULT_LOCALE);
+ }
+
+ private static void generateAssetCache() {
+ try {
+ VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class);
+ String latestInfoURL = "";
+ for (Version version : versionManifest.getVersions()) {
+ if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) {
+ latestInfoURL = version.getUrl();
+ break;
+ }
+ }
+
+ if (latestInfoURL.isEmpty()) {
+ throw new Exception("Unable to get latest Minecraft version");
+ }
+
+ VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class);
+
+ int currentSize = Integer.MAX_VALUE;
+ for (VersionDownload download : versionInfo.getDownloads().values()) {
+ if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) {
+ smallestURL = download.getUrl();
+ currentSize = download.getSize();
+ }
+ }
+
+ JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects");
+
+ Iterator> assetIterator = assets.fields();
+ while (assetIterator.hasNext()) {
+ Map.Entry entry = assetIterator.next();
+ Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class);
+ ASSET_MAP.put(entry.getKey(), asset);
+ }
+ } catch (Exception e) {
+ GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()));
+ }
+ }
+
+ public static void downloadAndLoadLocale(String locale) {
+ locale = locale.toLowerCase();
+ if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) {
+ GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale);
+ return;
+ }
+
+ GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale);
+
+ downloadLocale(locale);
+ loadLocale(locale);
+ }
+
+ private static void downloadLocale(String locale) {
+ File localeFile = new File("locales/" + locale + ".json");
+
+ if (localeFile.exists()) {
+ GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale);
+ return;
+ }
+
+ // Create the en_us locale
+ if (locale.equals("en_us")) {
+ downloadEN_US(localeFile);
+
+ return;
+ }
+
+ // Get the hash and download the locale
+ String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
+ WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json");
+ }
+
+ private static void loadLocale(String locale) {
+ File localeFile = new File("locales/" + locale + ".json");
+
+ // Load the locale
+ if (localeFile.exists()) {
+ // Read the localefile
+ InputStream localeStream;
+ try {
+ localeStream = new FileInputStream(localeFile);
+ } catch (FileNotFoundException e) {
+ throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")");
+ }
+
+ // Parse the file as json
+ JsonNode localeObj;
+ try {
+ localeObj = Toolbox.JSON_MAPPER.readTree(localeStream);
+ } catch (Exception e) {
+ throw new AssertionError("Unable to load Java lang map for " + locale, e);
+ }
+
+ // Parse all the locale fields
+ Iterator> localeIterator = localeObj.fields();
+ Map langMap = new HashMap<>();
+ while (localeIterator.hasNext()) {
+ Map.Entry entry = localeIterator.next();
+ langMap.put(entry.getKey(), entry.getValue().asText());
+ }
+
+ // Insert the locale into the mappings
+ LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap);
+ } else {
+ GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale);
+ }
+ }
+
+ private static void downloadEN_US(File localeFile) {
+ try {
+ // Let the user know we are downloading the JAR
+ GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)");
+ GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL);
+
+ // Download the smallest JAR (client or server)
+ WebUtils.downloadFile(smallestURL, "tmp_locale.jar");
+
+ // Load in the JAR as a zip and extract the file
+ ZipFile localeJar = new ZipFile("tmp_locale.jar");
+ InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
+ FileOutputStream outputStream = new FileOutputStream(localeFile);
+
+ // Write the file to the locale dir
+ int data = inputStream.read();
+ while(data != -1){
+ outputStream.write(data);
+ data = inputStream.read();
+ }
+
+ // Flush all changes to disk and cleanup
+ outputStream.flush();
+ outputStream.close();
+
+ inputStream.close();
+ localeJar.close();
+
+ // Delete the nolonger needed client/server jar
+ Files.delete(Paths.get("tmp_locale.jar"));
+ } catch (Exception e) {
+ throw new AssertionError("Unable to download and extract en_us locale!", e);
+ }
+ }
+
+ public static String getLocaleString(String messageText, String locale) {
+ Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase());
+ if (localeStrings == null)
+ localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE);
+
+ return localeStrings.getOrDefault(messageText, messageText);
+ }
+
+ public static void init() {
+ // no-op
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class VersionManifest {
+ @JsonProperty("latest")
+ private LatestVersion latestVersion;
+
+ @JsonProperty("versions")
+ private List versions;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class LatestVersion {
+ @JsonProperty("release")
+ private String release;
+
+ @JsonProperty("snapshot")
+ private String snapshot;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class Version {
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("type")
+ private String type;
+
+ @JsonProperty("url")
+ private String url;
+
+ @JsonProperty("time")
+ private String time;
+
+ @JsonProperty("releaseTime")
+ private String releaseTime;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class VersionInfo {
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("type")
+ private String type;
+
+ @JsonProperty("time")
+ private String time;
+
+ @JsonProperty("releaseTime")
+ private String releaseTime;
+
+ @JsonProperty("assetIndex")
+ private AssetIndex assetIndex;
+
+ @JsonProperty("downloads")
+ private Map downloads;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class VersionDownload {
+ @JsonProperty("sha1")
+ private String sha1;
+
+ @JsonProperty("size")
+ private int size;
+
+ @JsonProperty("url")
+ private String url;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class AssetIndex {
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("sha1")
+ private String sha1;
+
+ @JsonProperty("size")
+ private int size;
+
+ @JsonProperty("totalSize")
+ private int totalSize;
+
+ @JsonProperty("url")
+ private String url;
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Getter
+class Asset {
+ @JsonProperty("hash")
+ private String hash;
+
+ @JsonProperty("size")
+ private int size;
+}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
index 7f6c8eda..300294a2 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java
@@ -40,9 +40,12 @@ import net.minidev.json.JSONObject;
import org.geysermc.common.window.CustomFormBuilder;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
+import org.geysermc.common.window.SimpleFormWindow;
+import org.geysermc.common.window.button.FormButton;
import org.geysermc.common.window.component.InputComponent;
import org.geysermc.common.window.component.LabelComponent;
import org.geysermc.common.window.response.CustomFormResponse;
+import org.geysermc.common.window.response.SimpleFormResponse;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.AuthData;
@@ -152,43 +155,60 @@ public class LoginEncryptionUtils {
session.getUpstream().sendPacketImmediately(packet);
}
- private static int AUTH_FORM_ID = 1337;
+ private static int AUTH_FORM_ID = 1336;
+ private static int AUTH_DETAILS_FORM_ID = 1337;
public static void showLoginWindow(GeyserSession session) {
- CustomFormWindow window = new CustomFormBuilder("Login")
- .addComponent(new LabelComponent("Minecraft: Java Edition account authentication."))
+ SimpleFormWindow window = new SimpleFormWindow("Login", "You need a Java Edition account to play on this server.");
+ window.getButtons().add(new FormButton("Login with Minecraft"));
+ window.getButtons().add(new FormButton("Disconnect"));
+
+ session.sendForm(window, AUTH_FORM_ID);
+ }
+
+ public static void showLoginDetailsWindow(GeyserSession session) {
+ CustomFormWindow window = new CustomFormBuilder("Login Details")
.addComponent(new LabelComponent("Enter the credentials for your Minecraft: Java Edition account below."))
.addComponent(new InputComponent("Email/Username", "account@geysermc.org", ""))
.addComponent(new InputComponent("Password", "123456", ""))
.build();
- session.sendForm(window, AUTH_FORM_ID);
+ session.sendForm(window, AUTH_DETAILS_FORM_ID);
}
- public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, String formData) {
+ public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
WindowCache windowCache = session.getWindowCache();
- if (!windowCache.getWindows().containsKey(AUTH_FORM_ID))
+ if (!windowCache.getWindows().containsKey(formId))
return false;
- FormWindow window = windowCache.getWindows().remove(AUTH_FORM_ID);
- window.setResponse(formData.trim());
+ if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) {
+ FormWindow window = windowCache.getWindows().remove(formId);
+ window.setResponse(formData.trim());
- if (!session.isLoggedIn()) {
- if (window instanceof CustomFormWindow) {
- CustomFormWindow customFormWindow = (CustomFormWindow) window;
- if (!customFormWindow.getTitle().equals("Login"))
- return false;
+ if (!session.isLoggedIn()) {
+ if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) {
+ CustomFormWindow customFormWindow = (CustomFormWindow) window;
- CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse();
- if (response != null) {
- String email = response.getInputResponses().get(2);
- String password = response.getInputResponses().get(3);
+ CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse();
+ if (response != null) {
+ String email = response.getInputResponses().get(1);
+ String password = response.getInputResponses().get(2);
- session.authenticate(email, password);
+ session.authenticate(email, password);
+ }
+
+ // Clear windows so authentication data isn't accidentally cached
+ windowCache.getWindows().clear();
+ } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
+ SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
+ if(response != null) {
+ if(response.getClickedButtonId() == 0) {
+ showLoginDetailsWindow(session);
+ } else if(response.getClickedButtonId() == 1) {
+ session.disconnect("Login is required");
+ }
+ }
}
-
- // Clear windows so authentication data isn't accidentally cached
- windowCache.getWindows().clear();
}
}
return true;
diff --git a/connector/src/main/java/org/geysermc/connector/utils/MapColor.java b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java
new file mode 100644
index 00000000..2db14464
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/MapColor.java
@@ -0,0 +1,244 @@
+package org.geysermc.connector.utils;
+
+import java.util.Arrays;
+
+public enum MapColor {
+ COLOR_0(-1, -1, -1),
+ COLOR_1(-1, -1, -1),
+ COLOR_2(-1, -1, -1),
+ COLOR_3(-1, -1, -1),
+ COLOR_4(89, 125, 39),
+ COLOR_5(109, 153, 48),
+ COLOR_6(127, 178, 56),
+ COLOR_7(67, 94, 29),
+ COLOR_8(174, 164, 115),
+ COLOR_9(213, 201, 140),
+ COLOR_10(247, 233, 163),
+ COLOR_11(130, 123, 86),
+ COLOR_12(140, 140, 140),
+ COLOR_13(171, 171, 171),
+ COLOR_14(199, 199, 199),
+ COLOR_15(105, 105, 105),
+ COLOR_16(180, 0, 0),
+ COLOR_17(220, 0, 0),
+ COLOR_18(255, 0, 0),
+ COLOR_19(135, 0, 0),
+ COLOR_20(112, 112, 180),
+ COLOR_21(138, 138, 220),
+ COLOR_22(160, 160, 255),
+ COLOR_23(84, 84, 135),
+ COLOR_24(117, 117, 117),
+ COLOR_25(144, 144, 144),
+ COLOR_26(167, 167, 167),
+ COLOR_27(88, 88, 88),
+ COLOR_28(0, 87, 0),
+ COLOR_29(0, 106, 0),
+ COLOR_30(0, 124, 0),
+ COLOR_31(0, 65, 0),
+ COLOR_32(180, 180, 180),
+ COLOR_33(220, 220, 220),
+ COLOR_34(255, 255, 255),
+ COLOR_35(135, 135, 135),
+ COLOR_36(115, 118, 129),
+ COLOR_37(141, 144, 158),
+ COLOR_38(164, 168, 184),
+ COLOR_39(86, 88, 97),
+ COLOR_40(106, 76, 54),
+ COLOR_41(130, 94, 66),
+ COLOR_42(151, 109, 77),
+ COLOR_43(79, 57, 40),
+ COLOR_44(79, 79, 79),
+ COLOR_45(96, 96, 96),
+ COLOR_46(112, 112, 112),
+ COLOR_47(59, 59, 59),
+ COLOR_48(45, 45, 180),
+ COLOR_49(55, 55, 220),
+ COLOR_50(64, 64, 255),
+ COLOR_51(33, 33, 135),
+ COLOR_52(100, 84, 50),
+ COLOR_53(123, 102, 62),
+ COLOR_54(143, 119, 72),
+ COLOR_55(75, 63, 38),
+ COLOR_56(180, 177, 172),
+ COLOR_57(220, 217, 211),
+ COLOR_58(255, 252, 245),
+ COLOR_59(135, 133, 129),
+ COLOR_60(152, 89, 36),
+ COLOR_61(186, 109, 44),
+ COLOR_62(216, 127, 51),
+ COLOR_63(114, 67, 27),
+ COLOR_64(125, 53, 152),
+ COLOR_65(153, 65, 186),
+ COLOR_66(178, 76, 216),
+ COLOR_67(94, 40, 114),
+ COLOR_68(72, 108, 152),
+ COLOR_69(88, 132, 186),
+ COLOR_70(102, 153, 216),
+ COLOR_71(54, 81, 114),
+ COLOR_72(161, 161, 36),
+ COLOR_73(197, 197, 44),
+ COLOR_74(229, 229, 51),
+ COLOR_75(121, 121, 27),
+ COLOR_76(89, 144, 17),
+ COLOR_77(109, 176, 21),
+ COLOR_78(127, 204, 25),
+ COLOR_79(67, 108, 13),
+ COLOR_80(170, 89, 116),
+ COLOR_81(208, 109, 142),
+ COLOR_82(242, 127, 165),
+ COLOR_83(128, 67, 87),
+ COLOR_84(53, 53, 53),
+ COLOR_85(65, 65, 65),
+ COLOR_86(76, 76, 76),
+ COLOR_87(40, 40, 40),
+ COLOR_88(108, 108, 108),
+ COLOR_89(132, 132, 132),
+ COLOR_90(153, 153, 153),
+ COLOR_91(81, 81, 81),
+ COLOR_92(53, 89, 108),
+ COLOR_93(65, 109, 132),
+ COLOR_94(76, 127, 153),
+ COLOR_95(40, 67, 81),
+ COLOR_96(89, 44, 125),
+ COLOR_97(109, 54, 153),
+ COLOR_98(127, 63, 178),
+ COLOR_99(67, 33, 94),
+ COLOR_100(36, 53, 125),
+ COLOR_101(44, 65, 153),
+ COLOR_102(51, 76, 178),
+ COLOR_103(27, 40, 94),
+ COLOR_104(72, 53, 36),
+ COLOR_105(88, 65, 44),
+ COLOR_106(102, 76, 51),
+ COLOR_107(54, 40, 27),
+ COLOR_108(72, 89, 36),
+ COLOR_109(88, 109, 44),
+ COLOR_110(102, 127, 51),
+ COLOR_111(54, 67, 27),
+ COLOR_112(108, 36, 36),
+ COLOR_113(132, 44, 44),
+ COLOR_114(153, 51, 51),
+ COLOR_115(81, 27, 27),
+ COLOR_116(17, 17, 17),
+ COLOR_117(21, 21, 21),
+ COLOR_118(25, 25, 25),
+ COLOR_119(13, 13, 13),
+ COLOR_120(176, 168, 54),
+ COLOR_121(215, 205, 66),
+ COLOR_122(250, 238, 77),
+ COLOR_123(132, 126, 40),
+ COLOR_124(64, 154, 150),
+ COLOR_125(79, 188, 183),
+ COLOR_126(92, 219, 213),
+ COLOR_127(48, 115, 112),
+ COLOR_128(52, 90, 180),
+ COLOR_129(63, 110, 220),
+ COLOR_130(74, 128, 255),
+ COLOR_131(39, 67, 135),
+ COLOR_132(0, 153, 40),
+ COLOR_133(0, 187, 50),
+ COLOR_134(0, 217, 58),
+ COLOR_135(0, 114, 30),
+ COLOR_136(91, 60, 34),
+ COLOR_137(111, 74, 42),
+ COLOR_138(129, 86, 49),
+ COLOR_139(68, 45, 25),
+ COLOR_140(79, 1, 0),
+ COLOR_141(96, 1, 0),
+ COLOR_142(112, 2, 0),
+ COLOR_143(59, 1, 0),
+ COLOR_144(147, 124, 113),
+ COLOR_145(180, 152, 138),
+ COLOR_146(209, 177, 161),
+ COLOR_147(110, 93, 85),
+ COLOR_148(112, 57, 25),
+ COLOR_149(137, 70, 31),
+ COLOR_150(159, 82, 36),
+ COLOR_151(84, 43, 19),
+ COLOR_152(105, 61, 76),
+ COLOR_153(128, 75, 93),
+ COLOR_154(149, 87, 108),
+ COLOR_155(78, 46, 57),
+ COLOR_156(79, 76, 97),
+ COLOR_157(96, 93, 119),
+ COLOR_158(112, 108, 138),
+ COLOR_159(59, 57, 73),
+ COLOR_160(131, 93, 25),
+ COLOR_161(160, 114, 31),
+ COLOR_162(186, 133, 36),
+ COLOR_163(98, 70, 19),
+ COLOR_164(72, 82, 37),
+ COLOR_165(88, 100, 45),
+ COLOR_166(103, 117, 53),
+ COLOR_167(54, 61, 28),
+ COLOR_168(112, 54, 55),
+ COLOR_169(138, 66, 67),
+ COLOR_170(160, 77, 78),
+ COLOR_171(84, 40, 41),
+ COLOR_172(40, 28, 24),
+ COLOR_173(49, 35, 30),
+ COLOR_174(57, 41, 35),
+ COLOR_175(30, 21, 18),
+ COLOR_176(95, 75, 69),
+ COLOR_177(116, 92, 84),
+ COLOR_178(135, 107, 98),
+ COLOR_179(71, 56, 51),
+ COLOR_180(61, 64, 64),
+ COLOR_181(75, 79, 79),
+ COLOR_182(87, 92, 92),
+ COLOR_183(46, 48, 48),
+ COLOR_184(86, 51, 62),
+ COLOR_185(105, 62, 75),
+ COLOR_186(122, 73, 88),
+ COLOR_187(64, 38, 46),
+ COLOR_188(53, 43, 64),
+ COLOR_189(65, 53, 79),
+ COLOR_190(76, 62, 92),
+ COLOR_191(40, 32, 48),
+ COLOR_192(53, 35, 24),
+ COLOR_193(65, 43, 30),
+ COLOR_194(76, 50, 35),
+ COLOR_195(40, 26, 18),
+ COLOR_196(53, 57, 29),
+ COLOR_197(65, 70, 36),
+ COLOR_198(76, 82, 42),
+ COLOR_199(40, 43, 22),
+ COLOR_200(100, 42, 32),
+ COLOR_201(122, 51, 39),
+ COLOR_202(142, 60, 46),
+ COLOR_203(75, 31, 24),
+ COLOR_204(26, 15, 11),
+ COLOR_205(31, 18, 13),
+ COLOR_206(37, 22, 16),
+ COLOR_207(19, 11, 8);
+
+ private final int red;
+ private final int green;
+ private final int blue;
+
+ MapColor(int red, int green, int blue) {
+ this.red = red;
+ this.green = green;
+ this.blue = blue;
+ }
+
+ int getId() {
+ return ordinal();
+ }
+
+ public static MapColor fromId(int id) {
+ return Arrays.stream(values()).filter(color -> color.getId() == id).findFirst().orElse(COLOR_0);
+ }
+
+ public int toARGB() {
+ int alpha = 255;
+ if (red == -1 && green == -1 && blue == -1)
+ alpha = 0; // transparent
+
+ long result = red & 0xff;
+ result |= (green & 0xff) << 8;
+ result |= (blue & 0xff) << 16;
+ result |= (alpha & 0xff) << 24;
+ return (int) (result & 0xFFFFFFFFL);
+ }
+}
\ No newline at end of file
diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java
index b5260ab7..5be8eab1 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java
@@ -25,31 +25,31 @@
package org.geysermc.connector.utils;
-import com.github.steveice10.mc.protocol.data.message.ChatColor;
-import com.github.steveice10.mc.protocol.data.message.ChatFormat;
-import com.github.steveice10.mc.protocol.data.message.Message;
-import com.github.steveice10.mc.protocol.data.message.TranslationMessage;
+import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
+import com.github.steveice10.mc.protocol.data.message.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
+import org.geysermc.connector.network.session.GeyserSession;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class MessageUtils {
- public static List getTranslationParams(Message[] messages) {
- List strings = new ArrayList();
- for (int i = 0; i < messages.length; i++) {
- if (messages[i] instanceof TranslationMessage) {
- TranslationMessage translation = (TranslationMessage) messages[i];
+ public static List getTranslationParams(Message[] messages, String locale) {
+ List strings = new ArrayList<>();
+ for (Message message : messages) {
+ if (message instanceof TranslationMessage) {
+ TranslationMessage translation = (TranslationMessage) message;
- StringBuilder builder = new StringBuilder("");
- builder.append("%");
- builder.append(translation.getTranslationKey());
- strings.add(builder.toString());
+ if (locale == null) {
+ String builder = "%" + translation.getTranslationKey();
+ strings.add(builder);
+ }
if (translation.getTranslationKey().equals("commands.gamemode.success.other")) {
strings.add("");
@@ -59,47 +59,96 @@ public class MessageUtils {
strings.add(" - no permission or invalid command!");
}
- for (int j = 0; j < getTranslationParams(translation.getTranslationParams()).size(); j++) {
- strings.add(getTranslationParams(translation.getTranslationParams()).get(j));
+ List furtherParams = getTranslationParams(translation.getTranslationParams(), locale);
+ if (locale != null) {
+ strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams));
+ }else{
+ strings.addAll(furtherParams);
}
} else {
- StringBuilder builder = new StringBuilder("");
- builder.append(getFormat(messages[i].getStyle().getFormats()));
- builder.append(getColor(messages[i].getStyle().getColor()));
- builder.append(getBedrockMessage(messages[i]));
- strings.add(builder.toString());
+ String builder = getFormat(message.getStyle().getFormats()) +
+ getColorOrParent(message.getStyle());
+ builder += getTranslatedBedrockMessage(message, locale, false);
+ strings.add(builder);
}
}
return strings;
}
- public static String getTranslationText(TranslationMessage message) {
- StringBuilder builder = new StringBuilder("");
- builder.append(getFormat(message.getStyle().getFormats()));
- builder.append(getColor(message.getStyle().getColor()));
- builder.append("%");
- builder.append(message.getTranslationKey());
- return builder.toString();
+ public static List getTranslationParams(Message[] messages) {
+ return getTranslationParams(messages, null);
}
- public static String getBedrockMessage(Message message) {
+ public static String getTranslationText(TranslationMessage message) {
+ return getFormat(message.getStyle().getFormats()) + getColorOrParent(message.getStyle())
+ + "%" + message.getTranslationKey();
+ }
+
+ public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) {
JsonParser parser = new JsonParser();
if (isMessage(message.getText())) {
JsonObject object = parser.parse(message.getText()).getAsJsonObject();
message = Message.fromJson(formatJson(object));
}
- StringBuilder builder = new StringBuilder(message.getText());
+ String messageText = message.getText();
+ if (locale != null && shouldTranslate) {
+ messageText = LocaleUtils.getLocaleString(messageText, locale);
+ }
+
+ StringBuilder builder = new StringBuilder();
+ builder.append(getFormat(message.getStyle().getFormats()));
+ builder.append(getColorOrParent(message.getStyle()));
+ builder.append(messageText);
+
for (Message msg : message.getExtra()) {
builder.append(getFormat(msg.getStyle().getFormats()));
- builder.append(getColor(msg.getStyle().getColor()));
+ builder.append(getColorOrParent(msg.getStyle()));
if (!(msg.getText() == null)) {
- builder.append(getBedrockMessage(msg));
+ boolean isTranslationMessage = (msg instanceof TranslationMessage);
+ builder.append(getTranslatedBedrockMessage(msg, locale, isTranslationMessage));
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String getTranslatedBedrockMessage(Message message, String locale) {
+ return getTranslatedBedrockMessage(message, locale, true);
+ }
+
+ public static String getBedrockMessage(Message message) {
+ return getTranslatedBedrockMessage(message, null, false);
+ }
+
+ public static String insertParams(String message, List params) {
+ String newMessage = message;
+
+ Pattern p = Pattern.compile("%([1-9])\\$s");
+ Matcher m = p.matcher(message);
+ while (m.find()) {
+ try {
+ newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s" , params.get(Integer.parseInt(m.group(1)) - 1));
+ } catch (Exception e) {
+ // Couldnt find the param to replace
}
}
- return builder.toString();
+ for (String text : params) {
+ newMessage = newMessage.replaceFirst("%s", text);
+ }
+
+ return newMessage;
+ }
+
+ private static String getColorOrParent(MessageStyle style) {
+ ChatColor chatColor = style.getColor();
+
+ if (chatColor == ChatColor.NONE && style.getParent() != null) {
+ return getColorOrParent(style.getParent());
+ }
+
+ return getColor(chatColor);
}
private static String getColor(ChatColor color) {
@@ -206,7 +255,6 @@ public class MessageUtils {
} catch (Exception ex) {
return false;
}
-
return true;
}
@@ -231,7 +279,30 @@ public class MessageUtils {
formatJson((JsonObject) a.get(i));
}
}
-
return object;
}
+
+ public static String toChatColor(TeamColor teamColor) {
+ for (ChatColor color : ChatColor.values()) {
+ if (color.name().equals(teamColor.name())) {
+ return getColor(color);
+ }
+ }
+ for (ChatFormat format : ChatFormat.values()) {
+ if (format.name().equals(teamColor.name())) {
+ return getFormat(Collections.singletonList(format));
+ }
+ }
+ return "";
+ }
+
+ public static boolean isTooLong(String message, GeyserSession session) {
+ if (message.length() > 256) {
+ // TODO: Add Geyser localization and translate this based on language
+ session.sendMessage("Your message is bigger than 256 characters (" + message.length() + ") so it has not been sent.");
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java
index db19ed2e..28e18521 100644
--- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java
+++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java
@@ -42,7 +42,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.sound.SoundMap;
-import java.io.InputStream;
+import java.io.*;
import java.util.*;
public class Toolbox {
@@ -54,6 +54,8 @@ public class Toolbox {
public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>();
+ public static final Map> LOCALE_MAPPINGS = new HashMap<>();
+
static {
/* Load biomes */
InputStream biomestream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/biome_definitions.dat");
@@ -105,9 +107,11 @@ public class Toolbox {
entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue()));
itemIndex++;
}
-
- /* Load sound mappings */
+
+ // Load sound mappings
SoundMap.get();
+ // Load the locale data
+ LocaleUtils.init();
}
public static InputStream getResource(String resource) {
diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
new file mode 100644
index 00000000..065d683a
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.connector.utils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+public class WebUtils {
+
+ public static String getBody(String reqURL) {
+ URL url = null;
+ try {
+ url = new URL(reqURL);
+ HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ con.setRequestMethod("GET");
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ String inputLine;
+ StringBuffer content = new StringBuffer();
+
+ while ((inputLine = in.readLine()) != null) {
+ content.append(inputLine);
+ content.append("\n");
+ }
+
+ in.close();
+ con.disconnect();
+
+ return content.toString();
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+ public static void downloadFile(String reqURL, String fileLocation) {
+ try {
+ InputStream in = new URL(reqURL).openStream();
+ Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING);
+ } catch (Exception e) {
+ throw new AssertionError("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e);
+ }
+ }
+}
diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml
index ba5700e2..ae0cbed8 100644
--- a/connector/src/main/resources/config.yml
+++ b/connector/src/main/resources/config.yml
@@ -57,6 +57,9 @@ general-thread-pool: 32
# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes
allow-third-party-capes: true
+# The default locale if we dont have the one the client requested
+default-locale: en_us
+
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.
diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings
index 278c7344..efc9db6b 160000
--- a/connector/src/main/resources/mappings
+++ b/connector/src/main/resources/mappings
@@ -1 +1 @@
-Subproject commit 278c73449aeeb4064c7513a68f98a49a5f463f0a
+Subproject commit efc9db6b7d51bdf145230933ac23b321ac1c132d