From 7c49391b9d38373bbe0164032c8d01b41929a8c7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 28 Sep 2020 22:43:50 +0100 Subject: [PATCH 01/46] 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 0a28b11f1..300877f81 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 a7bc7b61f..493a7ca1e 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 02/46] 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 e0404a934..f7201bb08 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 03/46] 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 3b051c5c3..f673a3f51 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 381872752..2dba29015 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 d37321ffe..c77e82718 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 fa3aaa3c3..afd6c3bfd 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 04/46] 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 6dfde5d1c..6afdb25dd 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 dde45cf89..820639b3d 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 05/46] 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 71d52e134..dc98c3630 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 06/46] 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 10d4bb895..a1df72d08 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 07/46] 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 def5715c7..949764b92 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 1f3af019f..850e4e059 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 51ae36e49..b9a213d84 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 000000000..7a5b576be --- /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 304ea3fb2..f4bfdfb64 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 675d42555..159b9ab49 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 7cb88d70c..24130a7f5 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 000000000..0b69d6a2e --- /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 08/46] 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 f52713cea..26ec0c4e9 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 300877f81..5225632c3 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 c43864816..6517f498f 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 4b2af9630..d480b526f 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 000000000..1dd156e4f --- /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 09/46] 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 ac2a80f7a..c3fbd2e92 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 10/46] 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 ffa4f1bdd..1a93391db 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 11/46] 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 3c814393d..924118976 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 4daa5d37d..3e945d3ad 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 6517f498f..b6c6f3aec 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 864e9fe03..ef6294944 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 163c451cb..03b346e35 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 fc7c852cb..e5f8d6aa0 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 54f593a61..4df4fd95e 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 12/46] 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 26ec0c4e9..741acee51 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 218615899..567a08ed3 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 7e97d4298..f76d64edd 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 39c586db7..75cab1521 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 93b2caed3..5f2179226 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 28a22d2ba..0fae8d3f0 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 13/46] 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 8e891d925..b92a84eb6 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 14/46] 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 b2f648612..3accbc120 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 635bafa3d..3dfd6ed38 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 ae1b82757..732a056eb 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 15/46] 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 02835151a..a220e389f 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 16/46] 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 000000000..c7a8f24d0 --- /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 500e135ed..24c15018c 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 979c5a205..67f137ff9 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 17/46] 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 99a3a7a5a..45676fbd8 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 18/46] 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 5225632c3..1a0bbfb29 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 b92a84eb6..b81025beb 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 19/46] 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 924118976..109a5a600 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 20/46] 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 109a5a600..afed4dfdf 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 21/46] 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 567a08ed3..308d2121a 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 22/46] 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 2cc9ea134..14825b71a 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 d54d8b6a8..652e12947 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 23/46] 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 6dae9b4d6..ddd5e004d 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 983e42b73..6972d77be 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 24/46] 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 6a9ef4dc6..da402f66c 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 fe2a8aa96..5505acdf6 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 25/46] 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 493a7ca1e..d7961dd98 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 26/46] 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 c6443bd05..8a92526f1 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 741acee51..5df525567 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 14825b71a..7bf84b8db 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 ddd5e004d..cd1a321c2 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 6972d77be..2ab3c0108 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 ba78b8f6f..fec3bb33a 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 30edf1781..d8cd75206 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 9e721aa9f..000000000 --- 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 48ec88064..979b79c93 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 20fa849c2..47a73f7c1 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 d47584e9d..a63eeb424 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 29dd2cc23..3ce4fea86 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 27/46] 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 ab631ebe5..6fe9e5927 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 28/46] 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 820639b3d..099de3170 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 29/46] 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 094d64df7..64f0e3e98 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 30/46] 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 5df525567..cf2b89948 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 31/46] 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 89e9fe67b..13db4682c 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 32/46] 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 cf2b89948..8615ad801 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 33/46] 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 1a0bbfb29..79949a5fa 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 34/46] 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 8615ad801..197319875 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 e45566264..1d21bbfb7 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 35/46] 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 0602bb546..43e3e8edc 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 36/46] 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 7c30e48ab..82fc3a3a1 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 5505acdf6..d65dbc81c 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 37/46] 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 79949a5fa..6b0f73ae3 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 000000000..ef5b1a569 --- /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 38/46] 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 6b0f73ae3..bb7602f3c 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 39/46] 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 efc71ae8d..045552b6e 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 7c1acc26f..3101f5fb3 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 afa75503e..2b35424af 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 000000000..ed9db58f5 --- /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 f76d64edd..f99abbe5a 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 bb7602f3c..a8a4adb93 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 000000000..9a80254b0 --- /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 e5f8d6aa0..5314292a1 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 000000000..3c42182d7 --- /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 5f2179226..a4125be98 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 40/46] 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 d0632d97d..b3632606b 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 850e4e059..597c87b26 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 c3fbd2e92..09d0eaa91 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 41/46] 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 acfdc3d63..345933ac5 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 42/46] 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 e3c3b0a56..85f8a6e9e 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 50deeb1bc..aeee84624 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 62bc6c73f..2bb8893cb 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 9ad0d23dd..9103755a9 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 b1b601328..d5e0a7927 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 ed9db58f5..52379145a 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 43/46] 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 91caf8d1c..e3bcbce9f 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 44/46] 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 d40dc902b..3ad8b54fd 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 f673a3f51..ff7a2e3dd 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 9cc0bc064..892f8feba 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 2dba29015..0edb8448d 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 55475a303..93a500669 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 afd6c3bfd..c329fb193 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 1b0d6f3e6..3a1c46033 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 afed4dfdf..1d535f547 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 2b35424af..7adce4300 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 be9e34305..9c398a2a2 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 2bb8893cb..adbebd0cd 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 9103755a9..f2c37da28 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 0407cf6ef..268dc4b5b 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 3c78c5540..255c6a9b6 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 d5e0a7927..4b6397f1e 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 9c24fa2be..6b2be2944 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 f7f62e597..562bc9fb5 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 5e825e892..2dfb0c04f 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 f99abbe5a..e871b7f79 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 a8a4adb93..ac72d219d 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 68e8519c1..fdc609ab9 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 cbcdce10b..8aa91b360 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 ef6294944..f95a0ccc6 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 d222d729b..186aaf660 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 432111119..f36da367b 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 d45042262..e7486c992 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 998e5effe..c621fc1f2 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 099de3170..2c10ded60 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 de6796a26..06d2936ef 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 d1d59490f..11e81eee1 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 4bc997bdf..fe63bc917 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 a127fd8db..b5a2bfdcc 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 13db4682c..eaf71058c 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 45/46] 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 aa2b4e026..14466dda2 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 000000000..bb5876ce8 --- /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 24c15018c..fddab5a42 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 b81025beb..b2a70146a 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 46/46] 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 191b285c6..411c0295b 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 9ffb4f0d9..70303baa0 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