From 4bbb057f62089fe23496a6936fee09fc073cb32b Mon Sep 17 00:00:00 2001 From: Luke <32024335+lukeeey@users.noreply.github.com> Date: Sat, 26 Sep 2020 02:37:13 +0100 Subject: [PATCH 001/161] Get the basics working --- .../org/geysermc/connector/entity/Entity.java | 7 +-- .../network/session/GeyserSession.java | 58 +++++++++++++++++++ ...BedrockInventoryTransactionTranslator.java | 11 ++++ .../player/BedrockInteractTranslator.java | 5 ++ .../java/JavaSwitchCameraTranslator.java | 54 +++++++++++++++++ .../JavaEntityPositionRotationTranslator.java | 4 ++ .../entity/JavaEntityPositionTranslator.java | 4 ++ .../spawn/JavaSpawnEntityTranslator.java | 1 + .../JavaSpawnLivingEntityTranslator.java | 1 + .../spawn/JavaSpawnPaintingTranslator.java | 1 + .../spawn/JavaSpawnPlayerTranslator.java | 1 + 11 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaSwitchCameraTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e89..c42c56ea 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -56,10 +56,7 @@ import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.utils.MessageUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Getter @Setter @@ -67,6 +64,8 @@ public class Entity { protected long entityId; protected long geyserId; + protected UUID javaUuid; + protected String dimension; protected Vector3f position; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 0a28b11f..bb2037e4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -47,10 +47,13 @@ import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; @@ -62,6 +65,7 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; @@ -261,6 +265,11 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + private Entity spectatingEntity = null; + private Vector3f originalPosition = null; + private Vector3f originalRotation = null; + private boolean originalOnGround = false; + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { @@ -751,4 +760,53 @@ public class GeyserSession implements CommandSender { adventureSettingsPacket.getSettings().addAll(flags); sendUpstreamPacket(adventureSettingsPacket); } + + public void stopSpectatingEntity() { + playerEntity.updatePositionAndRotation(this, originalPosition.getX(), originalPosition.getY(), originalPosition.getZ(), + originalRotation.getZ(), originalRotation.getY(), originalOnGround); + + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(spectatingEntity.getGeyserId(), playerEntity.getGeyserId(), EntityLinkData.Type.REMOVE, false)); + this.sendUpstreamPacket(linkPacket); + + spectatingEntity.getPassengers().remove(playerEntity.getEntityId()); + + spectatingEntity = null; + + this.sendMessage("Stopping spectating entity"); + } + + public void setSpectatingEntity(Entity entity) { + this.sendMessage("Starting spectating entity"); + + originalPosition = playerEntity.getPosition(); + originalRotation = playerEntity.getRotation(); + originalOnGround = playerEntity.isOnGround(); + + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + linkPacket.setEntityLink(new EntityLinkData(entity.getGeyserId(), playerEntity.getGeyserId(), + EntityLinkData.Type.RIDER, false)); + + this.sendUpstreamPacket(linkPacket); + + entity.getPassengers().add(playerEntity.getEntityId()); + + spectatingEntity = entity; + } + + public void sendSpectateLocation() { + Vector3f pos = spectatingEntity.getPosition(); + Vector3f rot = spectatingEntity.getRotation(); + + playerEntity.updatePositionAndRotation(this, pos.getX(), pos.getY(), pos.getZ(), rot.getZ(), rot.getY(), spectatingEntity.isOnGround()); + + SetTitlePacket setTitlePacket = new SetTitlePacket(); + setTitlePacket.setFadeOutTime(0); + setTitlePacket.setFadeInTime(0); + setTitlePacket.setStayTime(30); + setTitlePacket.setText("Sending position: " + pos.getX() + " " + pos.getY() + " " + pos.getZ()); + setTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); + + this.sendUpstreamPacket(setTitlePacket); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 8e891d92..6535226f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -36,6 +36,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSpectatePacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -45,6 +46,7 @@ import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; @@ -242,6 +244,15 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator ClientPlayerStatePacket sneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); session.sendDownstreamPacket(sneakPacket); session.setRidingVehicleEntity(null); + + if (session.getGameMode() == GameMode.SPECTATOR && session.getSpectatingEntity() != null) { + //session.stopSpectatingEntity(); + } break; case MOUSEOVER: // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaSwitchCameraTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaSwitchCameraTranslator.java new file mode 100644 index 00000000..19b58b26 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaSwitchCameraTranslator.java @@ -0,0 +1,54 @@ +/* + * 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.packet.ingame.server.ServerSwitchCameraPacket; +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 = ServerSwitchCameraPacket.class) +public class JavaSwitchCameraTranslator extends PacketTranslator { + + @Override + public void translate(ServerSwitchCameraPacket packet, GeyserSession session) { + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getCameraEntityId()); + if (session.getPlayerEntity().getEntityId() == packet.getCameraEntityId()) { + entity = session.getPlayerEntity(); + } + + if (entity == null) + return; + + if (session.getSpectatingEntity() != null && session.getPlayerEntity().getGeyserId() == entity.getGeyserId()) { + session.stopSpectatingEntity(); + return; + } + + session.setSpectatingEntity(entity); + } +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java index c4bb799e..aaf7c3a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPositionRotationTranslator.java @@ -43,5 +43,9 @@ public class JavaEntityPositionRotationTranslator extends PacketTranslator Date: Sat, 26 Sep 2020 02:45:31 +0100 Subject: [PATCH 002/161] Remove some debug --- .../connector/network/session/GeyserSession.java | 12 ------------ .../BedrockInventoryTransactionTranslator.java | 2 +- .../entity/player/BedrockInteractTranslator.java | 4 ---- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bb2037e4..1bf98bb3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -47,13 +47,11 @@ import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; @@ -65,7 +63,6 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; @@ -799,14 +796,5 @@ public class GeyserSession implements CommandSender { Vector3f rot = spectatingEntity.getRotation(); playerEntity.updatePositionAndRotation(this, pos.getX(), pos.getY(), pos.getZ(), rot.getZ(), rot.getY(), spectatingEntity.isOnGround()); - - SetTitlePacket setTitlePacket = new SetTitlePacket(); - setTitlePacket.setFadeOutTime(0); - setTitlePacket.setFadeInTime(0); - setTitlePacket.setStayTime(30); - setTitlePacket.setText("Sending position: " + pos.getX() + " " + pos.getY() + " " + pos.getZ()); - setTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - - this.sendUpstreamPacket(setTitlePacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 6535226f..61a64c8d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -246,7 +246,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator ClientPlayerStatePacket sneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING); session.sendDownstreamPacket(sneakPacket); session.setRidingVehicleEntity(null); - - if (session.getGameMode() == GameMode.SPECTATOR && session.getSpectatingEntity() != null) { - //session.stopSpectatingEntity(); - } break; case MOUSEOVER: // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc From 7c49391b9d38373bbe0164032c8d01b41929a8c7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 28 Sep 2020 22:43:50 +0100 Subject: [PATCH 003/161] Fix gamemodes not fully applying on server switch (#1348) * Fix gamemodes not fully applying on server switch * Revert previous commit and move session flag updating to the adventure settings method --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 ++++ .../translators/java/world/JavaNotifyClientTranslator.java | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 0a28b11f..300877f8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -729,6 +729,10 @@ public class GeyserSession implements CommandSender { // Required to make command blocks destroyable adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER); + // Update the noClip and worldImmutable values based on the current gamemode + noClip = gameMode == GameMode.SPECTATOR; + worldImmutable = gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR; + Set flags = new HashSet<>(); if (canFly) { flags.add(AdventureSetting.MAY_FLY); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index a7bc7b61..493a7ca1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -103,8 +103,6 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Tue, 29 Sep 2020 01:09:57 +0300 Subject: [PATCH 004/161] Update README.md (#1349) Make the server address look cool (for build 405, sorry) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0404a93..f7201bb0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Download: http://ci.geysermc.org - Discord: http://discord.geysermc.org/ - ~~Donate: https://patreon.com/GeyserMC~~ Currently disabled. -- Test Server: test.geysermc.org port 25565 for Java and 19132 for Bedrock +- Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock ## What's Left to be Added/Fixed - The Following Inventories From 650c02ef669c3a525c66810fbd834ad191991c47 Mon Sep 17 00:00:00 2001 From: bundabrg Date: Tue, 29 Sep 2020 11:49:46 +0800 Subject: [PATCH 005/161] Remove 'geyser' from parameters when executing a command under Spigot, Bungeecord, Sponge, Velocity (#1266) * Remove 'geyser' from parameters when executing a command under Spigot, Bungeecode, Sponge, Velocity Fixes https://github.com/bundabrg/GeyserReversion/issues/8 * Fix case when there are no sub commands Co-authored-by: bundabrg --- .../bungeecord/command/GeyserBungeeCommandExecutor.java | 4 ++-- .../spigot/command/GeyserSpigotCommandExecutor.java | 4 ++-- .../sponge/command/GeyserSpongeCommandExecutor.java | 4 ++-- .../velocity/command/GeyserVelocityCommandExecutor.java | 6 ++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index 3b051c5c..f673a3f5 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -64,10 +64,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); return; } - getCommand(args[0]).execute(new BungeeCommandSender(sender), args); + getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new BungeeCommandSender(sender), args); + getCommand("help").execute(new BungeeCommandSender(sender), new String[0]); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 38187275..2dba2901 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -59,11 +59,11 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { sender.sendMessage(ChatColor.RED + message); return true; } - getCommand(args[0]).execute(new SpigotCommandSender(sender), args); + getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); return true; } } else { - getCommand("help").execute(new SpigotCommandSender(sender), args); + getCommand("help").execute(new SpigotCommandSender(sender), new String[0]); return true; } return true; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index d37321ff..c77e8271 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -59,10 +59,10 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return CommandResult.success(); } - getCommand(args[0]).execute(new SpongeCommandSender(source), args); + getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new SpongeCommandSender(source), args); + getCommand("help").execute(new SpongeCommandSender(source), new String[0]); } return CommandResult.success(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index fa3aaa3c..afd6c3bf 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -37,6 +37,8 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.utils.LanguageUtils; +import java.util.Arrays; + @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { @@ -51,10 +53,10 @@ public class GeyserVelocityCommandExecutor implements Command { source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); return; } - getCommand(args[0]).execute(new VelocityCommandSender(source), args); + getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(source), args); + getCommand("help").execute(new VelocityCommandSender(source), new String[0]); } } From a5b00e09a1111d9f0999989ff773dc1d405e2786 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:19:37 -0400 Subject: [PATCH 006/161] Villager trade fixes (#1350) This commit mainly focuses on fixing the crashing of villagers that occurred pre-1.14. Co-authored-by: AJ Ferguson --- .../holder/BlockInventoryHolder.java | 5 ++--- .../java/window/JavaOpenWindowTranslator.java | 4 +++- .../java/world/JavaTradeListTranslator.java | 20 +++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 6dfde5d1..6afdb25d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -35,9 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; @AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { @@ -60,7 +59,7 @@ public class BlockInventoryHolder extends InventoryHolder { .putInt("x", position.getX()) .putInt("y", position.getY()) .putInt("z", position.getZ()) - .putString("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).build(); + .putString("CustomName", inventory.getTitle()).build(); BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); dataPacket.setData(tag); dataPacket.setBlockPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index dde45cf8..820639b3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClose import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; @@ -37,6 +36,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.utils.LocaleUtils; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -71,6 +71,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice())); if (trade.getSecondInput() != null) { recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0)); From 9bb52afc8a8ca4ebb89e7e7fc39a8a678bb56244 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:21:25 -0400 Subject: [PATCH 007/161] BedrockRespawnTranslator: prevent some respawn bugs (#1346) --- .../bedrock/BedrockRespawnTranslator.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java index 71d52e13..dc98c363 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockRespawnTranslator.java @@ -28,7 +28,10 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.ClientRequest; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; import com.nukkitx.protocol.bedrock.packet.RespawnPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +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; @@ -39,12 +42,30 @@ public class BedrockRespawnTranslator extends PacketTranslator { @Override public void translate(RespawnPacket packet, GeyserSession session) { if (packet.getState() == RespawnPacket.State.CLIENT_READY) { - if (!session.isSpawned()) { // Otherwise when immediate respawn is on the client never loads - RespawnPacket respawnPacket = new RespawnPacket(); - respawnPacket.setRuntimeEntityId(0); - respawnPacket.setPosition(Vector3f.ZERO); - respawnPacket.setState(RespawnPacket.State.SERVER_READY); - session.sendUpstreamPacket(respawnPacket); + // Previously we only sent the respawn packet before the server finished loading + // The message included was 'Otherwise when immediate respawn is on the client never loads' + // But I assume the new if statement below fixes that problem + RespawnPacket respawnPacket = new RespawnPacket(); + respawnPacket.setRuntimeEntityId(0); + respawnPacket.setPosition(Vector3f.ZERO); + respawnPacket.setState(RespawnPacket.State.SERVER_READY); + session.sendUpstreamPacket(respawnPacket); + + if (session.isSpawned()) { + // Client might be stuck; resend spawn information + PlayerEntity entity = session.getPlayerEntity(); + if (entity == null) return; + SetEntityDataPacket entityDataPacket = new SetEntityDataPacket(); + entityDataPacket.setRuntimeEntityId(entity.getGeyserId()); + entityDataPacket.getMetadata().putAll(entity.getMetadata()); + session.sendUpstreamPacket(entityDataPacket); + + MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); + movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); + movePlayerPacket.setPosition(entity.getPosition()); + movePlayerPacket.setRotation(entity.getBedrockRotation()); + movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); + session.sendUpstreamPacket(movePlayerPacket); } ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN); From aee9ccc7d2ca8ecc932ae92dcab38161e1653bc4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:21:43 -0400 Subject: [PATCH 008/161] DoorSoundInteractionHandler: ignore iron [trap]doors (#1343) --- .../translators/sound/block/DoorSoundInteractionHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java index 10d4bb89..a1df72d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/block/DoorSoundInteractionHandler.java @@ -37,6 +37,7 @@ public class DoorSoundInteractionHandler implements BlockSoundInteractionHandler @Override public void handleInteraction(GeyserSession session, Vector3f position, String identifier) { + if (identifier.contains("iron")) return; LevelEventPacket levelEventPacket = new LevelEventPacket(); levelEventPacket.setType(LevelEventType.SOUND_DOOR_OPEN); levelEventPacket.setPosition(position); From 3c4cde96779f490f302aa420513e03bcad1b9dd6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 13:30:37 -0400 Subject: [PATCH 009/161] Tipped arrow translation (#1331) * Tipped arrow translation - Tipped arrow items are now properly translated both ways - Tipped arrow particle effects are also translated, by having a list of all colors Java could send us and their Bedrock ID * Remove a whitespace --- .../connector/entity/TippedArrowEntity.java | 27 ++++ .../translators/item/ItemRegistry.java | 4 +- .../network/translators/item/Potion.java | 2 +- .../translators/item/TippedArrowPotion.java | 151 ++++++++++++++++++ .../item/translators/BannerTranslator.java | 2 +- .../item/translators/CompassTranslator.java | 2 +- .../item/translators/PotionTranslator.java | 4 +- .../translators/TippedArrowTranslator.java | 87 ++++++++++ 8 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java index def5715c..949764b9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/TippedArrowEntity.java @@ -25,12 +25,39 @@ package org.geysermc.connector.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.TippedArrowPotion; +/** + * Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows + */ public class TippedArrowEntity extends AbstractArrowEntity { public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Arrow potion effect color + if (entityMetadata.getId() == 9) { + int potionColor = (int) entityMetadata.getValue(); + // -1 means no color + if (potionColor == -1) { + metadata.remove(EntityData.CUSTOM_DISPLAY); + } else { + TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor); + if (potion != null && potion.getJavaColor() != -1) { + metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId()); + } else { + metadata.remove(EntityData.CUSTOM_DISPLAY); + } + } + } + super.updateBedrockMetadata(entityMetadata, session); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 1f3af019..850e4e05 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -197,7 +197,9 @@ public class ItemRegistry { */ public static ItemEntry getItem(ItemData data) { for (ItemEntry itemEntry : ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) { + if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || + // Make exceptions for potions and tipped arrows, whose damage values can vary + (itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) { return itemEntry; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java index 51ae36e4..b9a213d8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java @@ -48,7 +48,7 @@ public enum Potion { STRONG_SWIFTNESS(16), LONG_SWIFTNESS(15), SLOWNESS(17), - STRONG_SLOWNESS(18), //does not exist + STRONG_SLOWNESS(42), LONG_SLOWNESS(18), WATER_BREATHING(19), LONG_WATER_BREATHING(20), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java new file mode 100644 index 00000000..7a5b576b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/TippedArrowPotion.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item; + +import lombok.Getter; + +import java.util.Locale; + +/** + * Potion identifiers and their respective Bedrock IDs used with arrows. + * https://minecraft.gamepedia.com/Arrow#Item_Data + */ +@Getter +public enum TippedArrowPotion { + MUNDANE(2, ArrowParticleColors.NONE), // 3 is extended? + THICK(4, ArrowParticleColors.NONE), + AWKWARD(5, ArrowParticleColors.NONE), + NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION), + LONG_NIGHT_VISION(7, ArrowParticleColors.NIGHT_VISION), + INVISIBILITY(8, ArrowParticleColors.INVISIBILITY), + LONG_INVISIBILITY(9, ArrowParticleColors.INVISIBILITY), + LEAPING(10, ArrowParticleColors.LEAPING), + LONG_LEAPING(11, ArrowParticleColors.LEAPING), + STRONG_LEAPING(12, ArrowParticleColors.LEAPING), + FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE), + LONG_FIRE_RESISTANCE(14, ArrowParticleColors.FIRE_RESISTANCE), + SWIFTNESS(15, ArrowParticleColors.SWIFTNESS), + LONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS), + STRONG_SWIFTNESS(17, ArrowParticleColors.SWIFTNESS), + SLOWNESS(18, ArrowParticleColors.SLOWNESS), + LONG_SLOWNESS(19, ArrowParticleColors.SLOWNESS), + STRONG_SLOWNESS(43, ArrowParticleColors.SLOWNESS), + WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING), + LONG_WATER_BREATHING(21, ArrowParticleColors.WATER_BREATHING), + HEALING(22, ArrowParticleColors.HEALING), + STRONG_HEALING(23, ArrowParticleColors.HEALING), + HARMING(24, ArrowParticleColors.HARMING), + STRONG_HARMING(25, ArrowParticleColors.HARMING), + POISON(26, ArrowParticleColors.POISON), + LONG_POISON(27, ArrowParticleColors.POISON), + STRONG_POISON(28, ArrowParticleColors.POISON), + REGENERATION(29, ArrowParticleColors.REGENERATION), + LONG_REGENERATION(30, ArrowParticleColors.REGENERATION), + STRONG_REGENERATION(31, ArrowParticleColors.REGENERATION), + STRENGTH(32, ArrowParticleColors.STRENGTH), + LONG_STRENGTH(33, ArrowParticleColors.STRENGTH), + STRONG_STRENGTH(34, ArrowParticleColors.STRENGTH), + WEAKNESS(35, ArrowParticleColors.WEAKNESS), + LONG_WEAKNESS(36, ArrowParticleColors.WEAKNESS), + LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock + TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER), + LONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER), + STRONG_TURTLE_MASTER(40, ArrowParticleColors.TURTLE_MASTER), + SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING), + LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING); + + private final String javaIdentifier; + private final short bedrockId; + /** + * The Java color associated with this ID. + * Used for looking up Java arrow color entity metadata as Bedrock potion IDs, which is what is used for entities in Bedrock + */ + private final int javaColor; + + TippedArrowPotion(int bedrockId, ArrowParticleColors arrowParticleColor) { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + this.bedrockId = (short) bedrockId; + this.javaColor = arrowParticleColor.getColor(); + } + + public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.javaIdentifier.equals(javaIdentifier)) { + return potion; + } + } + return null; + } + + public static TippedArrowPotion getByBedrockId(short bedrockId) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.bedrockId == bedrockId) { + return potion; + } + } + return null; + } + + /** + * @param color the potion color to look up + * @return the tipped arrow potion that most closely resembles that color. + */ + public static TippedArrowPotion getByJavaColor(int color) { + for (TippedArrowPotion potion : TippedArrowPotion.values()) { + if (potion.javaColor == color) { + return potion; + } + } + return null; + } + + private enum ArrowParticleColors { + NONE(-1), + NIGHT_VISION(2039713), + INVISIBILITY(8356754), + LEAPING(2293580), + FIRE_RESISTANCE(14981690), + SWIFTNESS(8171462), + SLOWNESS(5926017), + TURTLE_MASTER(7691106), + WATER_BREATHING(3035801), + HEALING(16262179), + HARMING(4393481), + POISON(5149489), + REGENERATION(13458603), + STRENGTH(9643043), + WEAKNESS(4738376), + LUCK(3381504), + SLOW_FALLING(16773073); + + @Getter + private final int color; + + ArrowParticleColors(int color) { + this.color = color; + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 304ea3fb..f4bfdfb6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -50,7 +50,7 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public BannerTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index 675d4255..159b9ab4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; @ItemRemapper public class CompassTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public CompassTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java index 7cb88d70..24130a7f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/PotionTranslator.java @@ -42,7 +42,7 @@ import java.util.stream.Collectors; @ItemRemapper public class PotionTranslator extends ItemTranslator { - private List appliedItems; + private final List appliedItems; public PotionTranslator() { appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList()); @@ -57,7 +57,7 @@ public class PotionTranslator extends ItemTranslator { if (potion != null) { return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); } - GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue()); + GeyserConnector.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue()); } return super.translateToBedrock(itemStack, itemEntry); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java new file mode 100644 index 00000000..0b69d6a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.TippedArrowPotion; + +import java.util.List; +import java.util.stream.Collectors; + +@ItemRemapper +public class TippedArrowTranslator extends ItemTranslator { + + private final List appliedItems; + + private static final int TIPPED_ARROW_JAVA_ID = ItemRegistry.getItemEntry("minecraft:tipped_arrow").getJavaId(); + + public TippedArrowTranslator() { + appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> + entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")).collect(Collectors.toList()); + } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (!itemEntry.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) { + // We're only concerned about minecraft:arrow when translating Bedrock -> Java + return super.translateToBedrock(itemStack, itemEntry); + } + Tag potionTag = itemStack.getNbt().get("Potion"); + if (potionTag instanceof StringTag) { + TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByJavaIdentifier(((StringTag) potionTag).getValue()); + if (tippedArrowPotion != null) { + return ItemData.of(itemEntry.getBedrockId(), tippedArrowPotion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt())); + } + GeyserConnector.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue()); + } + return super.translateToBedrock(itemStack, itemEntry); + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + if (tippedArrowPotion != null) { + itemStack = new ItemStack(TIPPED_ARROW_JAVA_ID, itemStack.getAmount(), itemStack.getNbt()); + StringTag potionTag = new StringTag("Potion", tippedArrowPotion.getJavaIdentifier()); + itemStack.getNbt().put(potionTag); + } + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } +} From 772cb246f02bc6349d9a95f14fa62bcc9e2be0d2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 29 Sep 2020 14:15:11 -0400 Subject: [PATCH 010/161] Forward keep alive packets to the client (#1344) * Forward keep alive packets to the client Previously, MCProtocolLib (our Java protocol library) handled keep alive packets for us. This commit disables that option and 'forwards' the keep alive packets to the client, and sending the keep alive packet back once Bedrock sends us a ping response. * Delete DataCache * Update to latest MCProtocolLib * Swap values around as a sanity check --- connector/pom.xml | 4 +- .../network/session/GeyserSession.java | 13 +++-- .../translators/PacketTranslatorRegistry.java | 2 - ...BedrockNetworkStackLatencyTranslator.java} | 25 ++++++---- .../java/JavaKeepAliveTranslator.java | 48 +++++++++++++++++++ 5 files changed, 76 insertions(+), 16 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/{session/cache/DataCache.java => translators/bedrock/BedrockNetworkStackLatencyTranslator.java} (54%) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java diff --git a/connector/pom.xml b/connector/pom.xml index f52713ce..26ec0c4e 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.GeyserMC + com.github.steveice10 mcprotocollib - e4a3aa636a + 976c2d0f89 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 300877f8..5225632c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -28,6 +28,7 @@ package org.geysermc.connector.network.session; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException; import com.github.steveice10.mc.auth.exception.request.RequestException; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; @@ -117,8 +118,6 @@ public class GeyserSession implements CommandSender { */ private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); - private DataCache javaPacketCache; - @Setter private Vector2i lastChunkPosition = null; private int renderDistance; @@ -176,6 +175,12 @@ public class GeyserSession implements CommandSender { @Setter private long lastWindowCloseTime = 0; + /** + * Saves the timestamp of the last keep alive packet + */ + @Setter + private long lastKeepAliveTimestamp = 0; + @Setter private VillagerTrade[] villagerTrades; @Setter @@ -276,8 +281,6 @@ public class GeyserSession implements CommandSender { this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); this.inventory = new PlayerInventory(); - this.javaPacketCache = new DataCache<>(); - this.spawned = false; this.loggedIn = false; @@ -384,6 +387,8 @@ public class GeyserSession implements CommandSender { } downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); + // Let Geyser handle sending the keep alive + downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); downstream.getSession().addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index c4386481..6517f498 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDataPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket; import com.github.steveice10.packetlib.packet.Packet; @@ -75,7 +74,6 @@ public class PacketTranslatorRegistry { } } - IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java similarity index 54% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java rename to connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 4b2af963..d480b526 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/DataCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -23,15 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.session.cache; +package org.geysermc.connector.network.translators.bedrock; -import lombok.Getter; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; -import java.util.HashMap; -import java.util.Map; +/** + * Used to send the keep alive packet back to the server + */ +@Translator(packet = NetworkStackLatencyPacket.class) +public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { -public class DataCache { - - @Getter - private Map cachedValues = new HashMap(); + @Override + public void translate(NetworkStackLatencyPacket packet, GeyserSession session) { + // The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number + ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp()); + session.sendDownstreamPacket(keepAlivePacket); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java new file mode 100644 index 00000000..1dd156e4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java @@ -0,0 +1,48 @@ +/* + * 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.packet.ingame.server.ServerKeepAlivePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * Used to forward the keep alive packet to the client in order to get back a reliable ping. + */ +@Translator(packet = ServerKeepAlivePacket.class) +public class JavaKeepAliveTranslator extends PacketTranslator { + + @Override + public void translate(ServerKeepAlivePacket packet, GeyserSession session) { + session.setLastKeepAliveTimestamp(packet.getPingId()); + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(packet.getPingId()); + session.sendUpstreamPacket(latencyPacket); + } +} From ba6f174058dcfbc0e37aaa9af7e731bb18e17842 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 2 Oct 2020 15:44:46 -0400 Subject: [PATCH 011/161] Add support for fishing rods pulling Bedrock players (#1355) Fishing rods pulling players is a clientside feature on Java. On Bedrock, a SetEntityMotionPacket is sent to the client. Therefore this PR implements the Java fishing rod pulling mechanics and sends it off to Bedrock, which sends MovePlayerPackets that are sent to the server. --- .../entity/JavaEntityStatusTranslator.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index ac2a80f7..c3fbd2e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -30,8 +30,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -96,8 +96,20 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Wed, 7 Oct 2020 18:50:39 -0400 Subject: [PATCH 012/161] Jenkins improvements (#1368) - Show changes in the Discord webhook - Delete after 20 builds Co-authored-by: rtm516 --- Jenkinsfile | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ffa4f1bd..1a93391d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { jdk 'Java 8' } options { - buildDiscarder(logRotator(artifactNumToKeepStr: '5')) + buildDiscarder(logRotator(artifactNumToKeepStr: '20')) } stages { stage ('Build') { @@ -32,9 +32,40 @@ pipeline { post { always { + script { + def changeLogSets = currentBuild.changeSets + def message = "**Changes:**" + + if (changeLogSets.size() == 0) { + message += "\n*No changes.*" + } else { + def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") + def count = 0; + def extra = 0; + for (int i = 0; i < changeLogSets.size(); i++) { + def entries = changeLogSets[i].items + for (int j = 0; j < entries.length; j++) { + if (count <= 10) { + def entry = entries[j] + def commitId = entry.commitId.substring(0, 6) + message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" + count++ + } else { + extra++; + } + } + } + + if (extra != 0) { + message += "\n - ${extra} more commits" + } + } + + env.changes = message + } deleteDir() withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'NukkitX Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK } } } From 172a5a6db863624f65913d5672b9de60db1b70da Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 7 Oct 2020 18:51:36 -0400 Subject: [PATCH 013/161] Add Fabric as a platform type (#1376) * PlatformType: Add Fabric as a platform * Don't use XML reflections on Fabric --- .../org/geysermc/connector/GeyserConnector.java | 13 +++++++------ .../org/geysermc/connector/common/PlatformType.java | 3 ++- .../translators/PacketTranslatorRegistry.java | 2 +- .../network/translators/item/ItemTranslator.java | 2 +- .../translators/sound/SoundHandlerRegistry.java | 2 +- .../translators/world/block/BlockTranslator.java | 2 +- .../world/block/entity/BlockEntityTranslator.java | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3c814393..92411897 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -312,15 +312,16 @@ public class GeyserConnector { } /** - * Get the production status of the current runtime. - * Will return true if the version number is not 'DEV'. - * Should only happen in compiled jars. + * Whether to use XML reflections in the jar or manually find the reflections. + * Will return true if the version number is not 'DEV' and the platform is not Fabric. + * On Fabric - it complains about being unable to create a default XMLReader. + * On other platforms this should only be true in compiled jars. * - * @return If we are in a production build/environment + * @return whether to use XML reflections */ - public boolean isProduction() { + public boolean useXmlReflections() { //noinspection ConstantConditions - return !"DEV".equals(GeyserConnector.VERSION); + return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION); } public static GeyserConnector getInstance() { diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java index 4daa5d37..3e945d3a 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/connector/src/main/java/org/geysermc/connector/common/PlatformType.java @@ -34,10 +34,11 @@ public enum PlatformType { ANDROID("Android"), BUNGEECORD("BungeeCord"), + FABRIC("Fabric"), SPIGOT("Spigot"), SPONGE("Sponge"), STANDALONE("Standalone"), VELOCITY("Velocity"); - private String platformName; + private final String platformName; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index 6517f498..b6c6f3ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -48,7 +48,7 @@ public class PacketTranslatorRegistry { private static final ObjectArrayList> IGNORED_PACKETS = new ObjectArrayList<>(); static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators"); for (Class clazz : ref.getTypesAnnotatedWith(Translator.class)) { Class packet = clazz.getAnnotation(Translator.class).packet(); 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 864e9fe0..ef629494 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 @@ -64,7 +64,7 @@ public abstract class ItemTranslator { static { /* Load item translators */ - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item"); Map loadedNbtItemTranslators = new HashMap<>(); for (Class clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java index 163c451c..03b346e3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/SoundHandlerRegistry.java @@ -40,7 +40,7 @@ public class SoundHandlerRegistry { static final Map> INTERACTION_HANDLERS = new HashMap<>(); static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound"); for (Class clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) { try { SoundInteractionHandler interactionHandler = (SoundInteractionHandler) clazz.newInstance(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index fc7c852c..e5f8d6aa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -113,7 +113,7 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); ref.getTypesAnnotatedWith(BlockEntity.class); int waterRuntimeId = -1; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 54f593a6..4df4fd95 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -68,7 +68,7 @@ public abstract class BlockEntityTranslator { } static { - Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); From 16eb2a491a0e7d9b12061fb7dd91b516bb15ae20 Mon Sep 17 00:00:00 2001 From: Arktisfox <65837019+Arktisfox@users.noreply.github.com> Date: Thu, 8 Oct 2020 18:33:36 -0400 Subject: [PATCH 014/161] Area cloud fixes (#684) * Fix NotNull error with particles, replace incorrect string meta with int meta. * Add back newline * Remove debug line * Update Protocol and prepare for merge Co-authored-by: DoctorMacc --- connector/pom.xml | 2 +- .../entity/AreaEffectCloudEntity.java | 7 +- .../network/UpstreamPacketHandler.java | 2 +- .../translators/effect/EffectRegistry.java | 72 +++++++++++++------ connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 26ec0c4e..741acee5 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -33,7 +33,7 @@ com.github.CloudburstMC.Protocol bedrock-v408 - 250beb2a94 + 02f46a8700 compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java index 21861589..567a08ed 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -50,11 +50,12 @@ public class AreaEffectCloudEntity extends Entity { if (entityMetadata.getId() == 7) { metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); - } else if (entityMetadata.getId() == 10) { - Particle particle = (Particle) entityMetadata.getValue(); - metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType())); } else if (entityMetadata.getId() == 8) { metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue()); + } else if (entityMetadata.getId() == 10) { + Particle particle = (Particle) entityMetadata.getValue(); + int particleId = EffectRegistry.getParticleId(particle.getType()); + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 7e97d429..f76d64ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -117,7 +117,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { case HAVE_ALL_PACKS: ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); - stackPacket.setExperimental(false); + stackPacket.setExperimentsPreviouslyToggled(false); stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not stackPacket.setGameVersion(session.getClientData().getGameVersion()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java index 39c586db..75cab152 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/effect/EffectRegistry.java @@ -32,10 +32,11 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.SoundEvent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.NonNull; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; import java.util.HashMap; @@ -50,8 +51,19 @@ public class EffectRegistry { public static final Map SOUND_EFFECTS = new HashMap<>(); public static final Int2ObjectMap RECORDS = new Int2ObjectOpenHashMap<>(); - private static Map particleTypeMap = new HashMap<>(); - private static Map particleStringMap = new HashMap<>(); + /** + * Java particle type to Bedrock particle ID + * Used for area effect clouds. + */ + private static final Object2IntMap PARTICLE_TO_ID = new Object2IntOpenHashMap<>(); + /** + * Java particle type to Bedrock level event + */ + private static final Map PARTICLE_TO_LEVEL_EVENT = new HashMap<>(); + /** + * Java particle type to Bedrock namespaced string ID + */ + private static final Map PARTICLE_TO_STRING = new HashMap<>(); public static void init() { // no-op @@ -68,22 +80,24 @@ public class EffectRegistry { } Iterator> particlesIterator = particleEntries.fields(); - while (particlesIterator.hasNext()) { - Map.Entry entry = particlesIterator.next(); - try { - particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase())); - } catch (IllegalArgumentException e1) { - try { - particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText()); - GeyserConnector.getInstance().getLogger().debug("Force to map particle " - + entry.getKey() - + "=>" - + entry.getValue().asText() - + ", it will take effect."); - } catch (IllegalArgumentException e2){ - GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText())); + try { + while (particlesIterator.hasNext()) { + Map.Entry entry = particlesIterator.next(); + JsonNode bedrockId = entry.getValue().get("bedrockId"); + JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId"); + JsonNode eventType = entry.getValue().get("eventType"); + if (bedrockIdNumeric != null) { + PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt()); + } + if (bedrockId != null) { + PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText()); + } + if (eventType != null) { + PARTICLE_TO_LEVEL_EVENT.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(eventType.asText().toUpperCase())); } } + } catch (Exception e) { + e.printStackTrace(); } /* Load effects */ @@ -149,11 +163,27 @@ public class EffectRegistry { } } - public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { - return particleTypeMap.getOrDefault(type, null); + /** + * @param type the Java particle to search for + * @return the Bedrock integer ID of the particle, or -1 if it does not exist + */ + public static int getParticleId(@NonNull ParticleType type) { + return PARTICLE_TO_ID.getOrDefault(type, -1); } - public static String getParticleString(@NonNull ParticleType type){ - return particleStringMap.getOrDefault(type, null); + /** + * @param type the Java particle to search for + * @return the level event equivalent Bedrock particle + */ + public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) { + return PARTICLE_TO_LEVEL_EVENT.getOrDefault(type, null); + } + + /** + * @param type the Java particle to search for + * @return the namespaced ID equivalent for Bedrock + */ + public static String getParticleString(@NonNull ParticleType type) { + return PARTICLE_TO_STRING.getOrDefault(type, null); } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 93b2caed..5f217922 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 93b2caed3c4ecd94b3c77a87f1b2304a7bf4f062 +Subproject commit 5f21792264a364e32425014e0be79db93593da1e diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 28a22d2b..0fae8d3f 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 28a22d2baad680f511bffc36d90d06bf626f0527 +Subproject commit 0fae8d3f0de6210a10435a36128db14cb7650ae6 From 4514167835e44838980c7202204a769cd1784653 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 18:44:15 -0400 Subject: [PATCH 015/161] Fix fire being punched in all directions (#1370) Apply the fix we have for fire but for all block faces. --- ...BedrockInventoryTransactionTranslator.java | 24 ++---------------- .../player/BedrockActionTranslator.java | 19 +++++++------- .../geysermc/connector/utils/BlockUtils.java | 25 +++++++++++++++++++ 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 8e891d92..b92a84eb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -58,6 +58,7 @@ import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = InventoryTransactionPacket.class) @@ -151,28 +152,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Fri, 9 Oct 2020 01:30:05 +0200 Subject: [PATCH 016/161] Various Scoreboard fixes (#1381) * Various Scoreboard fixes Fixes #1328 and a few other potential Scoreboard problems * Consistent whitespacing Co-authored-by: DoctorMacc --- .../connector/scoreboard/Objective.java | 12 ++----- .../geysermc/connector/scoreboard/Score.java | 1 - .../connector/scoreboard/Scoreboard.java | 31 ++++++++----------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java index b2f64861..3accbc12 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Objective.java @@ -76,7 +76,8 @@ public class Objective { if (!scores.containsKey(id)) { Score score1 = new Score(this, id) .setScore(score) - .setTeam(scoreboard.getTeamFor(id)); + .setTeam(scoreboard.getTeamFor(id)) + .setUpdateType(UpdateType.ADD); scores.put(id, score1); } } @@ -96,15 +97,6 @@ public class Objective { return 0; } - public Score getScore(int line) { - for (Score score : scores.values()) { - if (score.getScore() == line) { - return score; - } - } - return null; - } - public void removeScore(String id) { if (scores.containsKey(id)) { scores.get(id).setUpdateType(UpdateType.REMOVE); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index 635bafa3..3dfd6ed3 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -49,7 +49,6 @@ public class Score { this.id = objective.getScoreboard().getNextId().getAndIncrement(); this.objective = objective; this.name = name; - update(); } public String getDisplayName() { diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index ae1b8275..732a056e 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -122,7 +122,7 @@ public class Scoreboard { for (Objective objective : objectives.values()) { if (!objective.isActive()) { - logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\''); + logger.debug("Ignoring non-active Scoreboard Objective '" + objective.getObjectiveName() + '\''); continue; } @@ -152,10 +152,6 @@ public class Scoreboard { boolean globalAdd = objective.getUpdateType() == ADD; boolean globalRemove = objective.getUpdateType() == REMOVE; - // Track if any scores changed - // Used to delete and resend scoreboard objectives; otherwise they won't update on Bedrock - boolean scoreChanged = false; - for (Score score : objective.getScores().values()) { Team team = score.getTeam(); @@ -171,16 +167,21 @@ public class Scoreboard { teamChanged |= team.getUpdateType() == UPDATE; add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; - remove |= team.getUpdateType() == REMOVE; + remove |= team.getUpdateType() != NOTHING; } add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; - remove |= score.getUpdateType() == REMOVE; - if (score.getUpdateType() == REMOVE) { + remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + + if (score.getUpdateType() == REMOVE || globalRemove) { add = false; } - if (score.getUpdateType() == UPDATE || teamChanged) { + if (score.getUpdateType() == ADD) { + remove = false; + } + + if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) { score.update(); } @@ -191,12 +192,6 @@ public class Scoreboard { removeScores.add(score.getCachedInfo()); } - if (add || remove) { - scoreChanged = true; - } - // score is pending to be updated, so we use the current score as the old score - score.setOldScore(score.getScore()); - // score is pending to be removed, so we can remove it from the objective if (score.getUpdateType() == REMOVE) { objective.removeScore0(score.getName()); @@ -205,17 +200,17 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate || scoreChanged) { + if (globalRemove || globalUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - if (objective.getUpdateType() == REMOVE) { + if (globalRemove) { objectives.remove(objective.getObjectiveName()); // now we can deregister objective.removed(); } } - if (globalAdd || globalUpdate || scoreChanged) { + if ((globalAdd || globalUpdate) && !globalRemove) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); From 59f72d0e65f3c421b92179b68d83c1b2ef1d7a4d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 20:07:50 -0400 Subject: [PATCH 017/161] BedrockMobEquipmentTranslator: Don't change item slot if already on that slot (#1353) * BedrockMobEquipmentTranslator: Don't change item slot if already on that slot * Update comment --- .../translators/bedrock/BedrockMobEquipmentTranslator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 02835151..a220e389 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -40,7 +40,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 || - packet.getContainerId() != ContainerId.INVENTORY) { + packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention return; } From ec609fa86817666348af0cc96ceb452212250c32 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 8 Oct 2020 20:40:50 -0400 Subject: [PATCH 018/161] Make crossbows prettier (#1359) - Fix crossbow NBT translation - now crossbows will show as loaded - Pillagers now more closely resemble Java Edition pose behavior --- .../living/monster/raid/PillagerEntity.java | 49 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 3 +- .../translators/nbt/CrossbowTranslator.java | 26 ++++++---- 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java new file mode 100644 index 00000000..c7a8f24d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/PillagerEntity.java @@ -0,0 +1,49 @@ +/* + * 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.entity.living.monster.raid; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class PillagerEntity extends AbstractIllagerEntity { + + public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + // Java Edition always has the Pillager entity as positioning the crossbow + metadata.getFlags().setFlag(EntityFlag.USING_ITEM, true); + metadata.getFlags().setFlag(EntityFlag.CHARGED, true); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 500e135e..24c15018 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -34,6 +34,7 @@ import org.geysermc.connector.entity.living.animal.tameable.*; import org.geysermc.connector.entity.living.merchant.*; import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; +import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; @@ -90,7 +91,7 @@ public enum EntityType { ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f), - PILLAGER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), + PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f), WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f), PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f), RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 979c5a20..67f137ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -25,14 +25,15 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; @ItemRemapper public class CrossbowTranslator extends NbtItemStackTranslator { @@ -44,12 +45,17 @@ public class CrossbowTranslator extends NbtItemStackTranslator { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - CompoundTag newProjectile = new CompoundTag("chargedItem"); - newProjectile.put(new ByteTag("Count", (byte) projectile.get("Count").getValue())); - newProjectile.put(new StringTag("Name", (String) projectile.get("id").getValue())); + ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); + if (entry == null) return; + CompoundTag tag = projectile.get("tag"); + ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); + ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); - // Not sure what this is for - newProjectile.put(new ByteTag("Damage", (byte) 0)); + CompoundTag newProjectile = new CompoundTag("chargedItem"); + newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); + newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry))); + + newProjectile.put(new ShortTag("Damage", itemData.getDamage())); itemTag.put(newProjectile); } From ffcff96bea6e43ec34725c648eb5e9c76cad40ea Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 10 Oct 2020 18:08:21 -0400 Subject: [PATCH 019/161] Set default values for classes as well (#1387) Geyser can now start even if the config file is empty. Tested on Spigot and doesn't affect custom values. --- .../configuration/GeyserJacksonConfiguration.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 99a3a7a5..45676fbd 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -34,6 +34,7 @@ import org.geysermc.connector.common.serializer.AsteriskSerializer; import java.nio.file.Path; import java.util.Map; +import java.util.UUID; @Getter @JsonIgnoreProperties(ignoreUnknown = true) @@ -45,8 +46,8 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @Setter private boolean autoconfiguredRemote = false; - private BedrockConfiguration bedrock; - private RemoteConfiguration remote; + private BedrockConfiguration bedrock = new BedrockConfiguration(); + private RemoteConfiguration remote = new RemoteConfiguration(); @JsonProperty("floodgate-key-file") private String floodgateKeyFile = "public-key.pem"; @@ -106,7 +107,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("force-resource-packs") private boolean forceResourcePacks = true; - private MetricsInfo metrics; + private MetricsInfo metrics = new MetricsInfo(); @Getter public static class BedrockConfiguration implements IBedrockConfiguration { @@ -154,7 +155,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private boolean enabled = true; @JsonProperty("uuid") - private String uniqueId = "generateuuid"; + private String uniqueId = UUID.randomUUID().toString(); } @JsonProperty("scoreboard-packet-threshold") From 96db37c14c924f9bce1c827d25416817281e2a6f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 12 Oct 2020 20:02:41 -0400 Subject: [PATCH 020/161] Fix bucket interactions on creative mode (#1369) * Fix bucket interactions on creative mode Bedrock uses the BLOCK_INTERACT enum of BedrockActionTranslator to truly indicate if a bucket should be used or not. In order to hook into this, we need to delay the bucket placing by about 5 milliseconds - this gives us time to cancel the interaction if needed. Bucket sounds will now not play in this case as well. --- .../network/session/GeyserSession.java | 8 ++++++++ ...BedrockInventoryTransactionTranslator.java | 20 ++++++++++--------- .../player/BedrockActionTranslator.java | 7 ++++++- .../block/BucketSoundInteractionHandler.java | 2 ++ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 5225632c..1a0bbfb2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -84,6 +84,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.*; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @Getter @@ -210,6 +211,13 @@ public class GeyserSession implements CommandSender { @Setter private long lastInteractionTime; + /** + * Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to + * interact with a block. + */ + @Setter + private ScheduledFuture bucketScheduledFuture; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b92a84eb..b81025be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -61,6 +61,8 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.InventoryUtils; +import java.util.concurrent.TimeUnit; + @Translator(packet = InventoryTransactionPacket.class) public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -120,18 +122,19 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); + session.sendDownstreamPacket(itemPacket); + }, 5, TimeUnit.MILLISECONDS)); } if (packet.getActions().isEmpty()) { @@ -167,10 +170,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Tue, 13 Oct 2020 02:17:15 +0100 Subject: [PATCH 021/161] Add player device OS to metrics (#1391) * Add player device os to metrics * Add player version, Geyser version, and default locale Co-authored-by: DoctorMacc --- .../geysermc/connector/GeyserConnector.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 92411897..109a5a60 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -66,7 +66,9 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -199,8 +201,39 @@ public class GeyserConnector { metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger("")); metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size)); - metrics.addCustomChart(new Metrics.SimplePie("authMode", authType.name()::toLowerCase)); + // Prevent unwanted words best we can + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString())); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); + metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale)); + metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION)); + metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : players) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String os = session.getClientData().getDeviceOS().toString(); + if (!valueMap.containsKey(os)) { + valueMap.put(os, 1); + } else { + valueMap.put(os, valueMap.get(os) + 1); + } + } + return valueMap; + })); + metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> { + Map valueMap = new HashMap<>(); + for (GeyserSession session : players) { + if (session == null) continue; + if (session.getClientData() == null) continue; + String version = session.getClientData().getGameVersion(); + if (!valueMap.containsKey(version)) { + valueMap.put(version, 1); + } else { + valueMap.put(version, valueMap.get(version) + 1); + } + } + return valueMap; + })); } boolean isGui = false; From 1b00eaca4aa722ff15647879c04e9708dc704458 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:28:54 -0400 Subject: [PATCH 022/161] Set AuthType in Metrics to lowercase (#1395) --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 109a5a60..afed4dfd 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -202,7 +202,7 @@ public class GeyserConnector { metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size)); // Prevent unwanted words best we can - metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString())); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase())); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale)); metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION)); From 9b3cd8f725ff26e6a0abfd87481f021d959a9be6 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Mon, 12 Oct 2020 20:35:31 -0500 Subject: [PATCH 023/161] Fix area effect clouds --- .../geysermc/connector/entity/AreaEffectCloudEntity.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java index 567a08ed..308d2121 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AreaEffectCloudEntity.java @@ -43,6 +43,8 @@ public class AreaEffectCloudEntity extends Entity { // This disabled client side shrink of the cloud metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f); + metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f); + metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f); } @Override @@ -51,11 +53,13 @@ public class AreaEffectCloudEntity extends Entity { metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue()); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue()); } else if (entityMetadata.getId() == 8) { - metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue()); + metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue()); } else if (entityMetadata.getId() == 10) { Particle particle = (Particle) entityMetadata.getValue(); int particleId = EffectRegistry.getParticleId(particle.getType()); - metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); + if (particleId != -1) { + metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId); + } } super.updateBedrockMetadata(entityMetadata, session); } From 191777773c5da59e4752e86dfeed202517af4a19 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Tue, 13 Oct 2020 16:44:47 +0200 Subject: [PATCH 024/161] Don't use wrapper objects for positions in ChunkCache (#1398) * make ChunkPosition use a hashCode implementation with far better hash distribution this should improve the performance when used as a hash table key * ChunkCache no longer uses position wrapper objects this yields a roughly 15-20% increase in performance when converting chunk data * fix code style issues --- .../network/session/cache/ChunkCache.java | 87 ++++++++++--------- .../java/world/JavaUnloadChunkTranslator.java | 3 +- .../translators/world/GeyserWorldManager.java | 6 +- .../world/chunk/ChunkPosition.java | 30 ++++++- .../geysermc/connector/utils/ChunkUtils.java | 5 +- 5 files changed, 79 insertions(+), 52 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 2cc9ea13..14825b71 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -27,22 +27,18 @@ package org.geysermc.connector.network.session.cache; 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 lombok.Getter; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; -import java.util.HashMap; -import java.util.Map; - public class ChunkCache { private final boolean cache; - @Getter - private final Map chunks = new HashMap<>(); + private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); public ChunkCache(GeyserSession session) { if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { @@ -56,57 +52,66 @@ public class ChunkCache { if (!cache) { return; } - ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ()); - if (chunk.getBiomeData() == null && chunks.containsKey(position)) { - Column newColumn = chunk; - chunk = chunks.get(position); - for (int i = 0; i < newColumn.getChunks().length; i++) { - if (newColumn.getChunks()[i] != null) { - chunk.getChunks()[i] = newColumn.getChunks()[i]; + + long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ()); + Column existingChunk; + if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk + && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing + for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away + if (chunk.getChunks()[i] != null) { + existingChunk.getChunks()[i] = chunk.getChunks()[i]; } } - } - chunks.put(position, chunk); - } - - public void updateBlock(Position position, int block) { - if (!cache) { - return; - } - ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); - if (!chunks.containsKey(chunkPosition)) - return; - - Column column = chunks.get(chunkPosition); - Chunk chunk = column.getChunks()[position.getY() >> 4]; - Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ()); - if (chunk != null) { - chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block); + } else { + chunks.put(chunkPosition, chunk); } } - public int getBlockAt(Position position) { + public Column getChunk(int chunkX, int chunkZ) { + long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + return chunks.getOrDefault(chunkPosition, null); + } + + public void updateBlock(int x, int y, int z, int block) { + if (!cache) { + return; + } + + Column column = this.getChunk(x >> 4, z >> 4); + if (column == null) { + return; + } + + Chunk chunk = column.getChunks()[y >> 4]; + if (chunk != null) { + chunk.set(x & 0xF, y & 0xF, z & 0xF, block); + } + } + + public int getBlockAt(int x, int y, int z) { if (!cache) { return BlockTranslator.AIR; } - ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4); - if (!chunks.containsKey(chunkPosition)) - return BlockTranslator.AIR; - Column column = chunks.get(chunkPosition); - Chunk chunk = column.getChunks()[position.getY() >> 4]; - Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ()); + Column column = this.getChunk(x >> 4, z >> 4); + if (column == null) { + return BlockTranslator.AIR; + } + + Chunk chunk = column.getChunks()[y >> 4]; if (chunk != null) { - return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); + return chunk.get(x & 0xF, y & 0xF, z & 0xF); } return BlockTranslator.AIR; } - public void removeChunk(ChunkPosition position) { + public void removeChunk(int chunkX, int chunkZ) { if (!cache) { return; } - chunks.remove(position); + + long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + chunks.remove(chunkPosition); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java index d54d8b6a..652e1294 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java @@ -28,7 +28,6 @@ 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.network.translators.world.chunk.ChunkPosition; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket; @@ -37,6 +36,6 @@ public class JavaUnloadChunkTranslator extends PacketTranslator Date: Tue, 13 Oct 2020 17:11:52 +0200 Subject: [PATCH 025/161] fix some NPEs caused by race conditions in chunk conversion (#1396) * fix some NPEs caused by race conditions in chunk conversion tbh the whole session should be read-write locked for every operation * fix code style issues --- .../java/world/JavaChunkDataTranslator.java | 6 +++--- .../translators/world/GeyserWorldManager.java | 21 +++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) 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 6dae9b4d..ddd5e004 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 @@ -66,11 +66,11 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { + // Non-full chunks don't have all the chunk data, and Bedrock won't accept that + final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); ByteBuf byteBuf = Unpooled.buffer(32); ChunkSection[] sections = chunkData.sections; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 983e42b7..6972d77b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -25,12 +25,14 @@ package org.geysermc.connector.network.translators.world; +import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.ChunkCache; import org.geysermc.connector.utils.GameRule; public class GeyserWorldManager extends WorldManager { @@ -39,14 +41,25 @@ public class GeyserWorldManager extends WorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { - return session.getChunkCache().getBlockAt(x, y, z); + ChunkCache chunkCache = session.getChunkCache(); + if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously + return chunkCache.getBlockAt(x, y, z); + } + return 0; } @Override public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (!session.getConnector().getConfig().isCacheChunks()) - return new int[1024]; - return session.getChunkCache().getChunk(x, z).getBiomeData(); + if (session.getConnector().getConfig().isCacheChunks()) { + ChunkCache chunkCache = session.getChunkCache(); + if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously + Column column = chunkCache.getChunk(x, z); + if (column != null) { // Column can be null if the server sent a partial chunk update before the first ground-up-continuous one + return column.getBiomeData(); + } + } + } + return new int[1024]; } @Override From 2ca2436cdc47a94dbf50e469eeeb33f99f6bb79c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 15 Oct 2020 05:34:24 +0200 Subject: [PATCH 026/161] Don't use the general thread pool to run an async method (#1397) * Don't use the general thread pool for an async method * Align nested class at the bottom --- .../player/JavaPlayerListEntryTranslator.java | 5 +- .../geysermc/connector/utils/SkinUtils.java | 268 ++++++++---------- 2 files changed, 128 insertions(+), 145 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java index 6a9ef4dc..da402f66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerListEntryTranslator.java @@ -57,9 +57,8 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator { - GeyserConnector.getInstance().getLogger().debug("Loading Local Bedrock Java Skin Data"); - }); + SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> + GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data")); } else { playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId()); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index fe2a8aa9..5505acdf 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -33,9 +33,8 @@ import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.connector.common.AuthType; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -73,21 +72,6 @@ public class SkinUtils { ); } - public static PlayerListPacket.Entry buildDefaultEntry(GeyserSession session, GameProfile profile, long geyserId) { - return buildEntryManually( - session, - profile.getId(), - profile.getName(), - geyserId, - "default", - SkinProvider.STEVE_SKIN, - SkinProvider.EMPTY_CAPE.getCapeId(), - SkinProvider.EMPTY_CAPE.getCapeData(), - SkinProvider.EMPTY_GEOMETRY.getGeometryName(), - SkinProvider.EMPTY_GEOMETRY.getGeometryData() - ); - } - public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, String skinId, byte[] skinData, String capeId, byte[] capeData, @@ -115,7 +99,7 @@ public class SkinUtils { } else { entry = new PlayerListPacket.Entry(uuid); } - + entry.setName(username); entry.setEntityId(geyserId); entry.setSkin(serializedSkin); @@ -126,12 +110,133 @@ public class SkinUtils { return entry; } + public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, + Consumer skinAndCapeConsumer) { + GameProfileData data = GameProfileData.from(entity.getProfile()); + + SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) + .whenCompleteAsync((skinAndCape, throwable) -> { + try { + SkinProvider.Skin skin = skinAndCape.getSkin(); + SkinProvider.Cape cape = skinAndCape.getCape(); + + if (cape.isFailed()) { + cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( + entity.getUuid(), false + ), SkinProvider.EMPTY_CAPE, 3); + } + + if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { + cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( + cape, entity.getUuid(), + entity.getUsername(), false + ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); + } + + SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); + geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( + geometry, entity.getUuid(), false + ), geometry, 3); + + // Not a bedrock player check for ears + if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { + boolean isEars; + + // Its deadmau5, gotta support his skin :) + if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { + isEars = true; + } else { + // Get the ears texture for the player + skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( + skin, entity.getUuid(), entity.getUsername(), false + ), skin, 3); + + isEars = skin.isEars(); + } + + // Does the skin have an ears texture + if (isEars) { + // Get the new geometry + geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); + + // Store the skin and geometry for the ears + SkinProvider.storeEarSkin(entity.getUuid(), skin); + SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); + } + } + + entity.setLastSkinUpdate(skin.getRequestedOn()); + + if (session.getUpstream().isInitialized()) { + PlayerListPacket.Entry updatedEntry = buildEntryManually( + session, + entity.getUuid(), + entity.getUsername(), + entity.getGeyserId(), + skin.getTextureUrl(), + skin.getSkinData(), + cape.getCapeId(), + cape.getCapeData(), + geometry.getGeometryName(), + geometry.getGeometryData() + ); + + + PlayerListPacket playerAddPacket = new PlayerListPacket(); + playerAddPacket.setAction(PlayerListPacket.Action.ADD); + playerAddPacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerAddPacket); + + if (!entity.isPlayerList()) { + PlayerListPacket playerRemovePacket = new PlayerListPacket(); + playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); + playerRemovePacket.getEntries().add(updatedEntry); + session.sendUpstreamPacket(playerRemovePacket); + + } + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); + } + + if (skinAndCapeConsumer != null) { + skinAndCapeConsumer.accept(skinAndCape); + } + }); + } + + public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); + + try { + byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes(StandardCharsets.UTF_8)); + byte[] capeBytes = clientData.getCapeData(); + + byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes(StandardCharsets.UTF_8)); + byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes(StandardCharsets.UTF_8)); + + if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { + SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); + SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); + } else { + GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); + GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); + } + + if (!clientData.getCapeId().equals("")) { + SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); + } + } catch (Exception e) { + throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); + } + } + @AllArgsConstructor @Getter public static class GameProfileData { - private String skinUrl; - private String capeUrl; - private boolean alex; + private final String skinUrl; + private final String capeUrl; + private final boolean alex; /** * Generate the GameProfileData from the given GameProfile @@ -170,125 +275,4 @@ public class SkinUtils { } } } - - public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, - Consumer skinAndCapeConsumer) { - GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> { - GameProfileData data = GameProfileData.from(entity.getProfile()); - - SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) - .whenCompleteAsync((skinAndCape, throwable) -> { - try { - SkinProvider.Skin skin = skinAndCape.getSkin(); - SkinProvider.Cape cape = skinAndCape.getCape(); - - if (cape.isFailed()) { - cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape( - entity.getUuid(), false - ), SkinProvider.EMPTY_CAPE, 3); - } - - if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) { - cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( - cape, entity.getUuid(), - entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); - } - - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex()); - geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry( - geometry, entity.getUuid(), false - ), geometry, 3); - - // Not a bedrock player check for ears - if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) { - boolean isEars = false; - - // Its deadmau5, gotta support his skin :) - if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) { - isEars = true; - } else { - // Get the ears texture for the player - skin = SkinProvider.getOrDefault(SkinProvider.requestUnofficialEars( - skin, entity.getUuid(), entity.getUsername(), false - ), skin, 3); - - isEars = skin.isEars(); - } - - // Does the skin have an ears texture - if (isEars) { - // Get the new geometry - geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); - - // Store the skin and geometry for the ears - SkinProvider.storeEarSkin(entity.getUuid(), skin); - SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); - } - } - - entity.setLastSkinUpdate(skin.getRequestedOn()); - - if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = buildEntryManually( - session, - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData(), - cape.getCapeId(), - cape.getCapeData(), - geometry.getGeometryName(), - geometry.getGeometryData() - ); - - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - - if (!entity.isPlayerList()) { - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); - - } - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); - } - - if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); - }); - }); - } - - public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.register", playerEntity.getUsername(), playerEntity.getUuid())); - - try { - byte[] skinBytes = Base64.getDecoder().decode(clientData.getSkinData().getBytes("UTF-8")); - byte[] capeBytes = clientData.getCapeData(); - - byte[] geometryNameBytes = Base64.getDecoder().decode(clientData.getGeometryName().getBytes("UTF-8")); - byte[] geometryBytes = Base64.getDecoder().decode(clientData.getGeometryData().getBytes("UTF-8")); - - if (skinBytes.length <= (128 * 128 * 4) && !clientData.isPersonaSkin()) { - SkinProvider.storeBedrockSkin(playerEntity.getUuid(), clientData.getSkinId(), skinBytes); - SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes); - } else { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.skin.bedrock.fail", playerEntity.getUsername())); - GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight()); - } - - if (!clientData.getCapeId().equals("")) { - SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes); - } - } catch (Exception e) { - throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e); - } - } } From 40de801eb061f3ea6dbef2c8acf56f321b46174f Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Thu, 15 Oct 2020 01:21:12 -0500 Subject: [PATCH 027/161] Add sound when an arrow hits a player --- .../java/world/JavaNotifyClientTranslator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 493a7ca1..d7961dd9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -145,6 +145,14 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Thu, 15 Oct 2020 08:30:25 +0200 Subject: [PATCH 028/161] Faster chunk conversion (#1400) * BlockStorage is never used concurrently, no need to synchronize * initial, semi-functional, faster chunk conversion * faster chunk conversion works well for every situation except spigot * delete unused ChunkPosition class * preallocate and pool chunk encoding buffers * make it work correctly on spigot * make field naming more consistent * attempt to upgrade to latest MCProtocolLib * remove debug code * compile against my MCProtocolLib fork while i wait for my upstream PR to be accepted * return to Steveice10 MCProtocolLib --- .../world/GeyserSpigotWorldManager.java | 63 +++++- connector/pom.xml | 2 +- .../network/session/cache/ChunkCache.java | 18 +- .../java/world/JavaChunkDataTranslator.java | 81 ++++--- .../translators/world/GeyserWorldManager.java | 26 +++ .../translators/world/WorldManager.java | 22 ++ .../translators/world/chunk/BlockStorage.java | 26 ++- .../world/chunk/ChunkPosition.java | 79 ------- .../translators/world/chunk/ChunkSection.java | 65 +----- .../world/chunk/bitarray/BitArrayVersion.java | 18 +- .../geysermc/connector/utils/ChunkUtils.java | 213 +++++++++++++----- .../geysermc/connector/utils/MathUtils.java | 11 + 12 files changed, 371 insertions(+), 253 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index c6443bd0..8a92526f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -26,12 +26,14 @@ package org.geysermc.platform.spigot.world; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.bukkit.entity.Player; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.GeyserWorldManager; @@ -93,23 +95,32 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { - if (session.getPlayerEntity() == null) { - return BlockTranslator.AIR; - } - if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) { + Player bukkitPlayer; + if ((this.isLegacy && !this.isViaVersion) + || session.getPlayerEntity() == null + || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return BlockTranslator.AIR; } + World world = bukkitPlayer.getWorld(); if (isLegacy) { - return getLegacyBlock(session, x, y, z, isViaVersion); + return getLegacyBlock(session, x, y, z, true); } //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0); + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); + } + + public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { + if (isViaVersion) { + return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true); + } else { + return BlockTranslator.AIR; + } } @SuppressWarnings("deprecation") - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { + public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) { if (isViaVersion) { - Block block = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z); + Block block = world.getBlockAt(x, y, z); // Black magic that gets the old block state ID int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 @@ -124,6 +135,42 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } } + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + Player bukkitPlayer; + if ((this.isLegacy && !this.isViaVersion) + || session.getPlayerEntity() == null + || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + return; + } + World world = bukkitPlayer.getWorld(); + if (this.isLegacy) { + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true)); + } + } + } + } else { + //TODO: see above TODO in getBlockAt + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0); + chunk.set(blockX, blockY, blockZ, id); + } + } + } + } + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + return true; + } + @Override @SuppressWarnings("deprecation") public int[] getBiomeDataAt(GeyserSession session, int x, int z) { diff --git a/connector/pom.xml b/connector/pom.xml index 741acee5..5df52556 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 976c2d0f89 + 3a69a0614c compile diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 14825b71..7bf84b8d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -32,7 +32,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; +import org.geysermc.connector.utils.MathUtils; public class ChunkCache { @@ -48,27 +48,31 @@ public class ChunkCache { } } - public void addToCache(Column chunk) { + public Column addToCache(Column chunk) { if (!cache) { - return; + return chunk; } - long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ()); + long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()); Column existingChunk; - if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk + if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing + boolean changed = false; for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away if (chunk.getChunks()[i] != null) { existingChunk.getChunks()[i] = chunk.getChunks()[i]; + changed = true; } } + return changed ? existingChunk : null; } else { chunks.put(chunkPosition, chunk); + return chunk; } } public Column getChunk(int chunkX, int chunkZ) { - long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); return chunks.getOrDefault(chunkPosition, null); } @@ -111,7 +115,7 @@ public class ChunkCache { return; } - long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ); + long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ); chunks.remove(chunkPosition); } } 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 ddd5e004..cd1a321c 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 @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; @@ -32,8 +33,8 @@ import com.nukkitx.nbt.NbtUtils; import com.nukkitx.network.VarInts; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.Unpooled; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; @@ -66,57 +67,69 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { - // Non-full chunks don't have all the chunk data, and Bedrock won't accept that - final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null); - - ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk); - ByteBuf byteBuf = Unpooled.buffer(32); - ChunkSection[] sections = chunkData.sections; + ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk); + ChunkSection[] sections = chunkData.getSections(); + // Find highest section int sectionCount = sections.length - 1; - while (sectionCount >= 0 && sections[sectionCount].isEmpty()) { + while (sectionCount >= 0 && sections[sectionCount] == null) { sectionCount--; } sectionCount++; + // Estimate chunk size + int size = 0; for (int i = 0; i < sectionCount; i++) { - ChunkSection section = chunkData.sections[i]; - section.writeToNetwork(byteBuf); + ChunkSection section = sections[i]; + size += (section != null ? section : ChunkUtils.EMPTY_SECTION).estimateNetworkSize(); } + size += 256; // Biomes + size += 1; // Border blocks + size += 1; // Extra data length (always 0) + size += chunkData.getBlockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity - byte[] bedrockBiome; - if (packet.getColumn().getBiomeData() == null) { - bedrockBiome = BiomeTranslator.toBedrockBiome(session.getConnector().getWorldManager().getBiomeDataAt(session, packet.getColumn().getX(), packet.getColumn().getZ())); - } else { - bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData()); + // Allocate output buffer + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size); + byte[] payload; + try { + for (int i = 0; i < sectionCount; i++) { + ChunkSection section = sections[i]; + (section != null ? section : ChunkUtils.EMPTY_SECTION).writeToNetwork(byteBuf); + } + + byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes + byteBuf.writeByte(0); // Border blocks - Edu edition only + VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now + + // Encode tile entities into buffer + NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf)); + for (NbtMap blockEntity : chunkData.getBlockEntities()) { + nbtStream.writeTag(blockEntity); + } + + // Copy data into byte[], because the protocol lib really likes things that are s l o w + byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]); + } finally { + byteBuf.release(); // Release buffer to allow buffer pooling to be useful } - byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes - byteBuf.writeByte(0); // Border blocks - Edu edition only - VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now - - ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer()); - NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream); - for (NbtMap blockEntity : chunkData.getBlockEntities()) { - nbtStream.writeTag(blockEntity); - } - - byteBuf.writeBytes(stream.buffer()); - - 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.setChunkX(mergedColumn.getX()); + levelChunkPacket.setChunkZ(mergedColumn.getZ()); levelChunkPacket.setData(payload); session.sendUpstreamPacket(levelChunkPacket); - - session.getChunkCache().addToCache(packet.getColumn()); } catch (Exception ex) { ex.printStackTrace(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 6972d77b..2ab3c010 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.world; +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.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; @@ -48,6 +49,31 @@ public class GeyserWorldManager extends WorldManager { return 0; } + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + ChunkCache chunkCache = session.getChunkCache(); + Column cachedColumn; + Chunk cachedChunk; + if (chunkCache == null || (cachedColumn = chunkCache.getChunk(x, z)) == null || (cachedChunk = cachedColumn.getChunks()[y]) == null) { + return; + } + + // Copy state IDs from cached chunk to output chunk + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + chunk.set(blockX, blockY, blockZ, cachedChunk.get(blockX, blockY, blockZ)); + } + } + } + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + // This implementation can only fetch data from the session chunk cache + return false; + } + @Override public int[] getBiomeDataAt(GeyserSession session, int x, int z) { if (session.getConnector().getConfig().isCacheChunks()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index ba78b8f6..fec3bb33 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.world; +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; @@ -74,6 +75,27 @@ public abstract class WorldManager { */ public abstract int getBlockAt(GeyserSession session, int x, int y, int z); + /** + * Gets all block states in the specified chunk section. + * + * @param session the session + * @param x the chunk's X coordinate + * @param y the chunk's Y coordinate + * @param z the chunk's Z coordinate + * @param section the chunk section to store the block data in + */ + public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section); + + /** + * Checks whether or not this world manager has access to more block data than the chunk cache. + *

+ * Some world managers (e.g. Spigot) can provide access to block data outside of the chunk cache, and even with chunk caching disabled. This + * method provides a means to check if this manager has this capability. + * + * @return whether or not this world manager has access to more block data than the chunk cache + */ + public abstract boolean hasMoreBlockDataThanChunkCache(); + /** * Gets the biome data for the specified chunk. * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 30edf178..d8cd7520 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -29,14 +29,16 @@ import com.nukkitx.network.VarInts; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; import java.util.function.IntConsumer; +@Getter public class BlockStorage { - private static final int SIZE = 4096; + public static final int SIZE = 4096; private final IntList palette; private BitArray bitArray; @@ -46,12 +48,12 @@ public class BlockStorage { } public BlockStorage(BitArrayVersion version) { - this.bitArray = version.createPalette(SIZE); + this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); this.palette.add(0); // Air is at the start of every palette. } - private BlockStorage(BitArray bitArray, IntArrayList palette) { + public BlockStorage(BitArray bitArray, IntList palette) { this.palette = palette; this.bitArray = bitArray; } @@ -64,16 +66,16 @@ public class BlockStorage { return BitArrayVersion.get(header >> 1, true); } - public synchronized int getFullBlock(int index) { + public int getFullBlock(int index) { return this.palette.getInt(this.bitArray.get(index)); } - public synchronized void setFullBlock(int index, int runtimeId) { + public void setFullBlock(int index, int runtimeId) { int idx = this.idFor(runtimeId); this.bitArray.set(index, idx); } - public synchronized void writeToNetwork(ByteBuf buffer) { + public void writeToNetwork(ByteBuf buffer) { buffer.writeByte(getPaletteHeader(bitArray.getVersion(), true)); for (int word : bitArray.getWords()) { @@ -84,8 +86,18 @@ public class BlockStorage { palette.forEach((IntConsumer) id -> VarInts.writeInt(buffer, id)); } + public int estimateNetworkSize() { + int size = 1; // Palette header + size += this.bitArray.getWords().length * 4; + + // We assume that none of the VarInts will be larger than 3 bytes + size += 3; // Palette size + size += this.palette.size() * 3; + return size; + } + private void onResize(BitArrayVersion version) { - BitArray newBitArray = version.createPalette(SIZE); + BitArray newBitArray = version.createArray(SIZE); for (int i = 0; i < SIZE; i++) { newBitArray.set(i, this.bitArray.get(i)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java deleted file mode 100644 index 9e721aa9..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkPosition.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.world.chunk; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -public class ChunkPosition { - - /** - * Packs a chunk's X and Z coordinates into a single {@code long}. - * - * @param x the X coordinate - * @param z the Z coordinate - * @return the packed coordinates - */ - public static long toLong(int x, int z) { - return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL); - } - - private int x; - private int z; - - public Position getBlock(int x, int y, int z) { - return new Position((this.x << 4) + x, y, (this.z << 4) + z); - } - - public Position getChunkBlock(int x, int y, int z) { - int chunkX = x & 15; - int chunkY = y & 15; - int chunkZ = z & 15; - return new Position(chunkX, chunkY, chunkZ); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof ChunkPosition) { - ChunkPosition chunkPosition = (ChunkPosition) obj; - return this.x == chunkPosition.x && this.z == chunkPosition.z; - } else { - return false; - } - } - - @Override - public int hashCode() { - return this.x * 2061811133 + this.z * 1424368303; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java index 48ec8806..979b79c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java @@ -27,42 +27,19 @@ package org.geysermc.connector.network.translators.world.chunk; import com.nukkitx.network.util.Preconditions; import io.netty.buffer.ByteBuf; -import lombok.Synchronized; public class ChunkSection { private static final int CHUNK_SECTION_VERSION = 8; - public static final int SIZE = 4096; private final BlockStorage[] storage; - private final NibbleArray blockLight; - private final NibbleArray skyLight; public ChunkSection() { - this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}, new NibbleArray(SIZE), - new NibbleArray(SIZE)); + this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}); } - public ChunkSection(BlockStorage[] blockStorage) { - this(blockStorage, new NibbleArray(SIZE), new NibbleArray(SIZE)); - } - - public ChunkSection(BlockStorage[] storage, byte[] blockLight, byte[] skyLight) { - Preconditions.checkNotNull(storage, "storage"); - Preconditions.checkArgument(storage.length > 1, "Block storage length must be at least 2"); - for (BlockStorage blockStorage : storage) { - Preconditions.checkNotNull(blockStorage, "storage"); - } - + public ChunkSection(BlockStorage[] storage) { this.storage = storage; - this.blockLight = new NibbleArray(blockLight); - this.skyLight = new NibbleArray(skyLight); - } - - private ChunkSection(BlockStorage[] storage, NibbleArray blockLight, NibbleArray skyLight) { - this.storage = storage; - this.blockLight = blockLight; - this.skyLight = skyLight; } public int getFullBlock(int x, int y, int z, int layer) { @@ -77,30 +54,6 @@ public class ChunkSection { this.storage[layer].setFullBlock(blockPosition(x, y, z), fullBlock); } - @Synchronized("skyLight") - public byte getSkyLight(int x, int y, int z) { - checkBounds(x, y, z); - return this.skyLight.get(blockPosition(x, y, z)); - } - - @Synchronized("skyLight") - public void setSkyLight(int x, int y, int z, byte val) { - checkBounds(x, y, z); - this.skyLight.set(blockPosition(x, y, z), val); - } - - @Synchronized("blockLight") - public byte getBlockLight(int x, int y, int z) { - checkBounds(x, y, z); - return this.blockLight.get(blockPosition(x, y, z)); - } - - @Synchronized("blockLight") - public void setBlockLight(int x, int y, int z, byte val) { - checkBounds(x, y, z); - this.blockLight.set(blockPosition(x, y, z), val); - } - public void writeToNetwork(ByteBuf buffer) { buffer.writeByte(CHUNK_SECTION_VERSION); buffer.writeByte(this.storage.length); @@ -109,12 +62,12 @@ public class ChunkSection { } } - public NibbleArray getSkyLightArray() { - return skyLight; - } - - public NibbleArray getBlockLightArray() { - return blockLight; + public int estimateNetworkSize() { + int size = 2; // Version + storage count + for (BlockStorage blockStorage : this.storage) { + size += blockStorage.estimateNetworkSize(); + } + return size; } public BlockStorage[] getBlockStorageArray() { @@ -135,7 +88,7 @@ public class ChunkSection { for (int i = 0; i < storage.length; i++) { storage[i] = this.storage[i].copy(); } - return new ChunkSection(storage, skyLight.copy(), blockLight.copy()); + return new ChunkSection(storage); } public static int blockPosition(int x, int y, int z) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java index 20fa849c..47a73f7c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java @@ -37,6 +37,8 @@ public enum BitArrayVersion { V2(2, 16, V3), V1(1, 32, V2); + private static final BitArrayVersion[] VALUES = values(); + final byte bits; final byte entriesPerWord; final int maxEntryValue; @@ -58,8 +60,14 @@ public enum BitArrayVersion { throw new IllegalArgumentException("Invalid palette version: " + version); } - public BitArray createPalette(int size) { - return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]); + public static BitArrayVersion forBitsCeil(int bits) { + for (int i = VALUES.length - 1; i >= 0; i--) { + BitArrayVersion version = VALUES[i]; + if (version.bits >= bits) { + return version; + } + } + return null; } public byte getId() { @@ -78,7 +86,11 @@ public enum BitArrayVersion { return next; } - public BitArray createPalette(int size, int[] words) { + public BitArray createArray(int size) { + return this.createArray(size, new int[MathUtils.ceil((float) size / entriesPerWord)]); + } + + public BitArray createArray(int size, int[] words) { if (this == V3 || this == V5 || this == V6) { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); 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 d47584e9..a63eeb42 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -25,8 +25,10 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; 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.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; @@ -36,27 +38,39 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; +import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import lombok.Getter; +import lombok.Data; +import lombok.experimental.UtilityClass; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.entity.*; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.world.chunk.ChunkPosition; +import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; +import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; +import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState; +import org.geysermc.connector.network.translators.world.chunk.BlockStorage; import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; +import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +@UtilityClass public class ChunkUtils { /** @@ -67,6 +81,9 @@ public class ChunkUtils { private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; + public static final BlockStorage EMPTY_STORAGE = new BlockStorage(); + public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE }); + static { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size @@ -76,72 +93,144 @@ public class ChunkUtils { } EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - }catch (IOException e) { + } catch (IOException e) { throw new AssertionError("Unable to generate empty level chunk data"); } } - public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { - ChunkData chunkData = new ChunkData(); - Chunk[] chunks = column.getChunks(); - chunkData.sections = new ChunkSection[chunks.length]; + private static int indexYZXtoXZY(int yzx) { + return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); + } - CompoundTag[] blockEntities = column.getTileEntities(); - // Temporarily stores positions of BlockState values per chunk load - Object2IntMap blockEntityPositions = new Object2IntOpenHashMap<>(); + public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) { + Chunk[] javaSections = column.getChunks(); + ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities - ObjectArrayList bedrockOnlyBlockEntities = new ObjectArrayList<>(); + List bedrockOnlyBlockEntities = Collections.emptyList(); - for (int chunkY = 0; chunkY < chunks.length; chunkY++) { - chunkData.sections[chunkY] = new ChunkSection(); - Chunk chunk = chunks[chunkY]; + BitSet waterloggedPaletteIds = new BitSet(); + BitSet pistonOrFlowerPaletteIds = new BitSet(); - // Chunk is null and caching chunks is off or this isn't a non-full chunk - if (chunk == null && (!session.getConnector().getConfig().isCacheChunks() || !isNonFullChunk)) + boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasMoreBlockDataThanChunkCache(); + + // If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager + boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache; + Chunk temporarySection = null; + + for (int sectionY = 0; sectionY < javaSections.length; sectionY++) { + Chunk javaSection = javaSections[sectionY]; + + // Section is null, the cache will not contain anything of use + if (javaSection == null) { + // The column parameter contains all data currently available from the cache. If the chunk is null and the world manager + // reports the ability to access more data than the cache, attempt to fetch from the world manager instead. + if (shouldCheckWorldManagerOnMissingSections) { + // Ensure that temporary chunk is set + if (temporarySection == null) { + temporarySection = new Chunk(); + } + + // Read block data in section + session.getConnector().getWorldManager().getBlocksInSection(session, column.getX(), sectionY, column.getZ(), temporarySection); + + if (temporarySection.isEmpty()) { + // The world manager only contains air for the given section + // We can leave temporarySection as-is to allow it to potentially be re-used for later sections + continue; + } else { + javaSection = temporarySection; + + // Section contents have been modified, we can't re-use it + temporarySection = null; + } + } else { + continue; + } + } + + // No need to encode an empty section... + if (javaSection.isEmpty()) { continue; + } - // If chunk is empty then no need to process - if (chunk != null && chunk.isEmpty()) - continue; + Palette javaPalette = javaSection.getPalette(); + IntList bedrockPalette = new IntArrayList(javaPalette.size()); + waterloggedPaletteIds.clear(); + pistonOrFlowerPaletteIds.clear(); - ChunkSection section = chunkData.sections[chunkY]; - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - int blockState; - // If a non-full chunk, then grab the block that should be here to create a 'full' chunk - if (chunk == null) { - blockState = session.getConnector().getWorldManager().getBlockAt(session, (column.getX() << 4) + x, (chunkY << 4) + y, (column.getZ() << 4) + z); - } else { - blockState = chunk.get(x, y, z); - } - int id = BlockTranslator.getBedrockBlockId(blockState); + // Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go + for (int i = 0; i < javaPalette.size(); i++) { + int javaId = javaPalette.idToState(i); + bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId)); - // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently - if (BlockTranslator.getBlockEntityString(blockState) != null) { - Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); - blockEntityPositions.put(pos, blockState); - } + if (BlockTranslator.isWaterlogged(javaId)) { + waterloggedPaletteIds.set(i); + } - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { + pistonOrFlowerPaletteIds.set(i); + } + } - // Check if block is piston or flower - only block entities in Bedrock - if (BlockStateValues.getFlowerPotValues().containsKey(blockState) || - BlockStateValues.getPistonValues().containsKey(blockState)) { - Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(Vector3i.from(pos.getX(), pos.getY(), pos.getZ()), blockState)); - } + BitStorage javaData = javaSection.getStorage(); - if (BlockTranslator.isWaterlogged(blockState)) { - section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID); - } + // Add Bedrock-exclusive block entities + // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data + // for no reason, as most sections will not contain any pistons or flower pots + if (!pistonOrFlowerPaletteIds.isEmpty()) { + bedrockOnlyBlockEntities = new ArrayList<>(); + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int paletteId = javaData.get(yzx); + if (pistonOrFlowerPaletteIds.get(paletteId)) { + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), + javaPalette.idToState(paletteId) + )); } } } + BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE); + BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette); + BlockStorage[] layers; + + // Convert data array from YZX to XZY coordinate order + if (waterloggedPaletteIds.isEmpty()) { + // No blocks are waterlogged, simply convert coordinate order + // This could probably be optimized further... + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx)); + } + + layers = new BlockStorage[]{ layer0 }; + } else { + // The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for + // layer 1 with palette ID 1 indicating water + int[] layer1Data = new int[BlockStorage.SIZE >> 5]; + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int paletteId = javaData.get(yzx); + int xzy = indexYZXtoXZY(yzx); + bedrockData.set(xzy, paletteId); + + if (waterloggedPaletteIds.get(paletteId)) { + layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F); + } + } + + // V1 palette + IntList layer1Palette = new IntArrayList(2); + layer1Palette.add(0); // Air + layer1Palette.add(BEDROCK_WATER_ID); + + layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; + } + + sections[sectionY] = new ChunkSection(layers); } + CompoundTag[] blockEntities = column.getTileEntities(); NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()]; int i = 0; while (i < blockEntities.length) { @@ -155,7 +244,7 @@ public class ChunkUtils { for (Tag subTag : tag) { if (subTag instanceof StringTag) { StringTag stringTag = (StringTag) subTag; - if (stringTag.getValue().equals("")) { + if (stringTag.getValue().isEmpty()) { tagName = stringTag.getName(); break; } @@ -169,17 +258,25 @@ public class ChunkUtils { String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); - int blockState = blockEntityPositions.getOrDefault(pos, 0); + + // Get Java blockstate ID from block entity position + int blockState = 0; + Chunk section = column.getChunks()[pos.getY() >> 4]; + if (section != null) { + blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); + } + bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); i++; } + + // Append Bedrock-exclusive block entities to output array for (NbtMap tag : bedrockOnlyBlockEntities) { bedrockBlockEntities[i] = tag; i++; } - chunkData.blockEntities = bedrockBlockEntities; - return chunkData; + return new ChunkData(sections, bedrockBlockEntities); } public static void updateChunkPosition(GeyserSession session, Vector3i position) { @@ -277,10 +374,10 @@ public class ChunkUtils { } } + @Data public static final class ChunkData { - public ChunkSection[] sections; + private final ChunkSection[] sections; - @Getter - private NbtMap[] blockEntities = new NbtMap[0]; + private final NbtMap[] blockEntities; } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java index 29dd2cc2..3ce4fea8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MathUtils.java @@ -74,4 +74,15 @@ public class MathUtils { } return (Byte) value; } + + /** + * Packs a chunk's X and Z coordinates into a single {@code long}. + * + * @param x the X coordinate + * @param z the Z coordinate + * @return the packed coordinates + */ + public static long chunkPositionToLong(int x, int z) { + return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL); + } } From 64f223358183723c82eb30e3aaf076fbcdef530d Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Thu, 15 Oct 2020 01:54:05 -0500 Subject: [PATCH 029/161] Fix wolf collar color when it's no longer angry (Closes #1404) --- .../connector/entity/living/animal/tameable/WolfEntity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java index ab631ebe..6fe9e592 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/WolfEntity.java @@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession; public class WolfEntity extends TameableEntity { + private byte collarColor; + public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -57,12 +59,13 @@ public class WolfEntity extends TameableEntity { // Wolf collar color // Relies on EntityData.OWNER_EID being set in TameableEntity.java if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) { - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue()); } // Wolf anger (1.16+) if (entityMetadata.getId() == 20) { metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0); + metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor); } super.updateBedrockMetadata(entityMetadata, session); From ae70dbeece0094814b80ed2edd9e39add53b033d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:13:04 -0400 Subject: [PATCH 030/161] JavaOpenWindowTranslator: Use MessageUtils for inventory name (#1416) * JavaOpenWindowTranslator: Use MessageUtils for inventory name * Remove important messaging --- .../java/window/JavaOpenWindowTranslator.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 820639b3..099de317 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,11 +25,9 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -37,6 +35,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.connector.utils.MessageUtils; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -58,18 +57,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Sat, 17 Oct 2020 23:50:41 -0400 Subject: [PATCH 031/161] Fix mob mount positions (#1392) * Fix mob mount positions Uses offsets from Java Edition. * Fix Boat mount pos for multiple passengers and Fix Ravager Remove unnecessary horse metadata * Fix Minecart & Boat Mount Pos, Fix Player Height Offset * Use offset of EntityType.PLAYER * Add back metadata --- .../JavaEntitySetPassengersTranslator.java | 170 ++++++++++++++---- 1 file changed, 133 insertions(+), 37 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 094d64df..64f0e3e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -82,7 +82,6 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); rider = false; } @@ -90,6 +89,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator 1)); + this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1)); + } else { + this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1)); } // Force an update to the passenger metadata passenger.updateBedrockMetadata(session); } - if (entity.getEntityType() == EntityType.HORSE) { - entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f)); - entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); - - entity.updateBedrockMetadata(session); + switch (entity.getEntityType()) { + case HORSE: + case SKELETON_HORSE: + case DONKEY: + case MULE: + case RAVAGER: + entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f); + entity.updateBedrockMetadata(session); + break; } } - private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { - // Without the Y offset, Bedrock players will find themselves in the floor when mounting - float yOffset = 0; + private float getMountedHeightOffset(Entity mount) { + final EntityType mountType = mount.getEntityType(); + float mountedHeightOffset = mountType.getHeight() * 0.75f; switch (mountType) { - case BOAT: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f; - break; - case MINECART: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f; + case CHICKEN: + case SPIDER: + mountedHeightOffset = mountType.getHeight() * 0.5f; break; case DONKEY: - yOffset = 2.1f; - break; - case HORSE: - case SKELETON_HORSE: - case ZOMBIE_HORSE: case MULE: - yOffset = 2.3f; + mountedHeightOffset -= 0.25f; break; case LLAMA: - case TRADER_LLAMA: - yOffset = 2.5f; + mountedHeightOffset = mountType.getHeight() * 0.67f; break; - case PIG: - yOffset = 1.85001f; + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + mountedHeightOffset = 0; break; - case ARMOR_STAND: - yOffset = 1.3f; + case BOAT: + mountedHeightOffset = -0.1f; + break; + case HOGLIN: + case ZOGLIN: + boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY); + mountedHeightOffset = mountType.getHeight() - (isBaby ? 0.2f : 0.15f); + break; + case PIGLIN: + mountedHeightOffset = mountType.getHeight() * 0.92f; + break; + case RAVAGER: + mountedHeightOffset = 2.1f; + break; + case SKELETON_HORSE: + mountedHeightOffset -= 0.1875f; break; case STRIDER: - yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f; + mountedHeightOffset = mountType.getHeight() - 0.19f; break; } - Vector3f offset = Vector3f.from(0f, yOffset, 0f); - if (mountType == EntityType.STRIDER) { - offset = offset.add(0f, 0f, -0.2f); - } - // Without the X offset, more than one entity on a boat is stacked on top of each other - if (rider && moreThanOneEntity) { - offset = offset.add(Vector3f.from(0.2, 0, 0)); - } else if (moreThanOneEntity) { - offset = offset.add(Vector3f.from(-0.6, 0, 0)); + return mountedHeightOffset; + } + + private float getHeightOffset(Entity passenger) { + boolean isBaby; + switch (passenger.getEntityType()) { + case SKELETON: + case STRAY: + case WITHER_SKELETON: + return -0.6f; + case ARMOR_STAND: + // Armor stand isn't a marker + if (passenger.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT) != 0.0f) { + return 0.1f; + } else { + return 0.0f; + } + case ENDERMITE: + case SILVERFISH: + return 0.1f; + case PIGLIN: + case PIGLIN_BRUTE: + case ZOMBIFIED_PIGLIN: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? -0.05f : -0.45f; + case ZOMBIE: + isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY); + return isBaby ? 0.0f : -0.45f; + case EVOKER: + case ILLUSIONER: + case PILLAGER: + case RAVAGER: + case VINDICATOR: + case WITCH: + return -0.45f; + case PLAYER: + return -0.35f; } + return 0f; + } + + private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) { passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding); if (riding) { + // Without the Y offset, Bedrock players will find themselves in the floor when mounting + float mountedHeightOffset = getMountedHeightOffset(mount); + float heightOffset = getHeightOffset(passenger); + + float xOffset = 0; + float yOffset = mountedHeightOffset + heightOffset; + float zOffset = 0; + switch (mount.getEntityType()) { + case BOAT: + // Without the X offset, more than one entity on a boat is stacked on top of each other + if (rider && moreThanOneEntity) { + xOffset = 0.2f; + } else if (moreThanOneEntity) { + xOffset = -0.6f; + } + break; + case CHICKEN: + zOffset = -0.1f; + break; + case LLAMA: + zOffset = -0.3f; + break; + } + /* + * Bedrock Differences + * Zoglin & Hoglin seem to be taller in Bedrock edition + * Horses are tinier + * Players, Minecarts, and Boats have different origins + */ + if (passenger.getEntityType() == EntityType.PLAYER) { + yOffset += EntityType.PLAYER.getOffset(); + } + switch (mount.getEntityType()) { + case MINECART: + case MINECART_HOPPER: + case MINECART_TNT: + case MINECART_CHEST: + case MINECART_FURNACE: + case MINECART_SPAWNER: + case MINECART_COMMAND_BLOCK: + case BOAT: + yOffset -= mount.getEntityType().getHeight() * 0.5f; + } + Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset); passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset); } passenger.updateBedrockMetadata(session); From 0635605a24448ccec5b0498275a03038ba073025 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Sun, 18 Oct 2020 16:59:37 +0200 Subject: [PATCH 032/161] fix chunk section decoding (#1418) * fix chunk section decoding * switch back to official MCProtocolLib --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5df52556..cf2b8994 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 3a69a0614c + 8270ec65e3 compile From 45429a9357657024da2c914cb57220fe14ee650f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:29:11 -0400 Subject: [PATCH 033/161] SettingsUtils: fix 'show coordinates' setting persistence (#1429) The boolean that toggled this was accidentally in the wrong spot. --- .../main/java/org/geysermc/connector/utils/SettingsUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 89e9fe67..13db4682 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -58,7 +58,7 @@ public class SettingsUtils { builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates()))); + builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language), session.getWorldCache().isShowCoordinates())); if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { From 18e2a52d98dd833a362730ceb966494a4a2e5298 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Mon, 19 Oct 2020 10:43:51 +0200 Subject: [PATCH 034/161] fix decoding sections with duplicate palette entries (#1430) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index cf2b8994..8615ad80 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 8270ec65e3 + 1b01b1ffef compile From b02bc33393788c59d30a8ab30f229bee07aaac12 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:03:31 -0400 Subject: [PATCH 035/161] GeyserSession: Set a default value for attackSpeed (#1419) Fixes cooldowns not showing on a fresh world. --- .../org/geysermc/connector/network/session/GeyserSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1a0bbfb2..79949a5f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -194,9 +194,10 @@ public class GeyserSession implements CommandSender { /** * The current attack speed of the player. Used for sending proper cooldown timings. + * Setting a default fixes cooldowns not showing up on a fresh world. */ @Setter - private double attackSpeed; + private double attackSpeed = 4.0d; /** * The time of the last hit. Used to gauge how long the cooldown is taking. * This is a session variable in order to prevent more scheduled threads than necessary. From 7f5fac38c6466b1ad33e85def64dc9c245160135 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:09:16 -0400 Subject: [PATCH 036/161] Update to Adventure 4.1.1 (#1410) * Update to Adventure 4.0.0 * Update to 4.0.1 * Update again, I guess. --- connector/pom.xml | 12 ++++++------ .../item/translators/nbt/BasicItemTranslator.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8615ad80..19731987 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -137,21 +137,21 @@ 2.1.3 - com.github.kyoripowered.adventure + net.kyori adventure-api - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-gson - 557865caef + 4.1.1 compile - com.github.kyoripowered.adventure + net.kyori adventure-text-serializer-legacy - 557865caef + 4.1.1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index e4556626..1d21bbfb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -101,7 +101,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { if (message.startsWith("§r")) { message = message.replaceFirst("§r", ""); } - Component component = TextComponent.of(message); + Component component = Component.text(message); return GsonComponentSerializer.gson().serialize(component); } From 62d984da611b6cada4a6813a0f2655001e2a3fc8 Mon Sep 17 00:00:00 2001 From: Redned Date: Mon, 19 Oct 2020 20:56:01 -0500 Subject: [PATCH 037/161] Make userAuths information more clear --- connector/src/main/resources/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 0602bb54..43e3e8ed 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -38,18 +38,18 @@ remote: # You can ignore this when not using Floodgate. floodgate-key-file: public-key.pem -## the Xbox/MCPE username is the key for the Java server auth-info -## this allows automatic configuration/login to the remote Java server -## if you are brave/stupid enough to put your Mojang account info into -## a config file +# The Xbox/Minecraft Bedrock username is the key for the Java server auth-info. +# This allows automatic configuration/login to the remote Java server. +# If you are brave enough to put your Mojang account info into a config file. +# Uncomment the lines below to enable this feature. #userAuths: -# bluerkelp2: # MCPE/Xbox username -# email: not_really_my_email_address_mr_minecrafter53267@gmail.com # Mojang account email address -# password: "this isn't really my password" +# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username +# email: javaccountemail@example.com # Your Minecraft: Java Edition email +# password: javaccountpassword123 # Your Minecraft: Java Edition password # -# herpderp40300499303040503030300500293858393589: -# email: herpderp@derpherp.com -# password: dooooo +# bluerkelp2: +# email: not_really_my_email_address_mr_minecrafter53267@gmail.com +# password: "this isn't really my password" # Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands. # Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients. From 7f2b2e09133efa004964e807cd8e5a8a996335e2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 23 Oct 2020 05:01:03 +0100 Subject: [PATCH 038/161] Bedrock <-> Bedrock skin display fix (#1195) * Implement partial bedrock skin fix * Fix equals method * Fix ViaVersion Co-authored-by: DoctorMacc --- .../connector/utils/SkinProvider.java | 22 ++++++++++++------- .../geysermc/connector/utils/SkinUtils.java | 12 +++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 7c30e48a..82fc3a3a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -33,6 +33,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import javax.imageio.ImageIO; import java.awt.*; @@ -42,13 +43,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.concurrent.*; public class SkinProvider { @@ -157,10 +152,21 @@ public class SkinProvider { public static CompletableFuture requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + String newSkinUrl = skinUrl; + + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + // TODO: Don't have a for loop for this? Have a proper map? + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(playerId)) { + newSkinUrl = session.getClientData().getSkinId(); + break; + } + } + } CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( - getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), + getOrDefault(requestSkin(playerId, newSkinUrl, false), EMPTY_SKIN, 5), getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 5505acdf..d65dbc81 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -251,6 +251,7 @@ public class SkinUtils { try { GameProfile.Property skinProperty = profile.getProperty("textures"); + // TODO: Remove try/catch here JsonNode skinObject = new ObjectMapper().readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8)); JsonNode textures = skinObject.get("textures"); @@ -271,7 +272,16 @@ public class SkinUtils { GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); } // return default skin with default cape when texture data is invalid - return new GameProfileData((isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl()), SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); + String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); + if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { + if (session.getPlayerEntity().getUuid().equals(profile.getId())) { + skinUrl = session.getClientData().getSkinId(); + break; + } + } + } + return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex); } } } From ee8c718c621be0b6c3cde594a7b45004fd70e2e9 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:25:24 -0500 Subject: [PATCH 039/161] Translate emote list packet --- .../network/session/GeyserSession.java | 24 +++++++++++ .../bedrock/BedrockEmoteListTranslator.java | 40 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 79949a5f..6b0f73ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -275,6 +275,10 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + @Setter + private List selectedEmotes = new ArrayList<>(); + private final Set emotes = new HashSet<>(); + private MinecraftProtocol protocol; public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { @@ -295,6 +299,8 @@ public class GeyserSession implements CommandSender { this.inventoryCache.getInventories().put(0, inventory); + connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); + bedrockServerSession.addDisconnectHandler(disconnectReason -> { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); @@ -769,4 +775,22 @@ public class GeyserSession implements CommandSender { adventureSettingsPacket.getSettings().addAll(flags); sendUpstreamPacket(adventureSettingsPacket); } + + public void refreshEmotes(List emotes) { + this.selectedEmotes = emotes; + this.emotes.addAll(emotes); + for (GeyserSession player : connector.getPlayers()) { + List pieces = new ArrayList<>(); + for (UUID piece : emotes) { + if (!player.getEmotes().contains(piece)) { + this.emotes.add(piece); + } + pieces.add(piece); + } + EmoteListPacket emoteList = new EmoteListPacket(); + emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); + emoteList.getPieceIds().addAll(pieces); + player.sendUpstreamPacket(emoteList); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java new file mode 100644 index 00000000..ef5b1a56 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEmoteListTranslator.java @@ -0,0 +1,40 @@ +/* + * 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.bedrock; + +import com.nukkitx.protocol.bedrock.packet.EmoteListPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = EmoteListPacket.class) +public class BedrockEmoteListTranslator extends PacketTranslator { + + @Override + public void translate(EmoteListPacket packet, GeyserSession session) { + session.refreshEmotes(packet.getPieceIds()); + } +} From dfba278f4d16acdef839af431645bcb70182c142 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Fri, 23 Oct 2020 01:36:34 -0500 Subject: [PATCH 040/161] Use correct methods in refreshEmotes --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6b0f73ae..bb7602f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -783,9 +783,9 @@ public class GeyserSession implements CommandSender { List pieces = new ArrayList<>(); for (UUID piece : emotes) { if (!player.getEmotes().contains(piece)) { - this.emotes.add(piece); + pieces.add(piece); } - pieces.add(piece); + player.getEmotes().add(piece); } EmoteListPacket emoteList = new EmoteListPacket(); emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId()); From c30cb78e74921c611f3babfdcc8f6b96aa449232 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 24 Oct 2020 23:33:49 +0100 Subject: [PATCH 041/161] Add statistics menu (#1424) * Add statistics menu * Changed back button text * Add check to make sure the player requested the statistics display * Better item translation support; misc changes * Clean up session getting? * Remove extra debug that is likely unnecessary * Remove unused function * Update languages submodule * Clean up javadoc comment * Fix typo Co-authored-by: DoctorMacc Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../common/window/CustomFormWindow.java | 4 +- .../common/window/SimpleFormWindow.java | 4 +- .../connector/command/CommandManager.java | 1 + .../command/defaults/StatisticsCommand.java | 69 ++++++ .../network/UpstreamPacketHandler.java | 5 + .../network/session/GeyserSession.java | 23 ++ .../java/JavaStatisticsTranslator.java | 46 ++++ .../world/block/BlockTranslator.java | 11 + .../connector/utils/StatisticsUtils.java | 233 ++++++++++++++++++ connector/src/main/resources/languages | 2 +- 10 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java index efc71ae8..045552b6 100644 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java @@ -92,7 +92,7 @@ public class CustomFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { + if (data == null || data.trim().equalsIgnoreCase("null") || data.isEmpty()) { closed = true; return; } @@ -108,7 +108,7 @@ public class CustomFormWindow extends FormWindow { List componentResponses = new ArrayList<>(); try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); + componentResponses = new ObjectMapper().readValue(data.trim(), new TypeReference>(){}); } catch (IOException e) { } for (String response : componentResponses) { diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java index 7c1acc26..3101f5fb 100644 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java @@ -72,14 +72,14 @@ public class SimpleFormWindow extends FormWindow { } public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { + if (data == null || data.trim().equalsIgnoreCase("null")) { closed = true; return; } int buttonID; try { - buttonID = Integer.parseInt(data); + buttonID = Integer.parseInt(data.trim()); } catch (Exception ex) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index afa75503..2b35424a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -52,6 +52,7 @@ public abstract class CommandManager { registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java new file mode 100644 index 00000000..ed9db58f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -0,0 +1,69 @@ +/* + * 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.command.defaults; + +import com.github.steveice10.mc.protocol.data.game.ClientRequest; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.network.session.GeyserSession; + +public class StatisticsCommand extends GeyserCommand { + + private final GeyserConnector connector; + + public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) { + super(name, description, permission); + + this.connector = connector; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (sender.isConsole()) { + return; + } + + // Make sure the sender is a Bedrock edition client + GeyserSession session = null; + if (sender instanceof GeyserSession) { + session = (GeyserSession) sender; + } else { + // Needed for Spigot - sender is not an instance of GeyserSession + for (GeyserSession otherSession : connector.getPlayers()) { + if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) { + session = otherSession; + break; + } + } + } + if (session == null) return; + session.setWaitingForStatistics(true); + ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); + session.sendDownstreamPacket(clientRequestPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f76d64ed..f99abbe5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.connector.utils.StatisticsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -141,6 +142,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ModalFormResponsePacket packet) { if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { return SettingsUtils.handleSettingsForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) { + return StatisticsUtils.handleMenuForm(session, packet.getFormData()); + } else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) { + return StatisticsUtils.handleListForm(session, packet.getFormData()); } return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bb7602f3..a8a4adb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -55,6 +56,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; @@ -275,6 +277,18 @@ public class GeyserSession implements CommandSender { @Setter private String lastSignMessage; + /** + * Stores a map of all statistics sent from the server. + * The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones. + */ + private final Map statistics = new HashMap<>(); + + /** + * Whether we're expecting statistics to be sent back to us. + */ + @Setter + private boolean waitingForStatistics = false; + @Setter private List selectedEmotes = new ArrayList<>(); private final Set emotes = new HashSet<>(); @@ -776,6 +790,15 @@ public class GeyserSession implements CommandSender { sendUpstreamPacket(adventureSettingsPacket); } + /** + * Used for updating statistic values since we only get changes from the server + * + * @param statistics Updated statistics values + */ + public void updateStatistics(@NonNull Map statistics) { + this.statistics.putAll(statistics); + } + public void refreshEmotes(List emotes) { this.selectedEmotes = emotes; this.emotes.addAll(emotes); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java new file mode 100644 index 00000000..9a80254b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -0,0 +1,46 @@ +/* + * 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.packet.ingame.server.ServerStatisticsPacket; +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.StatisticsUtils; + +@Translator(packet = ServerStatisticsPacket.class) +public class JavaStatisticsTranslator extends PacketTranslator { + + @Override + public void translate(ServerStatisticsPacket packet, GeyserSession session) { + session.updateStatistics(packet.getStatistics()); + + if (session.isWaitingForStatistics()) { + session.setWaitingForStatistics(false); + session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e5f8d6aa..5314292a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -67,6 +67,11 @@ public class BlockTranslator { public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + /** + * Java numeric ID to java unique identifier, used for block names in the statistics screen + */ + public static final Int2ObjectMap JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>(); + /** * Runtime command block ID, used for fixing command block minecart appearances */ @@ -124,6 +129,7 @@ public class BlockTranslator { int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; + int uniqueJavaId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -166,6 +172,11 @@ public class BlockTranslator { String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { + uniqueJavaId++; + JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); + } + if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java new file mode 100644 index 00000000..3c42182d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -0,0 +1,233 @@ +/* + * 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.github.steveice10.mc.protocol.data.MagicValues; +import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; +import com.github.steveice10.mc.protocol.data.game.statistic.*; +import org.geysermc.common.window.SimpleFormWindow; +import org.geysermc.common.window.button.FormButton; +import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +import java.util.Map; + +public class StatisticsUtils { + + // Used in UpstreamPacketHandler.java + public static final int STATISTICS_MENU_FORM_ID = 1339; + public static final int STATISTICS_LIST_FORM_ID = 1340; + + /** + * Build a form for the given session with all statistic categories + * + * @param session The session to build the form for + */ + public static SimpleFormWindow buildMenuForm(GeyserSession session) { + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + + return window; + } + + /** + * Handle the menu form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleMenuForm(GeyserSession session, String response) { + SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); + menuForm.setResponse(response); + SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + + // Cache the language for cleaner access + String language = session.getClientData().getLanguageCode(); + + if (formResponse != null && formResponse.getClickedButton() != null) { + String title; + StringBuilder content = new StringBuilder(); + + switch (formResponse.getClickedButtonId()) { + case 0: + title = LocaleUtils.getLocaleString("stat.generalButton", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 1: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + block = LocaleUtils.getLocaleString(block, language); + content.append(block + ": " + entry.getValue() + "\n"); + } + } + break; + case 2: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 3: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 4: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 5: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 6: + title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); + } + } + break; + case 7: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + case 8: + title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); + + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); + content.append(mob + ": " + entry.getValue() + "\n"); + } + } + break; + default: + return false; + } + + if (content.length() == 0) { + content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); + } + + SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); + window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); + session.sendForm(window, STATISTICS_LIST_FORM_ID); + } + + return true; + } + + /** + * Handle the list form response + * + * @param session The session that sent the response + * @param response The response string to parse + * @return True if the form was parsed correctly, false if not + */ + public static boolean handleListForm(GeyserSession session, String response) { + SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); + listForm.setResponse(response); + + if (!listForm.isClosed()) { + session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); + } + + return true; + } + + /** + * Finds the item translation key from the Java locale. + * + * @param item the namespaced item to search for. + * @param language the language to search in + * @return the full name of the item + */ + private static String getItemTranslateKey(String item, String language) { + item = item.replace("minecraft:", "item.minecraft."); + String translatedItem = LocaleUtils.getLocaleString(item, language); + if (translatedItem.equals(item)) { + // Didn't translate; must be a block + translatedItem = LocaleUtils.getLocaleString(item.replace("item.", "block."), language); + } + return translatedItem; + } +} diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 5f217922..a4125be9 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5f21792264a364e32425014e0be79db93593da1e +Subproject commit a4125be98fefea6cefd43dc52ccb2ade4e70573e From d93d4d0942381046f9c0e7d63060db7b6ff1c557 Mon Sep 17 00:00:00 2001 From: David Choo Date: Mon, 26 Oct 2020 11:54:37 -0400 Subject: [PATCH 042/161] Projectile fixes (#1451) * Predict the trajectory of projectiles and add particles * Correct lingering potion gravity * Update last position on move absolute * Clean up * Add egg to ItemRegistry and update mappings --- .../connector/entity/ThrowableEntity.java | 54 +++++++++++++++++++ .../translators/item/ItemRegistry.java | 7 +++ .../entity/JavaEntityStatusTranslator.java | 25 +++++++++ connector/src/main/resources/mappings | 2 +- 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index d0632d97..b3632606 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -26,11 +26,65 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class ThrowableEntity extends Entity { + private Vector3f lastPosition; + private ScheduledFuture positionUpdater; + public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + this.lastPosition = position; + } + + @Override + public void spawnEntity(GeyserSession session) { + super.spawnEntity(session); + positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + float gravity = 0.03f; // Snowball, Egg, and Ender Pearl + if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { + gravity = 0.05f; + } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { + gravity = 0.07f; + } + motion = motion.down(gravity); + } + }, 0, 50, TimeUnit.MILLISECONDS); + } + + @Override + public boolean despawnEntity(GeyserSession session) { + positionUpdater.cancel(true); + if (entityType == EntityType.THROWN_ENDERPEARL) { + LevelEventPacket particlePacket = new LevelEventPacket(); + particlePacket.setType(LevelEventType.PARTICLE_TELEPORT); + particlePacket.setPosition(position); + session.sendUpstreamPacket(particlePacket); + } + return super.despawnEntity(session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + position = lastPosition; + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + lastPosition = position; + } + + @Override + public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + lastPosition = position; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 850e4e05..597c87b2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -63,6 +63,10 @@ public class ItemRegistry { * Bucket item entry, used in BedrockInventoryTransactionTranslator.java */ public static ItemEntry BUCKET; + /** + * Egg item entry, used in JavaEntityStatusTranslator.java + */ + public static ItemEntry EGG; /** * Gold item entry, used in PiglinEntity.java */ @@ -141,6 +145,9 @@ public class ItemRegistry { case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:egg": + EGG = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:gold_ingot": GOLD = ITEM_ENTRIES.get(itemIndex); break; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java index c3fbd2e9..09d0eaa9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityStatusTranslator.java @@ -25,10 +25,16 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.connector.entity.Entity; @@ -36,6 +42,9 @@ 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.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; @Translator(packet = ServerEntityStatusPacket.class) public class JavaEntityStatusTranslator extends PacketTranslator { @@ -88,6 +97,22 @@ public class JavaEntityStatusTranslator extends PacketTranslator Date: Tue, 27 Oct 2020 11:02:25 +0000 Subject: [PATCH 043/161] Use new Open Collaboration maven repository --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index acfdc3d6..345933ac 100644 --- a/pom.xml +++ b/pom.xml @@ -42,8 +42,8 @@ https://jitpack.io - nukkitx-release-repo - https://repo.nukkitx.com/maven-releases/ + opencollab-release-repo + https://repo.opencollab.dev/maven-releases/ true @@ -52,8 +52,8 @@ - nukkitx-snapshot-repo - https://repo.nukkitx.com/maven-snapshots/ + opencollab-snapshot-repo + https://repo.opencollab.dev/maven-snapshots/ false @@ -74,13 +74,13 @@ releases - nukkitx-releases - https://repo.nukkitx.com/maven-releases + opencollab-releases + https://repo.opencollab.dev/maven-releases snapshots - nukkitx-snapshots - https://repo.nukkitx.com/maven-snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots From a2a7e99402d3f074141f7b70f49212ed720a740d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 27 Oct 2020 18:40:00 -0400 Subject: [PATCH 044/161] GUI Improvements (#1462) - Added `GeyserCommand.isExecutableOnConsole()`. If this is set to false, the command will not appear as an option in the GUI. - Added `GeyserCommand.getSubCommands()`. If not empty, the subcommand options will now appear in the GUI. --- .gitignore | 3 +- .../standalone/gui/GeyserStandaloneGUI.java | 24 +++++++++++++--- .../connector/command/GeyserCommand.java | 28 +++++++++++++++++++ .../command/defaults/DumpCommand.java | 11 ++++++-- .../command/defaults/OffhandCommand.java | 5 ++++ .../command/defaults/StatisticsCommand.java | 5 ++++ 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index e3c3b0a5..85f8a6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -242,4 +242,5 @@ logs/ public-key.pem locales/ /cache/ -/packs/ \ No newline at end of file +/packs/ +/dump.json \ No newline at end of file diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java index 50deeb1b..aeee8462 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/gui/GeyserStandaloneGUI.java @@ -261,14 +261,30 @@ public class GeyserStandaloneGUI { for (Map.Entry command : geyserCommandManager.getCommands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list - if ("offhand".equals(command.getValue().getName()) || command.getValue().getAliases().contains(command.getKey())) { + if (!command.getValue().isExecutableOnConsole() || command.getValue().getAliases().contains(command.getKey())) { continue; } // Create the button that runs the command - JMenuItem commandButton = new JMenuItem(command.getValue().getName()); + boolean hasSubCommands = command.getValue().hasSubCommands(); + // Add an extra menu if there are more commands that can be run + JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName()); commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); - commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + if (!hasSubCommands) { + commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + } else { + // Add a submenu that's the same name as the menu can't be pressed + JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName()); + otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription()); + otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ })); + commandButton.add(otherCommandButton); + // Add a menu option for all possible subcommands + for (String subCommandName : command.getValue().getSubCommands()) { + JMenuItem item = new JMenuItem(subCommandName); + item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName})); + commandButton.add(item); + } + } commandsMenu.add(commandButton); } @@ -291,7 +307,7 @@ public class GeyserStandaloneGUI { playerTableModel.getDataVector().removeAllElements(); for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { - Vector row = new Vector(); + Vector row = new Vector<>(); row.add(player.getSocketAddress().getHostName()); row.add(player.getPlayerEntity().getUsername()); diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 62bc6c73..2bb8893c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -30,6 +30,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @Getter @@ -44,4 +45,31 @@ public abstract class GeyserCommand { private List aliases = new ArrayList<>(); public abstract void execute(CommandSender sender, String[] args); + + /** + * If false, hides the command from being shown on the Geyser Standalone GUI. + * + * @return true if the command can be run on the server console + */ + public boolean isExecutableOnConsole() { + return true; + } + + /** + * Used in the GUI to know what subcommands can be run + * + * @return a list of all possible subcommands, or empty if none. + */ + public List getSubCommands() { + return Collections.emptyList(); + } + + /** + * Shortcut to {@link #getSubCommands()}{@code .isEmpty()}. + * + * @return true if there are subcommand present for this command. + */ + public boolean hasSubCommands() { + return !getSubCommands().isEmpty(); + } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9ad0d23d..9103755a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -27,12 +27,10 @@ package org.geysermc.connector.command.defaults; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.serializer.AsteriskSerializer; import org.geysermc.connector.dump.DumpInfo; import org.geysermc.connector.utils.LanguageUtils; @@ -40,6 +38,8 @@ import org.geysermc.connector.utils.WebUtils; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; +import java.util.List; public class DumpCommand extends GeyserCommand { @@ -130,4 +130,9 @@ public class DumpCommand extends GeyserCommand { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } } + + @Override + public List getSubCommands() { + return Arrays.asList("offline", "full"); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index b1b60132..d5e0a792 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -68,4 +68,9 @@ public class OffhandCommand extends GeyserCommand { } } } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java index ed9db58f..52379145 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StatisticsCommand.java @@ -66,4 +66,9 @@ public class StatisticsCommand extends GeyserCommand { ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS); session.sendDownstreamPacket(clientRequestPacket); } + + @Override + public boolean isExecutableOnConsole() { + return false; + } } From 9b46bf8bc9f08315bf2a8241857a3cfc5d955b54 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 29 Oct 2020 16:44:45 -0400 Subject: [PATCH 045/161] BedrockActionTranslator: Fix occasional death stall (#1432) Usually this happened when joining from another dimension after the player exited to the main menu on the death screen. The player would not realize that they are dead. --- .../bedrock/entity/player/BedrockActionTranslator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 91caf8d1..e3bcbce9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -68,6 +68,8 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:30:52 -0400 Subject: [PATCH 046/161] Introduce CommandSender.getLocale() (#1431) * Introduce CommandSender.getLocale() This allows Geyser-specific commands (e.g. `/geyser help`) to be displayed in the (Java or Bedrock) player's default language, which stops those commands from simply being displayed in the default locale. * Tweak Javadoc * Set CommandManager's GeyserConnector to final * Clean up --- .../command/BungeeCommandSender.java | 23 ++++-- .../command/GeyserBungeeCommandExecutor.java | 15 ++-- .../platform/spigot/GeyserSpigotPlugin.java | 4 ++ .../command/GeyserSpigotCommandExecutor.java | 13 ++-- .../spigot/command/SpigotCommandSender.java | 71 +++++++++++++++++-- .../GeyserVelocityCommandExecutor.java | 13 ++-- .../command/VelocityCommandSender.java | 25 +++++-- .../geysermc/connector/GeyserConnector.java | 2 +- .../connector/command/CommandManager.java | 18 ++--- .../connector/command/CommandSender.java | 18 +++++ .../connector/command/GeyserCommand.java | 3 + .../command/defaults/DumpCommand.java | 16 ++--- .../command/defaults/HelpCommand.java | 14 ++-- .../command/defaults/ListCommand.java | 10 ++- .../command/defaults/OffhandCommand.java | 4 +- .../command/defaults/ReloadCommand.java | 9 +-- .../command/defaults/VersionCommand.java | 10 +-- .../org/geysermc/connector/entity/Entity.java | 2 +- .../network/UpstreamPacketHandler.java | 4 +- .../network/session/GeyserSession.java | 7 +- .../network/session/cache/BossBar.java | 4 +- .../EnchantmentInventoryTranslator.java | 4 +- .../translators/item/ItemTranslator.java | 2 +- .../translators/java/JavaChatTranslator.java | 2 +- .../java/JavaDisconnectPacket.java | 2 +- .../java/JavaJoinGameTranslator.java | 2 +- .../java/JavaLoginDisconnectTranslator.java | 2 +- .../translators/java/JavaTitleTranslator.java | 2 +- .../java/scoreboard/JavaTeamTranslator.java | 8 +-- .../java/window/JavaOpenWindowTranslator.java | 4 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 2 +- .../connector/utils/LanguageUtils.java | 4 +- .../geysermc/connector/utils/LocaleUtils.java | 1 - .../connector/utils/LoginEncryptionUtils.java | 6 +- .../connector/utils/MessageUtils.java | 2 +- .../connector/utils/SettingsUtils.java | 2 +- 37 files changed, 212 insertions(+), 120 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java index d40dc902..3ad8b54f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java @@ -25,17 +25,20 @@ package org.geysermc.platform.bungeecord.command; -import lombok.AllArgsConstructor; - import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; -@AllArgsConstructor public class BungeeCommandSender implements CommandSender { - private net.md_5.bungee.api.CommandSender handle; + private final net.md_5.bungee.api.CommandSender handle; + + public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -51,4 +54,14 @@ public class BungeeCommandSender implements CommandSender { public boolean isConsole() { return !(handle instanceof ProxiedPlayer); } + + @Override + public String getLocale() { + if (handle instanceof ProxiedPlayer) { + ProxiedPlayer player = (ProxiedPlayer) handle; + String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry(); + return LanguageUtils.formatLocale(locale); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index f673a3f5..ff7a2e3d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord.command; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; - import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -41,7 +38,7 @@ import java.util.Arrays; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; public GeyserBungeeCommandExecutor(GeyserConnector connector) { super("geyser"); @@ -54,14 +51,10 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + BungeeCommandSender commandSender = new BungeeCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale()); - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message)); + commandSender.sendMessage(ChatColor.RED + message); return; } getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 9cc0bc06..892f8feb 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; @@ -130,6 +131,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); } + // Set if we need to use a different method for getting a player's locale + SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); + this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 2dba2901..0edb8448 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -32,7 +32,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.ArrayList; @@ -42,21 +41,17 @@ import java.util.List; @AllArgsConstructor public class GeyserSpigotCommandExecutor implements TabExecutor { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!sender.hasPermission(getCommand(args[0]).getPermission())) { - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"); - } + SpigotCommandSender commandSender = new SpigotCommandSender(sender); + String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());; - sender.sendMessage(ChatColor.RED + message); + commandSender.sendMessage(ChatColor.RED + message); return true; } getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java index 55475a30..93a50066 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java @@ -25,15 +25,33 @@ package org.geysermc.platform.spigot.command; -import lombok.AllArgsConstructor; - import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; -@AllArgsConstructor public class SpigotCommandSender implements CommandSender { - private org.bukkit.command.CommandSender handle; + /** + * Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version. + * 1.12 or greater should not use the legacy method. + */ + private static boolean USE_LEGACY_METHOD = false; + private static Method LOCALE_METHOD; + + private final org.bukkit.command.CommandSender handle; + private final String locale; + + public SpigotCommandSender(org.bukkit.command.CommandSender handle) { + this.handle = handle; + this.locale = getSpigotLocale(); + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(locale); + } @Override public String getName() { @@ -49,4 +67,49 @@ public class SpigotCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSender; } + + @Override + public String getLocale() { + return locale; + } + + /** + * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get + * {@code player.spigot().getLocale()}. + * + * @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale + */ + public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) { + USE_LEGACY_METHOD = useLegacyMethod; + if (USE_LEGACY_METHOD) { + try { + //noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it + LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale"); + } catch (NoSuchMethodException e) { + GeyserConnector.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!"); + } + } + } + + /** + * So we only have to do nasty reflection stuff once per command + * + * @return the locale of the Spigot player + */ + private String getSpigotLocale() { + if (handle instanceof Player) { + Player player = (Player) handle; + if (USE_LEGACY_METHOD) { + try { + // sigh + // This was the only option on older Spigot instances and now it's gone + return (String) LOCALE_METHOD.invoke(player.spigot()); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } else { + return player.getLocale(); + } + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index afd6c3bf..c329fb19 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -27,14 +27,11 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; - import lombok.AllArgsConstructor; - -import net.kyori.text.TextComponent; - -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Arrays; @@ -42,15 +39,15 @@ import java.util.Arrays; @AllArgsConstructor public class GeyserVelocityCommandExecutor implements Command { - private GeyserConnector connector; + private final GeyserConnector connector; @Override public void execute(CommandSource source, String[] args) { if (args.length > 0) { if (getCommand(args[0]) != null) { if (!source.hasPermission(getCommand(args[0]).getPermission())) { - // Not ideal to use log here but we dont get a session - source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail"))); + CommandSender sender = new VelocityCommandSender(source); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java index 1b0d6f3e..3a1c4603 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java @@ -28,17 +28,21 @@ package org.geysermc.platform.velocity.command; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; - -import lombok.AllArgsConstructor; - import net.kyori.text.TextComponent; - import org.geysermc.connector.command.CommandSender; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.Locale; -@AllArgsConstructor public class VelocityCommandSender implements CommandSender { - private CommandSource handle; + private final CommandSource handle; + + public VelocityCommandSender(CommandSource handle) { + this.handle = handle; + // Ensure even Java players' languages are loaded + LanguageUtils.loadGeyserLocale(getLocale()); + } @Override public String getName() { @@ -59,4 +63,13 @@ public class VelocityCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleCommandSource; } + + @Override + public String getLocale() { + if (handle instanceof Player) { + Locale locale = ((Player) handle).getPlayerSettings().getLocale(); + return LanguageUtils.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); + } + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index afed4dfd..1d535f54 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -271,7 +271,7 @@ public class GeyserConnector { // Make a copy to prevent ConcurrentModificationException final List tmpPlayers = new ArrayList<>(players); for (GeyserSession playerSession : tmpPlayers) { - playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode())); + playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getLocale())); } CompletableFuture future = CompletableFuture.runAsync(new Runnable() { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 2b35424a..7adce430 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -40,19 +40,19 @@ public abstract class CommandManager { @Getter private final Map commands = Collections.synchronizedMap(new HashMap<>()); - private GeyserConnector connector; + private final GeyserConnector connector; public CommandManager(GeyserConnector connector) { this.connector = connector; - registerCommand(new HelpCommand(connector, "help", LanguageUtils.getLocaleStringLog("geyser.commands.help.desc"), "geyser.command.help")); - registerCommand(new ListCommand(connector, "list", LanguageUtils.getLocaleStringLog("geyser.commands.list.desc"), "geyser.command.list")); - registerCommand(new ReloadCommand(connector, "reload", LanguageUtils.getLocaleStringLog("geyser.commands.reload.desc"), "geyser.command.reload")); - registerCommand(new StopCommand(connector, "stop", LanguageUtils.getLocaleStringLog("geyser.commands.stop.desc"), "geyser.command.stop")); - registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand")); - registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump")); - registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version")); - registerCommand(new StatisticsCommand(connector, "statistics", LanguageUtils.getLocaleStringLog("geyser.commands.statistics.desc"), "geyser.command.statistics")); + registerCommand(new HelpCommand(connector, "help", "geyser.commands.help.desc", "geyser.command.help")); + registerCommand(new ListCommand(connector, "list", "geyser.commands.list.desc", "geyser.command.list")); + registerCommand(new ReloadCommand(connector, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); + registerCommand(new StopCommand(connector, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); + registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); + registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump")); + registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); + registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java index be9e3430..9c398a2a 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java @@ -25,6 +25,12 @@ package org.geysermc.connector.command; +import org.geysermc.connector.utils.LanguageUtils; + +/** + * Implemented on top of any class that can send a command. + * For example, it wraps around Spigot's CommandSender class. + */ public interface CommandSender { String getName(); @@ -37,5 +43,17 @@ public interface CommandSender { void sendMessage(String message); + /** + * @return true if the specified sender is from the console. + */ boolean isConsole(); + + /** + * Returns the locale of the command sender. Defaults to the default locale at {@link LanguageUtils#getDefaultLocale()}. + * + * @return the locale of the command sender. + */ + default String getLocale() { + return LanguageUtils.getDefaultLocale(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java index 2bb8893c..adbebd0c 100644 --- a/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/GeyserCommand.java @@ -38,6 +38,9 @@ import java.util.List; public abstract class GeyserCommand { protected final String name; + /** + * The description of the command - will attempt to be translated. + */ protected final String description; protected final String permission; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java index 9103755a..f2c37da2 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/DumpCommand.java @@ -73,7 +73,7 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale())); String dumpData = ""; try { if (offlineDump) { @@ -82,7 +82,7 @@ public class DumpCommand extends GeyserCommand { dumpData = MAPPER.writeValueAsString(new DumpInfo()); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -90,21 +90,21 @@ public class DumpCommand extends GeyserCommand { String uploadedDumpUrl = ""; if (offlineDump) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale())); String response; JsonNode responseNode; @@ -112,20 +112,20 @@ public class DumpCommand extends GeyserCommand { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale())); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); if (!sender.isConsole()) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl)); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index 0407cf6e..268dc4b5 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -25,11 +25,10 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; -import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; @@ -52,17 +51,12 @@ public class HelpCommand extends GeyserCommand { public void execute(CommandSender sender, String[] args) { int page = 1; int maxPage = 1; - String header = ""; - - if (sender instanceof GeyserSession) { - header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", ((GeyserSession) sender).getClientData().getLanguageCode(), page, maxPage); - } else { - header = LanguageUtils.getLocaleStringLog("geyser.commands.help.header", page, maxPage); - } + String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); sender.sendMessage(header); Map cmds = connector.getCommandManager().getCommands(); List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); - commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + cmds.get(cmd).getDescription())); + commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + + LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale()))); } } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java index 3c78c554..255c6a9b 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ListCommand.java @@ -35,7 +35,7 @@ import java.util.stream.Collectors; public class ListCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public ListCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -46,11 +46,9 @@ public class ListCommand extends GeyserCommand { @Override public void execute(CommandSender sender, String[] args) { String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", ((GeyserSession) sender).getClientData().getLanguageCode(), connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); - } + message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(), + connector.getPlayers().size(), + connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" "))); sender.sendMessage(message); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java index d5e0a792..4b6397f1 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/OffhandCommand.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class OffhandCommand extends GeyserCommand { - private GeyserConnector connector; + private final GeyserConnector connector; public OffhandCommand(GeyserConnector connector, String name, String description, String permission) { super(name, description, permission); @@ -58,7 +58,7 @@ public class OffhandCommand extends GeyserCommand { session.sendDownstreamPacket(releaseItemPacket); return; } - // Needed for Bukkit - sender is not an instance of GeyserSession + // Needed for Spigot - sender is not an instance of GeyserSession for (GeyserSession session : connector.getPlayers()) { if (sender.getName().equals(session.getPlayerEntity().getUsername())) { ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0), diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 9c24fa2b..6b2be294 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -47,17 +47,12 @@ public class ReloadCommand extends GeyserCommand { return; } - String message = ""; - if (sender instanceof GeyserSession) { - message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", ((GeyserSession) sender).getClientData().getLanguageCode()); - } else { - message = LanguageUtils.getLocaleStringLog("geyser.commands.reload.message"); - } + String message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale()); sender.sendMessage(message); for (GeyserSession session : connector.getPlayers()) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale())); } connector.reload(); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java index f7f62e59..562bc9fb 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/VersionCommand.java @@ -61,12 +61,12 @@ public class VersionCommand extends GeyserCommand { bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion(); } - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions)); // Disable update checking in dev mode //noinspection ConstantConditions - changes in production if (!GeyserConnector.VERSION.equals("DEV")) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.checking")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); try { Properties gitProp = new Properties(); gitProp.load(FileUtils.getResource("git.properties")); @@ -76,16 +76,16 @@ public class VersionCommand extends GeyserCommand { int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number")); if (latestBuildNum == buildNum) { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.no_updates")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale())); } else { - sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/")); + sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.outdated", sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); } } else { throw new AssertionError("buildNumber missing"); } } catch (IOException | AssertionError | NumberFormatException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.version.failed")); + sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale())); } } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e89..2dfb0c04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -321,7 +321,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true)); + metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index f99abbe5..e871b7f7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -170,7 +170,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(SetLocalPlayerAsInitializedPacket packet) { - LanguageUtils.loadGeyserLocale(session.getClientData().getLanguageCode()); + LanguageUtils.loadGeyserLocale(session.getLocale()); if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) @@ -185,7 +185,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(MovePlayerPacket packet) { if (session.isLoggingIn()) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getClientData().getLanguageCode())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale())); } return translateAndDefault(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a8a4adb9..ac72d219 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -467,7 +467,7 @@ public class GeyserSession implements CommandSender { // as it has to be extracted from a JAR if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients - sendMessage("Downloading your locale (en_us) this may take some time"); + sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } // Download and load the language for the player @@ -587,6 +587,11 @@ public class GeyserSession implements CommandSender { return false; } + @Override + public String getLocale() { + return clientData.getLanguageCode(); + } + public void sendForm(FormWindow window, int id) { windowCache.showWindow(window, id); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index 68e8519c..fdc609ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode())); + bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java index cbcdce10..8aa91b36 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java @@ -199,7 +199,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { private String toRomanNumeral(GeyserSession session, int level) { return LocaleUtils.getLocaleString("enchantment.level." + level, - session.getClientData().getLanguageCode()); + session.getLocale()); } /** @@ -261,7 +261,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { public String toString(GeyserSession session) { return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), - session.getClientData().getLanguageCode()); + session.getLocale()); } } } 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 ef629494..f95a0ccc 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 @@ -400,7 +400,7 @@ public abstract class ItemTranslator { // Check if its a message to translate if (MessageUtils.isMessage(name)) { // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode()); + name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); // Add the new name tag display.put(new StringTag("Name", name)); 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 d222d729..186aaf66 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 @@ -59,7 +59,7 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index 43211111..f36da367 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -36,6 +36,6 @@ public class JavaDisconnectPacket extends PacketTranslator skinParts = Arrays.asList(SkinPart.values()); ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND); session.sendDownstreamPacket(clientSettingsPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index d4504226..e7486c99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { @Override public void translate(ServerTitlePacket packet, GeyserSession session) { SetTitlePacket titlePacket = new SetTitlePacket(); - String locale = session.getClientData().getLanguageCode(); + String locale = session.getLocale(); switch (packet.getAction()) { case TITLE: 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 998e5eff..c621fc1f 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 @@ -62,8 +62,8 @@ public class JavaTeamTranslator extends PacketTranslator { .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())); + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -77,8 +77,8 @@ public class JavaTeamTranslator extends PacketTranslator { team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode())) + .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) + .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 099de317..2c10ded6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -58,9 +58,9 @@ public class JavaOpenWindowTranslator extends PacketTranslator params = new ArrayList<>(); String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc"; - params.add(LocaleUtils.getLocaleString(recordString, session.getClientData().getLanguageCode())); + params.add(LocaleUtils.getLocaleString(recordString, session.getLocale())); textPacket.setParameters(params); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index de6796a2..06d2936e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -59,6 +59,8 @@ public class LanguageUtils { */ public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); + // Don't load the locale if it's already loaded. + if (LOCALE_MAPPINGS.containsKey(locale)) return; InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -131,7 +133,7 @@ public class LanguageUtils { * @param locale The locale to format * @return The formatted locale */ - private static String formatLocale(String locale) { + public static String formatLocale(String locale) { try { String[] parts = locale.toLowerCase().split("_"); return parts[0] + "_" + parts[1].toUpperCase(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index d1d59490..11e81eee 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -35,7 +35,6 @@ import org.geysermc.connector.GeyserConnector; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Iterator; import java.util.List; 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 4bc997bd..fe63bc91 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -160,7 +160,7 @@ public class LoginEncryptionUtils { private static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage))); window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); @@ -169,7 +169,7 @@ public class LoginEncryptionUtils { } public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); + String userLanguage = session.getLocale(); CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) @@ -210,7 +210,7 @@ public class LoginEncryptionUtils { if (response.getClickedButtonId() == 0) { showLoginDetailsWindow(session); } else if(response.getClickedButtonId() == 1) { - session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); + session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } } else { showLoginWindow(session); 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 a127fd8d..b5a2bfdc 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -485,7 +485,7 @@ public class MessageUtils { */ public static boolean isTooLong(String message, GeyserSession session) { if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getClientData().getLanguageCode(), message.length())); + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 13db4682..eaf71058 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -52,7 +52,7 @@ public class SettingsUtils { */ public static void buildForm(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + String language = session.getLocale(); CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); From 8fdaf6a3850b2590ce930e476d799e0e12f190af Mon Sep 17 00:00:00 2001 From: David Choo Date: Thu, 29 Oct 2020 18:35:46 -0400 Subject: [PATCH 047/161] Ender dragon Melee Attacks (#1466) * Create and position Ender Dragon Bounding Box Currently allows the player to "kill aura" target the ender dragon. * Use an entity to handle attacks for each hitbox * Use the proper flag to make entities invisible * Clean up and add some comments * Ender dragon entity metadata improvements * Add doc to segment functions * Add changes Co-authored-by: DoctorMacc --- .../living/monster/EnderDragonEntity.java | 163 ++++++++++++++++-- .../living/monster/EnderDragonPartEntity.java | 42 +++++ .../connector/entity/type/EntityType.java | 9 +- ...BedrockInventoryTransactionTranslator.java | 15 +- 4 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java index aa2b4e02..14466dda 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonEntity.java @@ -32,33 +32,60 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AddEntityPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; +import lombok.Data; import org.geysermc.connector.entity.living.InsentientEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + public class EnderDragonEntity extends InsentientEntity { + /** + * The Ender Dragon has multiple hit boxes, which + * are each its own invisible entity + */ + private EnderDragonPartEntity head; + private EnderDragonPartEntity neck; + private EnderDragonPartEntity body; + private EnderDragonPartEntity leftWing; + private EnderDragonPartEntity rightWing; + private EnderDragonPartEntity[] tail; + + private EnderDragonPartEntity[] allParts; + + /** + * A circular buffer that stores a history of + * y and yaw values. + */ + private final Segment[] segmentHistory = new Segment[19]; + private int latestSegment = -1; + + private boolean hovering; + + private ScheduledFuture partPositionUpdater; public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + // Phase if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true); - switch ((int) entityMetadata.getValue()) { + int value = (int) entityMetadata.getValue(); + if (value == 5) { // Performing breath attack - case 5: - EntityEventPacket entityEventPacket = new EntityEventPacket(); - entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); - entityEventPacket.setRuntimeEntityId(geyserId); - entityEventPacket.setData(0); - session.sendUpstreamPacket(entityEventPacket); - case 6: - case 7: - metadata.getFlags().setFlag(EntityFlag.SITTING, true); - break; + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setType(EntityEventType.DRAGON_FLAMING); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setData(0); + session.sendUpstreamPacket(entityEventPacket); } + metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7); + hovering = value == 10; } super.updateBedrockMetadata(entityMetadata, session); } @@ -81,6 +108,118 @@ public class EnderDragonEntity extends InsentientEntity { valid = true; session.sendUpstreamPacket(addEntityPacket); + head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1); + neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3); + body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3); + leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2); + tail = new EnderDragonPartEntity[3]; + for (int i = 0; i < 3; i++) { + tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2); + } + + allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]}; + + for (EnderDragonPartEntity part : allParts) { + session.getEntityCache().spawnEntity(part); + } + + for (int i = 0; i < segmentHistory.length; i++) { + segmentHistory[i] = new Segment(); + segmentHistory[i].yaw = rotation.getZ(); + segmentHistory[i].y = position.getY(); + } + + partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { + pushSegment(); + updateBoundingBoxes(session); + }, 0, 50, TimeUnit.MILLISECONDS); + session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")"); } + + @Override + public boolean despawnEntity(GeyserSession session) { + partPositionUpdater.cancel(true); + + for (EnderDragonPartEntity part : allParts) { + part.despawnEntity(session); + } + return super.despawnEntity(session); + } + + /** + * Updates the positions of the Ender Dragon's multiple bounding boxes + * + * @param session GeyserSession. + */ + private void updateBoundingBoxes(GeyserSession session) { + Vector3f facingDir = Vector3f.createDirectionDeg(0, rotation.getZ()); + Segment baseSegment = getSegment(5); + // Used to angle the head, neck, and tail when the dragon flies up/down + float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY())); + float pitchXZ = (float) Math.cos(pitch); + float pitchY = (float) Math.sin(pitch); + + // Lowers the head when the dragon sits/hovers + float headDuck; + if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) { + headDuck = -1f; + } else { + headDuck = baseSegment.y - getSegment(0).y; + } + + head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck)); + neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck)); + body.setPosition(facingDir.mul(0.5f, 0f, -0.5f)); + + Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - rotation.getZ()).mul(4.5f).up(2f); + rightWing.setPosition(wingPos); + leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally + + Vector3f tailBase = facingDir.mul(1.5f); + for (int i = 0; i < tail.length; i++) { + float distance = (i + 1) * 2f; + // Curls the tail when the dragon turns + Segment targetSegment = getSegment(12 + 2 * i); + float angle = rotation.getZ() + targetSegment.yaw - baseSegment.yaw; + + float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f; + tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset)); + } + // Send updated positions + for (EnderDragonPartEntity part : allParts) { + part.moveAbsolute(session, part.getPosition().add(position), Vector3f.ZERO, false, false); + } + } + + /** + * Store the current yaw and y into the circular buffer + */ + private void pushSegment() { + latestSegment = (latestSegment + 1) % segmentHistory.length; + segmentHistory[latestSegment].yaw = rotation.getZ(); + segmentHistory[latestSegment].y = position.getY(); + } + + /** + * Gets the previous yaw and y + * Used to curl the tail and pitch the head and tail up/down + * + * @param index Number of ticks in the past + * @return Segment with the yaw and y + */ + private Segment getSegment(int index) { + index = (latestSegment - index) % segmentHistory.length; + if (index < 0) { + index += segmentHistory.length; + } + return segmentHistory[index]; + } + + @Data + private static class Segment { + private float yaw; + private float y; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java new file mode 100644 index 00000000..bb5876ce --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EnderDragonPartEntity.java @@ -0,0 +1,42 @@ +/* + * 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.entity.living.monster; + +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; + +public class EnderDragonPartEntity extends Entity { + public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) { + super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 24c15018..fddab5a4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -86,7 +86,7 @@ public enum EntityType { ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f), NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f), WITHER(WitherEntity.class, 52, 3.5f, 0.9f), - ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f), + ENDER_DRAGON(EnderDragonEntity.class, 53, 0f, 0f), SHULKER(ShulkerEntity.class, 54, 1f, 1f), ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f), AGENT(Entity.class, 56, 0f), @@ -166,7 +166,12 @@ public enum EntityType { /** * Not an entity in Bedrock, so we replace it with a Pillager */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"); + ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + + /** + * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes + */ + ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); private static final EntityType[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b81025be..b2a70146 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -49,6 +49,7 @@ import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -251,9 +252,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator Date: Thu, 29 Oct 2020 18:40:42 -0400 Subject: [PATCH 048/161] Add support for more recipes (#1434) * Add support for more recipes - Tool repairing - Book cloning - Suspicious stew - Tipped arrows What still needs to be done: - Map cloning/extending (though there may be item mapping mismatch getting in the way) - Banner duplication (couldn't figure this out) * Add some more spacing * Explain recipe UUIDs --- .../translators/item/RecipeRegistry.java | 72 +++++++++++++++++-- .../java/JavaDeclareRecipesTranslator.java | 21 +++++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 191b285c..411c0295 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -34,8 +34,7 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; -import java.util.List; -import java.util.UUID; +import java.util.*; /** * Manages any recipe-related storing @@ -46,22 +45,45 @@ public class RecipeRegistry { * A list of all possible leather armor dyeing recipes. * Created manually. */ - public static List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List LEATHER_DYEING_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework rocket recipes, including the base rocket. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21); + public static final List FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(); /** * A list of all possible firework star recipes. * Obtained from a ProxyPass dump of protocol v407 */ - public static List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40); + public static final List FIREWORK_STAR_RECIPES = new ObjectArrayList<>(); /** * A list of all possible shulker box dyeing options. * Obtained from a ProxyPass dump of protocol v407 */ - public static List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + public static final List SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible suspicious stew recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List SUSPICIOUS_STEW_RECIPES = new ObjectArrayList<>(); + /** + * A list of all possible tipped arrow recipes. + * Obtained from a ProxyPass dump of protocol v407 + */ + public static final List TIPPED_ARROW_RECIPES = new ObjectArrayList<>(); + + // TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables. + // 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc + // d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4 + // d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327 + /** + * Recipe data that, when sent to the client, enables book cloning + */ + public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d")); + /** + * Recipe data that, when sent to the client, enables tool repairing in a crafting table + */ + public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001")); static { // Get all recipes that are not directly sent from a Java server @@ -88,6 +110,12 @@ public class RecipeRegistry { for (JsonNode entry : items.get("shulker_boxes")) { SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); } + for (JsonNode entry : items.get("suspicious_stew")) { + SUSPICIOUS_STEW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } + for (JsonNode entry : items.get("tipped_arrows")) { + TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); + } } /** @@ -97,11 +125,41 @@ public class RecipeRegistry { */ private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + UUID uuid = UUID.randomUUID(); + if (node.get("type").asInt() == 1) { + // Shaped recipe + List shape = new ArrayList<>(); + // Get the shape of the recipe + for (JsonNode chars : node.get("shape")) { + shape.add(chars.asText()); + } + + // In recipes.json each recipe is mapped by a letter + Map letterToRecipe = new HashMap<>(); + Iterator> iterator = node.get("input").fields(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue())); + } + + ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()]; + int i = 0; + // Create a linear array of items from the "cube" of the shape + for (int j = 0; i < shape.size() * shape.get(0).length(); j++) { + for (char c : shape.get(j).toCharArray()) { + ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR); + inputs[i] = data; + i++; + } + } + + return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), + inputs, new ItemData[]{output}, uuid, "crafting_table", 0); + } List inputs = new ObjectArrayList<>(); for (JsonNode entry : node.get("input")) { inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); } - UUID uuid = UUID.randomUUID(); if (node.get("type").asInt() == 5) { // Shulker box return CraftingData.fromShulkerBox(uuid.toString(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 9ffb4f0d..70303baa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -80,8 +80,19 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator Date: Sat, 31 Oct 2020 22:38:56 -0400 Subject: [PATCH 049/161] MapItemTranslator: support map tag being of short value (#1475) GSigns sends this as a short. Who knows why. --- .../translators/nbt/MapItemTranslator.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java index c9b49efd..d325af48 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/MapItemTranslator.java @@ -25,10 +25,7 @@ package org.geysermc.connector.network.translators.item.translators.nbt; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; @@ -39,14 +36,22 @@ public class MapItemTranslator extends NbtItemStackTranslator { @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - IntTag mapId = itemTag.get("map"); + // Can be either an IntTag or ShortTag + Tag mapId = itemTag.get("map"); + if (mapId == null) return; - if (mapId != null) { - itemTag.put(new LongTag("map_uuid", mapId.getValue())); - itemTag.put(new IntTag("map_name_index", mapId.getValue())); - itemTag.put(new ByteTag("map_display_players", (byte) 1)); - itemTag.remove("map"); + int mapValue; + if (mapId.getValue() instanceof Short) { + // Convert to int if necessary + mapValue = (int) (short) mapId.getValue(); + } else { + mapValue = (int) mapId.getValue(); } + + itemTag.put(new LongTag("map_uuid", mapValue)); + itemTag.put(new IntTag("map_name_index", mapValue)); + itemTag.put(new ByteTag("map_display_players", (byte) 1)); + itemTag.remove("map"); } @Override From 3e0d898b6a53e655bffd3b72e779054b04b4b765 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 2 Nov 2020 14:28:31 -0500 Subject: [PATCH 050/161] Update to 1.16.4 (#1487) * 1.16.4-pre1 support * Update to support ViaVersion 3.2.0 (#1358) * Update to support ViaVersion 3.2.0 This commit updates ViaVersion integration to support their recent mappings changes. Co-authored-by: Nassim * Send adventure settings on gamemode change after gamemode swap * Update Velocity to 1.1.0 proper * Update to 1.16.4 Co-authored-by: Nassim --- bootstrap/spigot/pom.xml | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 8 ++++ .../world/GeyserSpigotWorldManager.java | 38 ++++++++++++++----- bootstrap/velocity/pom.xml | 2 +- .../velocity/GeyserVelocityPlugin.java | 2 +- connector/pom.xml | 4 +- .../world/JavaNotifyClientTranslator.java | 4 +- 7 files changed, 43 insertions(+), 17 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d4dc3326..067b446a 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -26,7 +26,7 @@ us.myles viaversion - 3.1.1 + 3.2.0 provided diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 892f8feb..59a8db06 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -43,6 +43,7 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import us.myles.ViaVersion.api.Via; import java.io.File; import java.io.IOException; @@ -121,6 +122,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserCommandManager = new GeyserSpigotCommandManager(this, connector); boolean isViaVersion = (Bukkit.getPluginManager().getPlugin("ViaVersion") != null); + if (isViaVersion) { + if (!isCompatible(Via.getAPI().getVersion().replace("-SNAPSHOT", ""), "3.2.0")) { + geyserLogger.warning(LanguageUtils.getLocaleStringLog("geyser.bootstrap.viaversion.too_old", + "https://ci.viaversion.com/job/ViaVersion/")); + isViaVersion = false; + } + } // Used to determine if Block.getBlockData() is present. boolean isLegacy = !isCompatible(Bukkit.getServer().getVersion(), "1.13.0"); if (isLegacy) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 8a92526f..ad3d1cf1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -26,6 +26,7 @@ package org.geysermc.platform.spigot.world; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; @@ -41,14 +42,29 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13; -import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; import java.io.InputStream; +import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { + /** + * The current client protocol version for ViaVersion usage. + */ + private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + + /** + * Whether the server is pre-1.13. + */ private final boolean isLegacy; + /** + * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. + */ private final boolean use3dBiomes; /** * You need ViaVersion to connect to an older server with Geyser. @@ -122,14 +138,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if (isViaVersion) { Block block = world.getBlockAt(x, y, z); // Black magic that gets the old block state ID - int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - int thirteenBlockId = us.myles.ViaVersion.protocols.protocol1_13to1_12_2.data.MappingData.blockMappings.getNewId(oldBlockId); - int thirteenPointOneBlockId = Protocol1_13_1To1_13.getNewBlockStateId(thirteenBlockId); - int fourteenBlockId = us.myles.ViaVersion.protocols.protocol1_14to1_13_2.data.MappingData.blockStateMappings.getNewId(thirteenPointOneBlockId); - int fifteenBlockId = us.myles.ViaVersion.protocols.protocol1_15to1_14_4.data.MappingData.blockStateMappings.getNewId(fourteenBlockId); - int sixteenBlockId = us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData.blockStateMappings.getNewId(fifteenBlockId); - return MappingData.blockStateMappings.getNewId(sixteenBlockId); + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + for (int i = protocolList.size() - 1; i >= 0; i--) { + if (protocolList.get(i).getValue().getMappingData() == null) continue; + blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + } + return blockId; } else { return BlockTranslator.AIR; } diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index ee445b6e..babb9a3e 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -20,7 +20,7 @@ com.velocitypowered velocity-api - 1.0.0-SNAPSHOT + 1.1.0 provided diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index 291d7a00..f75c683a 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -121,7 +121,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.connector = GeyserConnector.start(PlatformType.VELOCITY, this); this.geyserCommandManager = new GeyserVelocityCommandManager(connector); - this.commandManager.register(new GeyserVelocityCommandExecutor(connector), "geyser"); + this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(connector)); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); } else { diff --git a/connector/pom.xml b/connector/pom.xml index 19731987..edf14abb 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile - com.github.steveice10 + com.github.GeyserMC mcprotocollib - 1b01b1ffef + e4181064d1 compile diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 1e102a60..26ecb1e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -103,13 +103,13 @@ public class JavaNotifyClientTranslator extends PacketTranslator Date: Mon, 2 Nov 2020 16:04:08 -0500 Subject: [PATCH 051/161] Send position update every 3 seconds if idle (#1421) * Send position update every 3 seconds if idle Prevents timeouts in certain instances when AFK. * Cancel position sending on dimension switching * Remove debug lines * Create function to centralize movement translation --- .../network/session/GeyserSession.java | 6 ++ .../player/BedrockMovePlayerTranslator.java | 56 +++++++++++++++---- .../connector/utils/DimensionUtils.java | 4 ++ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ac72d219..6a2913c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -221,6 +221,12 @@ public class GeyserSession implements CommandSender { @Setter private ScheduledFuture bucketScheduledFuture; + /** + * Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances. + */ + @Setter + private ScheduledFuture movementSendIfIdle; + private boolean reducedDebugInfo = false; @Setter diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java index 8809941b..c5988bf0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockMovePlayerTranslator.java @@ -25,7 +25,13 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; +import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; @@ -34,11 +40,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; -import com.nukkitx.math.vector.Vector3f; -import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; +import java.util.concurrent.TimeUnit; @Translator(packet = MovePlayerPacket.class) public class BedrockMovePlayerTranslator extends PacketTranslator { @@ -59,13 +61,11 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } + + /** + * Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between + * the two versions. + * + * @param position the current Bedrock position of the client + * @param onGround whether the Bedrock player is on the ground + * @return the position to send to the Java server. + */ + private Vector3d adjustBedrockPosition(Vector3f position, boolean onGround) { + // We need to parse the float as a string since casting a float to a double causes us to + // lose precision and thus, causes players to get stuck when walking near walls + double javaY = position.getY() - EntityType.PLAYER.getOffset(); + if (onGround) javaY = Math.ceil(javaY * 2) / 2; + + return Vector3d.from(Double.parseDouble(Float.toString(position.getX())), javaY, + Double.parseDouble(Float.toString(position.getZ()))); } public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { @@ -147,4 +169,16 @@ public class BedrockMovePlayerTranslator extends PacketTranslator sendPositionIfIdle(session), + 3, TimeUnit.SECONDS)); + } } 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 7b283e9c..30d69258 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,10 @@ public class DimensionUtils { if (javaDimension.equals(player.getDimension())) return; + if (session.getMovementSendIfIdle() != null) { + session.getMovementSendIfIdle().cancel(true); + } + session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); if (session.getPendingDimSwitches().getAndIncrement() > 0) { From ea521078e11b9d158fa28427d940fb191dc3d671 Mon Sep 17 00:00:00 2001 From: Matthias Neid Date: Mon, 2 Nov 2020 22:44:45 +0100 Subject: [PATCH 052/161] Update supported version in README to 1.16.4 (#1488) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7201bb0..e2efc5b4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.3. +### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From ce64dd27887789cff36b0e9c8bf2be160993f57d Mon Sep 17 00:00:00 2001 From: David Choo Date: Wed, 4 Nov 2020 20:30:55 -0500 Subject: [PATCH 053/161] Fix sitting with armor stand and animal mount offsets (#1492) * Fix sitting with armor stand and animal offsets * Fix riding players * Moved @Getter --- .../entity/living/ArmorStandEntity.java | 4 ++- .../JavaEntitySetPassengersTranslator.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 07496093..b61aeda9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import lombok.Getter; import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -36,6 +37,7 @@ import org.geysermc.connector.network.session.GeyserSession; public class ArmorStandEntity extends LivingEntity { // These are used to store the state of the armour stand for use when handling invisibility + @Getter private boolean isMarker = false; private boolean isInvisible = false; private boolean isSmall = false; @@ -47,7 +49,7 @@ public class ArmorStandEntity extends LivingEntity { @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible) { + if (!isMarker && isInvisible && passengers.isEmpty()) { position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java index 64f0e3e9..0fecf118 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntitySetPassengersTranslator.java @@ -33,6 +33,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.ArmorStandEntity; +import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -46,6 +48,10 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 14:59:01 -0500 Subject: [PATCH 054/161] Fix bell sound and visuals (#1502) * Fix bell sound and visuals The bell sound now correctly plays. Bells will also visually ring when rung by another player. * Compress elses * Add more whitespace to new code --- .../java/world/JavaBlockValueTranslator.java | 51 +++++++++++++++---- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java index 3d3df51c..903cad0c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.value.*; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; import com.nukkitx.math.vector.Vector3i; @@ -53,16 +54,12 @@ public class JavaBlockValueTranslator extends PacketTranslator 0 ? 1 : 0); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof EndGatewayValue) { + } else if (packet.getValue() instanceof EndGatewayValue) { blockEventPacket.setEventType(1); session.sendUpstreamPacket(blockEventPacket); - } - if (packet.getValue() instanceof NoteBlockValue) { + } else if (packet.getValue() instanceof NoteBlockValue) { NoteblockBlockEntityTranslator.translate(session, packet.getPosition()); - return; - } - if (packet.getValue() instanceof PistonValue) { + } else if (packet.getValue() instanceof PistonValue) { PistonValueType type = (PistonValueType) packet.getType(); // Unlike everything else, pistons need a block entity packet to convey motion @@ -73,14 +70,46 @@ public class JavaBlockValueTranslator extends PacketTranslator Date: Thu, 5 Nov 2020 22:36:22 +0100 Subject: [PATCH 055/161] Block entity performance improvements (#1481) * BlockEntity performance improvements * Use chunk cache if possible for block caching * Get new block state from ViaVersion if block entity * Add Javadoc for FlowerPotBlockEntityTranslator.isFlowerBlock * Remove debug line * Don't add all RequiresBlockState instances if cache chunks is enabled * Double chest map get optimization * Last changes Co-authored-by: DoctorMacc --- .../world/GeyserSpigotWorldManager.java | 58 ++++++---- .../connector/entity/ItemFrameEntity.java | 13 +-- ...BedrockInventoryTransactionTranslator.java | 7 +- .../translators/item/ItemRegistry.java | 10 +- .../translators/item/ItemTranslator.java | 7 +- .../item/translators/BannerTranslator.java | 108 +++++++++--------- .../java/world/JavaChunkDataTranslator.java | 7 +- .../world/JavaUpdateTileEntityTranslator.java | 25 ++-- .../world/block/BlockStateValues.java | 41 +++---- .../entity/BannerBlockEntityTranslator.java | 18 +-- .../entity/BedBlockEntityTranslator.java | 16 +-- .../block/entity/BedrockOnlyBlockEntity.java | 3 +- .../block/entity/BlockEntityTranslator.java | 30 +++-- .../entity/CampfireBlockEntityTranslator.java | 10 +- .../CommandBlockBlockEntityTranslator.java | 33 +++--- .../DoubleChestBlockEntityTranslator.java | 59 ++++------ .../entity/EmptyBlockEntityTranslator.java | 9 +- .../EndGatewayBlockEntityTranslator.java | 15 +-- .../FlowerPotBlockEntityTranslator.java | 46 +++++--- .../JigsawBlockBlockEntityTranslator.java | 19 ++- .../NoteblockBlockEntityTranslator.java | 7 +- .../entity/PistonBlockEntityTranslator.java | 20 ++-- .../ShulkerBoxBlockEntityTranslator.java | 17 +-- .../entity/SignBlockEntityTranslator.java | 73 ++++++------ .../entity/SkullBlockEntityTranslator.java | 19 ++- .../entity/SpawnerBlockEntityTranslator.java | 57 +++++---- .../connector/utils/BlockEntityUtils.java | 15 ++- .../geysermc/connector/utils/ChunkUtils.java | 24 ++-- 28 files changed, 362 insertions(+), 404 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index ad3d1cf1..28b2da3a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -43,16 +43,19 @@ import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; import us.myles.ViaVersion.api.protocol.Protocol; import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.api.protocol.ProtocolVersion; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; import java.util.List; public class GeyserSpigotWorldManager extends GeyserWorldManager { - /** * The current client protocol version for ViaVersion usage. */ @@ -99,8 +102,9 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } // Only load in the biomes that are present in this version of Minecraft for (Biome enumBiome : Biome.values()) { - if (biomes.has(enumBiome.toString())) { - biomeToIdMap.put(enumBiome.ordinal(), biomes.get(enumBiome.toString()).intValue()); + JsonNode biome = biomes.get(enumBiome.toString()); + if (biome != null) { + biomeToIdMap.put(enumBiome.ordinal(), biome.intValue()); } else { GeyserConnector.getInstance().getLogger().debug("No biome mapping found for " + enumBiome.toString() + ", defaulting to 0"); @@ -127,30 +131,38 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { if (isViaVersion) { - return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true); + Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); + return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { return BlockTranslator.AIR; } } @SuppressWarnings("deprecation") - public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - for (int i = protocolList.size() - 1; i >= 0; i--) { - if (protocolList.get(i).getValue().getMappingData() == null) continue; - blockId = protocolList.get(i).getValue().getMappingData().getNewBlockStateId(blockId); + public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); + List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getId()); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); } - return blockId; - } else { - return BlockTranslator.AIR; } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; } @Override @@ -162,11 +174,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { + if (this.isLegacy) { + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true)); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); } } } @@ -176,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index f9d2ace4..501c7e46 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -141,6 +141,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); + // TODO 1.16.100 set to BEDROCK_AIR updateBlockPacket.setRuntimeId(0); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); @@ -196,18 +197,6 @@ public class ItemFrameEntity extends Entity { return session.getItemFrameCache().getOrDefault(position, -1); } - /** - * Determines if the position contains an item frame. - * Does largely the same thing as getItemFrameEntityId, but for speed purposes is implemented separately, - * since every block destroy packet has to check for an item frame. - * @param position position of block. - * @param session GeyserSession. - * @return true if position contains item frame, false if not. - */ - public static boolean positionContainsItemFrame(GeyserSession session, Vector3i position) { - return session.getItemFrameCache().containsKey(position); - } - /** * Force-remove from the position-to-ID map so it doesn't cause conflicts. * @param session GeyserSession. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b2a70146..81a2cd50 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -194,10 +194,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator ITEM_ENTRIES.values() - .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + return JAVA_IDENTIFIER_MAP.computeIfAbsent(javaIdentifier, key -> { + for (ItemEntry entry : ITEM_ENTRIES.values()) { + if (entry.getJavaIdentifier().equals(key)) { + return entry; + } + } + return null; + }); } /** 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 f95a0ccc..55db9a25 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 @@ -51,7 +51,6 @@ import java.util.*; import java.util.stream.Collectors; public abstract class ItemTranslator { - private static final Int2ObjectMap ITEM_STACK_TRANSLATORS = new Int2ObjectOpenHashMap<>(); private static final List NBT_TRANSLATORS; @@ -220,7 +219,7 @@ public abstract class ItemTranslator { public abstract List getAppliedItems(); public NbtMap translateNbtToBedrock(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag) { - Map javaValue = new HashMap<>(); + NbtMapBuilder builder = NbtMap.builder(); if (tag.getValue() != null && !tag.getValue().isEmpty()) { for (String str : tag.getValue().keySet()) { com.github.steveice10.opennbt.tag.builtin.Tag javaTag = tag.get(str); @@ -228,11 +227,9 @@ public abstract class ItemTranslator { if (translatedTag == null) continue; - javaValue.put(javaTag.getName(), translatedTag); + builder.put(javaTag.getName(), translatedTag); } } - NbtMapBuilder builder = NbtMap.builder(); - javaValue.forEach(builder::put); return builder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index f4bfdfb6..200271cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -26,20 +26,16 @@ package org.geysermc.connector.network.translators.item.translators; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; import java.util.ArrayList; import java.util.HashMap; @@ -49,54 +45,13 @@ import java.util.stream.Collectors; @ItemRemapper public class BannerTranslator extends ItemTranslator { - private final List appliedItems; public BannerTranslator() { - appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList()); - } - - @Override - public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { - if (itemStack.getNbt() == null) return super.translateToBedrock(itemStack, itemEntry); - - ItemData itemData = super.translateToBedrock(itemStack, itemEntry); - - CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { - ListTag patterns = blockEntityTag.get("Patterns"); - - NbtMapBuilder builder = itemData.getTag().toBuilder(); - builder.put("Patterns", convertBannerPattern(patterns)); - - itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); - } - - return itemData; - } - - @Override - public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - if (itemData.getTag() == null) return super.translateToJava(itemData, itemEntry); - - ItemStack itemStack = super.translateToJava(itemData, itemEntry); - - NbtMap nbtTag = itemData.getTag(); - if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { - List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); - - CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - blockEntityTag.put(convertBannerPattern(patterns)); - - itemStack.getNbt().put(blockEntityTag); - } - - return itemStack; - } - - @Override - public List getAppliedItems() { - return appliedItems; + appliedItems = ItemRegistry.ITEM_ENTRIES.values() + .stream() + .filter(entry -> entry.getJavaIdentifier().endsWith("banner")) + .collect(Collectors.toList()); } /** @@ -133,7 +88,6 @@ public class BannerTranslator extends ItemTranslator { return NbtMap.builder() .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", (String) pattern.get("Pattern").getValue()) .putString("Pattern", patternName) .build(); } @@ -147,8 +101,7 @@ public class BannerTranslator extends ItemTranslator { public static ListTag convertBannerPattern(List patterns) { List tagsList = new ArrayList<>(); for (Object patternTag : patterns) { - CompoundTag newPatternTag = getJavaBannerPattern((NbtMap) patternTag); - tagsList.add(newPatternTag); + tagsList.add(getJavaBannerPattern((NbtMap) patternTag)); } return new ListTag("Patterns", tagsList); @@ -167,4 +120,51 @@ public class BannerTranslator extends ItemTranslator { return new CompoundTag("", tags); } + + @Override + public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) { + if (itemStack.getNbt() == null) { + return super.translateToBedrock(itemStack, itemEntry); + } + + ItemData itemData = super.translateToBedrock(itemStack, itemEntry); + + CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); + if (blockEntityTag.contains("Patterns")) { + ListTag patterns = blockEntityTag.get("Patterns"); + + NbtMapBuilder builder = itemData.getTag().toBuilder(); + builder.put("Patterns", convertBannerPattern(patterns)); + + itemData = ItemData.of(itemData.getId(), itemData.getDamage(), itemData.getCount(), builder.build()); + } + + return itemData; + } + + @Override + public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + if (itemData.getTag() == null) { + return super.translateToJava(itemData, itemEntry); + } + + ItemStack itemStack = super.translateToJava(itemData, itemEntry); + + NbtMap nbtTag = itemData.getTag(); + if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { + List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); + + CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); + blockEntityTag.put(convertBannerPattern(patterns)); + + itemStack.getNbt().put(blockEntityTag); + } + + return itemStack; + } + + @Override + public List getAppliedItems() { + return appliedItems; + } } 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 cd1a321c..741632bc 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 @@ -45,14 +45,13 @@ import org.geysermc.connector.utils.ChunkUtils; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { - /** * Determines if we should process non-full chunks */ - private final boolean isCacheChunks; + private final boolean cacheChunks; public JavaChunkDataTranslator() { - isCacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); } @Override @@ -61,7 +60,7 @@ public class JavaChunkDataTranslator extends PacketTranslator { + private final boolean cacheChunks; + + public JavaUpdateTileEntityTranslator() { + cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); + } @Override public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { @@ -48,16 +54,17 @@ public class JavaUpdateTileEntityTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE && packet.getNbt().size() > 5) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 305118e6..bfd59cc7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -36,7 +36,6 @@ import java.util.Map; * Used for block entities if the Java block state contains Bedrock block information. */ public class BlockStateValues { - private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap(); private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap(); private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap(); @@ -52,7 +51,8 @@ public class BlockStateValues { /** * Determines if the block state contains Bedrock block information - * @param entry The String to JsonNode map used in BlockTranslator + * + * @param entry The String to JsonNode map used in BlockTranslator * @param javaBlockState the Java Block State of the block */ public static void storeBlockStateValues(Map.Entry entry, int javaBlockState) { @@ -101,7 +101,7 @@ public class BlockStateValues { } JsonNode skullVariation = entry.getValue().get("variation"); - if(skullVariation != null) { + if (skullVariation != null) { SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); } @@ -124,10 +124,7 @@ public class BlockStateValues { * @return Banner color integer or -1 if no color */ public static int getBannerColor(int state) { - if (BANNER_COLORS.containsKey(state)) { - return BANNER_COLORS.get(state); - } - return -1; + return BANNER_COLORS.getOrDefault(state, -1); } /** @@ -138,10 +135,7 @@ public class BlockStateValues { * @return Bed color byte or -1 if no color */ public static byte getBedColor(int state) { - if (BED_COLORS.containsKey(state)) { - return BED_COLORS.get(state); - } - return -1; + return BED_COLORS.getOrDefault(state, (byte) -1); } /** @@ -157,6 +151,7 @@ public class BlockStateValues { /** * All double chest values are part of the block state in Java and part of the block entity tag in Bedrock. * This gives the DoubleChestValue that can be calculated into the final tag. + * * @return The map of all DoubleChestValues. */ public static Int2ObjectMap getDoubleChestValues() { @@ -165,6 +160,7 @@ public class BlockStateValues { /** * Get the Int2ObjectMap of flower pot block states to containing plant + * * @return Int2ObjectMap of flower pot values */ public static Int2ObjectMap getFlowerPotValues() { @@ -173,6 +169,7 @@ public class BlockStateValues { /** * Get the map of contained flower pot plants to Bedrock CompoundTag + * * @return Map of flower pot blocks. */ public static Map getFlowerPotBlocks() { @@ -182,18 +179,17 @@ public class BlockStateValues { /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. + * * @param state BlockState of the block * @return note block note integer or -1 if not present */ public static int getNoteblockPitch(int state) { - if (NOTEBLOCK_PITCHES.containsKey(state)) { - return NOTEBLOCK_PITCHES.get(state); - } - return -1; + return NOTEBLOCK_PITCHES.getOrDefault(state, -1); } /** * Get the Int2BooleanMap showing if a piston block state is extended or not. + * * @return the Int2BooleanMap of piston extensions. */ public static Int2BooleanMap getPistonValues() { @@ -212,10 +208,7 @@ public class BlockStateValues { * @return Skull variant byte or -1 if no variant */ public static byte getSkullVariant(int state) { - if (SKULL_VARIANTS.containsKey(state)) { - return SKULL_VARIANTS.get(state); - } - return -1; + return SKULL_VARIANTS.getOrDefault(state, (byte) -1); } /** @@ -226,10 +219,7 @@ public class BlockStateValues { * @return Skull rotation value or -1 if no value */ public static byte getSkullRotation(int state) { - if (SKULL_ROTATIONS.containsKey(state)) { - return SKULL_ROTATIONS.get(state); - } - return -1; + return SKULL_ROTATIONS.getOrDefault(state, (byte) -1); } @@ -241,9 +231,6 @@ public class BlockStateValues { * @return Shulker direction value or -1 if no value */ public static byte getShulkerBoxDirection(int state) { - if (SHULKERBOX_DIRECTIONS.containsKey(state)) { - return SHULKERBOX_DIRECTIONS.get(state); - } - return -1; + return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index 57393a6c..f5e1d594 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -27,39 +27,31 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Banner", regex = "banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBannerColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { int bannerColor = BlockStateValues.getBannerColor(blockState); if (bannerColor != -1) { - tags.put("Base", 15 - bannerColor); + builder.put("Base", 15 - bannerColor); } if (tag.contains("Patterns")) { ListTag patterns = tag.get("Patterns"); - tags.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); + builder.put("Patterns", BannerTranslator.convertBannerPattern(patterns)); } if (tag.contains("CustomName")) { - tags.put("CustomName", tag.get("CustomName").getValue()); + builder.put("CustomName", tag.get("CustomName").getValue()); } - - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 080bdc3b..0067cc41 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -26,27 +26,23 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Bed", regex = "bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getBedColor(blockState) != -1; } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte bedcolor = BlockStateValues.getBedColor(blockState); // Just in case... - if (bedcolor == -1) bedcolor = 0; - tags.put("color", bedcolor); - return tags; + if (bedcolor == -1) { + bedcolor = 0; + } + builder.put("color", bedcolor); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index d2e4537f..646929f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -33,7 +33,6 @@ import org.geysermc.connector.network.session.GeyserSession; * Implemented only if a block is a block entity in Bedrock and not Java Edition. */ public interface BedrockOnlyBlockEntity { - /** * Update the block on Bedrock Edition. * @param session GeyserSession. @@ -49,7 +48,7 @@ public interface BedrockOnlyBlockEntity { * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ static NbtMap getTag(Vector3i position, int blockState) { - if (new FlowerPotBlockEntityTranslator().isBlock(blockState)) { + if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { return FlowerPotBlockEntityTranslator.getTag(blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 4df4fd95..67963652 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -41,10 +41,16 @@ import org.reflections.Reflections; import java.util.HashMap; import java.util.Map; +/** + * The class that all block entities (on both Java and Bedrock) should translate with + */ public abstract class BlockEntityTranslator { - public static final Map BLOCK_ENTITY_TRANSLATORS = new HashMap<>(); - public static ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); + /** + * A list of all block entities that require the Java block state in order to fill out their block entity information. + * This list will be smaller with cache chunks on as we don't need to double-cache data + */ + public static final ObjectArrayList REQUIRES_BLOCK_STATE_LIST = new ObjectArrayList<>(); /** * Contains a list of irregular block entity name translations that can't be fit into the regex @@ -78,27 +84,33 @@ public abstract class BlockEntityTranslator { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_entity.failed", clazz.getCanonicalName())); } } + boolean cacheChunks = GeyserConnector.getInstance().getConfig().isCacheChunks(); for (Class clazz : ref.getSubTypesOf(RequiresBlockState.class)) { GeyserConnector.getInstance().getLogger().debug("Found block entity that requires block state: " + clazz.getCanonicalName()); try { - REQUIRES_BLOCK_STATE_LIST.add((RequiresBlockState) clazz.newInstance()); + RequiresBlockState requiresBlockState = (RequiresBlockState) clazz.newInstance(); + if (cacheChunks && !(requiresBlockState instanceof BedrockOnlyBlockEntity)) { + // Not needed to put this one in the map; cache chunks takes care of that for us + GeyserConnector.getInstance().getLogger().debug("Not adding because cache chunks is enabled."); + continue; + } + REQUIRES_BLOCK_STATE_LIST.add(requiresBlockState); } catch (InstantiationException | IllegalAccessException e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.block_state.failed", clazz.getCanonicalName())); } } } - public abstract Map translateTag(CompoundTag tag, int blockState); + public abstract void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState); public NbtMap getBlockEntityTag(String id, CompoundTag tag, int blockState) { - int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); - int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); - int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + int x = ((IntTag) tag.getValue().get("x")).getValue(); + int y = ((IntTag) tag.getValue().get("y")).getValue(); + int z = ((IntTag) tag.getValue().get("z")).getValue(); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); - Map translatedTags = translateTag(tag, blockState); - translatedTags.forEach(tagBuilder::put); + translateTag(tagBuilder, tag, blockState); return tagBuilder.build(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index d6ac0281..3e4f9fb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -32,22 +32,16 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Campfire", regex = "campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { ListTag items = tag.get("Items"); int i = 1; for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { - tags.put("Item" + i, getItem((CompoundTag) itemTag)); + builder.put("Item" + i, getItem((CompoundTag) itemTag)); i++; } - return tags; } protected NbtMap getItem(CompoundTag tag) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index 6bc940ad..2484dba7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -26,38 +26,33 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.*; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.utils.MessageUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "CommandBlock", regex = "command_block") public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { if (tag.size() < 5) { - return map; // These values aren't here + return; // These values aren't here } // Java infers from the block state, but Bedrock needs it in the tag - map.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); + builder.put("conditionalMode", BlockStateValues.getCommandBlockValues().getOrDefault(blockState, (byte) 0)); // Java and Bedrock values - map.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); - map.put("auto", ((ByteTag) tag.get("auto")).getValue()); - map.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); - map.put("powered", ((ByteTag) tag.get("powered")).getValue()); - map.put("Command", ((StringTag) tag.get("Command")).getValue()); - map.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); - map.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); - map.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); + builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); + builder.put("auto", ((ByteTag) tag.get("auto")).getValue()); + builder.put("CustomName", MessageUtils.getBedrockMessage(((StringTag) tag.get("CustomName")).getValue())); + builder.put("powered", ((ByteTag) tag.get("powered")).getValue()); + builder.put("Command", ((StringTag) tag.get("Command")).getValue()); + builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); + builder.put("TrackOutput", ((ByteTag) tag.get("TrackOutput")).getValue()); + builder.put("UpdateLastExecution", ((ByteTag) tag.get("UpdateLastExecution")).getValue()); if (tag.get("LastExecution") != null) { - map.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); + builder.put("LastExecution", ((LongTag) tag.get("LastExecution")).getValue()); } else { - map.put("LastExecution", (long) 0); + builder.put("LastExecution", (long) 0); } - return map; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index 5b59420e..47bcf489 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -33,15 +33,11 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.DoubleChestValue; import org.geysermc.connector.utils.BlockEntityUtils; -import java.util.HashMap; -import java.util.Map; - /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ @BlockEntity(name = "Chest", regex = "chest") public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getDoubleChestValues().containsKey(blockState); @@ -51,44 +47,39 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl public void updateBlock(GeyserSession session, int blockState, Vector3i position) { CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); - translateTag(javaTag, blockState).forEach(tagBuilder::put); + translateTag(tagBuilder, javaTag, blockState); BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - if (BlockStateValues.getDoubleChestValues().containsKey(blockState)) { - DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().get(blockState); - if (chestValues != null) { - int x = (int) tag.getValue().get("x").getValue(); - int z = (int) tag.getValue().get("z").getValue(); - // Calculate the position of the other chest based on the Java block state - if (chestValues.isFacingEast) { - if (chestValues.isDirectionPositive) { - // East - z = z + (chestValues.isLeft ? 1 : -1); - } else { - // West - z = z + (chestValues.isLeft ? -1 : 1); - } + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + DoubleChestValue chestValues = BlockStateValues.getDoubleChestValues().getOrDefault(blockState, null); + if (chestValues != null) { + int x = (int) tag.getValue().get("x").getValue(); + int z = (int) tag.getValue().get("z").getValue(); + // Calculate the position of the other chest based on the Java block state + if (chestValues.isFacingEast) { + if (chestValues.isDirectionPositive) { + // East + z = z + (chestValues.isLeft ? 1 : -1); } else { - if (chestValues.isDirectionPositive) { - // South - x = x + (chestValues.isLeft ? -1 : 1); - } else { - // North - x = x + (chestValues.isLeft ? 1 : -1); - } + // West + z = z + (chestValues.isLeft ? -1 : 1); } - tags.put("pairx", x); - tags.put("pairz", z); - if (!chestValues.isLeft) { - tags.put("pairlead", (byte) 1); + } else { + if (chestValues.isDirectionPositive) { + // South + x = x + (chestValues.isLeft ? -1 : 1); + } else { + // North + x = x + (chestValues.isLeft ? 1 : -1); } } + builder.put("pairx", x); + builder.put("pairz", z); + if (!chestValues.isLeft) { + builder.put("pairlead", (byte) 1); + } } - return tags; } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index e9715bd3..3926b866 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -26,16 +26,11 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "Empty", regex = "") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - return new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index af94c560..0bf58822 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -28,21 +28,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.Map; @BlockEntity(name = "EndGateway", regex = "end_gateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - tags.put("Age", (int) ((long) tag.get("Age").getValue())); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + builder.put("Age", (int) ((long) tag.get("Age").getValue())); // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Linked coordinates IntList tagsList = new IntArrayList(); @@ -50,8 +47,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { tagsList.add(getExitPortalCoordinate(tag, "X")); tagsList.add(getExitPortalCoordinate(tag, "Y")); tagsList.add(getExitPortalCoordinate(tag, "Z")); - tags.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); - return tags; + builder.put("ExitPortal", new NbtList<>(NbtType.INT, tagsList)); } private int getExitPortalCoordinate(CompoundTag tag, String axis) { @@ -60,6 +56,7 @@ public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); IntTag intTag = (IntTag) compoundTag.get(axis); return intTag.getValue(); - } return 0; + } + return 0; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 7bfcc7ee..f64474ae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -35,30 +35,19 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { - - @Override - public boolean isBlock(int blockState) { - return (BlockStateValues.getFlowerPotValues().containsKey(blockState)); - } - - @Override - public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); - updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); - updateBlockPacket.setBlockPosition(position); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); - session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + /** + * @param blockState the Java block state of a potential flower pot block + * @return true if the block is a flower pot + */ + public static boolean isFlowerBlock(int blockState) { + return BlockStateValues.getFlowerPotValues().containsKey(blockState); } /** * Get the Nukkit CompoundTag of the flower pot. + * * @param blockState Java block state of flower pot. - * @param position Bedrock position of flower pot. + * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -80,4 +69,23 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R } return tagBuilder.build(); } + + @Override + public boolean isBlock(int blockState) { + return isFlowerBlock(blockState); + } + + @Override + public void updateBlock(GeyserSession session, int blockState, Vector3i position) { + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setBlockPosition(position); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); + session.sendUpstreamPacket(updateBlockPacket); + BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java index 43ac1a96..4fcdfe54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -27,21 +27,16 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; - -import java.util.HashMap; -import java.util.Map; +import com.nukkitx.nbt.NbtMapBuilder; @BlockEntity(name = "JigsawBlock", regex = "jigsaw") public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map map = new HashMap<>(); - map.put("joint", ((StringTag) tag.get("joint")).getValue()); - map.put("name", ((StringTag) tag.get("name")).getValue()); - map.put("target_pool", ((StringTag) tag.get("pool")).getValue()); - map.put("final_state", ((StringTag) tag.get("final_state")).getValue()); - map.put("target", ((StringTag) tag.get("target")).getValue()); - return map; + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + builder.put("joint", ((StringTag) tag.get("joint")).getValue()); + builder.put("name", ((StringTag) tag.get("name")).getValue()); + builder.put("target_pool", ((StringTag) tag.get("pool")).getValue()); + builder.put("final_state", ((StringTag) tag.get("final_state")).getValue()); + builder.put("target", ((StringTag) tag.get("target")).getValue()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java index f3e43009..fce0a056 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/NoteblockBlockEntityTranslator.java @@ -36,21 +36,20 @@ import org.geysermc.connector.utils.ChunkUtils; * Does not implement BlockEntityTranslator because it's only a block entity in Bedrock */ public class NoteblockBlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getNoteblockPitch(blockState) != -1; } public static void translate(GeyserSession session, Position position) { - int blockState = ChunkUtils.CACHED_BLOCK_ENTITIES.getOrDefault(position, 0); + int blockState = session.getConnector().getConfig().isCacheChunks() ? + session.getConnector().getWorldManager().getBlockAt(session, position) : + ChunkUtils.CACHED_BLOCK_ENTITIES.removeInt(position); BlockEventPacket blockEventPacket = new BlockEventPacket(); blockEventPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); blockEventPacket.setEventType(0); blockEventPacket.setEventData(BlockStateValues.getNoteblockPitch(blockState)); session.sendUpstreamPacket(blockEventPacket); - - ChunkUtils.CACHED_BLOCK_ENTITIES.remove(position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java index 54feacbe..c8a6e868 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/PistonBlockEntityTranslator.java @@ -34,9 +34,9 @@ import org.geysermc.connector.network.translators.world.block.BlockStateValues; * Pistons are a special case where they are only a block entity on Bedrock. */ public class PistonBlockEntityTranslator { - /** * Used in ChunkUtils to determine if the block is a piston. + * * @param blockState Java BlockState of block. * @return if block is a piston or not. */ @@ -46,8 +46,9 @@ public class PistonBlockEntityTranslator { /** * Calculates the Nukkit CompoundTag to send to the client on chunk + * * @param blockState Java block state of block. - * @param position Bedrock position of piston. + * @param position Bedrock position of piston. * @return Bedrock tag of piston. */ public static NbtMap getTag(int blockState, Vector3i position) { @@ -57,14 +58,13 @@ public class PistonBlockEntityTranslator { .putInt("z", position.getZ()) .putByte("isMovable", (byte) 1) .putString("id", "PistonArm"); - if (BlockStateValues.getPistonValues().containsKey(blockState)) { - boolean extended = BlockStateValues.getPistonValues().get(blockState); - // 1f if extended, otherwise 0f - tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); - // 1 if sticky, 0 if not - tagBuilder.putByte("Sticky", (byte)((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); - } + + boolean extended = BlockStateValues.getPistonValues().get(blockState); + // 1f if extended, otherwise 0f + tagBuilder.putFloat("Progress", (extended) ? 1.0f : 0.0f); + // 1 if sticky, 0 if not + tagBuilder.putByte("Sticky", (byte) ((BlockStateValues.isStickyPiston(blockState)) ? 1 : 0)); + return tagBuilder.build(); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 08e3abaa..69fa1084 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -26,23 +26,18 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "ShulkerBox", regex = "shulker_box") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... - if (direction == -1) direction = 1; - tags.put("facing", direction); - return tags; + if (direction == -1) { + direction = 1; + } + builder.put("facing", direction); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index b40ed42c..a4f800da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -27,51 +27,12 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.SignUtils; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Sign", regex = "sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { - - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); - - StringBuilder signText = new StringBuilder(); - for(int i = 0; i < 4; i++) { - int currentLine = i+1; - String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); - - // Check the character width on the sign to ensure there is no overflow that is usually hidden - // to Java Edition clients but will appear to Bedrock clients - int signWidth = 0; - StringBuilder finalSignLine = new StringBuilder(); - for (char c : signLine.toCharArray()) { - signWidth += SignUtils.getCharacterWidth(c); - if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { - finalSignLine.append(c); - } else { - break; - } - } - - // Java Edition 1.14 added the ability to change the text color of the whole sign using dye - if (tag.contains("Color")) { - signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); - } - - signText.append(finalSignLine.toString()); - signText.append("\n"); - } - - tags.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); - return tags; - } - /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
@@ -133,4 +94,36 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { return base; } + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + StringBuilder signText = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int currentLine = i + 1; + String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); + signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + + // Check the character width on the sign to ensure there is no overflow that is usually hidden + // to Java Edition clients but will appear to Bedrock clients + int signWidth = 0; + StringBuilder finalSignLine = new StringBuilder(); + for (char c : signLine.toCharArray()) { + signWidth += SignUtils.getCharacterWidth(c); + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { + finalSignLine.append(c); + } else { + break; + } + } + + // Java Edition 1.14 added the ability to change the text color of the whole sign using dye + if (tag.contains("Color")) { + signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); + } + + signText.append(finalSignLine.toString()); + signText.append("\n"); + } + + builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 6d350c0c..c5f47994 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -25,29 +25,26 @@ package org.geysermc.connector.network.translators.world.block.entity; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "Skull", regex = "skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { - @Override public boolean isBlock(int blockState) { return BlockStateValues.getSkullVariant(blockState) != -1; } @Override - public Map translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { byte skullVariant = BlockStateValues.getSkullVariant(blockState); float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; // Just in case... - if (skullVariant == -1) skullVariant = 0; - tags.put("Rotation", rotation); - tags.put("SkullType", skullVariant); - return tags; + if (skullVariant == -1) { + skullVariant = 0; + } + builder.put("Rotation", rotation); + builder.put("SkullType", skullVariant); } - } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 2601e3de..38507f54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -26,63 +26,58 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.entity.type.EntityType; -import java.util.HashMap; -import java.util.Map; - @BlockEntity(name = "MobSpawner", regex = "mob_spawner") public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { - @Override - public Map translateTag(CompoundTag tag, int blockState) { - Map tags = new HashMap<>(); + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + Tag current; - if (tag.get("MaxNearbyEntities") != null) { - tags.put("MaxNearbyEntities", (short) tag.get("MaxNearbyEntities").getValue()); + if ((current = tag.get("MaxNearbyEntities")) != null) { + builder.put("MaxNearbyEntities", current.getValue()); } - if (tag.get("RequiredPlayerRange") != null) { - tags.put("RequiredPlayerRange", (short) tag.get("RequiredPlayerRange").getValue()); + if ((current = tag.get("RequiredPlayerRange")) != null) { + builder.put("RequiredPlayerRange", current.getValue()); } - if (tag.get("SpawnCount") != null) { - tags.put("SpawnCount", (short) tag.get("SpawnCount").getValue()); + if ((current = tag.get("SpawnCount")) != null) { + builder.put("SpawnCount", current.getValue()); } - if (tag.get("MaxSpawnDelay") != null) { - tags.put("MaxSpawnDelay", (short) tag.get("MaxSpawnDelay").getValue()); + if ((current = tag.get("MaxSpawnDelay")) != null) { + builder.put("MaxSpawnDelay", current.getValue()); } - if (tag.get("Delay") != null) { - tags.put("Delay", (short) tag.get("Delay").getValue()); + if ((current = tag.get("Delay")) != null) { + builder.put("Delay", current.getValue()); } - if (tag.get("SpawnRange") != null) { - tags.put("SpawnRange", (short) tag.get("SpawnRange").getValue()); + if ((current = tag.get("SpawnRange")) != null) { + builder.put("SpawnRange", current.getValue()); } - if (tag.get("MinSpawnDelay") != null) { - tags.put("MinSpawnDelay", (short) tag.get("MinSpawnDelay").getValue()); + if ((current = tag.get("MinSpawnDelay")) != null) { + builder.put("MinSpawnDelay", current.getValue()); } - if (tag.get("SpawnData") != null) { - CompoundTag spawnData = tag.get("SpawnData"); + CompoundTag spawnData = tag.get("SpawnData"); + if (spawnData != null) { String entityID = (String) spawnData.get("id").getValue(); - tags.put("EntityIdentifier", entityID); + builder.put("EntityIdentifier", entityID); EntityType type = EntityType.getFromIdentifier(entityID); if (type != null) { - tags.put("DisplayEntityWidth", type.getWidth()); - tags.put("DisplayEntityHeight", type.getHeight()); - tags.put("DisplayEntityScale", 1.0f); + builder.put("DisplayEntityWidth", type.getWidth()); + builder.put("DisplayEntityHeight", type.getHeight()); + builder.put("DisplayEntityScale", 1.0f); } } - tags.put("id", "MobSpawner"); - tags.put("isMovable", (byte) 1); - - return tags; + builder.put("id", "MobSpawner"); + builder.put("isMovable", (byte) 1); } - } diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java index 0ae31b33..e8fd8291 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -33,17 +33,17 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; public class BlockEntityUtils { - private static final BlockEntityTranslator EMPTY_TRANSLATOR = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("Empty"); public static String getBedrockBlockEntityId(String id) { // These are the only exceptions when it comes to block entity ids - if (BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.containsKey(id)) { - return BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + String value = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATIONS.get(id); + if (value != null) { + return value; } id = id.replace("minecraft:", "") - .replace("_", " "); + .replace("_", " "); // Split at every space or capital letter - for the latter, some legacy Java block entity tags are the correct format already String[] words; if (!id.toUpperCase().equals(id)) { // Otherwise we get [S, K, U, L, L] @@ -60,11 +60,10 @@ public class BlockEntityUtils { public static BlockEntityTranslator getBlockEntityTranslator(String name) { BlockEntityTranslator blockEntityTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get(name); - if (blockEntityTranslator == null) { - return EMPTY_TRANSLATOR; + if (blockEntityTranslator != null) { + return blockEntityTranslator; } - - return blockEntityTranslator; + return EMPTY_TRANSLATOR; } public static void updateBlockEntity(GeyserSession session, NbtMap blockEntity, Position position) { 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 a63eeb42..0769a4d1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -72,9 +72,9 @@ import static org.geysermc.connector.network.translators.world.block.BlockTransl @UtilityClass public class ChunkUtils { - /** - * Temporarily stores positions of BlockState values that are needed for certain block entities actively + * Temporarily stores positions of BlockState values that are needed for certain block entities actively. + * Not used if cache chunks is enabled */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); @@ -300,11 +300,16 @@ public class ChunkUtils { public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed - if (ItemFrameEntity.positionContainsItemFrame(session, position) && blockState == AIR) { - ((ItemFrameEntity) session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position))).updateBlock(session); - return; - } else if (ItemFrameEntity.positionContainsItemFrame(session, position)) { - Entity entity = session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, position)); + long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); + if (frameEntityId != -1) { + // TODO: Very occasionally the item frame doesn't sync up when destroyed + Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); + if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + ((ItemFrameEntity) entity).updateBlock(session); + return; + } + + // Otherwise the item frame is gone if (entity != null) { session.getEntityCache().removeEntity(entity, false); } else { @@ -342,7 +347,10 @@ public class ChunkUtils { ((BedrockOnlyBlockEntity) requiresBlockState).updateBlock(session, blockState, position); break; } - CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + if (!session.getConnector().getConfig().isCacheChunks()) { + // Blocks aren't saved to a chunk cache; resort to this smaller cache + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + } break; //No block will be a part of two classes } } From 0e15aa7441eff940c8b5f0bbb475eb46b5c0af15 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Thu, 5 Nov 2020 18:42:33 -0500 Subject: [PATCH 056/161] Fireball and ghast improvements (#1469) * Fireball and ghast improvements - Ghasts now visually show if they're charging a fireball - Fireballs are now vastly better and will update better * Add gravity and drag to projectiles * Add check for session close and improve fireball * Remove motion stuff from fireball * Make fireball hittable * Add wither skull entity * Small changes * Add note about laggy fireballs Co-authored-by: David Choo --- .../entity/ItemedFireballEntity.java | 18 +++- .../connector/entity/ThrowableEntity.java | 97 ++++++++++++++++--- .../connector/entity/WitherSkullEntity.java | 58 +++++++++++ .../entity/living/monster/GhastEntity.java | 49 ++++++++++ .../connector/entity/type/EntityType.java | 6 +- 5 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java index e04e0411..1544f767 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java @@ -27,10 +27,24 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; -public class ItemedFireballEntity extends Entity { +public class ItemedFireballEntity extends ThrowableEntity { + private final Vector3f acceleration; public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { - super(entityId, geyserId, entityType, position, motion, rotation); + super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); + acceleration = motion; + } + + @Override + protected void updatePosition(GeyserSession session) { + position = position.add(motion); + // TODO: While this reduces latency in position updating (needed for better fireball reflecting), + // TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not, + // TODO: only use this laggy movement for fireballs that be reflected + moveAbsoluteImmediate(session, position, rotation, false, true); + float drag = getDrag(session); + motion = motion.add(acceleration).mul(drag); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index b3632606..5b7ba5c0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -31,14 +31,23 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +/** + * Used as a class for any object-like entity that moves as a projectile + */ public class ThrowableEntity extends Entity { private Vector3f lastPosition; - private ScheduledFuture positionUpdater; + /** + * Updates the position for the Bedrock client. + * + * Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions + */ + protected ScheduledFuture positionUpdater; public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); @@ -49,20 +58,86 @@ public class ThrowableEntity extends Entity { public void spawnEntity(GeyserSession session) { super.spawnEntity(session); positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> { - super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); - - if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { - float gravity = 0.03f; // Snowball, Egg, and Ender Pearl - if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) { - gravity = 0.05f; - } else if (entityType == EntityType.THROWN_EXP_BOTTLE) { - gravity = 0.07f; - } - motion = motion.down(gravity); + if (session.isClosed()) { + positionUpdater.cancel(true); + return; } + updatePosition(session); }, 0, 50, TimeUnit.MILLISECONDS); } + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + super.moveAbsolute(session, position, rotation, isOnGround, teleported); + } + + protected void updatePosition(GeyserSession session) { + super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + float drag = getDrag(session); + float gravity = getGravity(); + motion = motion.mul(drag).down(gravity); + } + + /** + * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. + * + * @return the amount of gravity to apply to this entity while in motion. + */ + protected float getGravity() { + if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + return 0.05f; + case THROWN_EXP_BOTTLE: + return 0.07f; + case FIREBALL: + return 0; + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.03f; + } + } + return 0; + } + + /** + * @param session the session of the Bedrock client. + * @return the drag that should be multiplied to the entity's motion + */ + protected float getDrag(GeyserSession session) { + if (isInWater(session)) { + return 0.8f; + } else { + switch (entityType) { + case THROWN_POTION: + case LINGERING_POTION: + case THROWN_EXP_BOTTLE: + case SNOWBALL: + case THROWN_EGG: + case THROWN_ENDERPEARL: + return 0.99f; + case FIREBALL: + case SMALL_FIREBALL: + case DRAGON_FIREBALL: + return 0.95f; + } + } + return 1; + } + + /** + * @param session the session of the Bedrock client. + * @return true if this entity is currently in water. + */ + protected boolean isInWater(GeyserSession session) { + if (session.getConnector().getConfig().isCacheChunks()) { + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.BEDROCK_WATER_ID; + } + return false; + } + @Override public boolean despawnEntity(GeyserSession session) { positionUpdater.cancel(true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java new file mode 100644 index 00000000..99b3df3d --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -0,0 +1,58 @@ +/* + * 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.entity; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class WitherSkullEntity extends ItemedFireballEntity { + private boolean isCharged; + + public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + protected float getDrag(GeyserSession session) { + return isCharged ? 0.73f : super.getDrag(session); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 7) { + boolean newIsCharged = (boolean) entityMetadata.getValue(); + if (newIsCharged != isCharged) { + isCharged = newIsCharged; + entityType = isCharged ? EntityType.WITHER_SKULL_DANGEROUS : EntityType.WITHER_SKULL; + despawnEntity(session); + spawnEntity(session); + } + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java new file mode 100644 index 00000000..3d3be87c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/GhastEntity.java @@ -0,0 +1,49 @@ +/* + * 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.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.living.FlyingEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class GhastEntity extends FlyingEntity { + + public GhastEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // If the ghast is attacking + metadata.put(EntityData.CHARGE_AMOUNT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index fddab5a4..7557bd04 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -74,7 +74,7 @@ public enum EntityType { ENDERMAN(EndermanEntity.class, 38, 2.9f, 0.6f), SILVERFISH(MonsterEntity.class, 39, 0.3f, 0.4f), CAVE_SPIDER(MonsterEntity.class, 40, 0.5f, 0.7f), - GHAST(FlyingEntity.class, 41, 4.0f), + GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), @@ -125,9 +125,9 @@ public enum EntityType { THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"), THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"), LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f), - WITHER_SKULL(Entity.class, 89, 0.3125f), + WITHER_SKULL(WitherSkullEntity.class, 89, 0.3125f), BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f), - WITHER_SKULL_DANGEROUS(Entity.class, 91, 0f), + WITHER_SKULL_DANGEROUS(WitherSkullEntity.class, 91, 0f), LIGHTNING_BOLT(Entity.class, 93, 0f), SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f), AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f), From a6cc28ee80072b57b4f6793cc22a6ad6bfd8c90c Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 00:52:09 +0000 Subject: [PATCH 057/161] Add config option for enabling achievements (#1504) * Add config option for enabling achievements * Disabled achievements by default and added warning about commands being disabled * Update config.yml * Rename achievements-enabled to xbox-achievements-enabled for clarity Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../connector/configuration/GeyserConfiguration.java | 2 ++ .../connector/configuration/GeyserJacksonConfiguration.java | 3 +++ .../geysermc/connector/network/session/GeyserSession.java | 4 ++-- connector/src/main/resources/config.yml | 5 +++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index c1cc4d03..153a9174 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -81,6 +81,8 @@ public interface GeyserConfiguration { boolean isForceResourcePacks(); + boolean isXboxAchievementsEnabled(); + int getCacheImages(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 45676fbd..d19cfe49 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -107,6 +107,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("force-resource-packs") private boolean forceResourcePacks = true; + @JsonProperty("xbox-achievements-enabled") + private boolean xboxAchievementsEnabled = false; + private MetricsInfo metrics = new MetricsInfo(); @Getter diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 6a2913c8..4429268a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -634,7 +634,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); startGamePacket.setDefaultSpawn(Vector3i.ZERO); - startGamePacket.setAchievementsDisabled(true); + startGamePacket.setAchievementsDisabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setCurrentTick(-1); startGamePacket.setEduEditionOffers(0); startGamePacket.setEduFeaturesEnabled(false); @@ -645,7 +645,7 @@ public class GeyserSession implements CommandSender { startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true)); startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC); startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC); - startGamePacket.setCommandsEnabled(true); + startGamePacket.setCommandsEnabled(!connector.getConfig().isXboxAchievementsEnabled()); startGamePacket.setTexturePacksRequired(false); startGamePacket.setBonusChestEnabled(false); startGamePacket.setStartingWithMap(false); diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 43e3e8ed..b53fcdd0 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -117,6 +117,11 @@ above-bedrock-nether-building: false # want to download the resource packs force-resource-packs: true +# Allows Xbox achievements to be unlocked. +# This disables certain commands so the Bedrock client can't to "cheat" to get them. +# Commands such as /gamemode and /give will not work from Bedrock with this enabled +xbox-achievements-enabled: false + # 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/. From c7654ff98c3e25bf0e273b513f4a0b37088d0e31 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 18:47:45 +0000 Subject: [PATCH 058/161] Disable the sponsor button (#1509) --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b4b93dca..19b655c2 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: GeyserMC +patreon: #GeyserMC # Disabled currently open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 2d95302b1014ef53357c0f3e29cf91ca95634c1d Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 7 Nov 2020 19:17:17 +0000 Subject: [PATCH 059/161] Add support for passing config options as arguments (#1506) --- .../standalone/GeyserStandaloneBootstrap.java | 151 +++++++++++++++++- connector/src/main/resources/languages | 2 +- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 123a9a60..f58a997e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -25,6 +25,11 @@ package org.geysermc.platform.standalone; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; import org.apache.logging.log4j.LogManager; @@ -36,6 +41,7 @@ import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -50,7 +56,8 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -67,6 +74,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserConnector connector; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); @@ -74,6 +84,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args @@ -106,8 +118,43 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); return; default: - String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised"); - System.err.println(MessageFormat.format(badArgMsg, arg)); + // We have likely added a config option argument + if (arg.startsWith("--")) { + // Split the argument by an = + String[] argParts = arg.substring(2).split("="); + if (argParts.length == 2) { + // Split the config key by . to allow for nested options + String[] configKeyParts = argParts[0].split("\\."); + + // Loop the possible config options to check the passed key is valid + boolean found = false; + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop sub-section options to check the passed key is valid + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + found = true; + break; + } + } + } else { + found = true; + } + + break; + } + } + + // Add the found key to the stored list for later usage + if (found) { + argsConfigKeys.put(argParts[0], argParts[1]); + break; + } + } + } + + System.err.println(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); return; } } @@ -148,6 +195,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); + + handleArgsConfigOptions(); + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); @@ -223,4 +273,99 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); } + + /** + * Get the {@link BeanPropertyDefinition}s for the given class + * + * @param clazz The class to get the definitions for + * @return A list of {@link BeanPropertyDefinition} for the given class + */ + public static List getPOJOForClass(Class clazz) { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); + + // Introspect the given type + BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + + // Find properties + List properties = beanDescription.findProperties(); + + // Get the ignored properties + Set ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector() + .findPropertyIgnorals(beanDescription.getClassInfo()).getIgnored(); + + // Filter properties removing the ignored ones + return properties.stream() + .filter(property -> !ignoredProperties.contains(property.getName())) + .collect(Collectors.toList()); + } + + /** + * Set a POJO property value on an object + * + * @param property The {@link BeanPropertyDefinition} to set + * @param parentObject The object to alter + * @param value The new value of the property + */ + private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { + Object parsedValue = value; + + // Change the values type if needed + if (int.class.equals(property.getRawPrimaryType())) { + parsedValue = Integer.valueOf((String) parsedValue); + } else if (boolean.class.equals(property.getRawPrimaryType())) { + parsedValue = Boolean.valueOf((String) parsedValue); + } + + // Force the value to be set + AnnotatedField field = property.getField(); + field.fixAccess(true); + field.setValue(parentObject, parsedValue); + } + + /** + * Update the loaded {@link GeyserStandaloneConfiguration} with any values passed in the command line arguments + */ + private void handleArgsConfigOptions() { + // Get the available properties from the class + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + + for (Map.Entry configKey : argsConfigKeys.entrySet()) { + String[] configKeyParts = configKey.getKey().split("\\."); + + // Loop over the properties looking for any matches against the stored one from the argument + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop through the sub property if the first part matches + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the sub property value on the config + try { + Object subConfig = property.getGetter().callOn(geyserConfig); + setConfigOption(subProperty, subConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + + break; + } + } + } else { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the property value on the config + try { + setConfigOption(property, geyserConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + } + + break; + } + } + } + } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index b7ef31bd..bf4b0b71 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit b7ef31bd9c45aa3a0735883764c231f30cb55bfa +Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a From e00715ceabf9246cbfc63e72a559a1953f5fc156 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 9 Nov 2020 10:34:27 +0100 Subject: [PATCH 060/161] Fixed some issues related to Scoreboards (#1446) * Fixes some issues related to Scoreboard Teams * The cached Score info should update no matter what kind of update the Team got. * Team entities specified at the create Team packet should also be checked if they exist as Score in the registered Objectives * Rewrote some Scoreboard code and fixed various issues * Minor formatting changes --- .../connector/entity/PlayerEntity.java | 17 +- .../JavaDisplayScoreboardTranslator.java | 5 +- .../JavaScoreboardObjectiveTranslator.java | 17 +- .../connector/scoreboard/Objective.java | 62 ++++--- .../geysermc/connector/scoreboard/Score.java | 99 +++++++++--- .../connector/scoreboard/Scoreboard.java | 135 +++++++++------- .../geysermc/connector/scoreboard/Team.java | 152 +++++++++++++++--- 7 files changed, 333 insertions(+), 154 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 390110d1..bfadd83c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -27,7 +27,6 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; @@ -235,18 +234,12 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - // Cover different visibility settings - if (team.getNameTagVisibility() == NameTagVisibility.NEVER) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OTHER_TEAMS && - !team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else if (team.getNameTagVisibility() == NameTagVisibility.HIDE_FOR_OWN_TEAM && - team.getEntities().contains(session.getPlayerEntity().getUsername())) { - metadata.put(EntityData.NAMETAG, ""); - } else { - metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix()); + String displayName = ""; + if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { + displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = team.getCurrentData().getDisplayName(displayName); } + metadata.put(EntityData.NAMETAG, displayName); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java index 3ee174d7..b42ac78e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaDisplayScoreboardTranslator.java @@ -36,8 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator cachedData.updateTime || + (currentData.team != null && currentData.team.shouldUpdate()); + } + + public void update(String objectiveName) { + if (cachedData == null) { + cachedData = new ScoreData(); + cachedData.updateType = UpdateType.ADD; + if (currentData.updateType == UpdateType.REMOVE) { + cachedData.updateType = UpdateType.REMOVE; + } + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.team = currentData.team; + cachedData.score = currentData.score; + + String name = this.name; + if (cachedData.team != null) { + cachedData.team.prepareUpdate(); + name = cachedData.team.getDisplayName(name); + } + cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name); + } + + @Getter + public static final class ScoreData { + protected UpdateType updateType; + protected long updateTime; + + private Team team; + private int score; + + protected ScoreData() { + updateType = UpdateType.ADD; + } } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 732a056e..8eaa2e27 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicLong; import static org.geysermc.connector.scoreboard.UpdateType.*; @Getter -public class Scoreboard { +public final class Scoreboard { private final GeyserSession session; private final GeyserLogger logger; private final AtomicLong nextId = new AtomicLong(0); @@ -51,7 +51,8 @@ public class Scoreboard { private final Map objectives = new ConcurrentHashMap<>(); private final Map teams = new HashMap<>(); - private int lastScoreCount = 0; + private int lastAddScoreCount = 0; + private int lastRemoveScoreCount = 0; public Scoreboard(GeyserSession session) { this.session = session; @@ -59,19 +60,21 @@ public class Scoreboard { } public Objective registerNewObjective(String objectiveId, boolean active) { - if (active || objectives.containsKey(objectiveId)) { - return objectives.get(objectiveId); + Objective objective = objectives.get(objectiveId); + if (active || objective != null) { + return objective; } - Objective objective = new Objective(this, objectiveId); + objective = new Objective(this, objectiveId); objectives.put(objectiveId, objective); return objective; } - public Objective registerNewObjective(String objectiveId, ScoreboardPosition displaySlot) { + public Objective displayObjective(String objectiveId, ScoreboardPosition displaySlot) { Objective objective = objectives.get(objectiveId); if (objective != null) { if (!objective.isActive()) { objective.setActive(displaySlot); + removeOldObjectives(objective); return objective; } despawnObjective(objective); @@ -79,9 +82,21 @@ public class Scoreboard { objective = new Objective(this, objectiveId, displaySlot, "unknown", 0); objectives.put(objectiveId, objective); + removeOldObjectives(objective); return objective; } + private void removeOldObjectives(Objective newObjective) { + for (Objective next : objectives.values()) { + if (next.getId() == newObjective.getId()) { + continue; + } + if (next.getDisplaySlot() == newObjective.getDisplaySlot()) { + next.setUpdateType(REMOVE); + } + } + } + public Team registerNewTeam(String teamName, Set players) { Team team = teams.get(teamName); if (team != null) { @@ -89,7 +104,7 @@ public class Scoreboard { return team; } - team = new Team(this, teamName).setEntities(players); + team = new Team(this, teamName).addEntities(players); teams.put(teamName, team); return team; } @@ -117,8 +132,9 @@ public class Scoreboard { } public void onUpdate() { - List addScores = new ArrayList<>(getLastScoreCount()); - List removeScores = new ArrayList<>(getLastScoreCount()); + List addScores = new ArrayList<>(getLastAddScoreCount()); + List removeScores = new ArrayList<>(getLastRemoveScoreCount()); + List removedObjectives = new ArrayList<>(); for (Objective objective : objectives.values()) { if (!objective.isActive()) { @@ -129,65 +145,58 @@ public class Scoreboard { // hearts can't hold teams, so we treat them differently if (objective.getType() == 1) { for (Score score : objective.getScores().values()) { - if (score.getUpdateType() == NOTHING) { - continue; - } + boolean update = score.shouldUpdate(); - boolean update = score.getUpdateType() == UPDATE; if (update) { - score.update(); + score.update(objective.getObjectiveName()); } - if (score.getUpdateType() == ADD || update) { + if (score.getUpdateType() != REMOVE && update) { addScores.add(score.getCachedInfo()); } - if (score.getUpdateType() == REMOVE || update) { + if (score.getUpdateType() != ADD && update) { removeScores.add(score.getCachedInfo()); } } continue; } - boolean globalUpdate = objective.getUpdateType() == UPDATE; - boolean globalAdd = objective.getUpdateType() == ADD; - boolean globalRemove = objective.getUpdateType() == REMOVE; + boolean objectiveUpdate = objective.getUpdateType() == UPDATE; + boolean objectiveAdd = objective.getUpdateType() == ADD; + boolean objectiveRemove = objective.getUpdateType() == REMOVE; for (Score score : objective.getScores().values()) { Team team = score.getTeam(); - boolean add = globalAdd || globalUpdate; - boolean remove = globalRemove; - boolean teamChanged = false; + boolean add = objectiveAdd || objectiveUpdate; + boolean remove = false; if (team != null) { if (team.getUpdateType() == REMOVE || !team.hasEntity(score.getName())) { score.setTeam(null); - teamChanged = true; + add = true; + remove = true; } - - teamChanged |= team.getUpdateType() == UPDATE; - - add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE; - remove |= team.getUpdateType() != NOTHING; } - add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE; - remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE; + add |= score.shouldUpdate(); + remove |= score.shouldUpdate(); - if (score.getUpdateType() == REMOVE || globalRemove) { + if (score.getUpdateType() == REMOVE || objectiveRemove) { add = false; } - if (score.getUpdateType() == ADD) { + if (score.getUpdateType() == ADD || objectiveRemove) { remove = false; } - if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) { - score.update(); + if (score.shouldUpdate()) { + score.update(objective.getObjectiveName()); } if (add) { addScores.add(score.getCachedInfo()); } + if (remove) { removeScores.add(score.getCachedInfo()); } @@ -200,17 +209,17 @@ public class Scoreboard { score.setUpdateType(NOTHING); } - if (globalRemove || globalUpdate) { + if (objectiveRemove) { + removedObjectives.add(objective); + } + + if (objectiveUpdate) { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - if (globalRemove) { - objectives.remove(objective.getObjectiveName()); // now we can deregister - objective.removed(); - } } - if ((globalAdd || globalUpdate) && !globalRemove) { + if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket(); displayObjectivePacket.setObjectiveId(objective.getObjectiveName()); displayObjectivePacket.setDisplayName(objective.getDisplayName()); @@ -223,6 +232,20 @@ public class Scoreboard { objective.setUpdateType(NOTHING); } + Iterator teamIterator = teams.values().iterator(); + while (teamIterator.hasNext()) { + Team current = teamIterator.next(); + + switch (current.getUpdateType()) { + case ADD: + case UPDATE: + current.markUpdated(); + break; + case REMOVE: + teamIterator.remove(); + } + } + if (!removeScores.isEmpty()) { SetScorePacket setScorePacket = new SetScorePacket(); setScorePacket.setAction(SetScorePacket.Action.REMOVE); @@ -237,37 +260,27 @@ public class Scoreboard { session.sendUpstreamPacket(setScorePacket); } - lastScoreCount = addScores.size(); + // prevents crashes in some cases + for (Objective objective : removedObjectives) { + despawnObjective(objective); + } + + lastAddScoreCount = addScores.size(); + lastRemoveScoreCount = removeScores.size(); } public void despawnObjective(Objective objective) { + objectives.remove(objective.getObjectiveName()); + objective.removed(); + RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); - objectives.remove(objective.getDisplayName()); - - List toRemove = new ArrayList<>(); - for (String identifier : objective.getScores().keySet()) { - Score score = objective.getScores().get(identifier); - toRemove.add(new ScoreInfo( - score.getId(), score.getObjective().getObjectiveName(), - 0, "" - )); - } - - objective.removed(); - - if (!toRemove.isEmpty()) { - SetScorePacket setScorePacket = new SetScorePacket(); - setScorePacket.setAction(SetScorePacket.Action.REMOVE); - setScorePacket.setInfos(toRemove); - session.sendUpstreamPacket(setScorePacket); - } } public Team getTeamFor(String entity) { for (Team team : teams.values()) { - if (team.getEntities().contains(entity)) { + if (team.hasEntity(entity)) { return team; } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index a073e2e9..377a15f1 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -28,6 +28,7 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.NameTagVisibility; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -36,62 +37,90 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -@Getter @Setter +@Getter @Accessors(chain = true) -public class Team { +public final class Team { private final Scoreboard scoreboard; private final String id; - private UpdateType updateType = UpdateType.ADD; - private String name; + @Getter(AccessLevel.NONE) + private final Set entities; + @Setter private NameTagVisibility nameTagVisibility; + @Setter private TeamColor color; - private NameTagVisibility nameTagVisibility; - private String prefix; - private TeamColor color; - private String suffix; - private Set entities = new ObjectOpenHashSet<>(); + private TeamData currentData; + private TeamData cachedData; + + private boolean updating; public Team(Scoreboard scoreboard, String id) { this.scoreboard = scoreboard; this.id = id; + currentData = new TeamData(); + entities = new ObjectOpenHashSet<>(); } - public void addEntities(String... names) { - List added = new ArrayList<>(); - for (String name : names) { - if (entities.add(name)) { - added.add(name); - } + private void checkAddedEntities(List added) { + if (added.size() == 0) { + return; } - setUpdateType(UpdateType.UPDATE); + // we don't have to change the updateType, + // because the scores itself need updating, not the team for (Objective objective : scoreboard.getObjectives().values()) { - for (Score score : objective.getScores().values()) { - if (added.contains(score.getName())) { + for (String addedEntity : added) { + Score score = objective.getScores().get(addedEntity); + if (score != null) { score.setTeam(this); } } } } + public Team addEntities(String... names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + + public Team addEntities(Set names) { + List added = new ArrayList<>(); + for (String name : names) { + if (entities.add(name)) { + added.add(name); + } + } + checkAddedEntities(added); + return this; + } + public void removeEntities(String... names) { for (String name : names) { entities.remove(name); } - setUpdateType(UpdateType.UPDATE); } public boolean hasEntity(String name) { return entities.contains(name); } + public Team setName(String name) { + currentData.name = name; + return this; + } + public Team setPrefix(String prefix) { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (prefix.length() == 4 && "null".equals(prefix)) { - this.prefix = ""; + currentData.prefix = ""; return this; } - this.prefix = prefix; + currentData.prefix = prefix; return this; } @@ -99,15 +128,92 @@ public class Team { // replace "null" to an empty string, // we do this here to improve the performance of Score#getDisplayName if (suffix.length() == 4 && "null".equals(suffix)) { - this.suffix = ""; + currentData.suffix = ""; return this; } - this.suffix = suffix; + currentData.suffix = suffix; return this; } + public String getDisplayName(String score) { + return cachedData != null ? + cachedData.getDisplayName(score) : + currentData.getDisplayName(score); + } + + public void markUpdated() { + updating = false; + } + + public boolean shouldUpdate() { + return updating || cachedData == null || currentData.updateTime > cachedData.updateTime; + } + + public void prepareUpdate() { + if (updating) { + return; + } + updating = true; + + if (cachedData == null) { + cachedData = new TeamData(); + cachedData.updateType = currentData.updateType != UpdateType.REMOVE ? UpdateType.ADD : UpdateType.REMOVE; + } else { + cachedData.updateType = currentData.updateType; + } + + cachedData.updateTime = currentData.updateTime; + cachedData.name = currentData.name; + cachedData.prefix = currentData.prefix; + cachedData.suffix = currentData.suffix; + } + + public UpdateType getUpdateType() { + return cachedData != null ? cachedData.updateType : currentData.updateType; + } + + public Team setUpdateType(UpdateType updateType) { + if (updateType != UpdateType.NOTHING) { + currentData.updateTime = System.currentTimeMillis(); + } + currentData.updateType = updateType; + return this; + } + + public boolean isVisibleFor(String entity) { + switch (nameTagVisibility) { + case HIDE_FOR_OTHER_TEAMS: + return hasEntity(entity); + case HIDE_FOR_OWN_TEAM: + return !hasEntity(entity); + case ALWAYS: + return true; + case NEVER: + return false; + } + return true; + } + @Override public int hashCode() { return id.hashCode(); } + + @Getter + public static final class TeamData { + protected UpdateType updateType; + protected long updateTime; + + protected String name; + protected String prefix; + protected String suffix; + + protected TeamData() { + updateType = UpdateType.ADD; + } + + public String getDisplayName(String score) { + return prefix + score + suffix; + } + } } From 109922f796c9f7c0f136e8a536357215a7afcf0c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 9 Nov 2020 14:26:29 -0500 Subject: [PATCH 061/161] Update MCProtocolLib to fix datapacks that send empty result recipes (#1522) Example: https://www.planetminecraft.com/data-pack/true-survival-a-hardcore-minecraft-experience/ --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index edf14abb..ee3164e1 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -109,9 +109,9 @@ compile
- com.github.GeyserMC + com.github.steveice10 mcprotocollib - e4181064d1 + 6ac79c14d6 compile From 4237503d6d0ae7c942f74f505f7706eba9996626 Mon Sep 17 00:00:00 2001 From: David Choo Date: Tue, 10 Nov 2020 12:05:16 -0500 Subject: [PATCH 062/161] Fix laying crash (#1510) * Fix crash with GSit lay and use Java bed position data * Fix GSit's lay position * Move Bed Position metadata to the right class * Actually fix lay for PosePlugin * Revert "Actually fix lay for PosePlugin" This reverts commit 3f21261162c439b3b167f057e75c2884193b75f7. Co-authored-by: DoctorMacc --- .../org/geysermc/connector/entity/Entity.java | 12 ---------- .../connector/entity/LivingEntity.java | 14 ++++++++++++ .../connector/entity/PlayerEntity.java | 13 +++++++++++ .../living/merchant/VillagerEntity.java | 7 +++--- .../spawn/JavaSpawnPlayerTranslator.java | 22 ++++++++++++------- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2dfb0c04..e579f4e9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -339,18 +339,6 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); // Has to be a byte or it does not work metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); - if (entityId == session.getPlayerEntity().getEntityId()) { - Vector3i lastInteractionPos = session.getLastInteractionPosition(); - metadata.put(EntityData.BED_POSITION, lastInteractionPos); - if (session.getConnector().getConfig().isCacheChunks()) { - int bed = session.getConnector().getWorldManager().getBlockAt(session, lastInteractionPos.getX(), - lastInteractionPos.getY(), lastInteractionPos.getZ()); - // Bed has to be updated, or else player is floating in the air - ChunkUtils.updateBlock(session, bed, lastInteractionPos); - } - } else { - metadata.put(EntityData.BED_POSITION, Vector3i.from(position.getFloorX(), position.getFloorY() - 2, position.getFloorZ())); - } metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index ae9eaee5..345c19de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -26,7 +26,9 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -42,6 +44,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; +import org.geysermc.connector.utils.ChunkUtils; import java.util.ArrayList; import java.util.List; @@ -84,6 +87,17 @@ public class LivingEntity extends Entity { case 10: metadata.put(EntityData.EFFECT_AMBIENT, (byte) ((boolean) entityMetadata.getValue() ? 1 : 0)); break; + case 13: // Bed Position + Position bedPosition = (Position) entityMetadata.getValue(); + if (bedPosition != null) { + metadata.put(EntityData.BED_POSITION, Vector3i.from(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ())); + if (session.getConnector().getConfig().isCacheChunks()) { + int bed = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); + // Bed has to be updated, or else player is floating in the air + ChunkUtils.updateBlock(session, bed, bedPosition); + } + } + break; } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index bfadd83c..8eeae473 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -30,10 +30,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.message.TextMessage; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; @@ -167,6 +169,17 @@ public class PlayerEntity extends LivingEntity { movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); + // If the player is moved while sleeping, we have to adjust their y, so it appears + // correctly on Bedrock. This fixes GSit's lay. + if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) { + // Force the player movement by using a teleport + movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - entityType.getOffset() + 0.2f, position.getZ())); + movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + } + } session.sendUpstreamPacket(movePlayerPacket); if (leftParrot != null) { leftParrot.moveRelative(session, relX, relY, relZ, rotation, true); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 7ada302c..98d5a631 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; @@ -99,9 +100,9 @@ public class VillagerEntity extends AbstractMerchantEntity { int bedId = 0; float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; - if (session.getConnector().getConfig().isCacheChunks()) { - Position bedLocation = new Position((int) position.getFloorX(), (int) position.getFloorY(), (int) position.getFloorZ()); - bedId = session.getConnector().getWorldManager().getBlockAt(session, bedLocation); + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { + bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } String bedRotationZ = BlockTranslator.getJavaIdBlockMap().inverse().get(bedId); setRotation(rotation); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 47beca8a..99a6b049 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -43,15 +43,21 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator Date: Wed, 11 Nov 2020 18:13:13 +0000 Subject: [PATCH 063/161] Fix even more entity metadata flags (#1483) * Fix even more entity metadata flags * Add comment explaining magic value * Fix horse flags and add more information * Add more information about the Horse eating particles --- .../connector/entity/living/BatEntity.java | 48 ++++++++++++++++++ .../entity/living/SnowGolemEntity.java | 49 ++++++++++++++++++ .../entity/living/animal/FoxEntity.java | 1 + .../entity/living/animal/MooshroomEntity.java | 47 +++++++++++++++++ .../entity/living/animal/RabbitEntity.java | 9 ++++ .../entity/living/animal/TurtleEntity.java | 49 ++++++++++++++++++ .../animal/horse/AbstractHorseEntity.java | 28 +++++++++++ .../entity/living/monster/VexEntity.java | 50 +++++++++++++++++++ .../connector/entity/type/EntityType.java | 10 ++-- .../translators/item/ItemRegistry.java | 7 +++ 10 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java new file mode 100644 index 00000000..b7b7534c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/BatEntity.java @@ -0,0 +1,48 @@ +/* + * 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.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class BatEntity extends AmbientEntity { + + public BatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.RESTING, (xd & 0x01) == 0x01); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java new file mode 100644 index 00000000..2f75e645 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/SnowGolemEntity.java @@ -0,0 +1,49 @@ +/* + * 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.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class SnowGolemEntity extends GolemEntity { + + public SnowGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Handle the visibility of the pumpkin + metadata.getFlags().setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 88c30cbf..1d924994 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -47,6 +47,7 @@ public class FoxEntity extends AnimalEntity { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.INTERESTED, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.SLEEPING, (xd & 0x20) == 0x20); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java new file mode 100644 index 00000000..69fb55fb --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/MooshroomEntity.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class MooshroomEntity extends AnimalEntity { + + public MooshroomEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + metadata.put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 0b61713a..79202792 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -48,6 +48,15 @@ public class RabbitEntity extends AnimalEntity { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } + } else if (entityMetadata.getId() == 16) { + int variant = (int) entityMetadata.getValue(); + + // Change the killer bunny to display as white since it only exists on Java Edition + if (variant == 99) { + variant = 1; + } + + metadata.put(EntityData.VARIANT, variant); } } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java new file mode 100644 index 00000000..555e2268 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/TurtleEntity.java @@ -0,0 +1,49 @@ +/* + * 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.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class TurtleEntity extends AnimalEntity { + + public TurtleEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 17) { + metadata.getFlags().setFlag(EntityFlag.IS_PREGNANT, (boolean) entityMetadata.getValue()); + } else if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.LAYING_EGG, (boolean) entityMetadata.getValue()); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index e08f9adf..cf9f84b4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -27,10 +27,14 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class AbstractHorseEntity extends AnimalEntity { @@ -47,6 +51,30 @@ public class AbstractHorseEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10); metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20); + + // HorseFlags + // Bred 0x10 + // Eating 0x20 + // Open mouth 0x80 + int horseFlags = 0x0; + horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags; + + // Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation + horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags; + + // Set the flags into the display item + metadata.put(EntityData.DISPLAY_ITEM, horseFlags); + + // Send the eating particles + // We use the wheat metadata as static particles since Java + // doesn't send over what item was used to feed the horse + if ((xd & 0x40) == 0x40) { + EntityEventPacket entityEventPacket = new EntityEventPacket(); + entityEventPacket.setRuntimeEntityId(geyserId); + entityEventPacket.setType(EntityEventType.EATING_ITEM); + entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16); + session.sendUpstreamPacket(entityEventPacket); + } } // Needed to control horses diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java new file mode 100644 index 00000000..70e41329 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/VexEntity.java @@ -0,0 +1,50 @@ +/* + * 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.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class VexEntity extends MonsterEntity { + + public VexEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + byte xd = (byte) entityMetadata.getValue(); + // Set the target to the player to force the attack animation + // even if the player isn't the target as we dont get the target on Java + metadata.put(EntityData.TARGET_EID, (xd & 0x01) == 0x01 ? session.getPlayerEntity().getGeyserId() : 0); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 7557bd04..f023ca10 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -47,12 +47,12 @@ public enum EntityType { SHEEP(SheepEntity.class, 13, 1.3f, 0.9f), WOLF(WolfEntity.class, 14, 0.85f, 0.6f), VILLAGER(VillagerEntity.class, 15, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:villager_v2"), - MOOSHROOM(AnimalEntity.class, 16, 1.4f, 0.9f), + MOOSHROOM(MooshroomEntity.class, 16, 1.4f, 0.9f), SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), - BAT(AmbientEntity.class, 19, 0.9f, 0.5f), + BAT(BatEntity.class, 19, 0.9f, 0.5f), IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), - SNOW_GOLEM(GolemEntity.class, 21, 1.9f, 0.7f), + SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), DONKEY(ChestedHorseEntity.class, 24, 1.6f, 1.3965f), @@ -109,7 +109,7 @@ public enum EntityType { END_CRYSTAL(EnderCrystalEntity.class, 71, 2.0f, 2.0f, 2.0f, 0f, "minecraft:ender_crystal"), FIREWORK_ROCKET(FireworkEntity.class, 72, 0.25f, 0.25f, 0.25f, 0f, "minecraft:fireworks_rocket"), TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), - TURTLE(AnimalEntity.class, 74, 0.4f, 1.2f), + TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), SHULKER_BULLET(Entity.class, 76, 0.3125f), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), @@ -141,7 +141,7 @@ public enum EntityType { LLAMA_SPIT(Entity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), - VEX(MonsterEntity.class, 105, 0.8f, 0.4f), + VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), BALLOON(Entity.class, 107, 0f), //TODO PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index b52d27ff..8370ba8e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -75,6 +75,10 @@ public class ItemRegistry { * Shield item entry, used in Entity.java and LivingEntity.java */ public static ItemEntry SHIELD; + /** + * Wheat item entry, used in AbstractHorseEntity.java + */ + public static ItemEntry WHEAT; public static int BARRIER_INDEX = 0; @@ -157,6 +161,9 @@ public class ItemRegistry { case "minecraft:bucket": BUCKET = ITEM_ENTRIES.get(itemIndex); break; + case "minecraft:wheat": + WHEAT = ITEM_ENTRIES.get(itemIndex); + break; default: break; } From e748240a02c9eb7062fb114f3dcd285f4efb47fe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 11 Nov 2020 19:28:45 -0500 Subject: [PATCH 064/161] Add more interactive tags (mobile buttons) (#1443) * Add more interactive tags (mobile buttons) This expands our support for showing the interactive tags on touchscreen and console setups. This is not complete - specifically, the food compatibility of creatures needs to be expanded upon (I will work on this later and does not stop this PR from being mergable). This also includes: - Creepers who are ignited with flint and steel now show up properly - Zombie villagers now shake properly when converting and show their region outfits * Add more food choices and add more panda entity metadata * Re-add eating flag * Remove debug line * Refactor dimension usage, finish interactive tag usage, bees * Print statements... ._. * Don't make eating item packet data a non-constant * Move BAMBOO to ItemRegistry * Add missing break * Make changes * Minor final changes --- .../standalone/GeyserStandaloneBootstrap.java | 4 +- .../geysermc/connector/GeyserConnector.java | 3 +- .../org/geysermc/connector/entity/Entity.java | 3 - .../entity/living/animal/BeeEntity.java | 18 +- .../entity/living/animal/FoxEntity.java | 2 +- .../entity/living/animal/HoglinEntity.java | 50 +++ .../entity/living/animal/PandaEntity.java | 54 ++- .../living/animal/horse/HorseEntity.java | 3 +- .../living/animal/tameable/CatEntity.java | 13 +- .../animal/tameable/TameableEntity.java | 24 +- .../living/merchant/VillagerEntity.java | 15 +- .../living/monster/BasePiglinEntity.java | 16 +- .../entity/living/monster/CreeperEntity.java | 13 +- .../entity/living/monster/PiglinEntity.java | 2 +- .../living/monster/ZombieVillagerEntity.java | 56 +++ .../connector/entity/type/EntityType.java | 4 +- .../network/session/GeyserSession.java | 9 +- .../player/BedrockInteractTranslator.java | 381 +++++++++++++++--- .../translators/item/ItemRegistry.java | 7 + .../java/JavaJoinGameTranslator.java | 4 +- .../java/JavaRespawnTranslator.java | 4 +- .../entity/JavaEntityAttachTranslator.java | 2 +- .../entity/JavaEntityStatusTranslator.java | 20 +- .../java/world/JavaMapDataTranslator.java | 2 +- .../world/JavaSpawnParticleTranslator.java | 2 +- .../world/JavaSpawnPositionTranslator.java | 2 +- .../connector/utils/DimensionUtils.java | 29 +- .../geysermc/connector/utils/LocaleUtils.java | 11 +- 28 files changed, 643 insertions(+), 110 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f58a997e..f4dfd454 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -103,11 +103,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { case "--config": case "-c": if (i >= args.length - 1) { - System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c")); + System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c")); return; } configFilenameOpt = args[i+1]; i++; - System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt)); + System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt)); break; case "--help": case "-h": diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1d535f54..2497166c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -179,8 +179,7 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); - if (config.isAboveBedrockNetherBuilding()) - DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether + DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether // https://github.com/GeyserMC/Geyser/issues/957 RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index e579f4e9..20cd2f76 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -67,8 +67,6 @@ public class Entity { protected long entityId; protected long geyserId; - protected String dimension; - protected Vector3f position; protected Vector3f motion; @@ -100,7 +98,6 @@ public class Entity { this.rotation = rotation; this.valid = false; - this.dimension = "minecraft:overworld"; setPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java index c46f00fe..ee17e2a2 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/BeeEntity.java @@ -27,7 +27,10 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,10 +44,23 @@ public class BeeEntity extends AnimalEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { byte xd = (byte) entityMetadata.getValue(); - metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); + // Bee is performing sting attack; trigger animation + if ((xd & 0x02) == 0x02) { + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.ATTACK_START); + packet.setData(0); + session.sendUpstreamPacket(packet); + } + // If the bee has stung + metadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0); // If the bee has nectar or not metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); } + if (entityMetadata.getId() == 17) { + // Converting "anger time" to a boolean + metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0); + } super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java index 1d924994..bbc2d7de 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/FoxEntity.java @@ -41,7 +41,7 @@ public class FoxEntity extends AnimalEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 16) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { byte xd = (byte) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java new file mode 100644 index 00000000..3fd29172 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/HoglinEntity.java @@ -0,0 +1,50 @@ +/* + * 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.entity.living.animal; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; + +public class HoglinEntity extends AnimalEntity { + + public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 16) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java index 7e555122..ed3ed80b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/PandaEntity.java @@ -27,23 +27,75 @@ package org.geysermc.connector.entity.living.animal; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemRegistry; public class PandaEntity extends AnimalEntity { + private int mainGene; + private int hiddenGene; + public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0); + metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue()); + if ((int) entityMetadata.getValue() != 0) { + // Particles and sound + EntityEventPacket packet = new EntityEventPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setType(EntityEventType.EATING_ITEM); + packet.setData(ItemRegistry.BAMBOO.getBedrockId() << 16); + session.sendUpstreamPacket(packet); + } + } + if (entityMetadata.getId() == 19) { + mainGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } + if (entityMetadata.getId() == 20) { + hiddenGene = (int) (byte) entityMetadata.getValue(); + updateAppearance(); + } if (entityMetadata.getId() == 21) { byte xd = (byte) entityMetadata.getValue(); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); - metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04); + metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08); + // Required to put these both for sitting to actually show + metadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f); + metadata.getFlags().setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10); } super.updateBedrockMetadata(entityMetadata, session); } + + /** + * Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up + * when both main and hidden genes match + */ + private void updateAppearance() { + if (mainGene == 4 || mainGene == 5) { + // Main gene is a recessive trait + if (mainGene == hiddenGene) { + // Main and hidden genes match; this is what the panda looks like. + metadata.put(EntityData.VARIANT, mainGene); + } else { + // Genes have no effect on appearance + metadata.put(EntityData.VARIANT, 0); + } + } else { + // No need to worry about hidden gene + metadata.put(EntityData.VARIANT, mainGene); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index da3ff349..349da5e0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -28,7 +28,6 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -41,7 +40,7 @@ public class HorseEntity extends AbstractHorseEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 18) { - metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); + metadata.put(EntityData.VARIANT, entityMetadata.getValue()); metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java index 067a360c..5c5de546 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/CatEntity.java @@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession; public class CatEntity extends TameableEntity { + private byte collarColor; + public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -45,6 +47,13 @@ public class CatEntity extends TameableEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 16) { + // Update collar color if tamed + if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { + metadata.put(EntityData.COLOR, collarColor); + } + } if (entityMetadata.getId() == 18) { // Different colors in Java and Bedrock for some reason int variantColor; @@ -67,11 +76,11 @@ public class CatEntity extends TameableEntity { metadata.put(EntityData.VARIANT, variantColor); } if (entityMetadata.getId() == 21) { + collarColor = (byte) (int) entityMetadata.getValue(); // Needed or else wild cats are a red color if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); + metadata.put(EntityData.COLOR, collarColor); } } - super.updateBedrockMetadata(entityMetadata, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java index 2e8ab816..9e73ebe5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/tameable/TameableEntity.java @@ -29,10 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import java.util.UUID; + public class TameableEntity extends AnimalEntity { public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { @@ -46,11 +49,22 @@ public class TameableEntity extends AnimalEntity { metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); - // Must be set for wolf collar color to work - // Extending it to all entities to prevent future bugs - if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { - metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); - } // Can't de-tame an entity so no resetting the owner ID + } + + // Note: Must be set for wolf collar color to work + if (entityMetadata.getId() == 17) { + if (entityMetadata.getValue() != null) { + // Owner UUID of entity + Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue()); + // Used as both a check since the player isn't in the entity cache and a normal fallback + if (entity == null) { + entity = session.getPlayerEntity(); + } + // Translate to entity ID + metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); + } else { + metadata.put(EntityData.OWNER_EID, 0L); // Reset + } } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 98d5a631..028d1831 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -26,27 +26,32 @@ package org.geysermc.connector.entity.living.merchant; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; + import java.util.regex.Matcher; import java.util.regex.Pattern; public class VillagerEntity extends AbstractMerchantEntity { + /** + * A map of Java profession IDs to Bedrock IDs + */ private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); - private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); + /** + * A map of all Java region IDs (plains, savanna...) to Bedrock + */ + public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); static { // Java villager profession IDs -> Bedrock diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java index 830c7ea3..b83a2ca7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/BasePiglinEntity.java @@ -1,11 +1,25 @@ package org.geysermc.connector.entity.living.monster; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.DimensionUtils; public class BasePiglinEntity extends MonsterEntity { public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } -} \ No newline at end of file + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 15) { + // Immune to zombification? + // Apply shaking effect if not in the nether and zombification is possible + metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER)); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java index 3c3a76bd..f4931861 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/CreeperEntity.java @@ -33,6 +33,12 @@ import org.geysermc.connector.network.session.GeyserSession; public class CreeperEntity extends MonsterEntity { + /** + * Whether the creeper has been ignited and is using ID 17. + * In this instance we ignore ID 15 since it's sending us -1 which confuses poor Bedrock. + */ + private boolean ignitedByFlintAndSteel = false; + public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } @@ -40,13 +46,16 @@ public class CreeperEntity extends MonsterEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 15) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + if (!ignitedByFlintAndSteel) { + metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); + } } if (entityMetadata.getId() == 16) { metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); } if (entityMetadata.getId() == 17) { - metadata.getFlags().setFlag(EntityFlag.IGNITED, (boolean) entityMetadata.getValue()); + ignitedByFlintAndSteel = (boolean) entityMetadata.getValue(); + metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel); } super.updateBedrockMetadata(entityMetadata, session); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java index 7b0d71e1..e0b443d3 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/PiglinEntity.java @@ -41,7 +41,7 @@ public class PiglinEntity extends BasePiglinEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 15) { + if (entityMetadata.getId() == 16) { boolean isBaby = (boolean) entityMetadata.getValue(); if (isBaby) { metadata.put(EntityData.SCALE, .55f); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java new file mode 100644 index 00000000..b8a62817 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ZombieVillagerEntity.java @@ -0,0 +1,56 @@ +/* + * 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.entity.living.monster; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import org.geysermc.connector.entity.living.merchant.VillagerEntity; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; + +public class ZombieVillagerEntity extends ZombieEntity { + + public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + if (entityMetadata.getId() == 18) { + metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue()); + metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue()); + } + if (entityMetadata.getId() == 19) { + VillagerData villagerData = (VillagerData) entityMetadata.getValue(); + // Region - only one used on Bedrock + metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType())); + } + super.updateBedrockMetadata(entityMetadata, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index f023ca10..05447760 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -77,7 +77,7 @@ public enum EntityType { GHAST(GhastEntity.class, 41, 4.0f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), - ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), + ZOMBIE_VILLAGER(ZombieVillagerEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_villager_v2"), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f), HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), @@ -153,7 +153,7 @@ public enum EntityType { FOX(FoxEntity.class, 121, 0.5f, 1.25f), BEE(BeeEntity.class, 122, 0.6f, 0.6f), STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), - HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), + HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 4429268a..a6085e21 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -146,6 +146,13 @@ public class GeyserSession implements CommandSender { @Setter private boolean jumping; + /** + * The dimension of the player. + * As all entities are in the same world, this can be safely applied to all other entities. + */ + @Setter + private String dimension = DimensionUtils.OVERWORLD; + @Setter private int breakingBlock; @@ -629,7 +636,7 @@ public class GeyserSession implements CommandSender { startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setSeed(-1); - startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(playerEntity.getDimension())); + startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension)); startGamePacket.setGeneratorId(1); startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setDifficulty(1); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index c5d6f2dd..6c03cd03 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -25,27 +25,65 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; -import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; -import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; -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; - import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; +import lombok.Getter; +import org.geysermc.connector.entity.Entity; +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.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; +import java.util.Arrays; +import java.util.List; + @Translator(packet = InteractPacket.class) public class BedrockInteractTranslator extends PacketTranslator { + /** + * A list of all foods a horse/donkey can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); + + /** + * A list of all flowers. Used for feeding bees. + */ + private static final List FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", + "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", + "sunflower", "lilac", "rose_bush", "peony"); + + /** + * All entity types that can be leashed on Java Edition + */ + private static final List LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, + EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.HOGLIN, EntityType.HORSE, EntityType.SKELETON_HORSE, + EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, + EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, EntityType.POLAR_BEAR, EntityType.RABBIT, + EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); + + private static final List SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE, + EntityType.ZOMBIE_HORSE, EntityType.MULE); + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final List WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); + @Override public void translate(InteractPacket packet, GeyserSession session) { Entity entity; @@ -84,50 +122,232 @@ public class BedrockInteractTranslator extends PacketTranslator if (interactEntity == null) return; EntityDataMap entityMetadata = interactEntity.getMetadata(); + ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand()); + String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - String interactiveTag; - switch (interactEntity.getEntityType()) { - case BOAT: - interactiveTag = "action.interact.ride.boat"; - break; - case DONKEY: - case HORSE: - case LLAMA: - case MULE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - interactiveTag = "action.interact.ride.horse"; - } else { - interactiveTag = "action.interact.mount"; - } - break; - case MINECART: - interactiveTag = "action.interact.ride.minecart"; - break; - case PIG: - if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = "action.interact.mount"; - } else interactiveTag = ""; - break; - case VILLAGER: - if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 - && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby - interactiveTag = "action.interact.trade"; - } else interactiveTag = ""; - break; - case WANDERING_TRADER: - interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably. - break; - default: - return; // No need to process any further since there is no interactive tag + // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen + // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food + InteractiveTag interactiveTag = InteractiveTag.NONE; + if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { + // Unleash the entity + interactiveTag = InteractiveTag.REMOVE_LEASH; + } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && + ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) || + interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) { + // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) + interactiveTag = InteractiveTag.SADDLE; + } else if (javaIdentifierStripped.equals("name_tag") && session.getInventory().getItemInHand().getNbt() != null && + session.getInventory().getItemInHand().getNbt().contains("display")) { + // Holding a named name tag + interactiveTag = InteractiveTag.NAME; + } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && + entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -1L) { + // Holding a leash and the mob is leashable for sure + // (Plugins can change this behavior so that's something to look into in the far far future) + interactiveTag = InteractiveTag.LEASH; + } else { + switch (interactEntity.getEntityType()) { + case BEE: + if (FLOWERS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + } + break; + case BOAT: + interactiveTag = InteractiveTag.BOARD_BOAT; + break; + case CAT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + break; + } + break; + case CHICKEN: + if (javaIdentifierStripped.contains("seeds")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MOOSHROOM: + // Shear the mooshroom + if (javaIdentifierStripped.equals("shears")) { + interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; + break; + } + // Bowls are acceptable here + else if (javaIdentifierStripped.equals("bowl")) { + interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; + break; + } + // Fall down to COW as this works on mooshrooms + case COW: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (javaIdentifierStripped.equals("bucket")) { + // Milk the cow + interactiveTag = InteractiveTag.MILK; + } + break; + case CREEPER: + if (javaIdentifierStripped.equals("flint_and_steel")) { + // Today I learned that you can ignite a creeper with flint and steel! Huh. + interactiveTag = InteractiveTag.IGNITE_CREEPER; + } + break; + case DONKEY: + case LLAMA: + case MULE: + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().getFlag(EntityFlag.CHESTED) + && javaIdentifierStripped.equals("chest")) { + // Can attach a chest + interactiveTag = InteractiveTag.ATTACH_CHEST; + break; + } + // Intentional fall-through + case HORSE: + case SKELETON_HORSE: + case TRADER_LLAMA: + case ZOMBIE_HORSE: + // have another switch statement as, while these share mount attributes they don't share food + switch (interactEntity.getEntityType()) { + case LLAMA: + case TRADER_LLAMA: + if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { + interactiveTag = InteractiveTag.FEED; + break; + } + case DONKEY: + case HORSE: + // Undead can't eat + if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + break; + } + } + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { + // Can't ride a baby + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + interactiveTag = InteractiveTag.RIDE_HORSE; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) { + // Can't hide an untamed entity without having your hand empty + interactiveTag = InteractiveTag.MOUNT; + } + } + break; + case FOX: + if (javaIdentifierStripped.equals("sweet_berries")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case HOGLIN: + if (javaIdentifierStripped.equals("crimson_fungus")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MINECART: + interactiveTag = InteractiveTag.RIDE_MINECART; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_HOPPER: + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + case OCELOT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PANDA: + if (javaIdentifierStripped.equals("bamboo")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PARROT: + if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PIG: + if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.MOUNT; + } + break; + case PIGLIN: + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { + interactiveTag = InteractiveTag.BARTER; + } + break; + case RABBIT: + if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case SHEEP: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { + if (javaIdentifierStripped.equals("shears")) { + // Shear the sheep + interactiveTag = InteractiveTag.SHEAR; + } else if (javaIdentifierStripped.contains("_dye")) { + // Dye the sheep + interactiveTag = InteractiveTag.DYE; + } + } + break; + case STRIDER: + if (javaIdentifierStripped.equals("warped_fungus")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.RIDE_STRIDER; + } + break; + case TURTLE: + if (javaIdentifierStripped.equals("seagrass")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case VILLAGER: + if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 + && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby + interactiveTag = InteractiveTag.TRADE; + } + break; + case WANDERING_TRADER: + interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. + break; + case WOLF: + if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + interactiveTag = InteractiveTag.TAME; + } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { + // Compatible food in hand - feed + // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + break; + case ZOMBIE_VILLAGER: + // We can't guarantee the existence of the weakness effect so we just always show it. + if (javaIdentifierStripped.equals("golden_apple")) { + interactiveTag = InteractiveTag.CURE; + } + break; + default: + break; + } } - session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag); + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); session.getPlayerEntity().updateBedrockMetadata(session); } else { - if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) || - !(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) { + if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { // No interactive tag should be sent session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); session.getPlayerEntity().updateBedrockMetadata(session); @@ -147,4 +367,65 @@ public class BedrockInteractTranslator extends PacketTranslator break; } } + + /** + * All interactive tags in enum form. For potential API usage. + */ + public enum InteractiveTag { + NONE(true), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(boolean isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 8370ba8e..18103087 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -55,6 +55,10 @@ public class ItemRegistry { public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + /** + * Bamboo item entry, used in PandaEntity.java + */ + public static ItemEntry BAMBOO; /** * Boat item entry, used in BedrockInventoryTransactionTranslator.java */ @@ -146,6 +150,9 @@ public class ItemRegistry { case "minecraft:barrier": BARRIER_INDEX = itemIndex; break; + case "minecraft:bamboo": + BAMBOO = ITEM_ENTRIES.get(itemIndex); + break; case "minecraft:oak_boat": BOAT = ITEM_ENTRIES.get(itemIndex); break; 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 a86c1a97..35ca7928 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 @@ -55,7 +55,7 @@ public class JavaJoinGameTranslator extends PacketTranslator } String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); - if (!entity.getDimension().equals(newDimension)) { + if (!session.getDimension().equals(newDimension)) { DimensionUtils.switchDimension(session, newDimension); } else { if (session.isManyDimPackets()) { //reloading world - String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; + String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, newDimension); } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java index b642a75b..1a6630ef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAttachTranslator.java @@ -62,7 +62,7 @@ public class JavaEntityAttachTranslator extends PacketTranslator { @@ -141,9 +137,15 @@ public class JavaEntityStatusTranslator extends PacketTranslator boolean shouldStore = false; mapItemDataPacket.setUniqueMapId(packet.getMapId()); - mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); + mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); mapItemDataPacket.setLocked(packet.isLocked()); mapItemDataPacket.setScale(packet.getScale()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java index 52d3f649..4620fc11 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaSpawnParticleTranslator.java @@ -94,7 +94,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); - if (localeStrings == null) - localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); if (localeStrings == null) { - // Don't cause a NPE if the locale is STILL missing - GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); - return messageText; + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale()); + if (localeStrings == null) { + // Don't cause a NPE if the locale is STILL missing + GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); + return messageText; + } } return localeStrings.getOrDefault(messageText, messageText); From 80cf407faee7306252e600ab7cf9563229ee8088 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Fri, 13 Nov 2020 23:00:09 -0500 Subject: [PATCH 065/161] Update MCProtocolLib to fix more custom recipe stuff (#1534) --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index ee3164e1..d837f057 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -111,7 +111,7 @@ com.github.steveice10 mcprotocollib - 6ac79c14d6 + 86e1901be5 compile From bf05f64fac2cedcb8bb41f6c44a50581029dea6b Mon Sep 17 00:00:00 2001 From: Niklas <7442307+niklaswa@users.noreply.github.com> Date: Sat, 14 Nov 2020 18:52:10 +0100 Subject: [PATCH 066/161] Update LabyMod cape url (#1540) --- .../main/java/org/geysermc/connector/utils/SkinProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 82fc3a3a..d848d95e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -607,7 +607,7 @@ public class SkinProvider { public enum CapeProvider { MINECRAFT, OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), + LABYMOD("https://dl.labymod.net/capes/%s", CapeUrlType.UUID_DASHED), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); From 445444204f017ba4b704478efc4a97c398da2ed2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sat, 14 Nov 2020 13:05:33 -0500 Subject: [PATCH 067/161] Fix GlobalPalette translation (#1528) * Fix GlobalPalette translation Global palettes don't have their own internal palette, which cannot be iterated through to create a Bedrock palette. Therefore we simply iterate over the whole palette one time. This commit also fixes a regression with flowers/pistons being on multiple chunk sections. * Don't declare bedrockPalette until after global palette check --- .../geysermc/connector/utils/ChunkUtils.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) 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 0769a4d1..70ac76df 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -28,6 +28,7 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage; 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.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; @@ -65,10 +66,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; -import java.util.Collections; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass public class ChunkUtils { @@ -107,7 +108,7 @@ public class ChunkUtils { ChunkSection[] sections = new ChunkSection[javaSections.length]; // Temporarily stores compound tags of Bedrock-only block entities - List bedrockOnlyBlockEntities = Collections.emptyList(); + List bedrockOnlyBlockEntities = new ArrayList<>(); BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); @@ -155,6 +156,33 @@ public class ChunkUtils { } Palette javaPalette = javaSection.getPalette(); + BitStorage javaData = javaSection.getStorage(); + + if (javaPalette instanceof GlobalPalette) { + // As this is the global palette, simply iterate through the whole chunk section once + ChunkSection section = new ChunkSection(); + for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { + int javaId = javaData.get(yzx); + int bedrockId = BlockTranslator.getBedrockBlockId(javaId); + int xzy = indexYZXtoXZY(yzx); + section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); + + if (BlockTranslator.isWaterlogged(javaId)) { + section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); + } + + // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock + if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), + javaId + )); + } + } + sections[sectionY] = section; + continue; + } + IntList bedrockPalette = new IntArrayList(javaPalette.size()); waterloggedPaletteIds.clear(); pistonOrFlowerPaletteIds.clear(); @@ -174,13 +202,10 @@ public class ChunkUtils { } } - BitStorage javaData = javaSection.getStorage(); - // Add Bedrock-exclusive block entities // We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data // for no reason, as most sections will not contain any pistons or flower pots if (!pistonOrFlowerPaletteIds.isEmpty()) { - bedrockOnlyBlockEntities = new ArrayList<>(); for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int paletteId = javaData.get(yzx); if (pistonOrFlowerPaletteIds.get(paletteId)) { From 981ac3bf118f331ded9fe4bcbd0e2f9e90792750 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 14 Nov 2020 17:49:56 -0600 Subject: [PATCH 068/161] Move PlatformType to common module --- .../org/geysermc/platform/bungeecord/GeyserBungeePlugin.java | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../java/org/geysermc/platform/sponge/GeyserSpongePlugin.java | 2 +- .../geysermc/platform/standalone/GeyserStandaloneBootstrap.java | 2 +- .../org/geysermc/platform/velocity/GeyserVelocityPlugin.java | 2 +- .../src/main/java/org/geysermc}/common/PlatformType.java | 2 +- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../org/geysermc/connector/command/defaults/ReloadCommand.java | 2 +- .../org/geysermc/connector/command/defaults/StopCommand.java | 2 +- .../java/org/geysermc/connector/dump/BootstrapDumpInfo.java | 2 +- .../translators/bedrock/BedrockCommandRequestTranslator.java | 2 +- .../translators/java/world/JavaBlockChangeTranslator.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) rename {connector/src/main/java/org/geysermc/connector => common/src/main/java/org/geysermc}/common/PlatformType.java (97%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index abb9789e..a65646bf 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.bungeecord; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 59a8db06..741763ec 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -27,10 +27,10 @@ package org.geysermc.platform.spigot; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.network.translators.world.WorldManager; diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 106d2b15..c3231f3b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -26,10 +26,10 @@ package org.geysermc.platform.sponge; import com.google.inject.Inject; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index f4dfd454..a0a8a3ae 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -36,10 +36,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java index f75c683a..b5255e62 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityPlugin.java @@ -33,9 +33,9 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; diff --git a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java similarity index 97% rename from connector/src/main/java/org/geysermc/connector/common/PlatformType.java rename to common/src/main/java/org/geysermc/common/PlatformType.java index 3e945d3a..88349020 100644 --- a/connector/src/main/java/org/geysermc/connector/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.common; +package org.geysermc.common; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 2497166c..c461fa96 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -32,10 +32,10 @@ import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.protocol.bedrock.BedrockServer; import lombok.Getter; import lombok.Setter; +import org.geysermc.common.PlatformType; import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.AuthType; -import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java index 6b2be294..64d0017e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/ReloadCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java index c69a8705..5be6253e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/StopCommand.java @@ -25,7 +25,7 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 9fbe82cc..58556553 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -27,7 +27,7 @@ package org.geysermc.connector.dump; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import java.util.List; 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 d05a667d..1f31367c 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 @@ -25,7 +25,7 @@ package org.geysermc.connector.network.translators.bedrock; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index 9e81ce59..50705ae2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.SoundEvent; import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket; -import org.geysermc.connector.common.PlatformType; +import org.geysermc.common.PlatformType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; From 47f25f1205f360a690a7510b2e79fc7c30ead61a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Sun, 15 Nov 2020 18:48:11 -0500 Subject: [PATCH 069/161] BannerTranslator: fix NPE when no block entity data (#1543) --- .../network/translators/item/translators/BannerTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 200271cf..14b93436 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -130,7 +130,7 @@ public class BannerTranslator extends ItemTranslator { ItemData itemData = super.translateToBedrock(itemStack, itemEntry); CompoundTag blockEntityTag = itemStack.getNbt().get("BlockEntityTag"); - if (blockEntityTag.contains("Patterns")) { + if (blockEntityTag != null && blockEntityTag.contains("Patterns")) { ListTag patterns = blockEntityTag.get("Patterns"); NbtMapBuilder builder = itemData.getTag().toBuilder(); From 512f8cd6c2cf002aaf8e3087c361784b8c603fd2 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 16 Nov 2020 23:57:57 +0000 Subject: [PATCH 070/161] Rewrite message handling in MessageUtils to use Adventure (#1498) * Rewrite message handling in MessageUtils to use Adventure * Move to static Adventure commit to fix a bug * Initial test implementation * Add RGB downgrade test * Move MessageUtils and rename * Clean-up and fix tests * Fixed sign and book content handling * Fix blank signs causing NPEs * Fix reset before message being stripped * Add comment about the reset character * Fix legacy style server motds * Fix more messages being handled wrong * Fix title packets being handled wrong * Fix trailing formatting characters on the end of sign lines * Add auto updating of Java locale files * Add en_us locale updating and hash caching * Changes to hash determining Co-authored-by: DoctorMacc --- connector/pom.xml | 23 +- .../entity/CommandBlockMinecartEntity.java | 5 +- .../org/geysermc/connector/entity/Entity.java | 4 +- .../connector/entity/PlayerEntity.java | 6 +- .../network/ConnectorServerEventHandler.java | 5 +- .../connector/network/QueryPacketHandler.java | 5 +- .../network/session/GeyserSession.java | 4 +- .../network/session/cache/BossBar.java | 6 +- .../BedrockCommandRequestTranslator.java | 4 +- .../bedrock/BedrockTextTranslator.java | 4 +- .../translators/chat/MessageTranslator.java | 278 ++++++++++ .../chat/MinecraftTranslationRegistry.java | 81 +++ .../translators/item/ItemTranslator.java | 26 +- .../translators/nbt/BasicItemTranslator.java | 3 +- .../translators/nbt/BookPagesTranslator.java | 6 +- .../translators/java/JavaChatTranslator.java | 22 +- .../java/JavaDisconnectPacket.java | 4 +- .../java/JavaLoginDisconnectTranslator.java | 4 +- .../translators/java/JavaTitleTranslator.java | 16 +- .../JavaScoreboardObjectiveTranslator.java | 4 +- .../java/scoreboard/JavaTeamTranslator.java | 14 +- .../java/window/JavaOpenWindowTranslator.java | 6 +- .../CommandBlockBlockEntityTranslator.java | 4 +- .../entity/SignBlockEntityTranslator.java | 16 +- .../geysermc/connector/utils/FileUtils.java | 21 +- .../geysermc/connector/utils/LocaleUtils.java | 52 +- .../connector/utils/MessageUtils.java | 494 ------------------ .../chat/MessageTranslatorTest.java | 67 +++ 28 files changed, 583 insertions(+), 601 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java delete mode 100644 connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java create mode 100644 connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java diff --git a/connector/pom.xml b/connector/pom.xml index d837f057..db267a71 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -143,17 +143,23 @@ compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-gson - 4.1.1 + 4d8a67d798 compile - net.kyori + com.github.kyoripowered.adventure adventure-text-serializer-legacy - 4.1.1 + 0599048 compile + + junit + junit + 4.13.1 + test + @@ -283,6 +289,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + -Dfile.encoding=${project.build.sourceEncoding} + + diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 8cabba64..7d34cc79 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -26,13 +26,12 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.message.Message; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @@ -51,7 +50,7 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { metadata.put(EntityData.COMMAND_BLOCK_COMMAND, entityMetadata.getValue()); } if (entityMetadata.getId() == 14) { - metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageUtils.getBedrockMessage((Message) entityMetadata.getValue())); + metadata.put(EntityData.COMMAND_BLOCK_LAST_OUTPUT, MessageTranslator.convertMessage(entityMetadata.getValue().toString())); } super.updateBedrockMetadata(entityMetadata, session); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 20cd2f76..7b1fa1cf 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -54,7 +54,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.utils.ChunkUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.HashMap; @@ -318,7 +318,7 @@ public class Entity { Message message = (Message) entityMetadata.getValue(); if (message != null) // Always translate even if it's a TextMessage since there could be translatable parameters - metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true)); + metadata.put(EntityData.NAMETAG, MessageTranslator.convertMessage(message.toString(), session.getLocale())); } break; case 3: // is custom name visible diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index 8eeae473..be65525c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -51,7 +51,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.AttributeUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -243,13 +243,13 @@ public class PlayerEntity extends LivingEntity { String username = this.username; TextMessage name = (TextMessage) entityMetadata.getValue(); if (name != null) { - username = MessageUtils.getBedrockMessage(name); + username = MessageTranslator.convertMessage(name.toString()); } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { String displayName = ""; if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - displayName = MessageUtils.toChatColor(team.getColor()) + username; + displayName = MessageTranslator.toChatColor(team.getColor()) + username; displayName = team.getCurrentData().getDisplayName(displayName); } metadata.put(EntityData.NAMETAG, displayName); diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 9fb4ad9e..150d298c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.nukkitx.protocol.bedrock.BedrockPong; import com.nukkitx.protocol.bedrock.BedrockServerEventHandler; import com.nukkitx.protocol.bedrock.BedrockServerSession; @@ -36,7 +35,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.ping.IGeyserPingPassthrough; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; @@ -76,7 +75,7 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setIpv4Port(config.getBedrock().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { - String[] motd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); String mainMotd = motd[0]; // First line of the motd. String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 7faf36bd..510bba2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -25,12 +25,11 @@ package org.geysermc.connector.network; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import org.geysermc.connector.common.ping.GeyserPingInfo; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -148,7 +147,7 @@ public class QueryPacketHandler { } if (connector.getConfig().isPassthroughMotd() && pingInfo != null) { - String[] javaMotd = MessageUtils.getBedrockMessage(MessageSerializer.fromString(pingInfo.getDescription())).split("\n"); + String[] javaMotd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); motd = javaMotd[0].trim(); // First line of the motd. } else { motd = connector.getConfig().getBedrock().getMotd1(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index a6085e21..00b48a56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -34,7 +34,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket; @@ -66,6 +65,7 @@ import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -496,7 +496,7 @@ public class GeyserSession implements CommandSender { event.getCause().printStackTrace(); } - upstream.disconnect(MessageUtils.getBedrockMessage(MessageSerializer.fromString(event.getReason()))); + upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason())); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java index fdc609ab..7eadb794 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java @@ -33,7 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.BossEventPacket; import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket; import lombok.AllArgsConstructor; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @AllArgsConstructor public class BossBar { @@ -58,7 +58,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.CREATE); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); bossEventPacket.setHealthPercentage(health); bossEventPacket.setColor(color); //ignored by client bossEventPacket.setOverlay(overlay); @@ -72,7 +72,7 @@ public class BossBar { BossEventPacket bossEventPacket = new BossEventPacket(); bossEventPacket.setBossUniqueEntityId(entityId); bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME); - bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale())); + bossEventPacket.setTitle(MessageTranslator.convertMessage(title.toString(), session.getLocale())); session.sendUpstreamPacket(bossEventPacket); } 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 1f31367c..f572538e 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,7 +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; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = CommandRequestPacket.class) public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -48,7 +48,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator { @@ -40,7 +40,7 @@ public class BedrockTextTranslator extends PacketTranslator { public void translate(TextPacket packet, GeyserSession session) { String message = packet.getMessage().replaceAll("^\\.", "/").trim(); - if (MessageUtils.isTooLong(message, session)) { + if (MessageTranslator.isTooLong(message, session)) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java new file mode 100644 index 00000000..be01362f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.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.chat; + +import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatColor; +import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.translation.TranslationRegistry; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.LanguageUtils; + +import java.util.*; + +public class MessageTranslator { + + // These are used for handling the translations of the messages + private static final TranslationRegistry REGISTRY = new MinecraftTranslationRegistry(); + private static final TranslatableComponentRenderer RENDERER = TranslatableComponentRenderer.usingTranslationSource(REGISTRY); + + // Store team colors for player names + private static final Map TEAM_COLORS = new HashMap<>(); + + static { + TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); + TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); + TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); + TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); + TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); + TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); + TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); + TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); + TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); + TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); + TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); + TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); + TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); + TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); + TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); + TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); + TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(ChatFormat.OBFUSCATED)); + TEAM_COLORS.put(TeamColor.BOLD, getFormat(ChatFormat.BOLD)); + TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(ChatFormat.STRIKETHROUGH)); + TEAM_COLORS.put(TeamColor.ITALIC, getFormat(ChatFormat.ITALIC)); + } + + /** + * Convert a Java message to the legacy format ready for bedrock + * + * @param message Java message + * @param locale Locale to use for translation strings + * @return Parsed and formatted message for bedrock + */ + public static String convertMessage(String message, String locale) { + Component component = GsonComponentSerializer.gson().deserialize(message); + + // Get a Locale from the given locale string + Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); + component = RENDERER.render(component, localeCode); + + return LegacyComponentSerializer.legacySection().serialize(component); + } + + public static String convertMessage(String message) { + return convertMessage(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. + * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. + * + * @param message Potentially lenient JSON message + * @param locale Locale to use for translation strings + * @return Bedrock formatted message + */ + public static String convertMessageLenient(String message, String locale) { + if (isMessage(message)) { + return convertMessage(message, locale); + } else { + String convertedMessage = convertMessage(convertToJavaMessage(message), locale); + + // We have to do this since Adventure strips the starting reset character + if (message.startsWith(getColor(ChatColor.RESET))) { + convertedMessage = getColor(ChatColor.RESET) + convertedMessage; + } + + return convertedMessage; + } + } + + public static String convertMessageLenient(String message) { + return convertMessageLenient(message, LanguageUtils.getDefaultLocale()); + } + + /** + * Convert a Bedrock message string back to a format Java can understand + * + * @param message Message to convert + * @return The formatted JSON string + */ + public static String convertToJavaMessage(String message) { + Component component = LegacyComponentSerializer.legacySection().deserialize(message); + return GsonComponentSerializer.gson().serialize(component); + } + + /** + * Checks if the given text string is a JSON message + * + * @param text String to test + * @return True if its a valid message JSON string, false if not + */ + public static boolean isMessage(String text) { + if (text.trim().isEmpty()) { + return false; + } + + try { + GsonComponentSerializer.gson().deserialize(text); + } catch (Exception ex) { + return false; + } + + return true; + } + + /** + * Convert a {@link ChatColor} into a string for inserting into messages + * + * @param color {@link ChatColor} to convert + * @return The converted color string + */ + private static String getColor(String color) { + String base = "\u00a7"; + switch (color) { + case ChatColor.BLACK: + base += "0"; + break; + case ChatColor.DARK_BLUE: + base += "1"; + break; + case ChatColor.DARK_GREEN: + base += "2"; + break; + case ChatColor.DARK_AQUA: + base += "3"; + break; + case ChatColor.DARK_RED: + base += "4"; + break; + case ChatColor.DARK_PURPLE: + base += "5"; + break; + case ChatColor.GOLD: + base += "6"; + break; + case ChatColor.GRAY: + base += "7"; + break; + case ChatColor.DARK_GRAY: + base += "8"; + break; + case ChatColor.BLUE: + base += "9"; + break; + case ChatColor.GREEN: + base += "a"; + break; + case ChatColor.AQUA: + base += "b"; + break; + case ChatColor.RED: + base += "c"; + break; + case ChatColor.LIGHT_PURPLE: + base += "d"; + break; + case ChatColor.YELLOW: + base += "e"; + break; + case ChatColor.WHITE: + base += "f"; + break; + case ChatColor.RESET: + base += "r"; + break; + default: + return ""; + } + + return base; + } + + /** + * Convert a {@link ChatFormat} into a string for inserting into messages + * + * @param format {@link ChatFormat} to convert + * @return The converted chat formatting string + */ + private static String getFormat(ChatFormat format) { + StringBuilder str = new StringBuilder(); + String base = "\u00a7"; + switch (format) { + case OBFUSCATED: + base += "k"; + break; + case BOLD: + base += "l"; + break; + case STRIKETHROUGH: + base += "m"; + break; + case UNDERLINED: + base += "n"; + break; + case ITALIC: + base += "o"; + break; + default: + return ""; + } + + str.append(base); + + return str.toString(); + } + + /** + * Convert a team color to a chat color + * + * @param teamColor + * @return The chat color character + */ + public static String toChatColor(TeamColor teamColor) { + return TEAM_COLORS.getOrDefault(teamColor, ""); + } + + /** + * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is + * + * @param message Message to check + * @param session {@link GeyserSession} for the user + * @return True if the message is too long, false if not + */ + public static boolean isTooLong(String message, GeyserSession session) { + if (message.length() > 256) { + session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); + return true; + } + + return false; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java new file mode 100644 index 00000000..a23167ac --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -0,0 +1,81 @@ +/* + * 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.chat; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.translation.TranslationRegistry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.connector.utils.LocaleUtils; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is used for mapping a translation key with the already loaded Java locale data + * Used in MessageTranslator.java as part of the KyoriPowered/Adventure library + */ +public class MinecraftTranslationRegistry implements TranslationRegistry { + @Override + public @NonNull Key name() { + return Key.key("", ""); + } + + @Override + public @Nullable MessageFormat translate(@NonNull String key, @NonNull Locale locale) { + // Get the locale string + String localeString = LocaleUtils.getLocaleString(key, locale.toString()); + + // Replace the `%s` with numbered inserts `{0}` + Pattern p = Pattern.compile("%s"); + Matcher m = p.matcher(localeString); + StringBuffer sb = new StringBuffer(); + int i = 0; + while (m.find()) { + m.appendReplacement(sb, "{" + (i++) + "}"); + } + m.appendTail(sb); + + return new MessageFormat(sb.toString(), locale); + } + + @Override + public void defaultLocale(@NonNull Locale locale) { + + } + + @Override + public void register(@NonNull String key, @NonNull Locale locale, @NonNull MessageFormat format) { + + } + + @Override + public void unregister(@NonNull String key) { + + } +} 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 55db9a25..00c9138a 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 @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.item; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMap; @@ -44,7 +43,7 @@ import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; @@ -385,26 +384,17 @@ public abstract class ItemTranslator { public static void translateDisplayProperties(GeyserSession session, CompoundTag tag) { if (tag != null) { CompoundTag display = tag.get("display"); - if (display != null && !display.isEmpty() && display.contains("Name")) { + if (display != null && display.contains("Name")) { String name = ((StringTag) display.get("Name")).getValue(); - // If its not a message convert it - if (!MessageUtils.isMessage(name)) { - Component component = LegacyComponentSerializer.legacySection().deserialize(name); - name = GsonComponentSerializer.gson().serialize(component); - } + // Get the translated name and prefix it with a reset char + name = MessageTranslator.convertMessageLenient(name, session.getLocale()); - // Check if its a message to translate - if (MessageUtils.isMessage(name)) { - // Get the translated name - name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale()); + // Add the new name tag + display.put(new StringTag("Name", name)); - // Add the new name tag - display.put(new StringTag("Name", name)); - - // Add to the new root tag - tag.put(display); - } + // Add to the new root tag + tag.put(display); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java index 1d21bbfb..3fd9df8a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BasicItemTranslator.java @@ -37,7 +37,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.MessageUtils; import java.util.ArrayList; import java.util.List; @@ -108,7 +107,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator { private String toBedrockMessage(StringTag tag) { String message = tag.getValue(); if (message == null) return null; - TextComponent component = (TextComponent) MessageUtils.phraseJavaMessage(message); + TextComponent component = (TextComponent) GsonComponentSerializer.gson().deserialize(message); String legacy = LegacyComponentSerializer.legacySection().serialize(component); if (hasFormatting(LegacyComponentSerializer.legacySection().deserialize(legacy))) { return "§r" + legacy; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java index 41ee4fbc..294dd81e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/BookPagesTranslator.java @@ -33,7 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -56,7 +56,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = new CompoundTag(""); pageTag.put(new StringTag("photoname", "")); - pageTag.put(new StringTag("text", MessageUtils.getBedrockMessageLenient(textTag.getValue()))); + pageTag.put(new StringTag("text", MessageTranslator.convertMessageLenient(textTag.getValue()))); pages.add(pageTag); } @@ -78,7 +78,7 @@ public class BookPagesTranslator extends NbtItemStackTranslator { CompoundTag pageTag = (CompoundTag) tag; StringTag textTag = pageTag.get("text"); - pages.add(new StringTag(MessageUtils.getJavaMessage(textTag.getValue()))); + pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue()))); } itemTag.remove("pages"); 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 186aaf66..f5128ed6 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 @@ -25,15 +25,12 @@ package org.geysermc.connector.network.translators.java; -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 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.MessageUtils; - -import java.util.List; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -59,21 +56,8 @@ public class JavaChatTranslator extends PacketTranslator { break; } - String locale = session.getLocale(); - - if (packet.getMessage() instanceof TranslationMessage) { - textPacket.setType(TextPacket.Type.TRANSLATION); - textPacket.setNeedsTranslation(true); - - List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getWith(), locale, packet.getMessage()); - textPacket.setParameters(paramsTranslated); - - textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, true, packet.getMessage()), paramsTranslated)); - } else { - textPacket.setNeedsTranslation(false); - - textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false, packet.getMessage())); - } + textPacket.setNeedsTranslation(false); + textPacket.setMessage(MessageTranslator.convertMessage(packet.getMessage().toString(), session.getLocale())); session.sendUpstreamPacket(textPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java index f36da367..1945a8e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDisconnectPacket.java @@ -29,13 +29,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDisconnectPa 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.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerDisconnectPacket.class) public class JavaDisconnectPacket extends PacketTranslator { @Override public void translate(ServerDisconnectPacket packet, GeyserSession session) { - session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale(), true)); + session.disconnect(MessageTranslator.convertMessage(packet.getReason().toString(), session.getLocale())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java index e7486c99..0a1cc3dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginDisconnectTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.login.server.LoginDisconnectPack 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.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = LoginDisconnectPacket.class) public class JavaLoginDisconnectTranslator extends PacketTranslator { @@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator { SetTitlePacket titlePacket = new SetTitlePacket(); String locale = session.getLocale(); + String text; + if (packet.getTitle() == null) { + text = " "; + } else { + text = MessageTranslator.convertMessage(packet.getTitle().toString(), locale); + } + switch (packet.getAction()) { case TITLE: titlePacket.setType(SetTitlePacket.Type.TITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case SUBTITLE: titlePacket.setType(SetTitlePacket.Type.SUBTITLE); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case CLEAR: case RESET: @@ -57,9 +64,10 @@ public class JavaTitleTranslator extends PacketTranslator { break; case ACTION_BAR: titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); - titlePacket.setText(MessageUtils.getTranslatedBedrockMessage(packet.getTitle(), locale)); + titlePacket.setText(text); break; case TIMES: + titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setFadeInTime(packet.getFadeIn()); titlePacket.setFadeOutTime(packet.getFadeOut()); titlePacket.setStayTime(packet.getStay()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index 31b9d95b..1996f696 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -32,7 +32,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; import org.geysermc.connector.scoreboard.ScoreboardUpdater; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; @@ -54,7 +54,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { switch (packet.getAction()) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) - .setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + .setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())); + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())); break; case UPDATE: if (team == null) { @@ -74,11 +74,11 @@ public class JavaTeamTranslator extends PacketTranslator { return; } - team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName())) + team.setName(MessageTranslator.convertMessage(packet.getDisplayName().toString())) .setColor(packet.getColor()) .setNameTagVisibility(packet.getNameTagVisibility()) - .setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale())) - .setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale())) + .setPrefix(MessageTranslator.convertMessage(packet.getPrefix().toString(), session.getLocale())) + .setSuffix(MessageTranslator.convertMessage(packet.getSuffix().toString(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); break; case ADD_PLAYER: diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 2c10ded6..1fb08871 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.java.window; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; import org.geysermc.connector.inventory.Inventory; @@ -35,7 +34,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.translators.chat.MessageTranslator; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @@ -57,8 +56,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator - * The color names correspond to dye names, because of this we can't use {@link MessageUtils#getColor(String)}. + * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}. * * @param javaColor The dye color stored in the sign's Color tag. * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. */ - private static String getBedrockSignColor(String javaColor) { + private String getBedrockSignColor(String javaColor) { String base = "\u00a7"; switch (javaColor) { case "white": @@ -100,7 +99,12 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { for (int i = 0; i < 4; i++) { int currentLine = i + 1; String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); - signLine = MessageUtils.getBedrockMessage(MessageSerializer.fromString(signLine)); + signLine = MessageTranslator.convertMessageLenient(signLine); + + // Trim any trailing formatting codes + if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') { + signLine = signLine.substring(0, signLine.length() - 2); + } // Check the character width on the sign to ensure there is no overflow that is usually hidden // to Java Edition clients but will appear to Bedrock clients @@ -124,6 +128,6 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { signText.append("\n"); } - builder.put("Text", MessageUtils.getBedrockMessage(MessageSerializer.fromString(signText.toString()))); + builder.put("Text", signText.toString()); } } 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 63255cfa..0b2b132a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -159,7 +159,8 @@ public class FileUtils { } /** - * Calculate the SHA256 hash of the resource pack file + * Calculate the SHA256 hash of a file + * * @param file File to calculate the hash for * @return A byte[] representation of the hash */ @@ -175,6 +176,24 @@ public class FileUtils { return sha256; } + /** + * Calculate the SHA1 hash of a file + * + * @param file File to calculate the hash for + * @return A byte[] representation of the hash + */ + public static byte[] calculateSHA1(File file) { + byte[] sha1; + + try { + sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath())); + } catch (Exception e) { + throw new RuntimeException("Could not calculate pack hash", e); + } + + return sha1; + } + /** * Get the stored reflection data for a given path * diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index dfde21b3..4e9e4b00 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -47,7 +47,7 @@ public class LocaleUtils { private static final Map ASSET_MAP = new HashMap<>(); - private static String smallestURL = ""; + private static VersionDownload clientJarInfo; static { // Create the locales folder @@ -87,9 +87,8 @@ public class LocaleUtils { // Get the client jar for use when downloading the en_us locale GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads())); - VersionDownload download = versionInfo.getDownloads().get("client"); - GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(download)); - smallestURL = download.getUrl(); + clientJarInfo = versionInfo.getDownloads().get("client"); + GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(clientJarInfo)); // Get the assets list JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); @@ -136,8 +135,28 @@ public class LocaleUtils { // Check if we have already downloaded the locale file if (localeFile.exists()) { - GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); - return; + String curHash = ""; + String targetHash = ""; + + if (locale.equals("en_us")) { + try { + Path hashFile = localeFile.getParentFile().toPath().resolve("en_us.hash"); + if (hashFile.toFile().exists()) { + curHash = String.join("", Files.readAllLines(hashFile)); + } + } catch (IOException ignored) { } + targetHash = clientJarInfo.getSha1(); + } else { + curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); + targetHash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + } + + if (!curHash.equals(targetHash)) { + GeyserConnector.getInstance().getLogger().debug("Locale out of date; re-downloading: " + locale); + } else { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded and up-to date: " + locale); + return; + } } // Create the en_us locale @@ -202,11 +221,11 @@ public class LocaleUtils { try { // Let the user know we are downloading the JAR GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us")); - GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + clientJarInfo.getUrl()); // Download the smallest JAR (client or server) Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar"); - WebUtils.downloadFile(smallestURL, tmpFilePath.toString()); + WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); // Load in the JAR as a zip and extract the file ZipFile localeJar = new ZipFile(tmpFilePath.toString()); @@ -227,6 +246,9 @@ public class LocaleUtils { fileStream.close(); localeJar.close(); + // Store the latest jar hash + FileUtils.writeFile(localeFile.getParentFile().toPath().resolve("en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); + // Delete the nolonger needed client/server jar Files.delete(tmpFilePath); } catch (Exception e) { @@ -255,6 +277,20 @@ public class LocaleUtils { return localeStrings.getOrDefault(messageText, messageText); } + /** + * Convert a byte array into a hex string + * + * @param b Byte array to convert + * @return The hex representation of the given byte array + */ + private static String byteArrayToHexString(byte[] b) { + StringBuilder result = new StringBuilder(); + for (byte value : b) { + result.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1)); + } + return result.toString(); + } + public static void init() { // no-op } diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java deleted file mode 100644 index b5a2bfdc..00000000 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * 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.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; -import com.github.steveice10.mc.protocol.data.message.Message; -import com.github.steveice10.mc.protocol.data.message.MessageSerializer; -import com.github.steveice10.mc.protocol.data.message.TextMessage; -import com.github.steveice10.mc.protocol.data.message.TranslationMessage; -import com.github.steveice10.mc.protocol.data.message.style.ChatColor; -import com.github.steveice10.mc.protocol.data.message.style.ChatFormat; -import com.github.steveice10.mc.protocol.data.message.style.MessageStyle; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.network.session.GeyserSession; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class MessageUtils { - - private static final Map COLORS = new HashMap<>(); - private static final Map TEAM_COLORS = new HashMap<>(); - - static { - COLORS.put(ChatColor.BLACK, 0x000000); - COLORS.put(ChatColor.DARK_BLUE, 0x0000aa); - COLORS.put(ChatColor.DARK_GREEN, 0x00aa00); - COLORS.put(ChatColor.DARK_AQUA, 0x00aaaa); - COLORS.put(ChatColor.DARK_RED, 0xaa0000); - COLORS.put(ChatColor.DARK_PURPLE, 0xaa00aa); - COLORS.put(ChatColor.GOLD, 0xffaa00); - COLORS.put(ChatColor.GRAY, 0xaaaaaa); - COLORS.put(ChatColor.DARK_GRAY, 0x555555); - COLORS.put(ChatColor.BLUE, 0x5555ff); - COLORS.put(ChatColor.GREEN, 0x55ff55); - COLORS.put(ChatColor.AQUA, 0x55ffff); - COLORS.put(ChatColor.RED, 0xff5555); - COLORS.put(ChatColor.LIGHT_PURPLE, 0xff55ff); - COLORS.put(ChatColor.YELLOW, 0xffff55); - COLORS.put(ChatColor.WHITE, 0xffffff); - - TEAM_COLORS.put(TeamColor.BLACK, getColor(ChatColor.BLACK)); - TEAM_COLORS.put(TeamColor.DARK_BLUE, getColor(ChatColor.DARK_BLUE)); - TEAM_COLORS.put(TeamColor.DARK_GREEN, getColor(ChatColor.DARK_GREEN)); - TEAM_COLORS.put(TeamColor.DARK_AQUA, getColor(ChatColor.DARK_AQUA)); - TEAM_COLORS.put(TeamColor.DARK_RED, getColor(ChatColor.DARK_RED)); - TEAM_COLORS.put(TeamColor.DARK_PURPLE, getColor(ChatColor.DARK_PURPLE)); - TEAM_COLORS.put(TeamColor.GOLD, getColor(ChatColor.GOLD)); - TEAM_COLORS.put(TeamColor.GRAY, getColor(ChatColor.GRAY)); - TEAM_COLORS.put(TeamColor.DARK_GRAY, getColor(ChatColor.DARK_GRAY)); - TEAM_COLORS.put(TeamColor.BLUE, getColor(ChatColor.BLUE)); - TEAM_COLORS.put(TeamColor.GREEN, getColor(ChatColor.GREEN)); - TEAM_COLORS.put(TeamColor.AQUA, getColor(ChatColor.AQUA)); - TEAM_COLORS.put(TeamColor.RED, getColor(ChatColor.RED)); - TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, getColor(ChatColor.LIGHT_PURPLE)); - TEAM_COLORS.put(TeamColor.YELLOW, getColor(ChatColor.YELLOW)); - TEAM_COLORS.put(TeamColor.WHITE, getColor(ChatColor.WHITE)); - TEAM_COLORS.put(TeamColor.OBFUSCATED, getFormat(Collections.singletonList(ChatFormat.OBFUSCATED))); - TEAM_COLORS.put(TeamColor.BOLD, getFormat(Collections.singletonList(ChatFormat.BOLD))); - TEAM_COLORS.put(TeamColor.STRIKETHROUGH, getFormat(Collections.singletonList(ChatFormat.STRIKETHROUGH))); - TEAM_COLORS.put(TeamColor.ITALIC, getFormat(Collections.singletonList(ChatFormat.ITALIC))); - } - - /** - * Recursively parse each message from a list for usage in a {@link TranslationMessage} - * - * @param messages A {@link List} of {@link Message} to parse - * @param locale A locale loaded to get the message for - * @param parent A {@link Message} to use as the parent (can be null) - * @return the translation parameters - */ - public static List getTranslationParams(List messages, String locale, Message parent) { - List strings = new ArrayList<>(); - for (Message message : messages) { - message = fixMessageStyle(message, parent); - - if (message instanceof TranslationMessage) { - TranslationMessage translation = (TranslationMessage) message; - - if (locale == null) { - String builder = "%" + translation.getKey(); - strings.add(builder); - } - - // Collect all params and add format corrections to the end of them - List furtherParams = new ArrayList<>(); - for (String param : getTranslationParams(translation.getWith(), locale, message)) { - String newParam = param; - if (parent.getStyle().getFormats().size() != 0) { - newParam += getFormat(parent.getStyle().getFormats()); - } - if (parent.getStyle().getColor() != ChatColor.NONE) { - newParam += getColor(parent.getStyle().getColor()); - } - - furtherParams.add(newParam); - } - - if (locale != null) { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += insertParams(LocaleUtils.getLocaleString(translation.getKey(), locale), furtherParams); - strings.add(builder); - } else { - String format = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - for (String param : furtherParams) { - strings.add(format + param); - } - } - } else { - String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()); - builder += getTranslatedBedrockMessage(message, locale, false, parent); - strings.add(builder); - } - } - - return strings; - } - - public static String getTranslatedBedrockMessage(Message message, String locale) { - return getTranslatedBedrockMessage(message, locale, true); - } - - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) { - return getTranslatedBedrockMessage(message, locale, shouldTranslate, null); - } - - /** - * Translate a given {@link TranslationMessage} to the given locale - * - * @param message The {@link Message} to send - * @param locale the locale - * @param shouldTranslate if the message should be translated - * @param parent the parent message - * @return the given translation message translated from the given locale - */ - public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate, Message parent) { - JsonParser parser = new JsonParser(); - if (isMessage(message.toString())) { - JsonObject object = parser.parse(message.toString()).getAsJsonObject(); - message = MessageSerializer.fromJson(object); - } - - message = fixMessageStyle(message, parent); - - String messageText = (message instanceof TranslationMessage) ? ((TranslationMessage) message).getKey() : ((TextMessage) message).getText(); - if (locale != null && shouldTranslate) { - messageText = LocaleUtils.getLocaleString(messageText, locale); - } - - StringBuilder builder = new StringBuilder(); - builder.append(getFormat(message.getStyle().getFormats())); - builder.append(getColor(message.getStyle().getColor())); - builder.append(messageText); - - for (Message msg : message.getExtra()) { - builder.append(getFormat(msg.getStyle().getFormats())); - builder.append(getColor(msg.getStyle().getColor())); - if (!(msg.toString() == null)) { - boolean isTranslationMessage = (msg instanceof TranslationMessage); - String extraText = ""; - - if (isTranslationMessage) { - List paramsTranslated = getTranslationParams(((TranslationMessage) msg).getWith(), locale, message); - extraText = insertParams(getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message), paramsTranslated); - } else { - extraText = getTranslatedBedrockMessage(msg, locale, isTranslationMessage, message); - } - - builder.append(extraText); - builder.append("\u00a7r"); - } - } - - return builder.toString(); - } - - /** - * If the passed {@link Message} color or format are empty then copy from parent - * - * @param message {@link Message} to update - * @param parent Parent {@link Message} for style - * @return The updated {@link Message} - */ - private static Message fixMessageStyle(Message message, Message parent) { - if (parent == null) { - return message; - } - MessageStyle.Builder styleBuilder = message.getStyle().toBuilder(); - - // Copy color from parent - if (message.getStyle().getColor() == ChatColor.NONE) { - styleBuilder.color(parent.getStyle().getColor()); - } - - // Copy formatting from parent - if (message.getStyle().getFormats().size() == 0) { - styleBuilder.formats(parent.getStyle().getFormats()); - } - - return message.toBuilder().style(styleBuilder.build()).build(); - } - - public static String getBedrockMessage(Message message) { - if (isMessage(((TextMessage) message).getText())) { - return getBedrockMessage(((TextMessage) message).getText()); - } else { - return getBedrockMessage(MessageSerializer.toJsonString(message)); - } - } - - /** - * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. - * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. - * - * @param message Potentially lenient JSON message - * @return Bedrock formatted message - */ - public static String getBedrockMessageLenient(String message) { - if (isMessage(message)) { - return getBedrockMessage(message); - } else { - final JsonObject obj = new JsonObject(); - obj.addProperty("text", message); - return getBedrockMessage(obj.toString()); - } - } - - public static String getBedrockMessage(String message) { - Component component = phraseJavaMessage(message); - return LegacyComponentSerializer.legacySection().serialize(component); - } - - public static Component phraseJavaMessage(String message) { - return GsonComponentSerializer.gson().deserialize(message); - } - - public static String getJavaMessage(String message) { - Component component = LegacyComponentSerializer.legacySection().deserialize(message); - return GsonComponentSerializer.gson().serialize(component); - } - - /** - * Inserts the given parameters into the given message both in sequence and as requested - * - * @param message Message containing possible parameter replacement strings - * @param params A list of parameter strings - * @return Parsed message with all params inserted as needed - */ - 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) { - // Couldn't find the param to replace - } - } - - for (String text : params) { - newMessage = newMessage.replaceFirst("%s", text.replaceAll("%s", "%r")); - } - - newMessage = newMessage.replaceAll("%r", "MISSING!"); - - return newMessage; - } - - /** - * Convert a ChatColor into a string for inserting into messages - * - * @param color ChatColor to convert - * @return The converted color string - */ - private static String getColor(String color) { - String base = "\u00a7"; - switch (color) { - case ChatColor.BLACK: - base += "0"; - break; - case ChatColor.DARK_BLUE: - base += "1"; - break; - case ChatColor.DARK_GREEN: - base += "2"; - break; - case ChatColor.DARK_AQUA: - base += "3"; - break; - case ChatColor.DARK_RED: - base += "4"; - break; - case ChatColor.DARK_PURPLE: - base += "5"; - break; - case ChatColor.GOLD: - base += "6"; - break; - case ChatColor.GRAY: - base += "7"; - break; - case ChatColor.DARK_GRAY: - base += "8"; - break; - case ChatColor.BLUE: - base += "9"; - break; - case ChatColor.GREEN: - base += "a"; - break; - case ChatColor.AQUA: - base += "b"; - break; - case ChatColor.RED: - base += "c"; - break; - case ChatColor.LIGHT_PURPLE: - base += "d"; - break; - case ChatColor.YELLOW: - base += "e"; - break; - case ChatColor.WHITE: - base += "f"; - break; - case ChatColor.RESET: - //case NONE: - base += "r"; - break; - case "": // To stop recursion - return ""; - default: - return getClosestColor(color); - } - - return base; - } - - /** - * Based on https://github.com/ViaVersion/ViaBackwards/blob/master/core/src/main/java/nl/matsv/viabackwards/protocol/protocol1_15_2to1_16/chat/TranslatableRewriter1_16.java - * - * @param color A color string - * @return The closest color to that string - */ - private static String getClosestColor(String color) { - if (!color.startsWith("#")) { - return ""; - } - - int rgb = Integer.parseInt(color.substring(1), 16); - int r = (rgb >> 16) & 0xFF; - int g = (rgb >> 8) & 0xFF; - int b = rgb & 0xFF; - - String closest = null; - int smallestDiff = 0; - - for (Map.Entry testColor : COLORS.entrySet()) { - if (testColor.getValue() == rgb) { - closest = testColor.getKey(); - break; - } - - int testR = (testColor.getValue() >> 16) & 0xFF; - int testG = (testColor.getValue() >> 8) & 0xFF; - int testB = testColor.getValue() & 0xFF; - - // Check by the greatest diff of the 3 values - int rAverage = (testR + r) / 2; - int rDiff = testR - r; - int gDiff = testG - g; - int bDiff = testB - b; - int diff = ((2 + (rAverage >> 8)) * rDiff * rDiff) - + (4 * gDiff * gDiff) - + ((2 + ((255 - rAverage) >> 8)) * bDiff * bDiff); - if (closest == null || diff < smallestDiff) { - closest = testColor.getKey(); - smallestDiff = diff; - } - } - - return getColor(closest); - } - - /** - * Convert a list of ChatFormats into a string for inserting into messages - * - * @param formats ChatFormats to convert - * @return The converted chat formatting string - */ - private static String getFormat(List formats) { - StringBuilder str = new StringBuilder(); - for (ChatFormat cf : formats) { - String base = "\u00a7"; - switch (cf) { - case OBFUSCATED: - base += "k"; - break; - case BOLD: - base += "l"; - break; - case STRIKETHROUGH: - base += "m"; - break; - case UNDERLINED: - base += "n"; - break; - case ITALIC: - base += "o"; - break; - default: - return ""; - } - - str.append(base); - } - - return str.toString(); - } - - /** - * Checks if the given text string is a json message - * - * @param text String to test - * @return True if its a valid message json string, false if not - */ - public static boolean isMessage(String text) { - JsonParser parser = new JsonParser(); - try { - JsonObject object = parser.parse(text).getAsJsonObject(); - try { - MessageSerializer.fromJson(object); - } catch (Exception ex) { - return false; - } - } catch (Exception ex) { - return false; - } - return true; - } - - public static String toChatColor(TeamColor teamColor) { - return TEAM_COLORS.getOrDefault(teamColor, ""); - } - - /** - * Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is - * - * @param message Message to check - * @param session GeyserSession for the user - * @return True if the message is too long, false if not - */ - public static boolean isTooLong(String message, GeyserSession session) { - if (message.length() > 256) { - session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.chat.too_long", session.getLocale(), message.length())); - return true; - } - - return false; - } -} diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java new file mode 100644 index 00000000..5d52c79b --- /dev/null +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -0,0 +1,67 @@ +/* + * 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.chat; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class MessageTranslatorTest { + + private Map messages = new HashMap<>(); + + @Before + public void setUp() throws Exception { + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", + "§eDoctorMad9952 joined the game"); + + messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", + "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + + // RGB downgrade test + messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", + " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + + " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + } + + @Test + public void convertMessage() { + for (Map.Entry entry : messages.entrySet()) { + String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); + Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + } + } + + @Test + public void convertMessageLenient() { + Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); + Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); + Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + } +} From 123b074cc76785135e9b374706791efb1cc41bb9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:03:12 -0500 Subject: [PATCH 071/161] Update to Bedrock 1.16.100 (#1552) * Initial work on 1.16.100 - currently crashes the client * Update runtime item states * Use new Bedrock runtime IDs Bedrock now hardcodes block runtime IDs in alphabetical order of the identifiers. This commit updates Geyser to accomodate. - Remove runtime_block_states.dat and replace it with blockpalette.nbt - Calculate the block runtime ID based on the order of the block palette - Separate BlockTranslator.AIR into Bedrock and Java values - Update the second layer of chunks to use air when not waterlogged - Don't send item palette for now, as that's what crashes the game (will look into for v415) - Other misc. changes * Improve second layer chunk translation * v415 support - Add a message warning people they are on a beta version of Geyser * Update to protocol v417 There are still some mappings changes that need to be gone through. * Update runtime item states and clean up item frames * Future-proof enchanment table * Update for v418 * Update to v419 * Apply proper air ID to waterlogged chunk layer * Fix missing import * Remove beta warning * Update mappings * Manually patch runtime_item_states and send the ITEMS registry * Update README * Disable grindstone and smithing inventories (since they're broken) * Use artifactory jenkins plugin (#1548) * Use artifactory jenkins plugin * Bump version to 1.2.0-SNAPSHOT Co-authored-by: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> --- Jenkinsfile | 23 +- README.md | 2 +- bootstrap/bungeecord/pom.xml | 6 +- bootstrap/pom.xml | 5 +- bootstrap/spigot/pom.xml | 6 +- .../world/GeyserSpigotWorldManager.java | 6 +- bootstrap/sponge/pom.xml | 6 +- bootstrap/standalone/pom.xml | 6 +- bootstrap/velocity/pom.xml | 6 +- common/pom.xml | 5 +- connector/pom.xml | 11 +- .../connector/entity/ItemFrameEntity.java | 10 +- .../connector/network/BedrockProtocol.java | 6 +- .../network/session/GeyserSession.java | 2 - .../network/session/cache/ChunkCache.java | 6 +- .../BedrockBlockPickRequestTranslator.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 10 +- .../EnchantmentInventoryTranslator.java | 9 +- .../inventory/InventoryTranslator.java | 4 +- .../translators/item/ItemRegistry.java | 45 +- .../translators/nbt/CrossbowTranslator.java | 2 +- .../nbt/ShulkerBoxItemTranslator.java | 2 +- .../java/world/JavaExplosionTranslator.java | 2 +- .../translators/world/WorldManager.java | 10 +- .../world/block/BlockTranslator.java | 85 +- .../translators/world/chunk/BlockStorage.java | 3 +- .../geysermc/connector/utils/ChunkUtils.java | 23 +- .../main/resources/bedrock/blockpalette.nbt | Bin 0 -> 1160789 bytes .../resources/bedrock/creative_items.json | 3503 +++++----- .../resources/bedrock/legacy_block_ids.json | 555 -- .../resources/bedrock/legacy_item_ids.json | 255 - .../bedrock/runtime_block_states.dat | Bin 1063028 -> 0 bytes .../{items.json => runtime_item_states.json} | 5852 +++++++++-------- connector/src/main/resources/mappings | 2 +- pom.xml | 15 +- 35 files changed, 5019 insertions(+), 5466 deletions(-) create mode 100644 connector/src/main/resources/bedrock/blockpalette.nbt delete mode 100644 connector/src/main/resources/bedrock/legacy_block_ids.json delete mode 100644 connector/src/main/resources/bedrock/legacy_item_ids.json delete mode 100644 connector/src/main/resources/bedrock/runtime_block_states.dat rename connector/src/main/resources/bedrock/{items.json => runtime_item_states.json} (84%) diff --git a/Jenkinsfile b/Jenkinsfile index 1a93391d..96b189f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,8 +24,29 @@ pipeline { when { branch "master" } + steps { - sh 'mvn javadoc:jar source:jar deploy -DskipTests' + rtMavenDeployer( + id: "maven-deployer", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtMavenResolver( + id: "maven-resolver", + serverId: "opencollab-artifactory", + releaseRepo: "release", + snapshotRepo: "snapshot" + ) + rtMavenRun( + pom: 'pom.xml', + goals: 'javadoc:jar source:jar install -DskipTests', + deployerId: "maven-deployer", + resolverId: "maven-resolver" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) } } } diff --git a/README.md b/README.md index e2efc5b4..ab1ff24a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here! -### Currently supporting Minecraft Bedrock v1.16.x and Minecraft Java v1.16.4. +### Currently supporting Minecraft Bedrock v1.16.100 and Minecraft Java v1.16.4. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 44b28e93..124967b0 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-bungeecord + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index d9bac67d..a5ad53cb 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,12 +6,11 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT bootstrap-parent - 1.1.0 pom + spigot-public diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 067b446a..f3b9cf88 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-spigot + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java index 28b2da3a..3493fc25 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java @@ -119,7 +119,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if ((this.isLegacy && !this.isViaVersion) || session.getPlayerEntity() == null || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); if (isLegacy) { @@ -136,7 +136,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); } else { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } } @@ -190,7 +190,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { for (int blockZ = 0; blockZ < 16; blockZ++) { for (int blockX = 0; blockX < 16; blockX++) { Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.AIR); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); chunk.set(blockX, blockY, blockZ, id); } } diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 132f3817..e6ce8f85 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-sponge + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 07458f73..831239f6 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-standalone + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index babb9a3e..86de99ba 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,15 +6,15 @@ org.geysermc bootstrap-parent - 1.1.0 - ../ + 1.2.0-SNAPSHOT bootstrap-velocity + org.geysermc connector - 1.1.0 + 1.2.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 85dde12c..32c4b187 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,11 +6,10 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT common - 1.1.0 + com.google.code.gson diff --git a/connector/pom.xml b/connector/pom.xml index db267a71..7c44ddfd 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,16 +6,15 @@ org.geysermc geyser-parent - parent - ../ + 1.2.0-SNAPSHOT connector - 1.1.0 + org.geysermc common - 1.1.0 + 1.2.0-SNAPSHOT compile @@ -32,8 +31,8 @@ com.github.CloudburstMC.Protocol - bedrock-v408 - 02f46a8700 + bedrock-v419 + ce59d39118 compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 501c7e46..62fe3afe 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -69,7 +69,6 @@ public class ItemFrameEntity extends Entity { public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - NbtMapBuilder builder = NbtMap.builder(); NbtMapBuilder blockBuilder = NbtMap.builder() .putString("name", "minecraft:frame") .putInt("version", BlockTranslator.getBlockStateVersion()); @@ -77,9 +76,7 @@ public class ItemFrameEntity extends Entity { .putInt("facing_direction", direction.ordinal()) .putByte("item_frame_map_bit", (byte) 0) .build()); - builder.put("block", blockBuilder.build()); - builder.putShort("id", (short) 199); - bedrockRuntimeId = BlockTranslator.getItemFrame(builder.build()); + bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); } @@ -101,7 +98,7 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifer(itemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { @@ -141,8 +138,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - // TODO 1.16.100 set to BEDROCK_AIR - updateBlockPacket.setRuntimeId(0); + updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index 5d4462b4..85043d37 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -26,8 +26,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; -import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; -import com.nukkitx.protocol.bedrock.v408.Bedrock_v408; +import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; import java.util.ArrayList; import java.util.List; @@ -40,14 +39,13 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v408.V408_CODEC; + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v419.V419_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ public static final List SUPPORTED_BEDROCK_CODECS = new ArrayList<>(); static { - SUPPORTED_BEDROCK_CODECS.add(Bedrock_v407.V407_CODEC); SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 00b48a56..b8722122 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -75,7 +75,6 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -674,7 +673,6 @@ public class GeyserSession implements CommandSender { // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); - startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index 7bf84b8d..cbf3721f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -94,12 +94,12 @@ public class ChunkCache { public int getBlockAt(int x, int y, int z) { if (!cache) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Column column = this.getChunk(x >> 4, z >> 4); if (column == null) { - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } Chunk chunk = column.getChunks()[y >> 4]; @@ -107,7 +107,7 @@ public class ChunkCache { return chunk.get(x & 0xF, y & 0xF, z & 0xF); } - return BlockTranslator.AIR; + return BlockTranslator.JAVA_AIR_ID; } public void removeChunk(int chunkX, int chunkZ) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java index 023a83af..3e40ddd6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java @@ -42,7 +42,7 @@ public class BedrockBlockPickRequestTranslator extends PacketTranslator> itemEntriesType = new TypeReference>() { }; @@ -104,8 +110,13 @@ public class ItemRegistry { throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_bedrock"), e); } + int lodestoneCompassId = 0; + for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { + lodestoneCompassId = entry.get("id").intValue(); + } } stream = FileUtils.getResource("mappings/items.json"); @@ -153,9 +164,6 @@ public class ItemRegistry { case "minecraft:bamboo": BAMBOO = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:oak_boat": - BOAT = ITEM_ENTRIES.get(itemIndex); - break; case "minecraft:egg": EGG = ITEM_ENTRIES.get(itemIndex); break; @@ -165,8 +173,8 @@ public class ItemRegistry { case "minecraft:shield": SHIELD = ITEM_ENTRIES.get(itemIndex); break; - case "minecraft:bucket": - BUCKET = ITEM_ENTRIES.get(itemIndex); + case "minecraft:milk_bucket": + MILK_BUCKET = ITEM_ENTRIES.get(itemIndex); break; case "minecraft:wheat": WHEAT = ITEM_ENTRIES.get(itemIndex); @@ -175,11 +183,22 @@ public class ItemRegistry { break; } + if (entry.getKey().contains("boat")) { + BOATS.add(entry.getValue().get("bedrock_id").intValue()); + } else if (entry.getKey().contains("bucket") && !entry.getKey().contains("milk")) { + BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); + } + itemIndex++; } - // Add the loadstonecompass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestonecompass", itemIndex, 741, 0, false)); + if (lodestoneCompassId == 0) { + throw new RuntimeException("Lodestone compass not found in item palette!"); + } + + // Add the loadstone compass since it doesn't exist on java but we need it for item conversion + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + lodestoneCompassId, 0, false)); /* Load creative items */ stream = FileUtils.getResource("bedrock/creative_items.json"); @@ -256,7 +275,7 @@ public class ItemRegistry { * @param entry the ItemEntry to search for * @return the Bedrock identifier */ - public static String getBedrockIdentifer(ItemEntry entry) { + public static String getBedrockIdentifier(ItemEntry entry) { String blockName = ""; for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index 67f137ff..a05b9c8b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -53,7 +53,7 @@ public class CrossbowTranslator extends NbtItemStackTranslator { CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry))); + newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index a9930f69..6ecb9a44 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,7 +50,7 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifer(boxItemEntry); + String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); boxItemTag.put(new StringTag("Name", blockName)); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java index efed6ba4..4a0ea3ec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaExplosionTranslator.java @@ -46,7 +46,7 @@ public class JavaExplosionTranslator extends PacketTranslator BLOCKS; - public static final int AIR = 0; + /** + * The Java block runtime ID of air + */ + public static final int JAVA_AIR_ID = 0; + /** + * The Bedrock block runtime ID of air + */ + public static final int BEDROCK_AIR_ID; public static final int BEDROCK_WATER_ID; private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); @@ -86,25 +90,31 @@ public class BlockTranslator { public static final int JAVA_RUNTIME_SPAWNER_ID; - private static final int BLOCK_STATE_VERSION = 17825806; + private static final int BLOCK_STATE_VERSION = 17825808; static { /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/runtime_block_states.dat"); + InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); NbtList blocksTag; - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) { - blocksTag = (NbtList) nbtInputStream.readTag(); + try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); } catch (Exception e) { throw new AssertionError("Unable to get blocks from runtime block states", e); } - Map blockStateMap = new HashMap<>(); + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, + // as we no longer send a block palette + Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); - for (NbtMap tag : blocksTag) { - if (blockStateMap.putIfAbsent(tag.getCompound("block"), tag) != null) { + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + NbtMap blockTag = tag.getCompound("block"); + if (blockStateOrderedMap.containsKey(blockTag)) { throw new AssertionError("Duplicate block states in Bedrock palette"); } + blockStateOrderedMap.put(blockTag, i); } stream = FileUtils.getResource("mappings/blocks.json"); @@ -114,16 +124,13 @@ public class BlockTranslator { } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Object2IntMap addedStatesMap = new Object2IntOpenHashMap<>(); - addedStatesMap.defaultReturnValue(-1); - List paletteList = new ArrayList<>(); - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - ref.getTypesAnnotatedWith(BlockEntity.class); + Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") + : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); int waterRuntimeId = -1; int javaRuntimeId = -1; - int bedrockRuntimeId = 0; + int airRuntimeId = -1; int cobwebRuntimeId = -1; int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; @@ -136,6 +143,10 @@ public class BlockTranslator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); NbtMap blockTag = buildBedrockState(entry.getValue()); + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); + } // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -199,22 +210,12 @@ public class BlockTranslator { BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); } - NbtMap runtimeTag = blockStateMap.remove(blockTag); - if (runtimeTag != null) { - addedStatesMap.put(blockTag, bedrockRuntimeId); - paletteList.add(runtimeTag); - } else { - int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1); - if (duplicateRuntimeId == -1) { - GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); - } else { - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, duplicateRuntimeId); - } - continue; - } JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - if (javaId.contains("wool")) { + if (bedrockIdentifier.equals("minecraft:air")) { + airRuntimeId = bedrockRuntimeId; + + } else if (javaId.contains("wool")) { JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); } else if (javaId.contains("cobweb")) { @@ -233,8 +234,6 @@ public class BlockTranslator { } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; } - - bedrockRuntimeId++; } if (cobwebRuntimeId == -1) { @@ -267,19 +266,17 @@ public class BlockTranslator { } BEDROCK_WATER_ID = waterRuntimeId; - paletteList.addAll(blockStateMap.values()); // Add any missing mappings that could crash the client + if (airRuntimeId == -1) { + throw new AssertionError("Unable to find air in palette"); + } + BEDROCK_AIR_ID = airRuntimeId; // Loop around again to find all item frame runtime IDs - int frameRuntimeId = 0; - for (NbtMap tag : paletteList) { - NbtMap blockTag = tag.getCompound("block"); - if (blockTag.getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(tag, frameRuntimeId); + for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { + if (entry.getKey().getString("name").equals("minecraft:frame")) { + ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); } - frameRuntimeId++; } - - BLOCKS = new NbtList<>(NbtType.COMPOUND, paletteList); } private BlockTranslator() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index d8cd7520..f195394d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -30,6 +30,7 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; @@ -50,7 +51,7 @@ public class BlockStorage { public BlockStorage(BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(0); // Air is at the start of every palette. + this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { 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 70ac76df..005a4960 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -68,7 +68,8 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_AIR_ID; import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID; @UtilityClass @@ -246,7 +247,7 @@ public class ChunkUtils { // V1 palette IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(0); // Air + layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information layer1Palette.add(BEDROCK_WATER_ID); layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; @@ -318,18 +319,32 @@ public class ChunkUtils { } } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Position position) { Vector3i pos = Vector3i.from(position.getX(), position.getY(), position.getZ()); updateBlock(session, blockState, pos); } + /** + * Sends a block update to the Bedrock client. If chunk caching is enabled and the platform is not Spigot, this also + * adds that block to the cache. + * @param session the Bedrock session to send/register the block to + * @param blockState the Java block state of the block + * @param position the position of the block + */ public static void updateBlock(GeyserSession session, int blockState, Vector3i position) { // Checks for item frames so they aren't tripped up and removed long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, position); if (frameEntityId != -1) { // TODO: Very occasionally the item frame doesn't sync up when destroyed Entity entity = session.getEntityCache().getEntityByJavaId(frameEntityId); - if (blockState == AIR && entity != null) { // Item frame is still present and no block overrides that; refresh it + if (blockState == JAVA_AIR_ID && entity != null) { // Item frame is still present and no block overrides that; refresh it ((ItemFrameEntity) entity).updateBlock(session); return; } @@ -358,7 +373,7 @@ public class ChunkUtils { if (BlockTranslator.isWaterlogged(blockState)) { waterPacket.setRuntimeId(BEDROCK_WATER_ID); } else { - waterPacket.setRuntimeId(0); + waterPacket.setRuntimeId(BEDROCK_AIR_ID); } session.sendUpstreamPacket(waterPacket); diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt new file mode 100644 index 0000000000000000000000000000000000000000..b92a06260912dfd8e741a6c8f559af277d6c9775 GIT binary patch literal 1160789 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 literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index eea92a95..90839b0d 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -258,31 +258,31 @@ "id" : -275 }, { - "id" : 324 + "id" : 359 }, { - "id" : 427 + "id" : 543 }, { - "id" : 428 + "id" : 544 }, { - "id" : 429 + "id" : 545 }, { - "id" : 430 + "id" : 546 }, { - "id" : 431 + "id" : 547 }, { - "id" : 330 + "id" : 370 }, { - "id" : 755 + "id" : 604 }, { - "id" : 756 + "id" : 605 }, { "id" : 96 @@ -314,9 +314,6 @@ { "id" : 101 }, - { - "id" : 758 - }, { "id" : 20 }, @@ -741,66 +738,15 @@ { "id" : 170 }, - { - "id" : -239 - }, { "id" : 216 }, - { - "id" : 214 - }, - { - "id" : -227 - }, { "id" : 112 }, { "id" : 215 }, - { - "id" : -225 - }, - { - "id" : -226 - }, - { - "id" : -240 - }, - { - "id" : -241 - }, - { - "id" : -299 - }, - { - "id" : -298 - }, - { - "id" : -300 - }, - { - "id" : -301 - }, - { - "id" : -230 - }, - { - "id" : -232 - }, - { - "id" : -233 - }, - { - "id" : -234 - }, - { - "id" : -235 - }, - { - "id" : -236 - }, { "id" : -270 }, @@ -1183,6 +1129,30 @@ "id" : 201, "damage" : 2 }, + { + "id" : 214 + }, + { + "id" : -227 + }, + { + "id" : -230 + }, + { + "id" : -232 + }, + { + "id" : -233 + }, + { + "id" : -234 + }, + { + "id" : -235 + }, + { + "id" : -236 + }, { "id" : 3 }, @@ -1190,6 +1160,9 @@ "id" : 3, "damage" : 1 }, + { + "id" : 60 + }, { "id" : 2 }, @@ -1318,6 +1291,18 @@ { "id" : -9 }, + { + "id" : -225 + }, + { + "id" : -240 + }, + { + "id" : -226 + }, + { + "id" : -241 + }, { "id" : -212 }, @@ -1365,6 +1350,18 @@ "id" : -212, "damage" : 13 }, + { + "id" : -299 + }, + { + "id" : -300 + }, + { + "id" : -298 + }, + { + "id" : -301 + }, { "id" : 18 }, @@ -1411,59 +1408,58 @@ "damage" : 5 }, { - "id" : -218, - "damage" : 3 + "id" : -218 + }, + { + "id" : 291 + }, + { + "id" : 292 + }, + { + "id" : 293 }, { "id" : 295 }, { - "id" : 361 + "id" : 334 }, { - "id" : 362 + "id" : 285 }, { - "id" : 458 + "id" : 280 }, { - "id" : 296 + "id" : 282 }, { - "id" : 457 + "id" : 279 }, { - "id" : 392 + "id" : 283 }, { - "id" : 394 + "id" : 257 }, { - "id" : 391 + "id" : 258 }, { - "id" : 396 - }, - { - "id" : 260 - }, - { - "id" : 322 - }, - { - "id" : 466 + "id" : 259 }, { "id" : 103 }, { - "id" : 360 + "id" : 272 }, { - "id" : 382 + "id" : 432 }, { - "id" : 477 + "id" : 287 }, { "id" : 86 @@ -1475,7 +1471,7 @@ "id" : 91 }, { - "id" : 736 + "id" : 580 }, { "id" : 31, @@ -1494,7 +1490,7 @@ "damage" : 2 }, { - "id" : 760 + "id" : 609 }, { "id" : -131, @@ -1574,7 +1570,7 @@ "damage" : 4 }, { - "id" : 335 + "id" : 380 }, { "id" : -130 @@ -1650,83 +1646,64 @@ "id" : -216 }, { - "id" : 351, - "damage" : 19 + "id" : 408 }, { - "id" : 351, - "damage" : 7 + "id" : 400 }, { - "id" : 351, - "damage" : 8 + "id" : 401 }, { - "id" : 351, - "damage" : 16 + "id" : 393 }, { - "id" : 351, - "damage" : 17 + "id" : 396 }, { - "id" : 351, - "damage" : 1 + "id" : 394 }, { - "id" : 351, - "damage" : 14 + "id" : 407 }, { - "id" : 351, - "damage" : 11 + "id" : 404 }, { - "id" : 351, - "damage" : 10 + "id" : 403 }, { - "id" : 351, - "damage" : 2 + "id" : 395 }, { - "id" : 351, - "damage" : 6 + "id" : 399 }, { - "id" : 351, - "damage" : 12 + "id" : 405 }, { - "id" : 351, - "damage" : 18 + "id" : 397 }, { - "id" : 351, - "damage" : 5 + "id" : 398 }, { - "id" : 351, - "damage" : 13 + "id" : 406 }, { - "id" : 351, - "damage" : 9 + "id" : 402 }, { - "id" : 351 + "id" : 411 }, { - "id" : 351, - "damage" : 3 + "id" : 410 }, { - "id" : 351, - "damage" : 4 + "id" : 412 }, { - "id" : 351, - "damage" : 15 + "id" : 409 }, { "id" : 106 @@ -1762,31 +1739,31 @@ "id" : 78 }, { - "id" : 365 + "id" : 275 }, { - "id" : 319 + "id" : 262 }, { - "id" : 363 + "id" : 273 }, { - "id" : 423 + "id" : 540 }, { - "id" : 411 + "id" : 288 }, { - "id" : 349 + "id" : 264 }, { - "id" : 460 + "id" : 265 }, { - "id" : 461 + "id" : 266 }, { - "id" : 462 + "id" : 267 }, { "id" : 39 @@ -1816,25 +1793,25 @@ "id" : 99 }, { - "id" : 344 + "id" : 388 }, { - "id" : 338 + "id" : 383 }, { - "id" : 353 + "id" : 414 }, { - "id" : 367 + "id" : 277 }, { - "id" : 352 + "id" : 413 }, { "id" : 30 }, { - "id" : 375 + "id" : 278 }, { "id" : 52 @@ -1869,252 +1846,193 @@ "id" : -159 }, { - "id" : 383, - "damage" : 10 + "id" : 433 }, { - "id" : 383, - "damage" : 122 + "id" : 492 }, { - "id" : 383, - "damage" : 11 + "id" : 434 }, { - "id" : 383, - "damage" : 12 + "id" : 435 }, { - "id" : 383, - "damage" : 13 + "id" : 436 }, { - "id" : 383, - "damage" : 14 + "id" : 437 }, { - "id" : 383, - "damage" : 28 + "id" : 470 }, { - "id" : 383, - "damage" : 22 + "id" : 449 }, { - "id" : 383, - "damage" : 75 + "id" : 486 }, { - "id" : 383, - "damage" : 16 + "id" : 438 }, { - "id" : 383, - "damage" : 19 + "id" : 451 }, { - "id" : 383, - "damage" : 30 + "id" : 476 }, { - "id" : 383, - "damage" : 18 + "id" : 457 }, { - "id" : 383, - "damage" : 29 + "id" : 471 }, { - "id" : 383, - "damage" : 23 + "id" : 456 }, { - "id" : 383, - "damage" : 24 + "id" : 463 }, { - "id" : 383, - "damage" : 25 + "id" : 464 }, { - "id" : 383, - "damage" : 26 + "id" : 465 }, { - "id" : 383, - "damage" : 27 + "id" : 466 }, { - "id" : 383, - "damage" : 111 + "id" : 477 }, { - "id" : 383, - "damage" : 112 + "id" : 478 }, { - "id" : 383, - "damage" : 108 + "id" : 479 }, { - "id" : 383, - "damage" : 109 + "id" : 480 }, { - "id" : 383, - "damage" : 31 + "id" : 482 }, { - "id" : 383, - "damage" : 74 + "id" : 483 }, { - "id" : 383, - "damage" : 113 + "id" : 487 }, { - "id" : 383, - "damage" : 121 + "id" : 488 }, { - "id" : 383, - "damage" : 33 + "id" : 439 }, { - "id" : 383, - "damage" : 38 + "id" : 440 }, { - "id" : 383, - "damage" : 39 + "id" : 441 }, { - "id" : 383, - "damage" : 34 + "id" : 442 }, { - "id" : 383, - "damage" : 48 + "id" : 462 }, { - "id" : 383, - "damage" : 46 + "id" : 460 }, { - "id" : 383, - "damage" : 37 + "id" : 443 }, { - "id" : 383, - "damage" : 35 + "id" : 444 }, { - "id" : 383, - "damage" : 32 + "id" : 445 }, { - "id" : 383, - "damage" : 36 + "id" : 446 }, { - "id" : 383, - "damage" : 47 + "id" : 461 }, { - "id" : 383, - "damage" : 110 + "id" : 481 }, { - "id" : 383, - "damage" : 17 + "id" : 448 }, { - "id" : 383, - "damage" : 40 + "id" : 455 }, { - "id" : 383, - "damage" : 45 + "id" : 450 }, { - "id" : 383, - "damage" : 49 + "id" : 459 }, { - "id" : 383, - "damage" : 50 + "id" : 469 }, { - "id" : 383, - "damage" : 55 + "id" : 458 }, { - "id" : 383, - "damage" : 42 + "id" : 453 }, { - "id" : 383, - "damage" : 125 + "id" : 493 }, { - "id" : 383, - "damage" : 124 + "id" : 494 }, { - "id" : 383, - "damage" : 123 + "id" : 495 }, { - "id" : 383, - "damage" : 126 + "id" : 496 }, { - "id" : 383, - "damage" : 41 + "id" : 497 }, { - "id" : 383, - "damage" : 43 + "id" : 452 }, { - "id" : 383, - "damage" : 54 + "id" : 454 }, { - "id" : 383, - "damage" : 57 + "id" : 467 }, { - "id" : 383, - "damage" : 104 + "id" : 472 }, { - "id" : 383, - "damage" : 105 + "id" : 473 }, { - "id" : 383, - "damage" : 115 + "id" : 474 }, { - "id" : 383, - "damage" : 118 + "id" : 447 }, { - "id" : 383, - "damage" : 116 + "id" : 490 }, { - "id" : 383, - "damage" : 58 + "id" : 475 }, { - "id" : 383, - "damage" : 114 + "id" : 484 }, { - "id" : 383, - "damage" : 59 + "id" : 489 + }, + { + "id" : 491 }, { "id" : 49 @@ -2135,7 +2053,7 @@ "id" : 213 }, { - "id" : 372 + "id" : 294 }, { "id" : 121 @@ -2147,10 +2065,10 @@ "id" : 240 }, { - "id" : 432 + "id" : 548 }, { - "id" : 433 + "id" : 549 }, { "id" : 19 @@ -2199,1023 +2117,1023 @@ "damage" : 12 }, { - "id" : 298 + "id" : 335 }, { - "id" : 302 + "id" : 339 }, { - "id" : 306 + "id" : 343 }, { - "id" : 314 - }, - { - "id" : 310 - }, - { - "id" : 748 - }, - { - "id" : 299 - }, - { - "id" : 303 - }, - { - "id" : 307 - }, - { - "id" : 315 - }, - { - "id" : 311 - }, - { - "id" : 749 - }, - { - "id" : 300 - }, - { - "id" : 304 - }, - { - "id" : 308 - }, - { - "id" : 316 - }, - { - "id" : 312 - }, - { - "id" : 750 - }, - { - "id" : 301 - }, - { - "id" : 305 - }, - { - "id" : 309 - }, - { - "id" : 317 - }, - { - "id" : 313 - }, - { - "id" : 751 - }, - { - "id" : 268 - }, - { - "id" : 272 - }, - { - "id" : 267 - }, - { - "id" : 283 - }, - { - "id" : 276 - }, - { - "id" : 743 - }, - { - "id" : 271 - }, - { - "id" : 275 - }, - { - "id" : 258 - }, - { - "id" : 286 - }, - { - "id" : 279 - }, - { - "id" : 746 - }, - { - "id" : 270 - }, - { - "id" : 274 - }, - { - "id" : 257 - }, - { - "id" : 285 - }, - { - "id" : 278 - }, - { - "id" : 745 - }, - { - "id" : 269 - }, - { - "id" : 273 - }, - { - "id" : 256 - }, - { - "id" : 284 - }, - { - "id" : 277 - }, - { - "id" : 744 - }, - { - "id" : 290 - }, - { - "id" : 291 - }, - { - "id" : 292 - }, - { - "id" : 294 - }, - { - "id" : 293 - }, - { - "id" : 747 - }, - { - "id" : 261 - }, - { - "id" : 471 - }, - { - "id" : 262 - }, - { - "id" : 262, - "damage" : 6 - }, - { - "id" : 262, - "damage" : 7 - }, - { - "id" : 262, - "damage" : 8 - }, - { - "id" : 262, - "damage" : 9 - }, - { - "id" : 262, - "damage" : 10 - }, - { - "id" : 262, - "damage" : 11 - }, - { - "id" : 262, - "damage" : 12 - }, - { - "id" : 262, - "damage" : 13 - }, - { - "id" : 262, - "damage" : 14 - }, - { - "id" : 262, - "damage" : 15 - }, - { - "id" : 262, - "damage" : 16 - }, - { - "id" : 262, - "damage" : 17 - }, - { - "id" : 262, - "damage" : 18 - }, - { - "id" : 262, - "damage" : 19 - }, - { - "id" : 262, - "damage" : 20 - }, - { - "id" : 262, - "damage" : 21 - }, - { - "id" : 262, - "damage" : 22 - }, - { - "id" : 262, - "damage" : 23 - }, - { - "id" : 262, - "damage" : 24 - }, - { - "id" : 262, - "damage" : 25 - }, - { - "id" : 262, - "damage" : 26 - }, - { - "id" : 262, - "damage" : 27 - }, - { - "id" : 262, - "damage" : 28 - }, - { - "id" : 262, - "damage" : 29 - }, - { - "id" : 262, - "damage" : 30 - }, - { - "id" : 262, - "damage" : 31 - }, - { - "id" : 262, - "damage" : 32 - }, - { - "id" : 262, - "damage" : 33 - }, - { - "id" : 262, - "damage" : 34 - }, - { - "id" : 262, - "damage" : 35 - }, - { - "id" : 262, - "damage" : 36 - }, - { - "id" : 262, - "damage" : 37 - }, - { - "id" : 262, - "damage" : 38 - }, - { - "id" : 262, - "damage" : 39 - }, - { - "id" : 262, - "damage" : 40 - }, - { - "id" : 262, - "damage" : 41 - }, - { - "id" : 262, - "damage" : 42 - }, - { - "id" : 262, - "damage" : 43 - }, - { - "id" : 513 - }, - { - "id" : 366 - }, - { - "id" : 320 - }, - { - "id" : 364 - }, - { - "id" : 424 - }, - { - "id" : 412 - }, - { - "id" : 350 - }, - { - "id" : 463 - }, - { - "id" : 297 - }, - { - "id" : 282 - }, - { - "id" : 459 - }, - { - "id" : 413 - }, - { - "id" : 393 - }, - { - "id" : 357 - }, - { - "id" : 400 - }, - { - "id" : 354 - }, - { - "id" : 464 - }, - { - "id" : 346 - }, - { - "id" : 398 - }, - { - "id" : 757 - }, - { - "id" : 332 - }, - { - "id" : 359 - }, - { - "id" : 259 - }, - { - "id" : 420 + "id" : 351 }, { "id" : 347 }, + { + "id" : 597 + }, + { + "id" : 336 + }, + { + "id" : 340 + }, + { + "id" : 344 + }, + { + "id" : 352 + }, + { + "id" : 348 + }, + { + "id" : 598 + }, + { + "id" : 337 + }, + { + "id" : 341 + }, { "id" : 345 }, { - "id" : 395 + "id" : 353 }, { - "id" : 395, - "damage" : 2 + "id" : 349 + }, + { + "id" : 599 + }, + { + "id" : 338 + }, + { + "id" : 342 + }, + { + "id" : 346 + }, + { + "id" : 354 + }, + { + "id" : 350 + }, + { + "id" : 600 + }, + { + "id" : 308 + }, + { + "id" : 312 + }, + { + "id" : 307 + }, + { + "id" : 322 + }, + { + "id" : 316 + }, + { + "id" : 592 + }, + { + "id" : 311 + }, + { + "id" : 315 + }, + { + "id" : 298 + }, + { + "id" : 325 + }, + { + "id" : 319 + }, + { + "id" : 595 + }, + { + "id" : 310 + }, + { + "id" : 314 + }, + { + "id" : 297 + }, + { + "id" : 324 + }, + { + "id" : 318 + }, + { + "id" : 594 + }, + { + "id" : 309 + }, + { + "id" : 313 + }, + { + "id" : 296 + }, + { + "id" : 323 + }, + { + "id" : 317 + }, + { + "id" : 593 }, { "id" : 329 }, { - "id" : 416 + "id" : 330 }, { - "id" : 417 + "id" : 331 }, { - "id" : 418 + "id" : 333 }, { - "id" : 419 + "id" : 332 }, { - "id" : 455 + "id" : 596 }, { - "id" : 469 + "id" : 300 }, { - "id" : 444 + "id" : 565 }, { - "id" : 450 + "id" : 301 }, { - "id" : 374 - }, - { - "id" : 384 - }, - { - "id" : 373 - }, - { - "id" : 373, - "damage" : 1 - }, - { - "id" : 373, - "damage" : 2 - }, - { - "id" : 373, - "damage" : 3 - }, - { - "id" : 373, - "damage" : 4 - }, - { - "id" : 373, - "damage" : 5 - }, - { - "id" : 373, + "id" : 301, "damage" : 6 }, { - "id" : 373, + "id" : 301, "damage" : 7 }, { - "id" : 373, + "id" : 301, "damage" : 8 }, { - "id" : 373, + "id" : 301, "damage" : 9 }, { - "id" : 373, + "id" : 301, "damage" : 10 }, { - "id" : 373, + "id" : 301, "damage" : 11 }, { - "id" : 373, + "id" : 301, "damage" : 12 }, { - "id" : 373, + "id" : 301, "damage" : 13 }, { - "id" : 373, + "id" : 301, "damage" : 14 }, { - "id" : 373, + "id" : 301, "damage" : 15 }, { - "id" : 373, + "id" : 301, "damage" : 16 }, { - "id" : 373, + "id" : 301, "damage" : 17 }, { - "id" : 373, + "id" : 301, "damage" : 18 }, { - "id" : 373, + "id" : 301, "damage" : 19 }, { - "id" : 373, + "id" : 301, "damage" : 20 }, { - "id" : 373, + "id" : 301, "damage" : 21 }, { - "id" : 373, + "id" : 301, "damage" : 22 }, { - "id" : 373, + "id" : 301, "damage" : 23 }, { - "id" : 373, + "id" : 301, "damage" : 24 }, { - "id" : 373, + "id" : 301, "damage" : 25 }, { - "id" : 373, + "id" : 301, "damage" : 26 }, { - "id" : 373, + "id" : 301, "damage" : 27 }, { - "id" : 373, + "id" : 301, "damage" : 28 }, { - "id" : 373, + "id" : 301, "damage" : 29 }, { - "id" : 373, + "id" : 301, "damage" : 30 }, { - "id" : 373, + "id" : 301, "damage" : 31 }, { - "id" : 373, + "id" : 301, "damage" : 32 }, { - "id" : 373, + "id" : 301, "damage" : 33 }, { - "id" : 373, + "id" : 301, "damage" : 34 }, { - "id" : 373, + "id" : 301, "damage" : 35 }, { - "id" : 373, + "id" : 301, "damage" : 36 }, { - "id" : 373, + "id" : 301, "damage" : 37 }, { - "id" : 373, + "id" : 301, "damage" : 38 }, { - "id" : 373, + "id" : 301, "damage" : 39 }, { - "id" : 373, + "id" : 301, "damage" : 40 }, { - "id" : 373, + "id" : 301, "damage" : 41 }, { - "id" : 373, + "id" : 301, "damage" : 42 }, { - "id" : 438 - }, - { - "id" : 438, - "damage" : 1 - }, - { - "id" : 438, - "damage" : 2 - }, - { - "id" : 438, - "damage" : 3 - }, - { - "id" : 438, - "damage" : 4 - }, - { - "id" : 438, - "damage" : 5 - }, - { - "id" : 438, - "damage" : 6 - }, - { - "id" : 438, - "damage" : 7 - }, - { - "id" : 438, - "damage" : 8 - }, - { - "id" : 438, - "damage" : 9 - }, - { - "id" : 438, - "damage" : 10 - }, - { - "id" : 438, - "damage" : 11 - }, - { - "id" : 438, - "damage" : 12 - }, - { - "id" : 438, - "damage" : 13 - }, - { - "id" : 438, - "damage" : 14 - }, - { - "id" : 438, - "damage" : 15 - }, - { - "id" : 438, - "damage" : 16 - }, - { - "id" : 438, - "damage" : 17 - }, - { - "id" : 438, - "damage" : 18 - }, - { - "id" : 438, - "damage" : 19 - }, - { - "id" : 438, - "damage" : 20 - }, - { - "id" : 438, - "damage" : 21 - }, - { - "id" : 438, - "damage" : 22 - }, - { - "id" : 438, - "damage" : 23 - }, - { - "id" : 438, - "damage" : 24 - }, - { - "id" : 438, - "damage" : 25 - }, - { - "id" : 438, - "damage" : 26 - }, - { - "id" : 438, - "damage" : 27 - }, - { - "id" : 438, - "damage" : 28 - }, - { - "id" : 438, - "damage" : 29 - }, - { - "id" : 438, - "damage" : 30 - }, - { - "id" : 438, - "damage" : 31 - }, - { - "id" : 438, - "damage" : 32 - }, - { - "id" : 438, - "damage" : 33 - }, - { - "id" : 438, - "damage" : 34 - }, - { - "id" : 438, - "damage" : 35 - }, - { - "id" : 438, - "damage" : 36 - }, - { - "id" : 438, - "damage" : 37 - }, - { - "id" : 438, - "damage" : 38 - }, - { - "id" : 438, - "damage" : 39 - }, - { - "id" : 438, - "damage" : 40 - }, - { - "id" : 438, - "damage" : 41 - }, - { - "id" : 438, - "damage" : 42 - }, - { - "id" : 441 - }, - { - "id" : 441, - "damage" : 1 - }, - { - "id" : 441, - "damage" : 2 - }, - { - "id" : 441, - "damage" : 3 - }, - { - "id" : 441, - "damage" : 4 - }, - { - "id" : 441, - "damage" : 5 - }, - { - "id" : 441, - "damage" : 6 - }, - { - "id" : 441, - "damage" : 7 - }, - { - "id" : 441, - "damage" : 8 - }, - { - "id" : 441, - "damage" : 9 - }, - { - "id" : 441, - "damage" : 10 - }, - { - "id" : 441, - "damage" : 11 - }, - { - "id" : 441, - "damage" : 12 - }, - { - "id" : 441, - "damage" : 13 - }, - { - "id" : 441, - "damage" : 14 - }, - { - "id" : 441, - "damage" : 15 - }, - { - "id" : 441, - "damage" : 16 - }, - { - "id" : 441, - "damage" : 17 - }, - { - "id" : 441, - "damage" : 18 - }, - { - "id" : 441, - "damage" : 19 - }, - { - "id" : 441, - "damage" : 20 - }, - { - "id" : 441, - "damage" : 21 - }, - { - "id" : 441, - "damage" : 22 - }, - { - "id" : 441, - "damage" : 23 - }, - { - "id" : 441, - "damage" : 24 - }, - { - "id" : 441, - "damage" : 25 - }, - { - "id" : 441, - "damage" : 26 - }, - { - "id" : 441, - "damage" : 27 - }, - { - "id" : 441, - "damage" : 28 - }, - { - "id" : 441, - "damage" : 29 - }, - { - "id" : 441, - "damage" : 30 - }, - { - "id" : 441, - "damage" : 31 - }, - { - "id" : 441, - "damage" : 32 - }, - { - "id" : 441, - "damage" : 33 - }, - { - "id" : 441, - "damage" : 34 - }, - { - "id" : 441, - "damage" : 35 - }, - { - "id" : 441, - "damage" : 36 - }, - { - "id" : 441, - "damage" : 37 - }, - { - "id" : 441, - "damage" : 38 - }, - { - "id" : 441, - "damage" : 39 - }, - { - "id" : 441, - "damage" : 40 - }, - { - "id" : 441, - "damage" : 41 - }, - { - "id" : 441, - "damage" : 42 - }, - { - "id" : 280 + "id" : 301, + "damage" : 43 }, { "id" : 355 }, { - "id" : 355, - "damage" : 8 + "id" : 276 }, { - "id" : 355, - "damage" : 7 + "id" : 263 }, { - "id" : 355, - "damage" : 15 + "id" : 274 }, { - "id" : 355, - "damage" : 12 + "id" : 541 }, { - "id" : 355, - "damage" : 14 + "id" : 289 }, { - "id" : 355, - "damage" : 1 + "id" : 268 }, { - "id" : 355, - "damage" : 4 + "id" : 269 }, { - "id" : 355, - "damage" : 5 + "id" : 261 }, { - "id" : 355, - "damage" : 13 + "id" : 260 }, { - "id" : 355, - "damage" : 9 + "id" : 286 }, { - "id" : 355, - "damage" : 3 + "id" : 290 }, { - "id" : 355, - "damage" : 11 + "id" : 281 }, { - "id" : 355, - "damage" : 10 + "id" : 271 }, { - "id" : 355, + "id" : 284 + }, + { + "id" : 415 + }, + { + "id" : 270 + }, + { + "id" : 390 + }, + { + "id" : 507 + }, + { + "id" : 606 + }, + { + "id" : 372 + }, + { + "id" : 419 + }, + { + "id" : 299 + }, + { + "id" : 537 + }, + { + "id" : 391 + }, + { + "id" : 389 + }, + { + "id" : 505 + }, + { + "id" : 505, "damage" : 2 }, { - "id" : 355, + "id" : 369 + }, + { + "id" : 520 + }, + { + "id" : 521 + }, + { + "id" : 522 + }, + { + "id" : 523 + }, + { + "id" : 536 + }, + { + "id" : 563 + }, + { + "id" : 554 + }, + { + "id" : 558 + }, + { + "id" : 425 + }, + { + "id" : 498 + }, + { + "id" : 424 + }, + { + "id" : 424, + "damage" : 1 + }, + { + "id" : 424, + "damage" : 2 + }, + { + "id" : 424, + "damage" : 3 + }, + { + "id" : 424, + "damage" : 4 + }, + { + "id" : 424, + "damage" : 5 + }, + { + "id" : 424, + "damage" : 6 + }, + { + "id" : 424, + "damage" : 7 + }, + { + "id" : 424, + "damage" : 8 + }, + { + "id" : 424, + "damage" : 9 + }, + { + "id" : 424, + "damage" : 10 + }, + { + "id" : 424, + "damage" : 11 + }, + { + "id" : 424, + "damage" : 12 + }, + { + "id" : 424, + "damage" : 13 + }, + { + "id" : 424, + "damage" : 14 + }, + { + "id" : 424, + "damage" : 15 + }, + { + "id" : 424, + "damage" : 16 + }, + { + "id" : 424, + "damage" : 17 + }, + { + "id" : 424, + "damage" : 18 + }, + { + "id" : 424, + "damage" : 19 + }, + { + "id" : 424, + "damage" : 20 + }, + { + "id" : 424, + "damage" : 21 + }, + { + "id" : 424, + "damage" : 22 + }, + { + "id" : 424, + "damage" : 23 + }, + { + "id" : 424, + "damage" : 24 + }, + { + "id" : 424, + "damage" : 25 + }, + { + "id" : 424, + "damage" : 26 + }, + { + "id" : 424, + "damage" : 27 + }, + { + "id" : 424, + "damage" : 28 + }, + { + "id" : 424, + "damage" : 29 + }, + { + "id" : 424, + "damage" : 30 + }, + { + "id" : 424, + "damage" : 31 + }, + { + "id" : 424, + "damage" : 32 + }, + { + "id" : 424, + "damage" : 33 + }, + { + "id" : 424, + "damage" : 34 + }, + { + "id" : 424, + "damage" : 35 + }, + { + "id" : 424, + "damage" : 36 + }, + { + "id" : 424, + "damage" : 37 + }, + { + "id" : 424, + "damage" : 38 + }, + { + "id" : 424, + "damage" : 39 + }, + { + "id" : 424, + "damage" : 40 + }, + { + "id" : 424, + "damage" : 41 + }, + { + "id" : 424, + "damage" : 42 + }, + { + "id" : 551 + }, + { + "id" : 551, + "damage" : 1 + }, + { + "id" : 551, + "damage" : 2 + }, + { + "id" : 551, + "damage" : 3 + }, + { + "id" : 551, + "damage" : 4 + }, + { + "id" : 551, + "damage" : 5 + }, + { + "id" : 551, + "damage" : 6 + }, + { + "id" : 551, + "damage" : 7 + }, + { + "id" : 551, + "damage" : 8 + }, + { + "id" : 551, + "damage" : 9 + }, + { + "id" : 551, + "damage" : 10 + }, + { + "id" : 551, + "damage" : 11 + }, + { + "id" : 551, + "damage" : 12 + }, + { + "id" : 551, + "damage" : 13 + }, + { + "id" : 551, + "damage" : 14 + }, + { + "id" : 551, + "damage" : 15 + }, + { + "id" : 551, + "damage" : 16 + }, + { + "id" : 551, + "damage" : 17 + }, + { + "id" : 551, + "damage" : 18 + }, + { + "id" : 551, + "damage" : 19 + }, + { + "id" : 551, + "damage" : 20 + }, + { + "id" : 551, + "damage" : 21 + }, + { + "id" : 551, + "damage" : 22 + }, + { + "id" : 551, + "damage" : 23 + }, + { + "id" : 551, + "damage" : 24 + }, + { + "id" : 551, + "damage" : 25 + }, + { + "id" : 551, + "damage" : 26 + }, + { + "id" : 551, + "damage" : 27 + }, + { + "id" : 551, + "damage" : 28 + }, + { + "id" : 551, + "damage" : 29 + }, + { + "id" : 551, + "damage" : 30 + }, + { + "id" : 551, + "damage" : 31 + }, + { + "id" : 551, + "damage" : 32 + }, + { + "id" : 551, + "damage" : 33 + }, + { + "id" : 551, + "damage" : 34 + }, + { + "id" : 551, + "damage" : 35 + }, + { + "id" : 551, + "damage" : 36 + }, + { + "id" : 551, + "damage" : 37 + }, + { + "id" : 551, + "damage" : 38 + }, + { + "id" : 551, + "damage" : 39 + }, + { + "id" : 551, + "damage" : 40 + }, + { + "id" : 551, + "damage" : 41 + }, + { + "id" : 551, + "damage" : 42 + }, + { + "id" : 552 + }, + { + "id" : 552, + "damage" : 1 + }, + { + "id" : 552, + "damage" : 2 + }, + { + "id" : 552, + "damage" : 3 + }, + { + "id" : 552, + "damage" : 4 + }, + { + "id" : 552, + "damage" : 5 + }, + { + "id" : 552, + "damage" : 6 + }, + { + "id" : 552, + "damage" : 7 + }, + { + "id" : 552, + "damage" : 8 + }, + { + "id" : 552, + "damage" : 9 + }, + { + "id" : 552, + "damage" : 10 + }, + { + "id" : 552, + "damage" : 11 + }, + { + "id" : 552, + "damage" : 12 + }, + { + "id" : 552, + "damage" : 13 + }, + { + "id" : 552, + "damage" : 14 + }, + { + "id" : 552, + "damage" : 15 + }, + { + "id" : 552, + "damage" : 16 + }, + { + "id" : 552, + "damage" : 17 + }, + { + "id" : 552, + "damage" : 18 + }, + { + "id" : 552, + "damage" : 19 + }, + { + "id" : 552, + "damage" : 20 + }, + { + "id" : 552, + "damage" : 21 + }, + { + "id" : 552, + "damage" : 22 + }, + { + "id" : 552, + "damage" : 23 + }, + { + "id" : 552, + "damage" : 24 + }, + { + "id" : 552, + "damage" : 25 + }, + { + "id" : 552, + "damage" : 26 + }, + { + "id" : 552, + "damage" : 27 + }, + { + "id" : 552, + "damage" : 28 + }, + { + "id" : 552, + "damage" : 29 + }, + { + "id" : 552, + "damage" : 30 + }, + { + "id" : 552, + "damage" : 31 + }, + { + "id" : 552, + "damage" : 32 + }, + { + "id" : 552, + "damage" : 33 + }, + { + "id" : 552, + "damage" : 34 + }, + { + "id" : 552, + "damage" : 35 + }, + { + "id" : 552, + "damage" : 36 + }, + { + "id" : 552, + "damage" : 37 + }, + { + "id" : 552, + "damage" : 38 + }, + { + "id" : 552, + "damage" : 39 + }, + { + "id" : 552, + "damage" : 40 + }, + { + "id" : 552, + "damage" : 41 + }, + { + "id" : 552, + "damage" : 42 + }, + { + "id" : 320 + }, + { + "id" : 416 + }, + { + "id" : 416, + "damage" : 8 + }, + { + "id" : 416, + "damage" : 7 + }, + { + "id" : 416, + "damage" : 15 + }, + { + "id" : 416, + "damage" : 12 + }, + { + "id" : 416, + "damage" : 14 + }, + { + "id" : 416, + "damage" : 1 + }, + { + "id" : 416, + "damage" : 4 + }, + { + "id" : 416, + "damage" : 5 + }, + { + "id" : 416, + "damage" : 13 + }, + { + "id" : 416, + "damage" : 9 + }, + { + "id" : 416, + "damage" : 3 + }, + { + "id" : 416, + "damage" : 11 + }, + { + "id" : 416, + "damage" : 10 + }, + { + "id" : 416, + "damage" : 2 + }, + { + "id" : 416, "damage" : 6 }, { @@ -3246,14 +3164,13 @@ "id" : -202 }, { - "id" : -219, - "damage" : 3 + "id" : -219 }, { - "id" : 720 + "id" : 578 }, { - "id" : 801 + "id" : 610 }, { "id" : 61 @@ -3268,7 +3185,7 @@ "id" : -272 }, { - "id" : 379 + "id" : 429 }, { "id" : 145 @@ -3294,7 +3211,7 @@ "id" : -194 }, { - "id" : 380 + "id" : 430 }, { "id" : -213 @@ -3378,7 +3295,7 @@ "damage" : 6 }, { - "id" : 425 + "id" : 542 }, { "id" : 25 @@ -3387,46 +3304,46 @@ "id" : 84 }, { - "id" : 500 + "id" : 524 }, { - "id" : 501 + "id" : 525 }, { - "id" : 502 + "id" : 526 }, { - "id" : 503 + "id" : 527 }, { - "id" : 504 + "id" : 528 }, { - "id" : 505 + "id" : 529 }, { - "id" : 506 + "id" : 530 }, { - "id" : 507 + "id" : 531 }, { - "id" : 508 + "id" : 532 }, { - "id" : 509 + "id" : 533 }, { - "id" : 510 + "id" : 534 }, { - "id" : 511 + "id" : 535 }, { - "id" : 759 + "id" : 608 }, { - "id" : 348 + "id" : 392 }, { "id" : 89 @@ -3438,96 +3355,89 @@ "id" : 169 }, { - "id" : 323 + "id" : 358 }, { - "id" : 472 + "id" : 566 }, { - "id" : 473 + "id" : 567 }, { - "id" : 474 + "id" : 568 }, { - "id" : 475 + "id" : 569 }, { - "id" : 476 + "id" : 570 }, { - "id" : 753 + "id" : 602 }, { - "id" : 754 + "id" : 603 + }, + { + "id" : 357 + }, + { + "id" : 503 + }, + { + "id" : 581 + }, + { + "id" : 504 }, { "id" : 321 }, { - "id" : 389 + "id" : 360 }, { - "id" : 737 + "id" : 361 }, { - "id" : 390 + "id" : 362 }, { - "id" : 281 + "id" : 363 }, { - "id" : 325 + "id" : 364 }, { - "id" : 325, - "damage" : 1 + "id" : 365 }, { - "id" : 325, - "damage" : 8 + "id" : 366 }, { - "id" : 325, - "damage" : 10 + "id" : 367 }, { - "id" : 325, - "damage" : 2 - }, - { - "id" : 325, + "id" : 506, "damage" : 3 }, { - "id" : 325, - "damage" : 4 - }, - { - "id" : 325, - "damage" : 5 - }, - { - "id" : 397, - "damage" : 3 - }, - { - "id" : 397, + "id" : 506, "damage" : 2 }, { - "id" : 397, + "id" : 506, "damage" : 4 }, { - "id" : 397, + "id" : 506, "damage" : 5 }, { - "id" : 397 + "id" : 506 }, { - "id" : 397, + "id" : 506, "damage" : 1 }, { @@ -3546,92 +3456,576 @@ "id" : 120 }, { - "id" : 263 + "id" : 302 }, { - "id" : 263, - "damage" : 1 + "id" : 303 }, { - "id" : 264 + "id" : 304 }, { - "id" : 452 + "id" : 559 }, { - "id" : 265 + "id" : 305 }, { - "id" : 752 + "id" : 601 }, { - "id" : 742 + "id" : 591 }, { - "id" : 371 + "id" : 423 }, { - "id" : 266 + "id" : 306 }, { - "id" : 388 + "id" : 502 }, { - "id" : 406 + "id" : 514 }, { - "id" : 337 + "id" : 382 }, { - "id" : 336 + "id" : 381 }, { - "id" : 405 + "id" : 513 }, { - "id" : 409 + "id" : 555 + }, + { + "id" : 539 + }, + { + "id" : 560 + }, + { + "id" : 561 + }, + { + "id" : 562 + }, + { + "id" : 564 + }, + { + "id" : 326 + }, + { + "id" : 327 + }, + { + "id" : 356 + }, + { + "id" : 328 + }, + { + "id" : 379 + }, + { + "id" : 519 + }, + { + "id" : 518 + }, + { + "id" : 499 + }, + { + "id" : 421 + }, + { + "id" : 427 + }, + { + "id" : 428 + }, + { + "id" : 426 + }, + { + "id" : 550 + }, + { + "id" : 556 }, { "id" : 422 }, { - "id" : 465 + "id" : 386 }, { - "id" : 467 + "id" : 420 }, { - "id" : 468 + "id" : 431 }, { - "id" : 470 + "id" : 508 }, { - "id" : 287 + "id" : 208 }, { - "id" : 288 + "id" : 615 }, { - "id" : 318 - }, - { - "id" : 289 - }, - { - "id" : 334 - }, - { - "id" : 415 - }, - { - "id" : 414 + "id" : 384 }, { "id" : 385 }, { - "id" : 369 + "id" : 500 + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA=" + }, + { + "id" : 511, + "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA=" + }, + { + "id" : 373 + }, + { + "id" : 376 + }, + { + "id" : 374 + }, + { + "id" : 375 }, { "id" : 377 @@ -3639,496 +4033,6 @@ { "id" : 378 }, - { - "id" : 376 - }, - { - "id" : 437 - }, - { - "id" : 445 - }, - { - "id" : 370 - }, - { - "id" : 341 - }, - { - "id" : 368 - }, - { - "id" : 381 - }, - { - "id" : 399 - }, - { - "id" : 208 - }, - { - "id" : 426 - }, - { - "id" : 339 - }, - { - "id" : 340 - }, - { - "id" : 386 - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZAsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZAwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZA8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZBMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBUAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBYAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBcAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZBgAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZBkAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBoAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBsAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZBwAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBQACAgBpZB0AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB4AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZB8AAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCAAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCEAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsBAACAgBpZCIAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCMAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAgACAgBpZCQAAAA=" - }, - { - "id" : 403, - "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAwACAgBpZCQAAAA=" - }, - { - "id" : 333 - }, - { - "id" : 333, - "damage" : 1 - }, - { - "id" : 333, - "damage" : 2 - }, - { - "id" : 333, - "damage" : 3 - }, - { - "id" : 333, - "damage" : 4 - }, - { - "id" : 333, - "damage" : 5 - }, { "id" : 66 }, @@ -4142,19 +4046,19 @@ "id" : 126 }, { - "id" : 328 + "id" : 368 }, { - "id" : 342 + "id" : 387 }, { - "id" : 408 + "id" : 516 }, { - "id" : 407 + "id" : 515 }, { - "id" : 331 + "id" : 371 }, { "id" : 152 @@ -4241,13 +4145,13 @@ "id" : 151 }, { - "id" : 356 + "id" : 417 }, { - "id" : 404 + "id" : 512 }, { - "id" : 410 + "id" : 517 }, { "id" : 125, @@ -4269,252 +4173,255 @@ "id" : 46 }, { - "id" : 421 + "id" : 538 }, { "id" : -204 }, { - "id" : 446 + "id" : 557 }, { - "id" : 446, + "id" : 557, "damage" : 8 }, { - "id" : 446, + "id" : 557, "damage" : 7 }, { - "id" : 446, + "id" : 557, "damage" : 15 }, { - "id" : 446, + "id" : 557, "damage" : 12 }, { - "id" : 446, + "id" : 557, "damage" : 14 }, { - "id" : 446, + "id" : 557, "damage" : 1 }, { - "id" : 446, + "id" : 557, "damage" : 4 }, { - "id" : 446, + "id" : 557, "damage" : 5 }, { - "id" : 446, + "id" : 557, "damage" : 13 }, { - "id" : 446, + "id" : 557, "damage" : 9 }, { - "id" : 446, + "id" : 557, "damage" : 3 }, { - "id" : 446, + "id" : 557, "damage" : 11 }, { - "id" : 446, + "id" : 557, "damage" : 10 }, { - "id" : 446, + "id" : 557, "damage" : 2 }, { - "id" : 446, + "id" : 557, "damage" : 6 }, { - "id" : 446, + "id" : 557, "damage" : 15, "nbt_b64" : "CgAAAwQAVHlwZQEAAAAA" }, { - "id" : 434 + "id" : 572 }, { - "id" : 434, - "damage" : 1 + "id" : 573 }, { - "id" : 434, - "damage" : 2 + "id" : 571 }, { - "id" : 434, - "damage" : 3 + "id" : 574 }, { - "id" : 434, - "damage" : 4 + "id" : 575 }, { - "id" : 434, - "damage" : 5 + "id" : 576 }, { - "id" : 434, - "damage" : 6 + "id" : 577 }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMAAAAAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 401, - "nbt_b64" : "CgAACgkARmlyZXdvcmtzAQYARmxpZ2h0AQkKAEV4cGxvc2lvbnMKAQAAAAcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAAA" + "id" : 509, + "nbt_b64" : "CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA" }, { - "id" : 402, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IhHR3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "id" : 510, + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 8, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3JST0f/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 7, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KXnZ3/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 15, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Lw8PD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 12, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Laszr/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 14, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IdgPn/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 1, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3ImLrD/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 4, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KqRDz/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 5, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K4Mon/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 13, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3K9Tsf/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 9, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Kqi/P/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 3, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IyVIP/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 11, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3I92P7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 10, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3Ifx4D/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 2, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3IWfF7/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA=" }, { - "id" : 402, + "id" : 510, "damage" : 6, - "nbt_b64" : "CgAAAwsAY3VzdG9tQ29sb3KcnBb/Cg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgEMAEZpcmV3b3JrVHlwZQAHDABGaXJld29ya0ZhZGUAAAAAAQ0ARmlyZXdvcmtUcmFpbAABDwBGaXJld29ya0ZsaWNrZXIAAAA=" + "nbt_b64" : "CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA=" + }, + { + "id" : 607 + }, + { + "id" : -239 + }, + { + "id" : 590 } ] } \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_block_ids.json b/connector/src/main/resources/bedrock/legacy_block_ids.json deleted file mode 100644 index 27594915..00000000 --- a/connector/src/main/resources/bedrock/legacy_block_ids.json +++ /dev/null @@ -1,555 +0,0 @@ -{ - "minecraft:air" : 0, - "minecraft:stone" : 1, - "minecraft:grass" : 2, - "minecraft:dirt" : 3, - "minecraft:cobblestone" : 4, - "minecraft:planks" : 5, - "minecraft:sapling" : 6, - "minecraft:bedrock" : 7, - "minecraft:flowing_water" : 8, - "minecraft:water" : 9, - "minecraft:flowing_lava" : 10, - "minecraft:lava" : 11, - "minecraft:sand" : 12, - "minecraft:gravel" : 13, - "minecraft:gold_ore" : 14, - "minecraft:iron_ore" : 15, - "minecraft:coal_ore" : 16, - "minecraft:log" : 17, - "minecraft:leaves" : 18, - "minecraft:sponge" : 19, - "minecraft:glass" : 20, - "minecraft:lapis_ore" : 21, - "minecraft:lapis_block" : 22, - "minecraft:dispenser" : 23, - "minecraft:sandstone" : 24, - "minecraft:noteblock" : 25, - "minecraft:bed" : 26, - "minecraft:golden_rail" : 27, - "minecraft:detector_rail" : 28, - "minecraft:sticky_piston" : 29, - "minecraft:web" : 30, - "minecraft:tallgrass" : 31, - "minecraft:deadbush" : 32, - "minecraft:piston" : 33, - "minecraft:pistonArmCollision" : 34, - "minecraft:wool" : 35, - "minecraft:element_0" : 36, - "minecraft:yellow_flower" : 37, - "minecraft:red_flower" : 38, - "minecraft:brown_mushroom" : 39, - "minecraft:red_mushroom" : 40, - "minecraft:gold_block" : 41, - "minecraft:iron_block" : 42, - "minecraft:double_stone_slab" : 43, - "minecraft:stone_slab" : 44, - "minecraft:brick_block" : 45, - "minecraft:tnt" : 46, - "minecraft:bookshelf" : 47, - "minecraft:mossy_cobblestone" : 48, - "minecraft:obsidian" : 49, - "minecraft:torch" : 50, - "minecraft:fire" : 51, - "minecraft:mob_spawner" : 52, - "minecraft:oak_stairs" : 53, - "minecraft:chest" : 54, - "minecraft:redstone_wire" : 55, - "minecraft:diamond_ore" : 56, - "minecraft:diamond_block" : 57, - "minecraft:crafting_table" : 58, - "minecraft:wheat" : 59, - "minecraft:farmland" : 60, - "minecraft:furnace" : 61, - "minecraft:lit_furnace" : 62, - "minecraft:standing_sign" : 63, - "minecraft:wooden_door" : 64, - "minecraft:ladder" : 65, - "minecraft:rail" : 66, - "minecraft:stone_stairs" : 67, - "minecraft:wall_sign" : 68, - "minecraft:lever" : 69, - "minecraft:stone_pressure_plate" : 70, - "minecraft:iron_door" : 71, - "minecraft:wooden_pressure_plate" : 72, - "minecraft:redstone_ore" : 73, - "minecraft:lit_redstone_ore" : 74, - "minecraft:unlit_redstone_torch" : 75, - "minecraft:redstone_torch" : 76, - "minecraft:stone_button" : 77, - "minecraft:snow_layer" : 78, - "minecraft:ice" : 79, - "minecraft:snow" : 80, - "minecraft:cactus" : 81, - "minecraft:clay" : 82, - "minecraft:reeds" : 83, - "minecraft:jukebox" : 84, - "minecraft:fence" : 85, - "minecraft:pumpkin" : 86, - "minecraft:netherrack" : 87, - "minecraft:soul_sand" : 88, - "minecraft:glowstone" : 89, - "minecraft:portal" : 90, - "minecraft:lit_pumpkin" : 91, - "minecraft:cake" : 92, - "minecraft:unpowered_repeater" : 93, - "minecraft:powered_repeater" : 94, - "minecraft:invisibleBedrock" : 95, - "minecraft:trapdoor" : 96, - "minecraft:monster_egg" : 97, - "minecraft:stonebrick" : 98, - "minecraft:brown_mushroom_block" : 99, - "minecraft:red_mushroom_block" : 100, - "minecraft:iron_bars" : 101, - "minecraft:glass_pane" : 102, - "minecraft:melon_block" : 103, - "minecraft:pumpkin_stem" : 104, - "minecraft:melon_stem" : 105, - "minecraft:vine" : 106, - "minecraft:fence_gate" : 107, - "minecraft:brick_stairs" : 108, - "minecraft:stone_brick_stairs" : 109, - "minecraft:mycelium" : 110, - "minecraft:waterlily" : 111, - "minecraft:nether_brick" : 112, - "minecraft:nether_brick_fence" : 113, - "minecraft:nether_brick_stairs" : 114, - "minecraft:nether_wart" : 115, - "minecraft:enchanting_table" : 116, - "minecraft:brewing_stand" : 117, - "minecraft:cauldron" : 118, - "minecraft:end_portal" : 119, - "minecraft:end_portal_frame" : 120, - "minecraft:end_stone" : 121, - "minecraft:dragon_egg" : 122, - "minecraft:redstone_lamp" : 123, - "minecraft:lit_redstone_lamp" : 124, - "minecraft:dropper" : 125, - "minecraft:activator_rail" : 126, - "minecraft:cocoa" : 127, - "minecraft:sandstone_stairs" : 128, - "minecraft:emerald_ore" : 129, - "minecraft:ender_chest" : 130, - "minecraft:tripwire_hook" : 131, - "minecraft:tripWire" : 132, - "minecraft:emerald_block" : 133, - "minecraft:spruce_stairs" : 134, - "minecraft:birch_stairs" : 135, - "minecraft:jungle_stairs" : 136, - "minecraft:command_block" : 137, - "minecraft:beacon" : 138, - "minecraft:cobblestone_wall" : 139, - "minecraft:flower_pot" : 140, - "minecraft:carrots" : 141, - "minecraft:potatoes" : 142, - "minecraft:wooden_button" : 143, - "minecraft:skull" : 144, - "minecraft:anvil" : 145, - "minecraft:trapped_chest" : 146, - "minecraft:light_weighted_pressure_plate" : 147, - "minecraft:heavy_weighted_pressure_plate" : 148, - "minecraft:unpowered_comparator" : 149, - "minecraft:powered_comparator" : 150, - "minecraft:daylight_detector" : 151, - "minecraft:redstone_block" : 152, - "minecraft:quartz_ore" : 153, - "minecraft:hopper" : 154, - "minecraft:quartz_block" : 155, - "minecraft:quartz_stairs" : 156, - "minecraft:double_wooden_slab" : 157, - "minecraft:wooden_slab" : 158, - "minecraft:stained_hardened_clay" : 159, - "minecraft:stained_glass_pane" : 160, - "minecraft:leaves2" : 161, - "minecraft:log2" : 162, - "minecraft:acacia_stairs" : 163, - "minecraft:dark_oak_stairs" : 164, - "minecraft:slime" : 165, - "minecraft:iron_trapdoor" : 167, - "minecraft:prismarine" : 168, - "minecraft:seaLantern" : 169, - "minecraft:hay_block" : 170, - "minecraft:carpet" : 171, - "minecraft:hardened_clay" : 172, - "minecraft:coal_block" : 173, - "minecraft:packed_ice" : 174, - "minecraft:double_plant" : 175, - "minecraft:standing_banner" : 176, - "minecraft:wall_banner" : 177, - "minecraft:daylight_detector_inverted" : 178, - "minecraft:red_sandstone" : 179, - "minecraft:red_sandstone_stairs" : 180, - "minecraft:double_stone_slab2" : 181, - "minecraft:stone_slab2" : 182, - "minecraft:spruce_fence_gate" : 183, - "minecraft:birch_fence_gate" : 184, - "minecraft:jungle_fence_gate" : 185, - "minecraft:dark_oak_fence_gate" : 186, - "minecraft:acacia_fence_gate" : 187, - "minecraft:repeating_command_block" : 188, - "minecraft:chain_command_block" : 189, - "minecraft:hard_glass_pane" : 190, - "minecraft:hard_stained_glass_pane" : 191, - "minecraft:chemical_heat" : 192, - "minecraft:spruce_door" : 193, - "minecraft:birch_door" : 194, - "minecraft:jungle_door" : 195, - "minecraft:acacia_door" : 196, - "minecraft:dark_oak_door" : 197, - "minecraft:grass_path" : 198, - "minecraft:frame" : 199, - "minecraft:chorus_flower" : 200, - "minecraft:purpur_block" : 201, - "minecraft:colored_torch_rg" : 202, - "minecraft:purpur_stairs" : 203, - "minecraft:colored_torch_bp" : 204, - "minecraft:undyed_shulker_box" : 205, - "minecraft:end_bricks" : 206, - "minecraft:frosted_ice" : 207, - "minecraft:end_rod" : 208, - "minecraft:end_gateway" : 209, - "minecraft:allow" : 210, - "minecraft:deny" : 211, - "minecraft:border_block" : 212, - "minecraft:magma" : 213, - "minecraft:nether_wart_block" : 214, - "minecraft:red_nether_brick" : 215, - "minecraft:bone_block" : 216, - "minecraft:structure_void" : 217, - "minecraft:shulker_box" : 218, - "minecraft:purple_glazed_terracotta" : 219, - "minecraft:white_glazed_terracotta" : 220, - "minecraft:orange_glazed_terracotta" : 221, - "minecraft:magenta_glazed_terracotta" : 222, - "minecraft:light_blue_glazed_terracotta" : 223, - "minecraft:yellow_glazed_terracotta" : 224, - "minecraft:lime_glazed_terracotta" : 225, - "minecraft:pink_glazed_terracotta" : 226, - "minecraft:gray_glazed_terracotta" : 227, - "minecraft:silver_glazed_terracotta" : 228, - "minecraft:cyan_glazed_terracotta" : 229, - "minecraft:blue_glazed_terracotta" : 231, - "minecraft:brown_glazed_terracotta" : 232, - "minecraft:green_glazed_terracotta" : 233, - "minecraft:red_glazed_terracotta" : 234, - "minecraft:black_glazed_terracotta" : 235, - "minecraft:concrete" : 236, - "minecraft:concretePowder" : 237, - "minecraft:chemistry_table" : 238, - "minecraft:underwater_torch" : 239, - "minecraft:chorus_plant" : 240, - "minecraft:stained_glass" : 241, - "minecraft:camera" : 242, - "minecraft:podzol" : 243, - "minecraft:beetroot" : 244, - "minecraft:stonecutter" : 245, - "minecraft:glowingobsidian" : 246, - "minecraft:netherreactor" : 247, - "minecraft:info_update" : 248, - "minecraft:info_update2" : 249, - "minecraft:movingBlock" : 250, - "minecraft:observer" : 251, - "minecraft:structure_block" : 252, - "minecraft:hard_glass" : 253, - "minecraft:hard_stained_glass" : 254, - "minecraft:reserved6" : 255, - "minecraft:prismarine_stairs" : 257, - "minecraft:dark_prismarine_stairs" : 258, - "minecraft:prismarine_bricks_stairs" : 259, - "minecraft:stripped_spruce_log" : 260, - "minecraft:stripped_birch_log" : 261, - "minecraft:stripped_jungle_log" : 262, - "minecraft:stripped_acacia_log" : 263, - "minecraft:stripped_dark_oak_log" : 264, - "minecraft:stripped_oak_log" : 265, - "minecraft:blue_ice" : 266, - "minecraft:element_1" : 267, - "minecraft:element_2" : 268, - "minecraft:element_3" : 269, - "minecraft:element_4" : 270, - "minecraft:element_5" : 271, - "minecraft:element_6" : 272, - "minecraft:element_7" : 273, - "minecraft:element_8" : 274, - "minecraft:element_9" : 275, - "minecraft:element_10" : 276, - "minecraft:element_11" : 277, - "minecraft:element_12" : 278, - "minecraft:element_13" : 279, - "minecraft:element_14" : 280, - "minecraft:element_15" : 281, - "minecraft:element_16" : 282, - "minecraft:element_17" : 283, - "minecraft:element_18" : 284, - "minecraft:element_19" : 285, - "minecraft:element_20" : 286, - "minecraft:element_21" : 287, - "minecraft:element_22" : 288, - "minecraft:element_23" : 289, - "minecraft:element_24" : 290, - "minecraft:element_25" : 291, - "minecraft:element_26" : 292, - "minecraft:element_27" : 293, - "minecraft:element_28" : 294, - "minecraft:element_29" : 295, - "minecraft:element_30" : 296, - "minecraft:element_31" : 297, - "minecraft:element_32" : 298, - "minecraft:element_33" : 299, - "minecraft:element_34" : 300, - "minecraft:element_35" : 301, - "minecraft:element_36" : 302, - "minecraft:element_37" : 303, - "minecraft:element_38" : 304, - "minecraft:element_39" : 305, - "minecraft:element_40" : 306, - "minecraft:element_41" : 307, - "minecraft:element_42" : 308, - "minecraft:element_43" : 309, - "minecraft:element_44" : 310, - "minecraft:element_45" : 311, - "minecraft:element_46" : 312, - "minecraft:element_47" : 313, - "minecraft:element_48" : 314, - "minecraft:element_49" : 315, - "minecraft:element_50" : 316, - "minecraft:element_51" : 317, - "minecraft:element_52" : 318, - "minecraft:element_53" : 319, - "minecraft:element_54" : 320, - "minecraft:element_55" : 321, - "minecraft:element_56" : 322, - "minecraft:element_57" : 323, - "minecraft:element_58" : 324, - "minecraft:element_59" : 325, - "minecraft:element_60" : 326, - "minecraft:element_61" : 327, - "minecraft:element_62" : 328, - "minecraft:element_63" : 329, - "minecraft:element_64" : 330, - "minecraft:element_65" : 331, - "minecraft:element_66" : 332, - "minecraft:element_67" : 333, - "minecraft:element_68" : 334, - "minecraft:element_69" : 335, - "minecraft:element_70" : 336, - "minecraft:element_71" : 337, - "minecraft:element_72" : 338, - "minecraft:element_73" : 339, - "minecraft:element_74" : 340, - "minecraft:element_75" : 341, - "minecraft:element_76" : 342, - "minecraft:element_77" : 343, - "minecraft:element_78" : 344, - "minecraft:element_79" : 345, - "minecraft:element_80" : 346, - "minecraft:element_81" : 347, - "minecraft:element_82" : 348, - "minecraft:element_83" : 349, - "minecraft:element_84" : 350, - "minecraft:element_85" : 351, - "minecraft:element_86" : 352, - "minecraft:element_87" : 353, - "minecraft:element_88" : 354, - "minecraft:element_89" : 355, - "minecraft:element_90" : 356, - "minecraft:element_91" : 357, - "minecraft:element_92" : 358, - "minecraft:element_93" : 359, - "minecraft:element_94" : 360, - "minecraft:element_95" : 361, - "minecraft:element_96" : 362, - "minecraft:element_97" : 363, - "minecraft:element_98" : 364, - "minecraft:element_99" : 365, - "minecraft:element_100" : 366, - "minecraft:element_101" : 367, - "minecraft:element_102" : 368, - "minecraft:element_103" : 369, - "minecraft:element_104" : 370, - "minecraft:element_105" : 371, - "minecraft:element_106" : 372, - "minecraft:element_107" : 373, - "minecraft:element_108" : 374, - "minecraft:element_109" : 375, - "minecraft:element_110" : 376, - "minecraft:element_111" : 377, - "minecraft:element_112" : 378, - "minecraft:element_113" : 379, - "minecraft:element_114" : 380, - "minecraft:element_115" : 381, - "minecraft:element_116" : 382, - "minecraft:element_117" : 383, - "minecraft:element_118" : 384, - "minecraft:seagrass" : 385, - "minecraft:coral" : 386, - "minecraft:coral_block" : 387, - "minecraft:coral_fan" : 388, - "minecraft:coral_fan_dead" : 389, - "minecraft:coral_fan_hang" : 390, - "minecraft:coral_fan_hang2" : 391, - "minecraft:coral_fan_hang3" : 392, - "minecraft:kelp" : 393, - "minecraft:dried_kelp_block" : 394, - "minecraft:acacia_button" : 395, - "minecraft:birch_button" : 396, - "minecraft:dark_oak_button" : 397, - "minecraft:jungle_button" : 398, - "minecraft:spruce_button" : 399, - "minecraft:acacia_trapdoor" : 400, - "minecraft:birch_trapdoor" : 401, - "minecraft:dark_oak_trapdoor" : 402, - "minecraft:jungle_trapdoor" : 403, - "minecraft:spruce_trapdoor" : 404, - "minecraft:acacia_pressure_plate" : 405, - "minecraft:birch_pressure_plate" : 406, - "minecraft:dark_oak_pressure_plate" : 407, - "minecraft:jungle_pressure_plate" : 408, - "minecraft:spruce_pressure_plate" : 409, - "minecraft:carved_pumpkin" : 410, - "minecraft:sea_pickle" : 411, - "minecraft:conduit" : 412, - "minecraft:turtle_egg" : 414, - "minecraft:bubble_column" : 415, - "minecraft:barrier" : 416, - "minecraft:stone_slab3" : 417, - "minecraft:bamboo" : 418, - "minecraft:bamboo_sapling" : 419, - "minecraft:scaffolding" : 420, - "minecraft:stone_slab4" : 421, - "minecraft:double_stone_slab3" : 422, - "minecraft:double_stone_slab4" : 423, - "minecraft:granite_stairs" : 424, - "minecraft:diorite_stairs" : 425, - "minecraft:andesite_stairs" : 426, - "minecraft:polished_granite_stairs" : 427, - "minecraft:polished_diorite_stairs" : 428, - "minecraft:polished_andesite_stairs" : 429, - "minecraft:mossy_stone_brick_stairs" : 430, - "minecraft:smooth_red_sandstone_stairs" : 431, - "minecraft:smooth_sandstone_stairs" : 432, - "minecraft:end_brick_stairs" : 433, - "minecraft:mossy_cobblestone_stairs" : 434, - "minecraft:normal_stone_stairs" : 435, - "minecraft:spruce_standing_sign" : 436, - "minecraft:spruce_wall_sign" : 437, - "minecraft:smooth_stone" : 438, - "minecraft:red_nether_brick_stairs" : 439, - "minecraft:smooth_quartz_stairs" : 440, - "minecraft:birch_standing_sign" : 441, - "minecraft:birch_wall_sign" : 442, - "minecraft:jungle_standing_sign" : 443, - "minecraft:jungle_wall_sign" : 444, - "minecraft:acacia_standing_sign" : 445, - "minecraft:acacia_wall_sign" : 446, - "minecraft:darkoak_standing_sign" : 447, - "minecraft:darkoak_wall_sign" : 448, - "minecraft:lectern" : 449, - "minecraft:grindstone" : 450, - "minecraft:blast_furnace" : 451, - "minecraft:stonecutter_block" : 452, - "minecraft:smoker" : 453, - "minecraft:lit_smoker" : 454, - "minecraft:cartography_table" : 455, - "minecraft:fletching_table" : 456, - "minecraft:smithing_table" : 457, - "minecraft:barrel" : 458, - "minecraft:loom" : 459, - "minecraft:bell" : 461, - "minecraft:sweet_berry_bush" : 462, - "minecraft:lantern" : 463, - "minecraft:campfire" : 464, - "minecraft:lava_cauldron" : 465, - "minecraft:jigsaw" : 466, - "minecraft:wood" : 467, - "minecraft:composter" : 468, - "minecraft:lit_blast_furnace" : 469, - "minecraft:light_block" : 470, - "minecraft:wither_rose" : 471, - "minecraft:stickyPistonArmCollision" : 472, - "minecraft:bee_nest" : 473, - "minecraft:beehive" : 474, - "minecraft:honey_block" : 475, - "minecraft:honeycomb_block" : 476, - "minecraft:lodestone" : 477, - "minecraft:crimson_roots" : 478, - "minecraft:warped_roots" : 479, - "minecraft:crimson_stem" : 480, - "minecraft:warped_stem" : 481, - "minecraft:warped_wart_block" : 482, - "minecraft:crimson_fungus" : 483, - "minecraft:warped_fungus" : 484, - "minecraft:shroomlight" : 485, - "minecraft:weeping_vines" : 486, - "minecraft:crimson_nylium" : 487, - "minecraft:warped_nylium" : 488, - "minecraft:basalt" : 489, - "minecraft:polished_basalt" : 490, - "minecraft:soul_soil" : 491, - "minecraft:soul_fire" : 492, - "minecraft:nether_sprouts" : 493, - "minecraft:target" : 494, - "minecraft:stripped_crimson_stem" : 495, - "minecraft:stripped_warped_stem" : 496, - "minecraft:crimson_planks" : 497, - "minecraft:warped_planks" : 498, - "minecraft:crimson_door" : 499, - "minecraft:warped_door" : 500, - "minecraft:crimson_trapdoor" : 501, - "minecraft:warped_trapdoor" : 502, - "minecraft:crimson_standing_sign" : 505, - "minecraft:warped_standing_sign" : 506, - "minecraft:crimson_wall_sign" : 507, - "minecraft:warped_wall_sign" : 508, - "minecraft:crimson_stairs" : 509, - "minecraft:warped_stairs" : 510, - "minecraft:crimson_fence" : 511, - "minecraft:warped_fence" : 512, - "minecraft:crimson_fence_gate" : 513, - "minecraft:warped_fence_gate" : 514, - "minecraft:crimson_button" : 515, - "minecraft:warped_button" : 516, - "minecraft:crimson_pressure_plate" : 517, - "minecraft:warped_pressure_plate" : 518, - "minecraft:crimson_slab" : 519, - "minecraft:warped_slab" : 520, - "minecraft:crimson_double_slab" : 521, - "minecraft:warped_double_slab" : 522, - "minecraft:soul_torch" : 523, - "minecraft:soul_lantern" : 524, - "minecraft:netherite_block" : 525, - "minecraft:ancient_debris" : 526, - "minecraft:respawn_anchor" : 527, - "minecraft:blackstone" : 528, - "minecraft:polished_blackstone_bricks" : 529, - "minecraft:polished_blackstone_brick_stairs" : 530, - "minecraft:blackstone_stairs" : 531, - "minecraft:blackstone_wall" : 532, - "minecraft:polished_blackstone_brick_wall" : 533, - "minecraft:chiseled_polished_blackstone" : 534, - "minecraft:cracked_polished_blackstone_bricks" : 535, - "minecraft:gilded_blackstone" : 536, - "minecraft:blackstone_slab" : 537, - "minecraft:blackstone_double_slab" : 538, - "minecraft:polished_blackstone_brick_slab" : 539, - "minecraft:polished_blackstone_brick_double_slab" : 540, - "minecraft:chain" : 541, - "minecraft:twisting_vines" : 542, - "minecraft:nether_gold_ore" : 543, - "minecraft:crying_obsidian" : 544, - "minecraft:soul_campfire" : 545, - "minecraft:polished_blackstone" : 546, - "minecraft:polished_blackstone_stairs" : 547, - "minecraft:polished_blackstone_slab" : 548, - "minecraft:polished_blackstone_double_slab" : 549, - "minecraft:polished_blackstone_pressure_plate" : 550, - "minecraft:polished_blackstone_button" : 551, - "minecraft:polished_blackstone_wall" : 552, - "minecraft:warped_hyphae" : 553, - "minecraft:crimson_hyphae" : 554, - "minecraft:stripped_crimson_hyphae" : 555, - "minecraft:stripped_warped_hyphae" : 556, - "minecraft:chiseled_nether_bricks" : 557, - "minecraft:cracked_nether_bricks" : 558, - "minecraft:quartz_bricks" : 559 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/legacy_item_ids.json b/connector/src/main/resources/bedrock/legacy_item_ids.json deleted file mode 100644 index 3f22c91c..00000000 --- a/connector/src/main/resources/bedrock/legacy_item_ids.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "minecraft:iron_shovel" : 256, - "minecraft:iron_pickaxe" : 257, - "minecraft:iron_axe" : 258, - "minecraft:flint_and_steel" : 259, - "minecraft:apple" : 260, - "minecraft:bow" : 261, - "minecraft:arrow" : 262, - "minecraft:coal" : 263, - "minecraft:diamond" : 264, - "minecraft:iron_ingot" : 265, - "minecraft:gold_ingot" : 266, - "minecraft:iron_sword" : 267, - "minecraft:wooden_sword" : 268, - "minecraft:wooden_shovel" : 269, - "minecraft:wooden_pickaxe" : 270, - "minecraft:wooden_axe" : 271, - "minecraft:stone_sword" : 272, - "minecraft:stone_shovel" : 273, - "minecraft:stone_pickaxe" : 274, - "minecraft:stone_axe" : 275, - "minecraft:diamond_sword" : 276, - "minecraft:diamond_shovel" : 277, - "minecraft:diamond_pickaxe" : 278, - "minecraft:diamond_axe" : 279, - "minecraft:stick" : 280, - "minecraft:bowl" : 281, - "minecraft:mushroom_stew" : 282, - "minecraft:golden_sword" : 283, - "minecraft:golden_shovel" : 284, - "minecraft:golden_pickaxe" : 285, - "minecraft:golden_axe" : 286, - "minecraft:string" : 287, - "minecraft:feather" : 288, - "minecraft:gunpowder" : 289, - "minecraft:wooden_hoe" : 290, - "minecraft:stone_hoe" : 291, - "minecraft:iron_hoe" : 292, - "minecraft:diamond_hoe" : 293, - "minecraft:golden_hoe" : 294, - "minecraft:wheat_seeds" : 295, - "minecraft:wheat" : 296, - "minecraft:bread" : 297, - "minecraft:leather_helmet" : 298, - "minecraft:leather_chestplate" : 299, - "minecraft:leather_leggings" : 300, - "minecraft:leather_boots" : 301, - "minecraft:chainmail_helmet" : 302, - "minecraft:chainmail_chestplate" : 303, - "minecraft:chainmail_leggings" : 304, - "minecraft:chainmail_boots" : 305, - "minecraft:iron_helmet" : 306, - "minecraft:iron_chestplate" : 307, - "minecraft:iron_leggings" : 308, - "minecraft:iron_boots" : 309, - "minecraft:diamond_helmet" : 310, - "minecraft:diamond_chestplate" : 311, - "minecraft:diamond_leggings" : 312, - "minecraft:diamond_boots" : 313, - "minecraft:golden_helmet" : 314, - "minecraft:golden_chestplate" : 315, - "minecraft:golden_leggings" : 316, - "minecraft:golden_boots" : 317, - "minecraft:flint" : 318, - "minecraft:porkchop" : 319, - "minecraft:cooked_porkchop" : 320, - "minecraft:painting" : 321, - "minecraft:golden_apple" : 322, - "minecraft:sign" : 323, - "minecraft:wooden_door" : 324, - "minecraft:bucket" : 325, - "minecraft:minecart" : 328, - "minecraft:saddle" : 329, - "minecraft:iron_door" : 330, - "minecraft:redstone" : 331, - "minecraft:snowball" : 332, - "minecraft:boat" : 333, - "minecraft:leather" : 334, - "minecraft:kelp" : 335, - "minecraft:brick" : 336, - "minecraft:clay_ball" : 337, - "minecraft:reeds" : 338, - "minecraft:paper" : 339, - "minecraft:book" : 340, - "minecraft:slime_ball" : 341, - "minecraft:chest_minecart" : 342, - "minecraft:egg" : 344, - "minecraft:compass" : 345, - "minecraft:fishing_rod" : 346, - "minecraft:clock" : 347, - "minecraft:glowstone_dust" : 348, - "minecraft:fish" : 349, - "minecraft:cooked_fish" : 350, - "minecraft:dye" : 351, - "minecraft:bone" : 352, - "minecraft:sugar" : 353, - "minecraft:cake" : 354, - "minecraft:bed" : 355, - "minecraft:repeater" : 356, - "minecraft:cookie" : 357, - "minecraft:map" : 358, - "minecraft:shears" : 359, - "minecraft:melon" : 360, - "minecraft:pumpkin_seeds" : 361, - "minecraft:melon_seeds" : 362, - "minecraft:beef" : 363, - "minecraft:cooked_beef" : 364, - "minecraft:chicken" : 365, - "minecraft:cooked_chicken" : 366, - "minecraft:rotten_flesh" : 367, - "minecraft:ender_pearl" : 368, - "minecraft:blaze_rod" : 369, - "minecraft:ghast_tear" : 370, - "minecraft:gold_nugget" : 371, - "minecraft:nether_wart" : 372, - "minecraft:potion" : 373, - "minecraft:glass_bottle" : 374, - "minecraft:spider_eye" : 375, - "minecraft:fermented_spider_eye" : 376, - "minecraft:blaze_powder" : 377, - "minecraft:magma_cream" : 378, - "minecraft:brewing_stand" : 379, - "minecraft:cauldron" : 380, - "minecraft:ender_eye" : 381, - "minecraft:speckled_melon" : 382, - "minecraft:spawn_egg" : 383, - "minecraft:experience_bottle" : 384, - "minecraft:fireball" : 385, - "minecraft:writable_book" : 386, - "minecraft:written_book" : 387, - "minecraft:emerald" : 388, - "minecraft:frame" : 389, - "minecraft:flower_pot" : 390, - "minecraft:carrot" : 391, - "minecraft:potato" : 392, - "minecraft:baked_potato" : 393, - "minecraft:poisonous_potato" : 394, - "minecraft:emptymap" : 395, - "minecraft:golden_carrot" : 396, - "minecraft:skull" : 397, - "minecraft:carrotonastick" : 398, - "minecraft:netherstar" : 399, - "minecraft:pumpkin_pie" : 400, - "minecraft:fireworks" : 401, - "minecraft:fireworkscharge" : 402, - "minecraft:enchanted_book" : 403, - "minecraft:comparator" : 404, - "minecraft:netherbrick" : 405, - "minecraft:quartz" : 406, - "minecraft:tnt_minecart" : 407, - "minecraft:hopper_minecart" : 408, - "minecraft:prismarine_shard" : 409, - "minecraft:hopper" : 410, - "minecraft:rabbit" : 411, - "minecraft:cooked_rabbit" : 412, - "minecraft:rabbit_stew" : 413, - "minecraft:rabbit_foot" : 414, - "minecraft:rabbit_hide" : 415, - "minecraft:horsearmorleather" : 416, - "minecraft:horsearmoriron" : 417, - "minecraft:horsearmorgold" : 418, - "minecraft:horsearmordiamond" : 419, - "minecraft:lead" : 420, - "minecraft:name_tag" : 421, - "minecraft:prismarine_crystals" : 422, - "minecraft:muttonraw" : 423, - "minecraft:muttoncooked" : 424, - "minecraft:armor_stand" : 425, - "minecraft:end_crystal" : 426, - "minecraft:spruce_door" : 427, - "minecraft:birch_door" : 428, - "minecraft:jungle_door" : 429, - "minecraft:acacia_door" : 430, - "minecraft:dark_oak_door" : 431, - "minecraft:chorus_fruit" : 432, - "minecraft:chorus_fruit_popped" : 433, - "minecraft:banner_pattern" : 434, - "minecraft:dragon_breath" : 437, - "minecraft:splash_potion" : 438, - "minecraft:lingering_potion" : 441, - "minecraft:sparkler" : 442, - "minecraft:command_block_minecart" : 443, - "minecraft:elytra" : 444, - "minecraft:shulker_shell" : 445, - "minecraft:banner" : 446, - "minecraft:medicine" : 447, - "minecraft:balloon" : 448, - "minecraft:rapid_fertilizer" : 449, - "minecraft:totem" : 450, - "minecraft:bleach" : 451, - "minecraft:iron_nugget" : 452, - "minecraft:ice_bomb" : 453, - "minecraft:trident" : 455, - "minecraft:beetroot" : 457, - "minecraft:beetroot_seeds" : 458, - "minecraft:beetroot_soup" : 459, - "minecraft:salmon" : 460, - "minecraft:clownfish" : 461, - "minecraft:pufferfish" : 462, - "minecraft:cooked_salmon" : 463, - "minecraft:dried_kelp" : 464, - "minecraft:nautilus_shell" : 465, - "minecraft:appleenchanted" : 466, - "minecraft:heart_of_the_sea" : 467, - "minecraft:turtle_shell_piece" : 468, - "minecraft:turtle_helmet" : 469, - "minecraft:phantom_membrane" : 470, - "minecraft:crossbow" : 471, - "minecraft:spruce_sign" : 472, - "minecraft:birch_sign" : 473, - "minecraft:jungle_sign" : 474, - "minecraft:acacia_sign" : 475, - "minecraft:darkoak_sign" : 476, - "minecraft:sweet_berries" : 477, - "minecraft:camera" : 498, - "minecraft:compound" : 499, - "minecraft:record_13" : 500, - "minecraft:record_cat" : 501, - "minecraft:record_blocks" : 502, - "minecraft:record_chirp" : 503, - "minecraft:record_far" : 504, - "minecraft:record_mall" : 505, - "minecraft:record_mellohi" : 506, - "minecraft:record_stal" : 507, - "minecraft:record_strad" : 508, - "minecraft:record_ward" : 509, - "minecraft:record_11" : 510, - "minecraft:record_wait" : 511, - "minecraft:shield" : 513, - "minecraft:campfire" : 720, - "minecraft:suspicious_stew" : 734, - "minecraft:honeycomb" : 736, - "minecraft:honey_bottle" : 737, - "minecraft:lodestonecompass" : 741, - "minecraft:netherite_ingot" : 742, - "minecraft:netherite_sword" : 743, - "minecraft:netherite_shovel" : 744, - "minecraft:netherite_pickaxe" : 745, - "minecraft:netherite_axe" : 746, - "minecraft:netherite_hoe" : 747, - "minecraft:netherite_helmet" : 748, - "minecraft:netherite_chestplate" : 749, - "minecraft:netherite_leggings" : 750, - "minecraft:netherite_boots" : 751, - "minecraft:netherite_scrap" : 752, - "minecraft:crimson_sign" : 753, - "minecraft:warped_sign" : 754, - "minecraft:crimson_door" : 755, - "minecraft:warped_door" : 756, - "minecraft:warped_fungus_on_a_stick" : 757, - "minecraft:chain" : 758, - "minecraft:record_pigstep" : 759, - "minecraft:nether_sprouts" : 760, - "minecraft:soul_campfire" : 801 -} \ No newline at end of file diff --git a/connector/src/main/resources/bedrock/runtime_block_states.dat b/connector/src/main/resources/bedrock/runtime_block_states.dat deleted file mode 100644 index feeeda664bdf469af04e3758e1a584b76bb71f34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1063028 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 daWF8y=mGU<3(8pke_4.0.0 org.geysermc geyser-parent - parent + 1.2.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. @@ -71,19 +71,6 @@ - - - releases - opencollab-releases - https://repo.opencollab.dev/maven-releases - - - snapshots - opencollab-snapshots - https://repo.opencollab.dev/maven-snapshots - - - org.projectlombok From 003a1bef03b6aa68dc37199d994bc4fe6628bc4e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 12:54:09 -0500 Subject: [PATCH 072/161] Revert artifactory changes preventing compilation (#1555) --- Jenkinsfile | 22 +--------------------- pom.xml | 13 +++++++++++++ 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 96b189f2..7dfdaf30 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,27 +26,7 @@ pipeline { } steps { - rtMavenDeployer( - id: "maven-deployer", - serverId: "opencollab-artifactory", - releaseRepo: "maven-releases", - snapshotRepo: "maven-snapshots" - ) - rtMavenResolver( - id: "maven-resolver", - serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" - ) - rtMavenRun( - pom: 'pom.xml', - goals: 'javadoc:jar source:jar install -DskipTests', - deployerId: "maven-deployer", - resolverId: "maven-resolver" - ) - rtPublishBuildInfo( - serverId: "opencollab-artifactory" - ) + sh 'mvn javadoc:jar source:jar deploy -DskipTests' } } } diff --git a/pom.xml b/pom.xml index 011b320f..1b544f9e 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,19 @@ + + + releases + opencollab-releases + https://repo.opencollab.dev/maven-releases + + + snapshots + opencollab-snapshots + https://repo.opencollab.dev/maven-snapshots + + + org.projectlombok From 99558b61a604d7c4c6ba85ec2bce563cdeb9f23a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Tue, 17 Nov 2020 13:28:08 -0500 Subject: [PATCH 073/161] Print reason for disconnect when outdated (#1556) --- .../org/geysermc/connector/network/UpstreamPacketHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index e871b7f7..3c4dc821 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -61,9 +61,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (packetCodec == null) { if (loginPacket.getProtocolVersion() > BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { // Too early to determine session locale + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.server", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } else if (loginPacket.getProtocolVersion() < BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) { + session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); session.disconnect(LanguageUtils.getLocaleStringLog("geyser.network.outdated.client", BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion())); return true; } From 199778faea74fc9b7928ef3711d99f31bc19af18 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Wed, 18 Nov 2020 01:10:49 -0500 Subject: [PATCH 074/161] Fix regressions from 1.16.100 (#1558) * Fix regressions from 1.16.100 - Update mappings to fix recipe regressions and item differences - Villager trading NBT now prefers the String identifier and not the integer ID * Fix lodestone compass breaking --- .../connector/entity/ItemFrameEntity.java | 4 +- .../network/translators/item/ItemEntry.java | 3 +- .../translators/item/ItemRegistry.java | 40 +++++++------------ .../translators/item/ItemTranslator.java | 5 +-- .../translators/item/ToolItemEntry.java | 4 +- .../item/translators/CompassTranslator.java | 4 +- .../translators/nbt/CrossbowTranslator.java | 6 +-- .../nbt/ShulkerBoxItemTranslator.java | 3 +- .../java/world/JavaTradeListTranslator.java | 2 +- connector/src/main/resources/mappings | 2 +- 10 files changed, 28 insertions(+), 45 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 62fe3afe..972fa8d0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -98,14 +98,12 @@ public class ItemFrameEntity extends Entity { ItemEntry itemEntry = ItemRegistry.getItem((ItemStack) entityMetadata.getValue()); NbtMapBuilder builder = NbtMap.builder(); - String blockName = ItemRegistry.getBedrockIdentifier(itemEntry); - builder.putByte("Count", (byte) itemData.getCount()); if (itemData.getTag() != null) { builder.put("tag", itemData.getTag().toBuilder().build()); } builder.putShort("Damage", itemData.getDamage()); - builder.putString("Name", blockName); + builder.putString("Name", itemEntry.getBedrockIdentifier()); NbtMapBuilder tag = getDefaultTag().toBuilder(); tag.put("Item", builder.build()); tag.putFloat("ItemDropChance", 1.0f); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index 19f83cd9..3fcae17d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -34,9 +34,10 @@ import lombok.ToString; @ToString public class ItemEntry { - public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0, false); + public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false); private final String javaIdentifier; + private final String bedrockIdentifier; private final int javaId; private final int bedrockId; private final int bedrockData; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 3e026885..a4a2fb75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -103,6 +103,9 @@ public class ItemRegistry { TypeReference> itemEntriesType = new TypeReference>() { }; + // Used to get the Bedrock namespaced ID (in instances where there are small differences) + Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); + List itemEntries; try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); @@ -114,6 +117,7 @@ public class ItemRegistry { for (JsonNode entry : itemEntries) { ITEMS.add(new StartGamePacket.ItemEntry(entry.get("name").textValue(), (short) entry.get("id").intValue())); + bedrockIdToIdentifier.put(entry.get("id").intValue(), entry.get("name").textValue()); if (entry.get("name").textValue().equals("minecraft:lodestone_compass")) { lodestoneCompassId = entry.get("id").intValue(); } @@ -132,28 +136,29 @@ public class ItemRegistry { Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); + int bedrockId = entry.getValue().get("bedrock_id").intValue(); + String bedrockIdentifier = bedrockIdToIdentifier.get(bedrockId); + if (bedrockIdentifier == null) { + throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); + } if (entry.getValue().has("tool_type")) { if (entry.getValue().has("tool_tier")) { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), entry.getValue().get("tool_tier").textValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } else { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), - "", - entry.getValue().get("is_block").booleanValue())); + "", entry.getValue().get("is_block").booleanValue())); } } else { ITEM_ENTRIES.put(itemIndex, new ItemEntry( - entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), + entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); } @@ -197,7 +202,7 @@ public class ItemRegistry { } // Add the loadstone compass since it doesn't exist on java but we need it for item conversion - ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", itemIndex, + ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex, lodestoneCompassId, 0, false)); /* Load creative items */ @@ -269,23 +274,6 @@ public class ItemRegistry { }); } - /** - * Finds the Bedrock string identifier of an ItemEntry - * - * @param entry the ItemEntry to search for - * @return the Bedrock identifier - */ - public static String getBedrockIdentifier(ItemEntry entry) { - String blockName = ""; - for (StartGamePacket.ItemEntry startGamePacketItemEntry : ItemRegistry.ITEMS) { - if (startGamePacketItemEntry.getId() == (short) entry.getBedrockId()) { - blockName = startGamePacketItemEntry.getIdentifier(); // Find the Bedrock string name - break; - } - } - return blockName; - } - /** * Gets a Bedrock {@link ItemData} from a {@link JsonNode} * @param itemNode the JSON node that contains ProxyPass-compatible Bedrock item data 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 00c9138a..0df179cf 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 @@ -34,16 +34,13 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.reflections.Reflections; import java.util.*; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java index 10edcdec..dde57700 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -32,8 +32,8 @@ public class ToolItemEntry extends ItemEntry { private final String toolType; private final String toolTier; - public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { - super(javaIdentifier, javaId, bedrockId, bedrockData, isBlock); + public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { + super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock); this.toolType = toolType; this.toolTier = toolTier; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java index 159b9ab4..92ec67dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/CompassTranslator.java @@ -53,7 +53,7 @@ public class CompassTranslator extends ItemTranslator { Tag lodestoneTag = itemStack.getNbt().get("LodestoneTracked"); if (lodestoneTag instanceof ByteTag) { // Get the fake lodestonecompass entry - itemEntry = ItemRegistry.getItemEntry("minecraft:lodestonecompass"); + itemEntry = ItemRegistry.getItemEntry("minecraft:lodestone_compass"); // Get the loadstone pos CompoundTag loadstonePos = itemStack.getNbt().get("LodestonePos"); @@ -83,7 +83,7 @@ public class CompassTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { boolean isLoadstone = false; - if (itemEntry.getJavaIdentifier().equals("minecraft:lodestonecompass")) { + if (itemEntry.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { // Revert the entry back to the compass itemEntry = ItemRegistry.getItemEntry("minecraft:compass"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java index a05b9c8b..97da8669 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/CrossbowTranslator.java @@ -45,15 +45,15 @@ public class CrossbowTranslator extends NbtItemStackTranslator { if (!chargedProjectiles.getValue().isEmpty()) { CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0); - ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); - if (entry == null) return; + ItemEntry projectileEntry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue()); + if (projectileEntry == null) return; CompoundTag tag = projectile.get("tag"); ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag); ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack); CompoundTag newProjectile = new CompoundTag("chargedItem"); newProjectile.put(new ByteTag("Count", (byte) itemData.getCount())); - newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifier(entry))); + newProjectile.put(new StringTag("Name", projectileEntry.getBedrockIdentifier())); newProjectile.put(new ShortTag("Damage", itemData.getDamage())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java index 6ecb9a44..126d2e1f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/ShulkerBoxItemTranslator.java @@ -50,9 +50,8 @@ public class ShulkerBoxItemTranslator extends NbtItemStackTranslator { boxItemTag.put(new ByteTag("WasPickedUp", (byte) 0)); // ??? ItemEntry boxItemEntry = ItemRegistry.getItemEntry(((StringTag) itemData.get("id")).getValue()); - String blockName = ItemRegistry.getBedrockIdentifier(boxItemEntry); - boxItemTag.put(new StringTag("Name", blockName)); + boxItemTag.put(new StringTag("Name", boxItemEntry.getBedrockIdentifier())); boxItemTag.put(new ShortTag("Damage", (short) boxItemEntry.getBedrockData())); boxItemTag.put(new ByteTag("Count", ((ByteTag) itemData.get("Count")).getValue())); if (itemData.contains("tag")) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index e7e6df08..ad422a4c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -139,7 +139,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Wed, 18 Nov 2020 23:18:36 +0000 Subject: [PATCH 075/161] More chat fixes (#1557) * Fix positional translation arguments not being handled * Fix locale fallback * Fix command completion * Remove the expensive call to `containsKey` * Unify adventure versions * Fix some more formatting issues due to parity * Fix and update tests * Update adventure * Add Javadoc for getCommandNames * Formatting Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- .../command/GeyserBungeeCommandExecutor.java | 2 +- .../command/GeyserSpigotCommandExecutor.java | 2 +- .../command/GeyserSpongeCommandExecutor.java | 2 +- .../GeyserVelocityCommandExecutor.java | 28 +++++++++++++------ connector/pom.xml | 8 +++--- .../connector/command/CommandManager.java | 11 ++++++-- .../translators/chat/MessageTranslator.java | 14 ++++++++-- .../chat/MinecraftTranslationRegistry.java | 10 +++++++ .../connector/utils/LanguageUtils.java | 6 +++- .../chat/MessageTranslatorTest.java | 20 +++++++++---- 10 files changed, 76 insertions(+), 27 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index ff7a2e3d..c25da086 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 0edb8448..1ff3e7fe 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -67,7 +67,7 @@ public class GeyserSpigotCommandExecutor implements TabExecutor { @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index c77e8271..3171a1a9 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -70,7 +70,7 @@ public class GeyserSpongeCommandExecutor implements CommandCallable { @Override public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) throws CommandException { if (arguments.split(" ").length == 1) { - return Arrays.asList("?", "help", "reload", "shutdown", "stop"); + return connector.getCommandManager().getCommandNames(); } return new ArrayList<>(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index c329fb19..30c47413 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -25,8 +25,8 @@ package org.geysermc.platform.velocity.command; -import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; @@ -34,29 +34,39 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.utils.LanguageUtils; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; @AllArgsConstructor -public class GeyserVelocityCommandExecutor implements Command { +public class GeyserVelocityCommandExecutor implements SimpleCommand { private final GeyserConnector connector; @Override - public void execute(CommandSource source, String[] args) { - if (args.length > 0) { - if (getCommand(args[0]) != null) { - if (!source.hasPermission(getCommand(args[0]).getPermission())) { - CommandSender sender = new VelocityCommandSender(source); + public void execute(Invocation invocation) { + if (invocation.arguments().length > 0) { + if (getCommand(invocation.arguments()[0]) != null) { + if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) { + CommandSender sender = new VelocityCommandSender(invocation.source()); sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale())); return; } - getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); + getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); } } else { - getCommand("help").execute(new VelocityCommandSender(source), new String[0]); + getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]); } } + @Override + public List suggest(Invocation invocation) { + if (invocation.arguments().length == 0) { + return connector.getCommandManager().getCommandNames(); + } + return new ArrayList<>(); + } + private GeyserCommand getCommand(String label) { return connector.getCommandManager().getCommands().get(label); } diff --git a/connector/pom.xml b/connector/pom.xml index 7c44ddfd..95794ff2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -136,21 +136,21 @@ 2.1.3 - net.kyori + com.github.kyoripowered.adventure adventure-api - 4.1.1 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-gson - 4d8a67d798 + 7acd956 compile com.github.kyoripowered.adventure adventure-text-serializer-legacy - 0599048 + 7acd956 compile diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 7adce430..822b6dea 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -31,9 +31,7 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.defaults.*; import org.geysermc.connector.utils.LanguageUtils; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public abstract class CommandManager { @@ -92,6 +90,13 @@ public abstract class CommandManager { cmd.execute(sender, args); } + /** + * @return a list of all subcommands under {@code /geyser}. + */ + public List getCommandNames() { + return Arrays.asList(connector.getCommandManager().getCommands().keySet().toArray(new String[0])); + } + /** * Returns the description of the given command * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java index be01362f..b7e6838e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MessageTranslator.java @@ -84,7 +84,17 @@ public class MessageTranslator { Locale localeCode = Locale.forLanguageTag(locale.replace('_', '-')); component = RENDERER.render(component, localeCode); - return LegacyComponentSerializer.legacySection().serialize(component); + String legacy = LegacyComponentSerializer.legacySection().serialize(component); + + // Strip strikethrough and underline as they are not supported on bedrock + legacy = legacy.replaceAll("\u00a7[mn]", ""); + + // Make color codes reset formatting like Java + // See https://minecraft.gamepedia.com/Formatting_codes#Usage + legacy = legacy.replaceAll("\u00a7([0-9a-f])", "\u00a7r\u00a7$1"); + legacy = legacy.replaceAll("\u00a7r\u00a7r", "\u00a7r"); + + return legacy; } public static String convertMessage(String message) { @@ -106,7 +116,7 @@ public class MessageTranslator { String convertedMessage = convertMessage(convertToJavaMessage(message), locale); // We have to do this since Adventure strips the starting reset character - if (message.startsWith(getColor(ChatColor.RESET))) { + if (message.startsWith(getColor(ChatColor.RESET)) && !convertedMessage.startsWith(getColor(ChatColor.RESET))) { convertedMessage = getColor(ChatColor.RESET) + convertedMessage; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java index a23167ac..127e0060 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/chat/MinecraftTranslationRegistry.java @@ -61,6 +61,16 @@ public class MinecraftTranslationRegistry implements TranslationRegistry { } m.appendTail(sb); + // Replace the `%x$s` with numbered inserts `{x}` + p = Pattern.compile("%([0-9]+)\\$s"); + m = p.matcher(sb.toString()); + sb = new StringBuffer(); + while (m.find()) { + i = Integer.parseInt(m.group(1)) - 1; + m.appendReplacement(sb, "{" + i + "}"); + } + m.appendTail(sb); + return new MessageFormat(sb.toString(), locale); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 06d2936e..87722e5b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -105,7 +105,11 @@ public class LanguageUtils { locale = formatLocale(locale); Properties properties = LOCALE_MAPPINGS.get(locale); - String formatString = properties.getProperty(key); + String formatString = null; + + if (properties != null) { + formatString = properties.getProperty(key); + } // Try and get the key from the default locale if (formatString == null) { diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index 5d52c79b..4dd6a04c 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -39,22 +39,31 @@ public class MessageTranslatorTest { @Before public void setUp() throws Exception { messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"DoctorMad9952 joined the game\",\"color\":\"yellow\"}]}", - "§eDoctorMad9952 joined the game"); + "§r§eDoctorMad9952 joined the game"); messages.put("{\"text\":\"\",\"extra\":[\"Plugins (3): \",{\"text\":\"WorldEdit\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"ViaVersion\",\"color\":\"green\"},{\"text\":\", \",\"color\":\"white\"},{\"text\":\"Geyser-Spigot\",\"color\":\"green\"}]}", - "Plugins (3): §aWorldEdit§f, §aViaVersion§f, §aGeyser-Spigot"); + "Plugins (3): §r§aWorldEdit§r§f, §r§aViaVersion§r§f, §r§aGeyser-Spigot"); // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §6The §c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §a[1.8/1.9+]\n" + - " §e✦ §d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §8(kinda sus) §e✦"); + " §r§6The §r§c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§8(kinda sus) §r§e✦"); + + // Color code format resetting + messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"\",\"extra\":[{\"text\":\"[\",\"color\":\"gray\"},{\"text\":\"H\",\"color\":\"yellow\"},{\"text\":\"]\",\"color\":\"gray\"},{\"text\":\" \",\"color\":\"white\"},{\"text\":\"GUEST\",\"color\":\"#b7b7b7\",\"bold\":true}]},{\"text\":\"\",\"extra\":[{\"text\":\" \",\"bold\":true},{\"text\":\"»\",\"color\":\"blue\"},{\"text\":\" \",\"color\":\"gray\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"rtm516\",\"color\":\"white\"},{\"text\":\": \",\"color\":\"gray\"},{\"text\":\"\",\"color\":\"white\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"This is an amazing bedrock test message\",\"color\":\"white\"}]}]}\n", + "§r§7[§r§eH§r§7]§r§f §r§7§lGUEST§r§l §r§9»§r§7 §r§frtm516§r§7: §r§fThis is an amazing bedrock test message"); + + // Test translation and positional arguments + // Disabled due to not having an GeyserConnector instance, hence it fails + //messages.put("{\"translate\":\"death.attack.player\",\"with\":[{\"text\":\"rtm516\",\"insertion\":\"rtm516\"},{\"text\":\"*invincible_rt\",\"insertion\":\"*invincible_rt\"}]}", + // "rtm516 was slain by *invincible_rt"); } @Test public void convertMessage() { for (Map.Entry entry : messages.entrySet()) { String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); - Assert.assertEquals("Translation of messages is incorrect", bedrockMessage, entry.getValue()); + Assert.assertEquals("Translation of messages is incorrect", entry.getValue(), bedrockMessage); } } @@ -63,5 +72,6 @@ public class MessageTranslatorTest { Assert.assertEquals("All newline message is not handled properly", "\n\n\n\n", MessageTranslator.convertMessageLenient("\n\n\n\n")); Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertMessageLenient("")); Assert.assertEquals("Reset before message is not handled properly", "§r§eGame Selector", MessageTranslator.convertMessageLenient("§r§eGame Selector")); + Assert.assertEquals("Unimplemented formatting chars not stripped", "Bold Underline", MessageTranslator.convertMessageLenient("§m§nBold Underline")); } } From a70d3e2150e8b50c3fd2b74710d6e8fa504a6d07 Mon Sep 17 00:00:00 2001 From: circuit10 Date: Fri, 20 Nov 2020 19:56:39 +0000 Subject: [PATCH 076/161] Fix inconsistencies with movement and position (#699) Movement is now significant better, especially on slabs, stairs, and other half-blocks. Co-authored-by: RednedEpic Co-authored-by: DoctorMacc Co-authored-by: Tim203 Co-authored-by: Camotoy <20743703+DoctorMacc@users.noreply.github.com> --- bootstrap/spigot/pom.xml | 5 + .../platform/spigot/GeyserSpigotPlugin.java | 99 ++++++++- .../world/GeyserSpigotBlockPlaceListener.java | 15 +- .../GeyserSpigot1_12NativeWorldManager.java | 59 +++++ .../manager/GeyserSpigot1_12WorldManager.java | 141 ++++++++++++ .../GeyserSpigotFallbackWorldManager.java | 62 ++++++ .../GeyserSpigotLegacyNativeWorldManager.java | 79 +++++++ .../GeyserSpigotNativeWorldManager.java | 51 +++++ .../GeyserSpigotWorldManager.java | 118 +++------- connector/pom.xml | 2 + .../geysermc/connector/GeyserConnector.java | 2 + .../configuration/GeyserConfiguration.java | 2 + .../GeyserJacksonConfiguration.java | 3 + .../org/geysermc/connector/entity/Entity.java | 3 +- .../connector/entity/FireworkEntity.java | 1 + .../entity/{ => player}/PlayerEntity.java | 27 ++- .../entity/player/SessionPlayerEntity.java | 67 ++++++ .../connector/entity/type/EntityType.java | 1 + .../network/session/GeyserSession.java | 112 ++++++++-- .../network/session/cache/EntityCache.java | 2 +- .../network/session/cache/TeleportCache.java | 42 +++- .../bedrock/BedrockRespawnTranslator.java | 2 +- ...SetLocalPlayerAsInitializedTranslator.java | 2 +- .../player/BedrockActionTranslator.java | 1 + .../player/BedrockMovePlayerTranslator.java | 163 +++++++++----- .../translators/collision/BoundingBox.java | 52 +++++ .../collision/CollisionManager.java | 208 ++++++++++++++++++ .../collision/CollisionRemapper.java | 57 +++++ .../collision/CollisionTranslator.java | 192 ++++++++++++++++ .../collision/translators/BlockCollision.java | 154 +++++++++++++ .../collision/translators/DoorCollision.java | 92 ++++++++ .../collision/translators/EmptyCollision.java | 35 +++ .../translators/GrassPathCollision.java | 49 +++++ .../collision/translators/OtherCollision.java | 53 +++++ .../translators/ScaffoldingCollision.java | 72 ++++++ .../collision/translators/SnowCollision.java | 103 +++++++++ .../collision/translators/SolidCollision.java | 39 ++++ .../translators/TrapdoorCollision.java | 105 +++++++++ .../java/JavaJoinGameTranslator.java | 2 +- .../entity/JavaEntityEffectTranslator.java | 8 +- .../JavaEntityRemoveEffectTranslator.java | 8 +- .../player/JavaPlayerAbilitiesTranslator.java | 10 +- .../player/JavaPlayerListEntryTranslator.java | 2 +- .../JavaPlayerPositionRotationTranslator.java | 16 +- .../spawn/JavaSpawnPlayerTranslator.java | 2 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../world/block/BlockTranslator.java | 8 + .../geysermc/connector/utils/BlockUtils.java | 4 +- .../connector/utils/DimensionUtils.java | 4 +- .../geysermc/connector/utils/SkinUtils.java | 2 +- connector/src/main/resources/config.yml | 21 +- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 53 files changed, 2117 insertions(+), 248 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java rename bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/{ => manager}/GeyserSpigotWorldManager.java (59%) rename connector/src/main/java/org/geysermc/connector/entity/{ => player}/PlayerEntity.java (94%) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/BoundingBox.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionRemapper.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index f3b9cf88..adaa7557 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -29,6 +29,11 @@ 3.2.0 provided + + org.geysermc.adapters + spigot-all + 1.0-SNAPSHOT + ${outputName}-Spigot diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 741763ec..39d4f993 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -25,8 +25,10 @@ package org.geysermc.platform.spigot; +import com.github.steveice10.mc.protocol.MinecraftConstants; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -42,18 +44,23 @@ import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager; +import org.geysermc.platform.spigot.world.manager.*; +import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.UUID; import java.util.logging.Level; public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { - private GeyserSpigotCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotLogger geyserLogger; @@ -142,8 +149,48 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Set if we need to use a different method for getting a player's locale SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); - this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion); - GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion); + if (connector.getConfig().isUseAdapters()) { + try { + String name = Bukkit.getServer().getClass().getPackage().getName(); + String nmsVersion = name.substring(name.lastIndexOf('.') + 1); + SpigotAdapters.registerWorldAdapter(nmsVersion); + if (isViaVersion && isViaVersionNeeded()) { + if (isLegacy) { + // Pre-1.13 + this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); + } + } else { + // No ViaVersion + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes); + } + geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); + } catch (Exception e) { + if (geyserConfig.isDebugMode()) { + geyserLogger.debug("Error while attempting to find NMS adapter. Most likely, this can be safely ignored. :)"); + e.printStackTrace(); + } + } + } else { + geyserLogger.debug("Not using NMS adapter as it is disabled in the config."); + } + if (this.geyserWorldManager == null) { + // No NMS adapter + if (isLegacy && isViaVersion) { + // Use ViaVersion for converting pre-1.13 block states + this.geyserWorldManager = new GeyserSpigot1_12WorldManager(); + } else if (isLegacy) { + // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air + this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(); + } else { + // Post-1.13 + this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes); + } + geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); + } + GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager); Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); @@ -152,8 +199,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onDisable() { - if (connector != null) + if (connector != null) { connector.shutdown(); + } } @Override @@ -186,6 +234,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return getDataFolder().toPath(); } + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserSpigotDumpInfo(); + } + public boolean isCompatible(String version, String whichVersion) { int[] currentVersion = parseVersion(version); int[] otherVersion = parseVersion(whichVersion); @@ -213,15 +266,43 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { String t = stringArray[index].replaceAll("\\D", ""); try { temp[index] = Integer.parseInt(t); - } catch(NumberFormatException ex) { + } catch (NumberFormatException ex) { temp[index] = 0; } } return temp; } - @Override - public BootstrapDumpInfo getDumpInfo() { - return new GeyserSpigotDumpInfo(); + /** + * @return the server version before ViaVersion finishes initializing + */ + public ProtocolVersion getServerProtocolVersion() { + String bukkitVersion = Bukkit.getServer().getVersion(); + // Turn "(MC: 1.16.4)" into 1.16.4. + String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0]; + return ProtocolVersion.getClosest(version); + } + + /** + * This function should not run unless ViaVersion is installed on the server. + * + * @return true if there is any block mappings difference between the server and client. + */ + private boolean isViaVersionNeeded() { + ProtocolVersion serverVersion = getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + if (protocolList == null) { + // No translation needed! + return false; + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + return true; + } + } + // All mapping data is null, which means client and server block states are the same + return false; } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index cb59e202..55a368be 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -36,13 +36,13 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.platform.spigot.world.manager.GeyserSpigotWorldManager; @AllArgsConstructor public class GeyserSpigotBlockPlaceListener implements Listener { private final GeyserConnector connector; - private final boolean isLegacy; - private final boolean isViaVersion; + private final GeyserSpigotWorldManager worldManager; @EventHandler public void place(final BlockPlaceEvent event) { @@ -52,14 +52,13 @@ public class GeyserSpigotBlockPlaceListener implements Listener { placeBlockSoundPacket.setSound(SoundEvent.PLACE); placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); placeBlockSoundPacket.setBabySound(false); - String javaBlockId; - if (isLegacy) { - javaBlockId = BlockTranslator.getJavaIdBlockMap().inverse().get(GeyserSpigotWorldManager.getLegacyBlock(session, - event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ(), isViaVersion)); + if (worldManager.isLegacy()) { + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session, + event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); } else { - javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); + placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); } - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, 0))); placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); session.setLastBlockPlacePosition(null); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java new file mode 100644 index 00000000..f58b75cd --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -0,0 +1,59 @@ +/* + * 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.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +/** + * Used with ViaVersion and pre-1.13. + */ +public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { + private final SpigotWorldAdapter adapter; + + public GeyserSpigot1_12NativeWorldManager() { + this.adapter = SpigotAdapters.getWorldAdapter(); + // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + int blockId = adapter.getBlockAt(player.getWorld(), x, y, z); + return getLegacyBlock(storage, blockId, x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java new file mode 100644 index 00000000..b00ddafa --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -0,0 +1,141 @@ +/* + * 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.minecraft.Position; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; + +import java.util.List; + +/** + * Should be used when ViaVersion is present, no NMS adapter is being used, and we are pre-1.13. + * + * You need ViaVersion to connect to an older server with the Geyser-Spigot plugin. + */ +public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 block into the 1.13 block state. + * (Block IDs did not change between server versions until 1.13 and after) + */ + private final MappingData mappingData1_12to1_13; + + /** + * The list of all protocols from the client's version to 1.13. + */ + private final List> protocolList; + + public GeyserSpigot1_12WorldManager() { + super(false); + this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, + ProtocolVersion.v1_13.getVersion()); + } + + @Override + @SuppressWarnings("deprecation") + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + Block block = player.getWorld().getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + return getLegacyBlock(storage, blockId, x, y, z); + } + + /** + * + * @param storage ViaVersion's block entity storage (used to fix block entity state differences) + * @param blockId the pre-1.13 block id + * @param x X coordinate of block + * @param y Y coordinate of block + * @param z Z coordinate of block + * @return the block state updated to the latest Minecraft version + */ + @SuppressWarnings("deprecation") + public int getLegacyBlock(BlockStorage storage, int blockId, int x, int y, int z) { + // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 + blockId = mappingData1_12to1_13.getNewBlockId(blockId); + // Translate block entity differences - some information was stored in block tags and not block states + if (storage.isWelcome(blockId)) { // No getOrDefault method + BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); + if (data != null && data.getReplacement() != -1) { + blockId = data.getReplacement(); + } + } + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + blockId = mappingData.getNewBlockStateId(blockId); + } + } + return blockId; + } + + @SuppressWarnings("deprecation") + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return; + } + World world = player.getWorld(); + // Get block entity storage + BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt(x, y, z); + // Black magic that gets the old block state ID + int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); + chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, blockId, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); + } + } + } + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java new file mode 100644 index 00000000..49c675a1 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -0,0 +1,62 @@ +/* + * 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +/** + * Should only be used when we know {@link GeyserSpigotWorldManager#getBlockAt(GeyserSession, int, int, int)} + * cannot be accurate. Typically, this is when ViaVersion is not installed but a client still manages to connect. + * If this occurs to you somehow, please let us know!! + */ +public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { + public GeyserSpigotFallbackWorldManager() { + // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. + super(false); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + return BlockTranslator.JAVA_AIR_ID; + } + + @Override + public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { + // Do nothing, since we can't do anything with the chunk + } + + @Override + public boolean hasMoreBlockDataThanChunkCache() { + return false; + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java new file mode 100644 index 00000000..dec9b414 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -0,0 +1,79 @@ +/* + * 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.platform.spigot.world.manager; + +import com.github.steveice10.mc.protocol.MinecraftConstants; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntList; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.platform.spigot.GeyserSpigotPlugin; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; + +import java.util.List; + +/** + * Used when block IDs need to be translated to the latest version + */ +public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorldManager { + + private final Int2IntMap oldToNewBlockId; + + public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { + super(use3dBiomes); + IntList allBlockStates = adapter.getAllBlockStates(); + oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); + ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); + List> protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + serverVersion.getVersion()); + for (int oldBlockId : allBlockStates) { + int newBlockId = oldBlockId; + // protocolList should *not* be null; we checked for that before initializing this class + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + newBlockId = mappingData.getNewBlockStateId(newBlockId); + } + } + oldToNewBlockId.put(oldBlockId, newBlockId); + } + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + int nativeBlockId = super.getBlockAt(session, x, y, z); + return oldToNewBlockId.getOrDefault(nativeBlockId, nativeBlockId); + } + + @Override + public boolean isLegacy() { + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java new file mode 100644 index 00000000..f703ecdb --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -0,0 +1,51 @@ +/* + * 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.platform.spigot.world.manager; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.geysermc.adapters.spigot.SpigotAdapters; +import org.geysermc.adapters.spigot.SpigotWorldAdapter; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { + protected final SpigotWorldAdapter adapter; + + public GeyserSpigotNativeWorldManager(boolean use3dBiomes) { + super(use3dBiomes); + adapter = SpigotAdapters.getWorldAdapter(); + } + + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + Player player = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); + if (player == null) { + return BlockTranslator.JAVA_AIR_ID; + } + return adapter.getBlockAt(player.getWorld(), x, y, z); + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java similarity index 59% rename from bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java rename to bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 3493fc25..cd1774ba 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.platform.spigot.world; +package org.geysermc.platform.spigot.world.manager; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; @@ -34,6 +34,7 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -42,38 +43,22 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; -import us.myles.ViaVersion.api.Pair; -import us.myles.ViaVersion.api.Via; -import us.myles.ViaVersion.api.data.MappingData; -import us.myles.ViaVersion.api.minecraft.Position; -import us.myles.ViaVersion.api.protocol.Protocol; -import us.myles.ViaVersion.api.protocol.ProtocolRegistry; -import us.myles.ViaVersion.api.protocol.ProtocolVersion; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; -import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; import java.io.InputStream; -import java.util.List; +/** + * The base world manager to use when there is no supported NMS revision + */ public class GeyserSpigotWorldManager extends GeyserWorldManager { /** * The current client protocol version for ViaVersion usage. */ - private static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; + protected static final int CLIENT_PROTOCOL_VERSION = MinecraftConstants.PROTOCOL_VERSION; - /** - * Whether the server is pre-1.13. - */ - private final boolean isLegacy; /** * Whether the server is pre-1.16 and therefore does not support 3D biomes on an API level guaranteed. */ private final boolean use3dBiomes; - /** - * You need ViaVersion to connect to an older server with Geyser. - * However, we still check for ViaVersion in case there's some other way that gets Geyser on a pre-1.13 Bukkit server - */ - private final boolean isViaVersion; /** * Stores a list of {@link Biome} ordinal numbers to Minecraft biome numeric IDs. * @@ -87,10 +72,8 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - public GeyserSpigotWorldManager(boolean isLegacy, boolean use3dBiomes, boolean isViaVersion) { - this.isLegacy = isLegacy; + public GeyserSpigotWorldManager(boolean use3dBiomes) { this.use3dBiomes = use3dBiomes; - this.isViaVersion = isViaVersion; // Load the values into the biome-to-ID map InputStream biomeStream = FileUtils.getResource("biomes.json"); @@ -116,83 +99,26 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public int getBlockAt(GeyserSession session, int x, int y, int z) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return BlockTranslator.JAVA_AIR_ID; } World world = bukkitPlayer.getWorld(); - if (isLegacy) { - return getLegacyBlock(session, x, y, z, true); - } - //TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below - return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0); - } - - public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) { - if (isViaVersion) { - Player bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername()); - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - return getLegacyBlock(storage, bukkitPlayer.getWorld(), x, y, z); - } else { - return BlockTranslator.JAVA_AIR_ID; - } - } - - @SuppressWarnings("deprecation") - public static int getLegacyBlock(BlockStorage storage, World world, int x, int y, int z) { - Block block = world.getBlockAt(x, y, z); - // Black magic that gets the old block state ID - int blockId = (block.getType().getId() << 4) | (block.getData() & 0xF); - // Convert block state from old version (1.12.2) -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2 - blockId = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData().getNewBlockId(blockId); - List> protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, - ProtocolVersion.v1_13.getId()); - // Translate block entity differences - some information was stored in block tags and not block states - if (storage.isWelcome(blockId)) { // No getOrDefault method - BlockStorage.ReplacementData data = storage.get(new Position(x, (short) y, z)); - if (data != null && data.getReplacement() != -1) { - blockId = data.getReplacement(); - } - } - for (int i = protocolList.size() - 1; i >= 0; i--) { - MappingData mappingData = protocolList.get(i).getValue().getMappingData(); - if (mappingData != null) { - blockId = mappingData.getNewBlockStateId(blockId); - } - } - return blockId; + return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); } @Override public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) { Player bukkitPlayer; - if ((this.isLegacy && !this.isViaVersion) - || session.getPlayerEntity() == null - || (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return; } World world = bukkitPlayer.getWorld(); - if (this.isLegacy) { - // Get block entity storage - BlockStorage storage = Via.getManager().getConnection(bukkitPlayer.getUniqueId()).get(BlockStorage.class); - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - chunk.set(blockX, blockY, blockZ, getLegacyBlock(storage, world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ)); - } - } - } - } else { - //TODO: see above TODO in getBlockAt - for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order - for (int blockZ = 0; blockZ < 16; blockZ++) { - for (int blockX = 0; blockX < 16; blockX++) { - Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); - int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); - chunk.set(blockX, blockY, blockZ, id); - } + for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order + for (int blockZ = 0; blockZ < 16; blockZ++) { + for (int blockX = 0; blockX < 16; blockX++) { + Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ); + int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), BlockTranslator.JAVA_AIR_ID); + chunk.set(blockX, blockY, blockZ, id); } } } @@ -254,4 +180,16 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { public boolean hasPermission(GeyserSession session, String permission) { return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); } + + /** + * This must be set to true if we are pre-1.13, and {@link BlockData#getAsString() does not exist}. + * + * This should be set to true if we are post-1.13 but before the latest version, and we should convert the old block state id + * to the current one. + * + * @return whether there is a difference between client block state and server block state that requires extra processing + */ + public boolean isLegacy() { + return false; + } } diff --git a/connector/pom.xml b/connector/pom.xml index 95794ff2..d7017b0d 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -257,6 +257,8 @@